From a3970f8e4334ab68a2f8ffcd813500efd1b8b69d Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sun, 2 Apr 2023 21:34:07 -0400 Subject: [PATCH] Format code --- .../net/proto/GetAllMailNotifyOuterClass.java | 1130 +++---- .../GetAllMailResultNotifyOuterClass.java | 2605 +++++++++-------- .../command/commands/EntityCommand.java | 278 +- .../command/commands/SetPropCommand.java | 558 ++-- .../java/emu/grasscutter/data/GameData.java | 1224 ++++---- .../emu/grasscutter/data/ResourceLoader.java | 1372 ++++----- .../grasscutter/data/common/PointData.java | 128 +- .../grasscutter/data/excels/GadgetData.java | 44 +- .../data/excels/monster/MonsterData.java | 253 +- .../excels/monster/MonsterDescribeData.java | 49 +- .../grasscutter/database/DatabaseHelper.java | 928 +++--- .../game/activity/PlayerActivityData.java | 268 +- .../emu/grasscutter/game/avatar/Avatar.java | 2540 ++++++++-------- .../dungeons/BasicDungeonSettleListener.java | 37 +- .../game/dungeons/DungeonManager.java | 645 ++-- .../game/dungeons/DungeonSettleListener.java | 14 +- .../game/dungeons/DungeonSystem.java | 327 ++- .../dungeons/TowerDungeonSettleListener.java | 78 +- .../dungeons/challenge/WorldChallenge.java | 345 +-- .../challenge/factory/ChallengeFactory.java | 80 +- .../factory/ChallengeFactoryHandler.java | 11 +- .../KillAndGuardChallengeFactoryHandler.java | 24 +- ...llMonsterCountChallengeFactoryHandler.java | 23 +- ...lMonsterInTimeChallengeFactoryHandler.java | 73 +- ...illMonsterTimeChallengeFactoryHandler.java | 27 +- .../SurviveChallengeFactoryHandler.java | 31 +- .../TriggerInTimeChallengeFactoryHandler.java | 23 +- .../challenge/trigger/ChallengeTrigger.java | 38 +- .../challenge/trigger/GuardTrigger.java | 76 +- .../challenge/trigger/KillMonsterTrigger.java | 47 +- .../grasscutter/game/entity/EntityAvatar.java | 740 ++--- .../game/entity/EntityBaseGadget.java | 126 +- .../grasscutter/game/entity/EntityGadget.java | 589 ++-- .../grasscutter/game/entity/EntityNPC.java | 164 +- .../grasscutter/game/entity/EntityRegion.java | 192 +- .../EntitySolarIsotomaClientGadget.java | 63 +- .../game/entity/EntityVehicle.java | 237 +- .../grasscutter/game/entity/GameEntity.java | 528 ++-- .../entity/gadget/GadgetRewardStatue.java | 67 +- .../EntitySolarIsotomaElevatorPlatform.java | 85 +- .../java/emu/grasscutter/game/mail/Mail.java | 336 +-- .../managers/blossom/BlossomActivity.java | 279 +- .../game/managers/energy/EnergyManager.java | 838 +++--- .../game/player/PlayerProgressManager.java | 556 ++-- .../grasscutter/game/player/TeamManager.java | 2162 +++++++------- .../grasscutter/game/props/ElementType.java | 203 +- .../ConditionPersonalLineUnlock.java | 44 +- .../quest/exec/ExecAddCurAvatarEnergy.java | 34 +- .../game/quest/exec/ExecAddQuestProgress.java | 42 +- .../game/quest/exec/ExecSetOpenState.java | 40 +- .../emu/grasscutter/game/world/Scene.java | 2303 +++++++-------- .../game/world/SceneGroupInstance.java | 171 +- .../scripts/SceneScriptManager.java | 2112 +++++++------ .../emu/grasscutter/scripts/ScriptUtils.java | 130 +- .../grasscutter/scripts/data/SceneConfig.java | 32 +- .../grasscutter/scripts/data/SceneGroup.java | 389 +-- .../scripts/data/SceneInitConfig.java | 26 +- .../scripts/data/SceneMonster.java | 30 +- .../grasscutter/scripts/data/SceneObject.java | 38 +- .../grasscutter/scripts/data/SceneSuite.java | 124 +- .../scripts/data/SceneTrigger.java | 102 +- .../grasscutter/scripts/data/ScriptArgs.java | 180 +- .../service/ScriptMonsterTideService.java | 194 +- .../event/entity/EntityDamageEvent.java | 51 +- .../grasscutter/server/game/GameServer.java | 558 ++-- .../packet/recv/HandlerAddCustomTeamReq.java | 28 +- .../HandlerAddQuestContentProgressReq.java | 51 +- .../HandlerAvatarChangeElementTypeReq.java | 77 +- .../packet/recv/HandlerChangeHomeBgmReq.java | 52 +- .../recv/HandlerCombatInvocationsNotify.java | 382 +-- .../recv/HandlerEvtDoSkillSuccNotify.java | 58 +- .../recv/HandlerExecuteGadgetLuaReq.java | 58 +- .../packet/recv/HandlerGadgetInteractReq.java | 46 +- .../packet/recv/HandlerGetAllMailNotify.java | 34 +- .../packet/recv/HandlerHomeUnknown2Req.java | 38 +- .../recv/HandlerMusicGameSettleReq.java | 141 +- .../server/packet/recv/HandlerNpcTalkReq.java | 134 +- .../packet/recv/HandlerPostEnterSceneReq.java | 50 +- .../recv/HandlerRemoveCustomTeamReq.java | 32 +- .../server/packet/recv/HandlerSaveUgcReq.java | 199 +- .../recv/HandlerSelectWorktopOptionReq.java | 88 +- .../recv/HandlerSkipPlayerGameTimeReq.java | 42 +- ...ateAbilityCreatedMovingPlatformNotify.java | 55 +- .../packet/send/PacketAddCustomTeamRsp.java | 41 +- .../PacketBeginCameraSceneLookNotify.java | 122 +- .../packet/send/PacketChangeGameTimeRsp.java | 36 +- .../send/PacketChangeHomeBgmNotify.java | 36 +- .../packet/send/PacketChangeHomeBgmRsp.java | 36 +- .../send/PacketCustomTeamListNotify.java | 58 +- .../send/PacketDungeonSettleNotify.java | 26 +- .../PacketEntityFightPropUpdateNotify.java | 64 +- .../send/PacketFinishedParentQuestNotify.java | 52 +- ...PacketFinishedParentQuestUpdateNotify.java | 66 +- .../send/PacketGetAllMailResultNotify.java | 82 +- .../packet/send/PacketHomeUnknown1Notify.java | 36 +- .../packet/send/PacketHomeUnknown2Rsp.java | 22 +- .../packet/send/PacketMusicGameSettleRsp.java | 65 +- .../send/PacketPlatformStartRouteNotify.java | 41 +- .../send/PacketPlatformStopRouteNotify.java | 41 +- .../send/PacketPlayerGameTimeNotify.java | 42 +- .../send/PacketRemoveCustomTeamRsp.java | 43 +- .../send/PacketUnlockHomeBgmNotify.java | 36 +- .../send/PacketUnlockedHomeBgmNotify.java | 50 +- .../java/emu/grasscutter/utils/Language.java | 1026 +++---- 104 files changed, 15623 insertions(+), 15004 deletions(-) diff --git a/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailNotifyOuterClass.java b/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailNotifyOuterClass.java index 506cd35b6..24958bcf9 100644 --- a/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailNotifyOuterClass.java +++ b/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailNotifyOuterClass.java @@ -1,547 +1,583 @@ -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: GetAllMailNotify.proto - -package emu.grasscutter.net.proto; - -public final class GetAllMailNotifyOuterClass { - private GetAllMailNotifyOuterClass() {} - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistryLite registry) { - } - - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistry registry) { - registerAllExtensions( - (com.google.protobuf.ExtensionRegistryLite) registry); - } - public interface GetAllMailNotifyOrBuilder extends - // @@protoc_insertion_point(interface_extends:GetAllMailNotify) - com.google.protobuf.MessageOrBuilder { - - /** - * bool is_collected = 6; - * @return The isCollected. - */ - boolean getIsCollected(); - } - /** - *
-   * Name: PFHIOOMPNBH
-   * CmdId: 1442
-   * 
- * - * Protobuf type {@code GetAllMailNotify} - */ - public static final class GetAllMailNotify extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:GetAllMailNotify) - GetAllMailNotifyOrBuilder { - private static final long serialVersionUID = 0L; - // Use GetAllMailNotify.newBuilder() to construct. - private GetAllMailNotify(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private GetAllMailNotify() { - } - - @java.lang.Override - @SuppressWarnings({"unused"}) - protected java.lang.Object newInstance( - UnusedPrivateParameter unused) { - return new GetAllMailNotify(); - } - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GetAllMailNotify( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 48: { - - isCollected_ = input.readBool(); - break; - } - default: { - if (!parseUnknownField( - input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.internal_static_GetAllMailNotify_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.internal_static_GetAllMailNotify_fieldAccessorTable - .ensureFieldAccessorsInitialized( - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.class, emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.Builder.class); - } - - public static final int IS_COLLECTED_FIELD_NUMBER = 6; - private boolean isCollected_; - /** - * bool is_collected = 6; - * @return The isCollected. - */ - @java.lang.Override - public boolean getIsCollected() { - return isCollected_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (isCollected_ != false) { - output.writeBool(6, isCollected_); - } - unknownFields.writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (isCollected_ != false) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(6, isCollected_); - } - size += unknownFields.getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify)) { - return super.equals(obj); - } - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify other = (emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) obj; - - if (getIsCollected() - != other.getIsCollected()) return false; - if (!unknownFields.equals(other.unknownFields)) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - hash = (37 * hash) + IS_COLLECTED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsCollected()); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - *
-     * Name: PFHIOOMPNBH
-     * CmdId: 1442
-     * 
- * - * Protobuf type {@code GetAllMailNotify} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:GetAllMailNotify) - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotifyOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.internal_static_GetAllMailNotify_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.internal_static_GetAllMailNotify_fieldAccessorTable - .ensureFieldAccessorsInitialized( - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.class, emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.Builder.class); - } - - // Construct using emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - @java.lang.Override - public Builder clear() { - super.clear(); - isCollected_ = false; - - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.internal_static_GetAllMailNotify_descriptor; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify getDefaultInstanceForType() { - return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.getDefaultInstance(); - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify build() { - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify buildPartial() { - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify result = new emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify(this); - result.isCollected_ = isCollected_; - onBuilt(); - return result; - } - - @java.lang.Override - public Builder clone() { - return super.clone(); - } - @java.lang.Override - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - java.lang.Object value) { - return super.setField(field, value); - } - @java.lang.Override - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return super.clearField(field); - } - @java.lang.Override - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return super.clearOneof(oneof); - } - @java.lang.Override - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, java.lang.Object value) { - return super.setRepeatedField(field, index, value); - } - @java.lang.Override - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - java.lang.Object value) { - return super.addRepeatedField(field, value); - } - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) { - return mergeFrom((emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify other) { - if (other == emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.getDefaultInstance()) return this; - if (other.getIsCollected() != false) { - setIsCollected(other.getIsCollected()); - } - this.mergeUnknownFields(other.unknownFields); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private boolean isCollected_ ; - /** - * bool is_collected = 6; - * @return The isCollected. - */ - @java.lang.Override - public boolean getIsCollected() { - return isCollected_; - } - /** - * bool is_collected = 6; - * @param value The isCollected to set. - * @return This builder for chaining. - */ - public Builder setIsCollected(boolean value) { - - isCollected_ = value; - onChanged(); - return this; - } - /** - * bool is_collected = 6; - * @return This builder for chaining. - */ - public Builder clearIsCollected() { - - isCollected_ = false; - onChanged(); - return this; - } - @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); - } - - @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.mergeUnknownFields(unknownFields); - } - - - // @@protoc_insertion_point(builder_scope:GetAllMailNotify) - } - - // @@protoc_insertion_point(class_scope:GetAllMailNotify) - private static final emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify(); - } - - public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GetAllMailNotify parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GetAllMailNotify(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_GetAllMailNotify_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_GetAllMailNotify_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor - getDescriptor() { - return descriptor; - } - private static com.google.protobuf.Descriptors.FileDescriptor - descriptor; - static { - java.lang.String[] descriptorData = { - "\n\026GetAllMailNotify.proto\"(\n\020GetAllMailNo" + - "tify\022\024\n\014is_collected\030\006 \001(\010B\033\n\031emu.grassc" + - "utter.net.protob\006proto3" - }; - descriptor = com.google.protobuf.Descriptors.FileDescriptor - .internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] { - }); - internal_static_GetAllMailNotify_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_GetAllMailNotify_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_GetAllMailNotify_descriptor, - new java.lang.String[] { "IsCollected", }); - } - - // @@protoc_insertion_point(outer_class_scope) -} +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: GetAllMailNotify.proto + +package emu.grasscutter.net.proto; + +public final class GetAllMailNotifyOuterClass { + private GetAllMailNotifyOuterClass() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + + public interface GetAllMailNotifyOrBuilder + extends + // @@protoc_insertion_point(interface_extends:GetAllMailNotify) + com.google.protobuf.MessageOrBuilder { + + /** + * bool is_collected = 6; + * + * @return The isCollected. + */ + boolean getIsCollected(); + } + /** + * + * + *
+     * Name: PFHIOOMPNBH
+     * CmdId: 1442
+     * 
+ * + * Protobuf type {@code GetAllMailNotify} + */ + public static final class GetAllMailNotify extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:GetAllMailNotify) + GetAllMailNotifyOrBuilder { + private static final long serialVersionUID = 0L; + // Use GetAllMailNotify.newBuilder() to construct. + private GetAllMailNotify(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private GetAllMailNotify() {} + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance(UnusedPrivateParameter unused) { + return new GetAllMailNotify(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet getUnknownFields() { + return this.unknownFields; + } + + private GetAllMailNotify( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 48: + { + isCollected_ = input.readBool(); + break; + } + default: + { + if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass + .internal_static_GetAllMailNotify_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass + .internal_static_GetAllMailNotify_fieldAccessorTable + .ensureFieldAccessorsInitialized( + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.class, + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.Builder.class); + } + + public static final int IS_COLLECTED_FIELD_NUMBER = 6; + private boolean isCollected_; + /** + * bool is_collected = 6; + * + * @return The isCollected. + */ + @java.lang.Override + public boolean getIsCollected() { + return isCollected_; + } + + private byte memoizedIsInitialized = -1; + + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { + if (isCollected_ != false) { + output.writeBool(6, isCollected_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (isCollected_ != false) { + size += com.google.protobuf.CodedOutputStream.computeBoolSize(6, isCollected_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify)) { + return super.equals(obj); + } + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify other = + (emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) obj; + + if (getIsCollected() != other.getIsCollected()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + IS_COLLECTED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(getIsCollected()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + java.nio.ByteBuffer data) throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + parseDelimitedFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + com.google.protobuf.CodedInputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder( + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * + * + *
+         * Name: PFHIOOMPNBH
+         * CmdId: 1442
+         * 
+ * + * Protobuf type {@code GetAllMailNotify} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:GetAllMailNotify) + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotifyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass + .internal_static_GetAllMailNotify_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass + .internal_static_GetAllMailNotify_fieldAccessorTable + .ensureFieldAccessorsInitialized( + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.class, + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.Builder + .class); + } + + // Construct using + // emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) {} + } + + @java.lang.Override + public Builder clear() { + super.clear(); + isCollected_ = false; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass + .internal_static_GetAllMailNotify_descriptor; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + getDefaultInstanceForType() { + return emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + .getDefaultInstance(); + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify build() { + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify result = + buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify buildPartial() { + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify result = + new emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify(this); + result.isCollected_ = isCollected_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.setField(field, value); + } + + @java.lang.Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @java.lang.Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.addRepeatedField(field, value); + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other + instanceof emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) { + return mergeFrom( + (emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom( + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify other) { + if (other + == emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + .getDefaultInstance()) return this; + if (other.getIsCollected() != false) { + setIsCollected(other.getIsCollected()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = + (emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify) + e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private boolean isCollected_; + /** + * bool is_collected = 6; + * + * @return The isCollected. + */ + @java.lang.Override + public boolean getIsCollected() { + return isCollected_; + } + /** + * bool is_collected = 6; + * + * @param value The isCollected to set. + * @return This builder for chaining. + */ + public Builder setIsCollected(boolean value) { + + isCollected_ = value; + onChanged(); + return this; + } + /** + * bool is_collected = 6; + * + * @return This builder for chaining. + */ + public Builder clearIsCollected() { + + isCollected_ = false; + onChanged(); + return this; + } + + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:GetAllMailNotify) + } + + // @@protoc_insertion_point(class_scope:GetAllMailNotify) + private static final emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = + new emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify(); + } + + public static emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @java.lang.Override + public GetAllMailNotify parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GetAllMailNotify(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify + getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_GetAllMailNotify_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_GetAllMailNotify_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + return descriptor; + } + + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + + static { + java.lang.String[] descriptorData = { + "\n\026GetAllMailNotify.proto\"(\n\020GetAllMailNo" + + "tify\022\024\n\014is_collected\030\006 \001(\010B\033\n\031emu.grassc" + + "utter.net.protob\006proto3" + }; + descriptor = + com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( + descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] {}); + internal_static_GetAllMailNotify_descriptor = getDescriptor().getMessageTypes().get(0); + internal_static_GetAllMailNotify_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_GetAllMailNotify_descriptor, + new java.lang.String[] { + "IsCollected", + }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailResultNotifyOuterClass.java b/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailResultNotifyOuterClass.java index 6b9963acf..9deeebad3 100644 --- a/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailResultNotifyOuterClass.java +++ b/src/generated/main/java/emu/grasscutter/net/proto/GetAllMailResultNotifyOuterClass.java @@ -1,1288 +1,1317 @@ -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: GetAllMailResultNotify.proto - -package emu.grasscutter.net.proto; - -public final class GetAllMailResultNotifyOuterClass { - private GetAllMailResultNotifyOuterClass() {} - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistryLite registry) { - } - - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistry registry) { - registerAllExtensions( - (com.google.protobuf.ExtensionRegistryLite) registry); - } - public interface GetAllMailResultNotifyOrBuilder extends - // @@protoc_insertion_point(interface_extends:GetAllMailResultNotify) - com.google.protobuf.MessageOrBuilder { - - /** - * int32 retcode = 6; - * @return The retcode. - */ - int getRetcode(); - - /** - * uint32 packet_be_sent_num = 1; - * @return The packetBeSentNum. - */ - int getPacketBeSentNum(); - - /** - * repeated .MailData mail_list = 10; - */ - java.util.List - getMailListList(); - /** - * repeated .MailData mail_list = 10; - */ - emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index); - /** - * repeated .MailData mail_list = 10; - */ - int getMailListCount(); - /** - * repeated .MailData mail_list = 10; - */ - java.util.List - getMailListOrBuilderList(); - /** - * repeated .MailData mail_list = 10; - */ - emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder( - int index); - - /** - * string transaction = 5; - * @return The transaction. - */ - java.lang.String getTransaction(); - /** - * string transaction = 5; - * @return The bytes for transaction. - */ - com.google.protobuf.ByteString - getTransactionBytes(); - - /** - * bool is_collected = 8; - * @return The isCollected. - */ - boolean getIsCollected(); - - /** - * uint32 packet_num = 13; - * @return The packetNum. - */ - int getPacketNum(); - } - /** - *
-   * Name: ILFMLLOCKFB
-   * CmdId: 1429
-   * 
- * - * Protobuf type {@code GetAllMailResultNotify} - */ - public static final class GetAllMailResultNotify extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:GetAllMailResultNotify) - GetAllMailResultNotifyOrBuilder { - private static final long serialVersionUID = 0L; - // Use GetAllMailResultNotify.newBuilder() to construct. - private GetAllMailResultNotify(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - private GetAllMailResultNotify() { - mailList_ = java.util.Collections.emptyList(); - transaction_ = ""; - } - - @java.lang.Override - @SuppressWarnings({"unused"}) - protected java.lang.Object newInstance( - UnusedPrivateParameter unused) { - return new GetAllMailResultNotify(); - } - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GetAllMailResultNotify( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - if (extensionRegistry == null) { - throw new java.lang.NullPointerException(); - } - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - case 8: { - - packetBeSentNum_ = input.readUInt32(); - break; - } - case 42: { - java.lang.String s = input.readStringRequireUtf8(); - - transaction_ = s; - break; - } - case 48: { - - retcode_ = input.readInt32(); - break; - } - case 64: { - - isCollected_ = input.readBool(); - break; - } - case 82: { - if (!((mutable_bitField0_ & 0x00000001) != 0)) { - mailList_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000001; - } - mailList_.add( - input.readMessage(emu.grasscutter.net.proto.MailDataOuterClass.MailData.parser(), extensionRegistry)); - break; - } - case 104: { - - packetNum_ = input.readUInt32(); - break; - } - default: { - if (!parseUnknownField( - input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000001) != 0)) { - mailList_ = java.util.Collections.unmodifiableList(mailList_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.internal_static_GetAllMailResultNotify_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.internal_static_GetAllMailResultNotify_fieldAccessorTable - .ensureFieldAccessorsInitialized( - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.class, emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.Builder.class); - } - - public static final int RETCODE_FIELD_NUMBER = 6; - private int retcode_; - /** - * int32 retcode = 6; - * @return The retcode. - */ - @java.lang.Override - public int getRetcode() { - return retcode_; - } - - public static final int PACKET_BE_SENT_NUM_FIELD_NUMBER = 1; - private int packetBeSentNum_; - /** - * uint32 packet_be_sent_num = 1; - * @return The packetBeSentNum. - */ - @java.lang.Override - public int getPacketBeSentNum() { - return packetBeSentNum_; - } - - public static final int MAIL_LIST_FIELD_NUMBER = 10; - private java.util.List mailList_; - /** - * repeated .MailData mail_list = 10; - */ - @java.lang.Override - public java.util.List getMailListList() { - return mailList_; - } - /** - * repeated .MailData mail_list = 10; - */ - @java.lang.Override - public java.util.List - getMailListOrBuilderList() { - return mailList_; - } - /** - * repeated .MailData mail_list = 10; - */ - @java.lang.Override - public int getMailListCount() { - return mailList_.size(); - } - /** - * repeated .MailData mail_list = 10; - */ - @java.lang.Override - public emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index) { - return mailList_.get(index); - } - /** - * repeated .MailData mail_list = 10; - */ - @java.lang.Override - public emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder( - int index) { - return mailList_.get(index); - } - - public static final int TRANSACTION_FIELD_NUMBER = 5; - private volatile java.lang.Object transaction_; - /** - * string transaction = 5; - * @return The transaction. - */ - @java.lang.Override - public java.lang.String getTransaction() { - java.lang.Object ref = transaction_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - transaction_ = s; - return s; - } - } - /** - * string transaction = 5; - * @return The bytes for transaction. - */ - @java.lang.Override - public com.google.protobuf.ByteString - getTransactionBytes() { - java.lang.Object ref = transaction_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - transaction_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int IS_COLLECTED_FIELD_NUMBER = 8; - private boolean isCollected_; - /** - * bool is_collected = 8; - * @return The isCollected. - */ - @java.lang.Override - public boolean getIsCollected() { - return isCollected_; - } - - public static final int PACKET_NUM_FIELD_NUMBER = 13; - private int packetNum_; - /** - * uint32 packet_num = 13; - * @return The packetNum. - */ - @java.lang.Override - public int getPacketNum() { - return packetNum_; - } - - private byte memoizedIsInitialized = -1; - @java.lang.Override - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - @java.lang.Override - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (packetBeSentNum_ != 0) { - output.writeUInt32(1, packetBeSentNum_); - } - if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(transaction_)) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 5, transaction_); - } - if (retcode_ != 0) { - output.writeInt32(6, retcode_); - } - if (isCollected_ != false) { - output.writeBool(8, isCollected_); - } - for (int i = 0; i < mailList_.size(); i++) { - output.writeMessage(10, mailList_.get(i)); - } - if (packetNum_ != 0) { - output.writeUInt32(13, packetNum_); - } - unknownFields.writeTo(output); - } - - @java.lang.Override - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (packetBeSentNum_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(1, packetBeSentNum_); - } - if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(transaction_)) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, transaction_); - } - if (retcode_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeInt32Size(6, retcode_); - } - if (isCollected_ != false) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(8, isCollected_); - } - for (int i = 0; i < mailList_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(10, mailList_.get(i)); - } - if (packetNum_ != 0) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(13, packetNum_); - } - size += unknownFields.getSerializedSize(); - memoizedSize = size; - return size; - } - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify)) { - return super.equals(obj); - } - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify other = (emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) obj; - - if (getRetcode() - != other.getRetcode()) return false; - if (getPacketBeSentNum() - != other.getPacketBeSentNum()) return false; - if (!getMailListList() - .equals(other.getMailListList())) return false; - if (!getTransaction() - .equals(other.getTransaction())) return false; - if (getIsCollected() - != other.getIsCollected()) return false; - if (getPacketNum() - != other.getPacketNum()) return false; - if (!unknownFields.equals(other.unknownFields)) return false; - return true; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptor().hashCode(); - hash = (37 * hash) + RETCODE_FIELD_NUMBER; - hash = (53 * hash) + getRetcode(); - hash = (37 * hash) + PACKET_BE_SENT_NUM_FIELD_NUMBER; - hash = (53 * hash) + getPacketBeSentNum(); - if (getMailListCount() > 0) { - hash = (37 * hash) + MAIL_LIST_FIELD_NUMBER; - hash = (53 * hash) + getMailListList().hashCode(); - } - hash = (37 * hash) + TRANSACTION_FIELD_NUMBER; - hash = (53 * hash) + getTransaction().hashCode(); - hash = (37 * hash) + IS_COLLECTED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsCollected()); - hash = (37 * hash) + PACKET_NUM_FIELD_NUMBER; - hash = (53 * hash) + getPacketNum(); - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - java.nio.ByteBuffer data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - java.nio.ByteBuffer data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - @java.lang.Override - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - public static Builder newBuilder(emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - @java.lang.Override - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - *
-     * Name: ILFMLLOCKFB
-     * CmdId: 1429
-     * 
- * - * Protobuf type {@code GetAllMailResultNotify} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:GetAllMailResultNotify) - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotifyOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.internal_static_GetAllMailResultNotify_descriptor; - } - - @java.lang.Override - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.internal_static_GetAllMailResultNotify_fieldAccessorTable - .ensureFieldAccessorsInitialized( - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.class, emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.Builder.class); - } - - // Construct using emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - getMailListFieldBuilder(); - } - } - @java.lang.Override - public Builder clear() { - super.clear(); - retcode_ = 0; - - packetBeSentNum_ = 0; - - if (mailListBuilder_ == null) { - mailList_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000001); - } else { - mailListBuilder_.clear(); - } - transaction_ = ""; - - isCollected_ = false; - - packetNum_ = 0; - - return this; - } - - @java.lang.Override - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.internal_static_GetAllMailResultNotify_descriptor; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify getDefaultInstanceForType() { - return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.getDefaultInstance(); - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify build() { - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify buildPartial() { - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify result = new emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify(this); - int from_bitField0_ = bitField0_; - result.retcode_ = retcode_; - result.packetBeSentNum_ = packetBeSentNum_; - if (mailListBuilder_ == null) { - if (((bitField0_ & 0x00000001) != 0)) { - mailList_ = java.util.Collections.unmodifiableList(mailList_); - bitField0_ = (bitField0_ & ~0x00000001); - } - result.mailList_ = mailList_; - } else { - result.mailList_ = mailListBuilder_.build(); - } - result.transaction_ = transaction_; - result.isCollected_ = isCollected_; - result.packetNum_ = packetNum_; - onBuilt(); - return result; - } - - @java.lang.Override - public Builder clone() { - return super.clone(); - } - @java.lang.Override - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - java.lang.Object value) { - return super.setField(field, value); - } - @java.lang.Override - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return super.clearField(field); - } - @java.lang.Override - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return super.clearOneof(oneof); - } - @java.lang.Override - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, java.lang.Object value) { - return super.setRepeatedField(field, index, value); - } - @java.lang.Override - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - java.lang.Object value) { - return super.addRepeatedField(field, value); - } - @java.lang.Override - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) { - return mergeFrom((emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify other) { - if (other == emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.getDefaultInstance()) return this; - if (other.getRetcode() != 0) { - setRetcode(other.getRetcode()); - } - if (other.getPacketBeSentNum() != 0) { - setPacketBeSentNum(other.getPacketBeSentNum()); - } - if (mailListBuilder_ == null) { - if (!other.mailList_.isEmpty()) { - if (mailList_.isEmpty()) { - mailList_ = other.mailList_; - bitField0_ = (bitField0_ & ~0x00000001); - } else { - ensureMailListIsMutable(); - mailList_.addAll(other.mailList_); - } - onChanged(); - } - } else { - if (!other.mailList_.isEmpty()) { - if (mailListBuilder_.isEmpty()) { - mailListBuilder_.dispose(); - mailListBuilder_ = null; - mailList_ = other.mailList_; - bitField0_ = (bitField0_ & ~0x00000001); - mailListBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getMailListFieldBuilder() : null; - } else { - mailListBuilder_.addAllMessages(other.mailList_); - } - } - } - if (!other.getTransaction().isEmpty()) { - transaction_ = other.transaction_; - onChanged(); - } - if (other.getIsCollected() != false) { - setIsCollected(other.getIsCollected()); - } - if (other.getPacketNum() != 0) { - setPacketNum(other.getPacketNum()); - } - this.mergeUnknownFields(other.unknownFields); - onChanged(); - return this; - } - - @java.lang.Override - public final boolean isInitialized() { - return true; - } - - @java.lang.Override - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - private int retcode_ ; - /** - * int32 retcode = 6; - * @return The retcode. - */ - @java.lang.Override - public int getRetcode() { - return retcode_; - } - /** - * int32 retcode = 6; - * @param value The retcode to set. - * @return This builder for chaining. - */ - public Builder setRetcode(int value) { - - retcode_ = value; - onChanged(); - return this; - } - /** - * int32 retcode = 6; - * @return This builder for chaining. - */ - public Builder clearRetcode() { - - retcode_ = 0; - onChanged(); - return this; - } - - private int packetBeSentNum_ ; - /** - * uint32 packet_be_sent_num = 1; - * @return The packetBeSentNum. - */ - @java.lang.Override - public int getPacketBeSentNum() { - return packetBeSentNum_; - } - /** - * uint32 packet_be_sent_num = 1; - * @param value The packetBeSentNum to set. - * @return This builder for chaining. - */ - public Builder setPacketBeSentNum(int value) { - - packetBeSentNum_ = value; - onChanged(); - return this; - } - /** - * uint32 packet_be_sent_num = 1; - * @return This builder for chaining. - */ - public Builder clearPacketBeSentNum() { - - packetBeSentNum_ = 0; - onChanged(); - return this; - } - - private java.util.List mailList_ = - java.util.Collections.emptyList(); - private void ensureMailListIsMutable() { - if (!((bitField0_ & 0x00000001) != 0)) { - mailList_ = new java.util.ArrayList(mailList_); - bitField0_ |= 0x00000001; - } - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - emu.grasscutter.net.proto.MailDataOuterClass.MailData, emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder> mailListBuilder_; - - /** - * repeated .MailData mail_list = 10; - */ - public java.util.List getMailListList() { - if (mailListBuilder_ == null) { - return java.util.Collections.unmodifiableList(mailList_); - } else { - return mailListBuilder_.getMessageList(); - } - } - /** - * repeated .MailData mail_list = 10; - */ - public int getMailListCount() { - if (mailListBuilder_ == null) { - return mailList_.size(); - } else { - return mailListBuilder_.getCount(); - } - } - /** - * repeated .MailData mail_list = 10; - */ - public emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index) { - if (mailListBuilder_ == null) { - return mailList_.get(index); - } else { - return mailListBuilder_.getMessage(index); - } - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder setMailList( - int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { - if (mailListBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMailListIsMutable(); - mailList_.set(index, value); - onChanged(); - } else { - mailListBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder setMailList( - int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { - if (mailListBuilder_ == null) { - ensureMailListIsMutable(); - mailList_.set(index, builderForValue.build()); - onChanged(); - } else { - mailListBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder addMailList(emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { - if (mailListBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMailListIsMutable(); - mailList_.add(value); - onChanged(); - } else { - mailListBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder addMailList( - int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { - if (mailListBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureMailListIsMutable(); - mailList_.add(index, value); - onChanged(); - } else { - mailListBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder addMailList( - emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { - if (mailListBuilder_ == null) { - ensureMailListIsMutable(); - mailList_.add(builderForValue.build()); - onChanged(); - } else { - mailListBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder addMailList( - int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { - if (mailListBuilder_ == null) { - ensureMailListIsMutable(); - mailList_.add(index, builderForValue.build()); - onChanged(); - } else { - mailListBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder addAllMailList( - java.lang.Iterable values) { - if (mailListBuilder_ == null) { - ensureMailListIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, mailList_); - onChanged(); - } else { - mailListBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder clearMailList() { - if (mailListBuilder_ == null) { - mailList_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000001); - onChanged(); - } else { - mailListBuilder_.clear(); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public Builder removeMailList(int index) { - if (mailListBuilder_ == null) { - ensureMailListIsMutable(); - mailList_.remove(index); - onChanged(); - } else { - mailListBuilder_.remove(index); - } - return this; - } - /** - * repeated .MailData mail_list = 10; - */ - public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder getMailListBuilder( - int index) { - return getMailListFieldBuilder().getBuilder(index); - } - /** - * repeated .MailData mail_list = 10; - */ - public emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder( - int index) { - if (mailListBuilder_ == null) { - return mailList_.get(index); } else { - return mailListBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .MailData mail_list = 10; - */ - public java.util.List - getMailListOrBuilderList() { - if (mailListBuilder_ != null) { - return mailListBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(mailList_); - } - } - /** - * repeated .MailData mail_list = 10; - */ - public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder addMailListBuilder() { - return getMailListFieldBuilder().addBuilder( - emu.grasscutter.net.proto.MailDataOuterClass.MailData.getDefaultInstance()); - } - /** - * repeated .MailData mail_list = 10; - */ - public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder addMailListBuilder( - int index) { - return getMailListFieldBuilder().addBuilder( - index, emu.grasscutter.net.proto.MailDataOuterClass.MailData.getDefaultInstance()); - } - /** - * repeated .MailData mail_list = 10; - */ - public java.util.List - getMailListBuilderList() { - return getMailListFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilderV3< - emu.grasscutter.net.proto.MailDataOuterClass.MailData, emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder> - getMailListFieldBuilder() { - if (mailListBuilder_ == null) { - mailListBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - emu.grasscutter.net.proto.MailDataOuterClass.MailData, emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder>( - mailList_, - ((bitField0_ & 0x00000001) != 0), - getParentForChildren(), - isClean()); - mailList_ = null; - } - return mailListBuilder_; - } - - private java.lang.Object transaction_ = ""; - /** - * string transaction = 5; - * @return The transaction. - */ - public java.lang.String getTransaction() { - java.lang.Object ref = transaction_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - transaction_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * string transaction = 5; - * @return The bytes for transaction. - */ - public com.google.protobuf.ByteString - getTransactionBytes() { - java.lang.Object ref = transaction_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - transaction_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * string transaction = 5; - * @param value The transaction to set. - * @return This builder for chaining. - */ - public Builder setTransaction( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - - transaction_ = value; - onChanged(); - return this; - } - /** - * string transaction = 5; - * @return This builder for chaining. - */ - public Builder clearTransaction() { - - transaction_ = getDefaultInstance().getTransaction(); - onChanged(); - return this; - } - /** - * string transaction = 5; - * @param value The bytes for transaction to set. - * @return This builder for chaining. - */ - public Builder setTransactionBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - checkByteStringIsUtf8(value); - - transaction_ = value; - onChanged(); - return this; - } - - private boolean isCollected_ ; - /** - * bool is_collected = 8; - * @return The isCollected. - */ - @java.lang.Override - public boolean getIsCollected() { - return isCollected_; - } - /** - * bool is_collected = 8; - * @param value The isCollected to set. - * @return This builder for chaining. - */ - public Builder setIsCollected(boolean value) { - - isCollected_ = value; - onChanged(); - return this; - } - /** - * bool is_collected = 8; - * @return This builder for chaining. - */ - public Builder clearIsCollected() { - - isCollected_ = false; - onChanged(); - return this; - } - - private int packetNum_ ; - /** - * uint32 packet_num = 13; - * @return The packetNum. - */ - @java.lang.Override - public int getPacketNum() { - return packetNum_; - } - /** - * uint32 packet_num = 13; - * @param value The packetNum to set. - * @return This builder for chaining. - */ - public Builder setPacketNum(int value) { - - packetNum_ = value; - onChanged(); - return this; - } - /** - * uint32 packet_num = 13; - * @return This builder for chaining. - */ - public Builder clearPacketNum() { - - packetNum_ = 0; - onChanged(); - return this; - } - @java.lang.Override - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); - } - - @java.lang.Override - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.mergeUnknownFields(unknownFields); - } - - - // @@protoc_insertion_point(builder_scope:GetAllMailResultNotify) - } - - // @@protoc_insertion_point(class_scope:GetAllMailResultNotify) - private static final emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify DEFAULT_INSTANCE; - static { - DEFAULT_INSTANCE = new emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify(); - } - - public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - private static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - @java.lang.Override - public GetAllMailResultNotify parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GetAllMailResultNotify(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - @java.lang.Override - public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - - } - - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_GetAllMailResultNotify_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_GetAllMailResultNotify_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor - getDescriptor() { - return descriptor; - } - private static com.google.protobuf.Descriptors.FileDescriptor - descriptor; - static { - java.lang.String[] descriptorData = { - "\n\034GetAllMailResultNotify.proto\032\016MailData" + - ".proto\"\242\001\n\026GetAllMailResultNotify\022\017\n\007ret" + - "code\030\006 \001(\005\022\032\n\022packet_be_sent_num\030\001 \001(\r\022\034" + - "\n\tmail_list\030\n \003(\0132\t.MailData\022\023\n\013transact" + - "ion\030\005 \001(\t\022\024\n\014is_collected\030\010 \001(\010\022\022\n\npacke" + - "t_num\030\r \001(\rB\033\n\031emu.grasscutter.net.proto" + - "b\006proto3" - }; - descriptor = com.google.protobuf.Descriptors.FileDescriptor - .internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] { - emu.grasscutter.net.proto.MailDataOuterClass.getDescriptor(), - }); - internal_static_GetAllMailResultNotify_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_GetAllMailResultNotify_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_GetAllMailResultNotify_descriptor, - new java.lang.String[] { "Retcode", "PacketBeSentNum", "MailList", "Transaction", "IsCollected", "PacketNum", }); - emu.grasscutter.net.proto.MailDataOuterClass.getDescriptor(); - } - - // @@protoc_insertion_point(outer_class_scope) -} +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: GetAllMailResultNotify.proto + +package emu.grasscutter.net.proto; + +public final class GetAllMailResultNotifyOuterClass { + private GetAllMailResultNotifyOuterClass() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + + public interface GetAllMailResultNotifyOrBuilder + extends + // @@protoc_insertion_point(interface_extends:GetAllMailResultNotify) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 retcode = 6; + * + * @return The retcode. + */ + int getRetcode(); + + /** + * uint32 packet_be_sent_num = 1; + * + * @return The packetBeSentNum. + */ + int getPacketBeSentNum(); + + /** repeated .MailData mail_list = 10; */ + java.util.List getMailListList(); + /** repeated .MailData mail_list = 10; */ + emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index); + /** repeated .MailData mail_list = 10; */ + int getMailListCount(); + /** repeated .MailData mail_list = 10; */ + java.util.List + getMailListOrBuilderList(); + /** repeated .MailData mail_list = 10; */ + emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder(int index); + + /** + * string transaction = 5; + * + * @return The transaction. + */ + java.lang.String getTransaction(); + /** + * string transaction = 5; + * + * @return The bytes for transaction. + */ + com.google.protobuf.ByteString getTransactionBytes(); + + /** + * bool is_collected = 8; + * + * @return The isCollected. + */ + boolean getIsCollected(); + + /** + * uint32 packet_num = 13; + * + * @return The packetNum. + */ + int getPacketNum(); + } + /** + * + * + *
+     * Name: ILFMLLOCKFB
+     * CmdId: 1429
+     * 
+ * + * Protobuf type {@code GetAllMailResultNotify} + */ + public static final class GetAllMailResultNotify extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:GetAllMailResultNotify) + GetAllMailResultNotifyOrBuilder { + private static final long serialVersionUID = 0L; + // Use GetAllMailResultNotify.newBuilder() to construct. + private GetAllMailResultNotify(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private GetAllMailResultNotify() { + mailList_ = java.util.Collections.emptyList(); + transaction_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance(UnusedPrivateParameter unused) { + return new GetAllMailResultNotify(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet getUnknownFields() { + return this.unknownFields; + } + + private GetAllMailResultNotify( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + packetBeSentNum_ = input.readUInt32(); + break; + } + case 42: + { + java.lang.String s = input.readStringRequireUtf8(); + + transaction_ = s; + break; + } + case 48: + { + retcode_ = input.readInt32(); + break; + } + case 64: + { + isCollected_ = input.readBool(); + break; + } + case 82: + { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + mailList_ = + new java.util.ArrayList< + emu.grasscutter.net.proto.MailDataOuterClass.MailData>(); + mutable_bitField0_ |= 0x00000001; + } + mailList_.add( + input.readMessage( + emu.grasscutter.net.proto.MailDataOuterClass.MailData.parser(), + extensionRegistry)); + break; + } + case 104: + { + packetNum_ = input.readUInt32(); + break; + } + default: + { + if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + mailList_ = java.util.Collections.unmodifiableList(mailList_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .internal_static_GetAllMailResultNotify_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .internal_static_GetAllMailResultNotify_fieldAccessorTable + .ensureFieldAccessorsInitialized( + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .class, + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .Builder.class); + } + + public static final int RETCODE_FIELD_NUMBER = 6; + private int retcode_; + /** + * int32 retcode = 6; + * + * @return The retcode. + */ + @java.lang.Override + public int getRetcode() { + return retcode_; + } + + public static final int PACKET_BE_SENT_NUM_FIELD_NUMBER = 1; + private int packetBeSentNum_; + /** + * uint32 packet_be_sent_num = 1; + * + * @return The packetBeSentNum. + */ + @java.lang.Override + public int getPacketBeSentNum() { + return packetBeSentNum_; + } + + public static final int MAIL_LIST_FIELD_NUMBER = 10; + private java.util.List mailList_; + /** repeated .MailData mail_list = 10; */ + @java.lang.Override + public java.util.List getMailListList() { + return mailList_; + } + /** repeated .MailData mail_list = 10; */ + @java.lang.Override + public java.util.List + getMailListOrBuilderList() { + return mailList_; + } + /** repeated .MailData mail_list = 10; */ + @java.lang.Override + public int getMailListCount() { + return mailList_.size(); + } + /** repeated .MailData mail_list = 10; */ + @java.lang.Override + public emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index) { + return mailList_.get(index); + } + /** repeated .MailData mail_list = 10; */ + @java.lang.Override + public emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder( + int index) { + return mailList_.get(index); + } + + public static final int TRANSACTION_FIELD_NUMBER = 5; + private volatile java.lang.Object transaction_; + /** + * string transaction = 5; + * + * @return The transaction. + */ + @java.lang.Override + public java.lang.String getTransaction() { + java.lang.Object ref = transaction_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + transaction_ = s; + return s; + } + } + /** + * string transaction = 5; + * + * @return The bytes for transaction. + */ + @java.lang.Override + public com.google.protobuf.ByteString getTransactionBytes() { + java.lang.Object ref = transaction_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + transaction_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int IS_COLLECTED_FIELD_NUMBER = 8; + private boolean isCollected_; + /** + * bool is_collected = 8; + * + * @return The isCollected. + */ + @java.lang.Override + public boolean getIsCollected() { + return isCollected_; + } + + public static final int PACKET_NUM_FIELD_NUMBER = 13; + private int packetNum_; + /** + * uint32 packet_num = 13; + * + * @return The packetNum. + */ + @java.lang.Override + public int getPacketNum() { + return packetNum_; + } + + private byte memoizedIsInitialized = -1; + + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { + if (packetBeSentNum_ != 0) { + output.writeUInt32(1, packetBeSentNum_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(transaction_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, transaction_); + } + if (retcode_ != 0) { + output.writeInt32(6, retcode_); + } + if (isCollected_ != false) { + output.writeBool(8, isCollected_); + } + for (int i = 0; i < mailList_.size(); i++) { + output.writeMessage(10, mailList_.get(i)); + } + if (packetNum_ != 0) { + output.writeUInt32(13, packetNum_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (packetBeSentNum_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeUInt32Size(1, packetBeSentNum_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(transaction_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, transaction_); + } + if (retcode_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeInt32Size(6, retcode_); + } + if (isCollected_ != false) { + size += com.google.protobuf.CodedOutputStream.computeBoolSize(8, isCollected_); + } + for (int i = 0; i < mailList_.size(); i++) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(10, mailList_.get(i)); + } + if (packetNum_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeUInt32Size(13, packetNum_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj + instanceof + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify)) { + return super.equals(obj); + } + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify other = + (emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) obj; + + if (getRetcode() != other.getRetcode()) return false; + if (getPacketBeSentNum() != other.getPacketBeSentNum()) return false; + if (!getMailListList().equals(other.getMailListList())) return false; + if (!getTransaction().equals(other.getTransaction())) return false; + if (getIsCollected() != other.getIsCollected()) return false; + if (getPacketNum() != other.getPacketNum()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + RETCODE_FIELD_NUMBER; + hash = (53 * hash) + getRetcode(); + hash = (37 * hash) + PACKET_BE_SENT_NUM_FIELD_NUMBER; + hash = (53 * hash) + getPacketBeSentNum(); + if (getMailListCount() > 0) { + hash = (37 * hash) + MAIL_LIST_FIELD_NUMBER; + hash = (53 * hash) + getMailListList().hashCode(); + } + hash = (37 * hash) + TRANSACTION_FIELD_NUMBER; + hash = (53 * hash) + getTransaction().hashCode(); + hash = (37 * hash) + IS_COLLECTED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(getIsCollected()); + hash = (37 * hash) + PACKET_NUM_FIELD_NUMBER; + hash = (53 * hash) + getPacketNum(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom( + java.nio.ByteBuffer data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseDelimitedFrom( + java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom(com.google.protobuf.CodedInputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder( + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * + * + *
+         * Name: ILFMLLOCKFB
+         * CmdId: 1429
+         * 
+ * + * Protobuf type {@code GetAllMailResultNotify} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:GetAllMailResultNotify) + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotifyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .internal_static_GetAllMailResultNotify_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .internal_static_GetAllMailResultNotify_fieldAccessorTable + .ensureFieldAccessorsInitialized( + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .class, + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .Builder.class); + } + + // Construct using + // emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) { + getMailListFieldBuilder(); + } + } + + @java.lang.Override + public Builder clear() { + super.clear(); + retcode_ = 0; + + packetBeSentNum_ = 0; + + if (mailListBuilder_ == null) { + mailList_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + mailListBuilder_.clear(); + } + transaction_ = ""; + + isCollected_ = false; + + packetNum_ = 0; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .internal_static_GetAllMailResultNotify_descriptor; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + getDefaultInstanceForType() { + return emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .getDefaultInstance(); + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + build() { + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify result = + buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + buildPartial() { + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify result = + new emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify( + this); + int from_bitField0_ = bitField0_; + result.retcode_ = retcode_; + result.packetBeSentNum_ = packetBeSentNum_; + if (mailListBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + mailList_ = java.util.Collections.unmodifiableList(mailList_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.mailList_ = mailList_; + } else { + result.mailList_ = mailListBuilder_.build(); + } + result.transaction_ = transaction_; + result.isCollected_ = isCollected_; + result.packetNum_ = packetNum_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.setField(field, value); + } + + @java.lang.Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @java.lang.Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, java.lang.Object value) { + return super.addRepeatedField(field, value); + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other + instanceof + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) { + return mergeFrom( + (emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) + other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom( + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify other) { + if (other + == emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + .getDefaultInstance()) return this; + if (other.getRetcode() != 0) { + setRetcode(other.getRetcode()); + } + if (other.getPacketBeSentNum() != 0) { + setPacketBeSentNum(other.getPacketBeSentNum()); + } + if (mailListBuilder_ == null) { + if (!other.mailList_.isEmpty()) { + if (mailList_.isEmpty()) { + mailList_ = other.mailList_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureMailListIsMutable(); + mailList_.addAll(other.mailList_); + } + onChanged(); + } + } else { + if (!other.mailList_.isEmpty()) { + if (mailListBuilder_.isEmpty()) { + mailListBuilder_.dispose(); + mailListBuilder_ = null; + mailList_ = other.mailList_; + bitField0_ = (bitField0_ & ~0x00000001); + mailListBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getMailListFieldBuilder() + : null; + } else { + mailListBuilder_.addAllMessages(other.mailList_); + } + } + } + if (!other.getTransaction().isEmpty()) { + transaction_ = other.transaction_; + onChanged(); + } + if (other.getIsCollected() != false) { + setIsCollected(other.getIsCollected()); + } + if (other.getPacketNum() != 0) { + setPacketNum(other.getPacketNum()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = + (emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify) + e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int bitField0_; + + private int retcode_; + /** + * int32 retcode = 6; + * + * @return The retcode. + */ + @java.lang.Override + public int getRetcode() { + return retcode_; + } + /** + * int32 retcode = 6; + * + * @param value The retcode to set. + * @return This builder for chaining. + */ + public Builder setRetcode(int value) { + + retcode_ = value; + onChanged(); + return this; + } + /** + * int32 retcode = 6; + * + * @return This builder for chaining. + */ + public Builder clearRetcode() { + + retcode_ = 0; + onChanged(); + return this; + } + + private int packetBeSentNum_; + /** + * uint32 packet_be_sent_num = 1; + * + * @return The packetBeSentNum. + */ + @java.lang.Override + public int getPacketBeSentNum() { + return packetBeSentNum_; + } + /** + * uint32 packet_be_sent_num = 1; + * + * @param value The packetBeSentNum to set. + * @return This builder for chaining. + */ + public Builder setPacketBeSentNum(int value) { + + packetBeSentNum_ = value; + onChanged(); + return this; + } + /** + * uint32 packet_be_sent_num = 1; + * + * @return This builder for chaining. + */ + public Builder clearPacketBeSentNum() { + + packetBeSentNum_ = 0; + onChanged(); + return this; + } + + private java.util.List mailList_ = + java.util.Collections.emptyList(); + + private void ensureMailListIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + mailList_ = + new java.util.ArrayList( + mailList_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + emu.grasscutter.net.proto.MailDataOuterClass.MailData, + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, + emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder> + mailListBuilder_; + + /** repeated .MailData mail_list = 10; */ + public java.util.List + getMailListList() { + if (mailListBuilder_ == null) { + return java.util.Collections.unmodifiableList(mailList_); + } else { + return mailListBuilder_.getMessageList(); + } + } + /** repeated .MailData mail_list = 10; */ + public int getMailListCount() { + if (mailListBuilder_ == null) { + return mailList_.size(); + } else { + return mailListBuilder_.getCount(); + } + } + /** repeated .MailData mail_list = 10; */ + public emu.grasscutter.net.proto.MailDataOuterClass.MailData getMailList(int index) { + if (mailListBuilder_ == null) { + return mailList_.get(index); + } else { + return mailListBuilder_.getMessage(index); + } + } + /** repeated .MailData mail_list = 10; */ + public Builder setMailList( + int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { + if (mailListBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMailListIsMutable(); + mailList_.set(index, value); + onChanged(); + } else { + mailListBuilder_.setMessage(index, value); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder setMailList( + int index, + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { + if (mailListBuilder_ == null) { + ensureMailListIsMutable(); + mailList_.set(index, builderForValue.build()); + onChanged(); + } else { + mailListBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder addMailList(emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { + if (mailListBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMailListIsMutable(); + mailList_.add(value); + onChanged(); + } else { + mailListBuilder_.addMessage(value); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder addMailList( + int index, emu.grasscutter.net.proto.MailDataOuterClass.MailData value) { + if (mailListBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMailListIsMutable(); + mailList_.add(index, value); + onChanged(); + } else { + mailListBuilder_.addMessage(index, value); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder addMailList( + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { + if (mailListBuilder_ == null) { + ensureMailListIsMutable(); + mailList_.add(builderForValue.build()); + onChanged(); + } else { + mailListBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder addMailList( + int index, + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder builderForValue) { + if (mailListBuilder_ == null) { + ensureMailListIsMutable(); + mailList_.add(index, builderForValue.build()); + onChanged(); + } else { + mailListBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder addAllMailList( + java.lang.Iterable + values) { + if (mailListBuilder_ == null) { + ensureMailListIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, mailList_); + onChanged(); + } else { + mailListBuilder_.addAllMessages(values); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder clearMailList() { + if (mailListBuilder_ == null) { + mailList_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + mailListBuilder_.clear(); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public Builder removeMailList(int index) { + if (mailListBuilder_ == null) { + ensureMailListIsMutable(); + mailList_.remove(index); + onChanged(); + } else { + mailListBuilder_.remove(index); + } + return this; + } + /** repeated .MailData mail_list = 10; */ + public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder getMailListBuilder( + int index) { + return getMailListFieldBuilder().getBuilder(index); + } + /** repeated .MailData mail_list = 10; */ + public emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder getMailListOrBuilder( + int index) { + if (mailListBuilder_ == null) { + return mailList_.get(index); + } else { + return mailListBuilder_.getMessageOrBuilder(index); + } + } + /** repeated .MailData mail_list = 10; */ + public java.util.List< + ? extends emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder> + getMailListOrBuilderList() { + if (mailListBuilder_ != null) { + return mailListBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(mailList_); + } + } + /** repeated .MailData mail_list = 10; */ + public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder addMailListBuilder() { + return getMailListFieldBuilder() + .addBuilder(emu.grasscutter.net.proto.MailDataOuterClass.MailData.getDefaultInstance()); + } + /** repeated .MailData mail_list = 10; */ + public emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder addMailListBuilder( + int index) { + return getMailListFieldBuilder() + .addBuilder( + index, emu.grasscutter.net.proto.MailDataOuterClass.MailData.getDefaultInstance()); + } + /** repeated .MailData mail_list = 10; */ + public java.util.List + getMailListBuilderList() { + return getMailListFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + emu.grasscutter.net.proto.MailDataOuterClass.MailData, + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, + emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder> + getMailListFieldBuilder() { + if (mailListBuilder_ == null) { + mailListBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + emu.grasscutter.net.proto.MailDataOuterClass.MailData, + emu.grasscutter.net.proto.MailDataOuterClass.MailData.Builder, + emu.grasscutter.net.proto.MailDataOuterClass.MailDataOrBuilder>( + mailList_, ((bitField0_ & 0x00000001) != 0), getParentForChildren(), isClean()); + mailList_ = null; + } + return mailListBuilder_; + } + + private java.lang.Object transaction_ = ""; + /** + * string transaction = 5; + * + * @return The transaction. + */ + public java.lang.String getTransaction() { + java.lang.Object ref = transaction_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + transaction_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string transaction = 5; + * + * @return The bytes for transaction. + */ + public com.google.protobuf.ByteString getTransactionBytes() { + java.lang.Object ref = transaction_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); + transaction_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string transaction = 5; + * + * @param value The transaction to set. + * @return This builder for chaining. + */ + public Builder setTransaction(java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + transaction_ = value; + onChanged(); + return this; + } + /** + * string transaction = 5; + * + * @return This builder for chaining. + */ + public Builder clearTransaction() { + + transaction_ = getDefaultInstance().getTransaction(); + onChanged(); + return this; + } + /** + * string transaction = 5; + * + * @param value The bytes for transaction to set. + * @return This builder for chaining. + */ + public Builder setTransactionBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + transaction_ = value; + onChanged(); + return this; + } + + private boolean isCollected_; + /** + * bool is_collected = 8; + * + * @return The isCollected. + */ + @java.lang.Override + public boolean getIsCollected() { + return isCollected_; + } + /** + * bool is_collected = 8; + * + * @param value The isCollected to set. + * @return This builder for chaining. + */ + public Builder setIsCollected(boolean value) { + + isCollected_ = value; + onChanged(); + return this; + } + /** + * bool is_collected = 8; + * + * @return This builder for chaining. + */ + public Builder clearIsCollected() { + + isCollected_ = false; + onChanged(); + return this; + } + + private int packetNum_; + /** + * uint32 packet_num = 13; + * + * @return The packetNum. + */ + @java.lang.Override + public int getPacketNum() { + return packetNum_; + } + /** + * uint32 packet_num = 13; + * + * @param value The packetNum to set. + * @return This builder for chaining. + */ + public Builder setPacketNum(int value) { + + packetNum_ = value; + onChanged(); + return this; + } + /** + * uint32 packet_num = 13; + * + * @return This builder for chaining. + */ + public Builder clearPacketNum() { + + packetNum_ = 0; + onChanged(); + return this; + } + + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:GetAllMailResultNotify) + } + + // @@protoc_insertion_point(class_scope:GetAllMailResultNotify) + private static final emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass + .GetAllMailResultNotify + DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = + new emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify(); + } + + public static emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @java.lang.Override + public GetAllMailResultNotify parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GetAllMailResultNotify(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify + getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_GetAllMailResultNotify_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_GetAllMailResultNotify_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + return descriptor; + } + + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + + static { + java.lang.String[] descriptorData = { + "\n\034GetAllMailResultNotify.proto\032\016MailData" + + ".proto\"\242\001\n\026GetAllMailResultNotify\022\017\n\007ret" + + "code\030\006 \001(\005\022\032\n\022packet_be_sent_num\030\001 \001(\r\022\034" + + "\n\tmail_list\030\n \003(\0132\t.MailData\022\023\n\013transact" + + "ion\030\005 \001(\t\022\024\n\014is_collected\030\010 \001(\010\022\022\n\npacke" + + "t_num\030\r \001(\rB\033\n\031emu.grasscutter.net.proto" + + "b\006proto3" + }; + descriptor = + com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( + descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + emu.grasscutter.net.proto.MailDataOuterClass.getDescriptor(), + }); + internal_static_GetAllMailResultNotify_descriptor = getDescriptor().getMessageTypes().get(0); + internal_static_GetAllMailResultNotify_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_GetAllMailResultNotify_descriptor, + new java.lang.String[] { + "Retcode", "PacketBeSentNum", "MailList", "Transaction", "IsCollected", "PacketNum", + }); + emu.grasscutter.net.proto.MailDataOuterClass.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/main/java/emu/grasscutter/command/commands/EntityCommand.java b/src/main/java/emu/grasscutter/command/commands/EntityCommand.java index 38b9c397e..6bfb6ebde 100644 --- a/src/main/java/emu/grasscutter/command/commands/EntityCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EntityCommand.java @@ -1,139 +1,139 @@ -package emu.grasscutter.command.commands; - -import static emu.grasscutter.command.CommandHelpers.*; -import static emu.grasscutter.utils.Language.translate; - -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.game.entity.*; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.server.event.entity.EntityDamageEvent; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.regex.Pattern; -import lombok.Setter; - -@Command( - label = "entity", - usage = { - " [state] [maxhp] [hp(0 for infinite)] [atk] [def]", - " [ai] [maxhp] [hp(0 for infinite)] [atk] [def]" - }, - permission = "server.entity") -public final class EntityCommand implements CommandHandler { - private static final Map> intCommandHandlers = - Map.ofEntries( - Map.entry(stateRegex, EntityParameters::setState), - Map.entry(maxHPRegex, EntityParameters::setMaxHP), - Map.entry(hpRegex, EntityParameters::setHp), - Map.entry(defRegex, EntityParameters::setDef), - Map.entry(atkRegex, EntityParameters::setAtk), - Map.entry(aiRegex, EntityParameters::setAi)); - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - EntityParameters param = new EntityParameters(); - - parseIntParameters(args, param, intCommandHandlers); - - // At this point, first remaining argument MUST be the id and the rest the pos - if (args.size() != 1) { - sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar - throw new IllegalArgumentException(); - } - - try { - param.configId = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.cfgId")); - } - - param.scene = targetPlayer.getScene(); - var entity = param.scene.getEntityByConfigId(param.configId); - - if (entity == null) { - CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error")); - return; - } - applyFightProps(entity, param); - applyGadgetParams(entity, param); - applyMonsterParams(entity, param); - - CommandHandler.sendMessage(sender, translate(sender, "commands.status.success")); - } - - private void applyGadgetParams(GameEntity entity, EntityParameters param) { - if (!(entity instanceof EntityGadget)) { - return; - } - if (param.state != -1) { - ((EntityGadget) entity).updateState(param.state); - } - } - - private void applyMonsterParams(GameEntity entity, EntityParameters param) { - if (!(entity instanceof EntityMonster)) { - return; - } - - if (param.ai != -1) { - ((EntityMonster) entity).setAiId(param.ai); - // TODO notify - } - } - - private void applyFightProps(GameEntity entity, EntityParameters param) { - var changedFields = new ArrayList(); - if (param.maxHP != -1) { - setFightProperty(entity, FightProperty.FIGHT_PROP_MAX_HP, param.maxHP, changedFields); - } - if (param.hp != -1) { - float targetHp = param.hp == 0 ? Float.MAX_VALUE : param.hp; - float oldHp = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_HP, targetHp, changedFields); - EntityDamageEvent event = - new EntityDamageEvent(entity, oldHp - targetHp, ElementType.None, null); - callHPEvents(entity, event); - } - if (param.atk != -1) { - setFightProperty(entity, FightProperty.FIGHT_PROP_ATTACK, param.atk, changedFields); - setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk, changedFields); - } - if (param.def != -1) { - setFightProperty(entity, FightProperty.FIGHT_PROP_DEFENSE, param.def, changedFields); - setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def, changedFields); - } - if (!changedFields.isEmpty()) { - entity - .getScene() - .broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, changedFields)); - } - } - - private void callHPEvents(GameEntity entity, EntityDamageEvent event) { - entity.runLuaCallbacks(event); - } - - private void setFightProperty( - GameEntity entity, FightProperty property, float value, List modifiedProps) { - entity.setFightProperty(property, value); - modifiedProps.add(property); - } - - private static class EntityParameters { - @Setter public int configId = -1; - @Setter public int state = -1; - @Setter public int hp = -1; - @Setter public int maxHP = -1; - @Setter public int atk = -1; - @Setter public int def = -1; - @Setter public int ai = -1; - public Scene scene = null; - } -} +package emu.grasscutter.command.commands; + +import static emu.grasscutter.command.CommandHelpers.*; +import static emu.grasscutter.utils.Language.translate; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.server.event.entity.EntityDamageEvent; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.regex.Pattern; +import lombok.Setter; + +@Command( + label = "entity", + usage = { + " [state] [maxhp] [hp(0 for infinite)] [atk] [def]", + " [ai] [maxhp] [hp(0 for infinite)] [atk] [def]" + }, + permission = "server.entity") +public final class EntityCommand implements CommandHandler { + private static final Map> intCommandHandlers = + Map.ofEntries( + Map.entry(stateRegex, EntityParameters::setState), + Map.entry(maxHPRegex, EntityParameters::setMaxHP), + Map.entry(hpRegex, EntityParameters::setHp), + Map.entry(defRegex, EntityParameters::setDef), + Map.entry(atkRegex, EntityParameters::setAtk), + Map.entry(aiRegex, EntityParameters::setAi)); + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + EntityParameters param = new EntityParameters(); + + parseIntParameters(args, param, intCommandHandlers); + + // At this point, first remaining argument MUST be the id and the rest the pos + if (args.size() != 1) { + sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar + throw new IllegalArgumentException(); + } + + try { + param.configId = Integer.parseInt(args.get(0)); + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.cfgId")); + } + + param.scene = targetPlayer.getScene(); + var entity = param.scene.getEntityByConfigId(param.configId); + + if (entity == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error")); + return; + } + applyFightProps(entity, param); + applyGadgetParams(entity, param); + applyMonsterParams(entity, param); + + CommandHandler.sendMessage(sender, translate(sender, "commands.status.success")); + } + + private void applyGadgetParams(GameEntity entity, EntityParameters param) { + if (!(entity instanceof EntityGadget)) { + return; + } + if (param.state != -1) { + ((EntityGadget) entity).updateState(param.state); + } + } + + private void applyMonsterParams(GameEntity entity, EntityParameters param) { + if (!(entity instanceof EntityMonster)) { + return; + } + + if (param.ai != -1) { + ((EntityMonster) entity).setAiId(param.ai); + // TODO notify + } + } + + private void applyFightProps(GameEntity entity, EntityParameters param) { + var changedFields = new ArrayList(); + if (param.maxHP != -1) { + setFightProperty(entity, FightProperty.FIGHT_PROP_MAX_HP, param.maxHP, changedFields); + } + if (param.hp != -1) { + float targetHp = param.hp == 0 ? Float.MAX_VALUE : param.hp; + float oldHp = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_HP, targetHp, changedFields); + EntityDamageEvent event = + new EntityDamageEvent(entity, oldHp - targetHp, ElementType.None, null); + callHPEvents(entity, event); + } + if (param.atk != -1) { + setFightProperty(entity, FightProperty.FIGHT_PROP_ATTACK, param.atk, changedFields); + setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk, changedFields); + } + if (param.def != -1) { + setFightProperty(entity, FightProperty.FIGHT_PROP_DEFENSE, param.def, changedFields); + setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def, changedFields); + } + if (!changedFields.isEmpty()) { + entity + .getScene() + .broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, changedFields)); + } + } + + private void callHPEvents(GameEntity entity, EntityDamageEvent event) { + entity.runLuaCallbacks(event); + } + + private void setFightProperty( + GameEntity entity, FightProperty property, float value, List modifiedProps) { + entity.setFightProperty(property, value); + modifiedProps.add(property); + } + + private static class EntityParameters { + @Setter public int configId = -1; + @Setter public int state = -1; + @Setter public int hp = -1; + @Setter public int maxHP = -1; + @Setter public int atk = -1; + @Setter public int def = -1; + @Setter public int ai = -1; + public Scene scene = null; + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java index 2609e3677..b30e1e4d6 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetPropCommand.java @@ -1,279 +1,279 @@ -package emu.grasscutter.command.commands; - -import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.tower.TowerLevelRecord; -import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; -import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Command( - label = "setProp", - aliases = {"prop"}, - usage = {" "}, - permission = "player.setprop", - permissionTargeted = "player.setprop.others") -public final class SetPropCommand implements CommandHandler { - // List of map areas. Unfortunately, there is no readily available source for them in excels or - // bins. - private static final List sceneAreas = - List.of( - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403); - Map props; - - public SetPropCommand() { - this.props = new HashMap<>(); - // Full PlayerProperty enum that won't be advertised but can be used by devs - for (PlayerProperty prop : PlayerProperty.values()) { - String name = prop.toString().substring(5); // PROP_EXP -> EXP - String key = name.toLowerCase(); // EXP -> exp - this.props.put(key, new Prop(name, prop)); - } - // Add special props - Prop worldlevel = - new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); - this.props.put("worldlevel", worldlevel); - this.props.put("wl", worldlevel); - - Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); - this.props.put("abyss", abyss); - this.props.put("abyssfloor", abyss); - this.props.put("ut", abyss); - this.props.put("tower", abyss); - this.props.put("towerlevel", abyss); - this.props.put("unlocktower", abyss); - - Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); - this.props.put("bplevel", bplevel); - this.props.put("bp", bplevel); - this.props.put("battlepass", bplevel); - - Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); - this.props.put("godmode", godmode); - this.props.put("god", godmode); - - Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); - this.props.put("unlimitedstamina", nostamina); - this.props.put("us", nostamina); - this.props.put("nostamina", nostamina); - this.props.put("nostam", nostamina); - this.props.put("ns", nostamina); - - Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); - this.props.put("unlimitedenergy", unlimitedenergy); - this.props.put("ue", unlimitedenergy); - - Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); - this.props.put("setopenstate", setopenstate); - this.props.put("so", setopenstate); - - Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE); - this.props.put("unsetopenstate", unsetopenstate); - this.props.put("uo", unsetopenstate); - - Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP); - this.props.put("unlockmap", unlockmap); - this.props.put("um", unlockmap); - } - - @Override - public void execute(Player sender, Player targetPlayer, List args) { - if (args.size() != 2) { - sendUsageMessage(sender); - return; - } - String propStr = args.get(0).toLowerCase(); - String valueStr = args.get(1).toLowerCase(); - int value; - - if (!props.containsKey(propStr)) { - sendUsageMessage(sender); - return; - } - try { - value = - switch (valueStr.toLowerCase()) { - case "on", "true" -> 1; - case "off", "false" -> 0; - case "toggle" -> -1; - default -> Integer.parseInt(valueStr); - }; - } catch (NumberFormatException ignored) { - CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); - return; - } - - boolean success = false; - Prop prop = props.get(propStr); - - success = - switch (prop.pseudoProp) { - case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); - case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); - case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); - case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool( - sender, targetPlayer, prop.pseudoProp, value); - case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); - case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); - case UNLOCK_MAP -> unlockMap(targetPlayer); - default -> targetPlayer.setProperty(prop.prop, value); - }; - - if (success) { - if (targetPlayer == sender) { - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.set_to", prop.name, valueStr); - } else { - String uidStr = targetPlayer.getAccount().getId(); - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); - } - } else { - if (prop.prop - != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages - int min = targetPlayer.getPropertyMin(prop.prop); - int max = targetPlayer.getPropertyMax(prop.prop); - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.invalid.value_between", prop.name, min, max); - } - } - } - - private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { - List floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); - if (topFloor < 0 || topFloor > floorIds.size()) { - CommandHandler.sendTranslatedMessage( - sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); - return false; - } - - Map recordMap = targetPlayer.getTowerManager().getRecordMap(); - // Add records for each unlocked floor - for (int floor : floorIds.subList(0, topFloor)) { - if (!recordMap.containsKey(floor)) { - recordMap.put(floor, new TowerLevelRecord(floor)); - } - } - // Remove records for each floor past our target - for (int floor : floorIds.subList(topFloor, floorIds.size())) { - recordMap.remove(floor); - } - // Six stars required on Floor 8 to unlock Floor 9+ - if (topFloor > 8) { - recordMap - .get(floorIds.get(7)) - .setLevelStars( - 0, - 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at - // all - } - return true; - } - - private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { - boolean enabled = - switch (pseudoProp) { - case GOD_MODE -> targetPlayer.isInGodMode(); - case UNLIMITED_STAMINA -> targetPlayer.isUnlimitedStamina(); - case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().isEnergyUsage(); - default -> false; - }; - enabled = - switch (value) { - case -1 -> !enabled; - case 0 -> false; - default -> true; - }; - - switch (pseudoProp) { - case GOD_MODE: - targetPlayer.setInGodMode(enabled); - break; - case UNLIMITED_STAMINA: - targetPlayer.setUnlimitedStamina(enabled); - break; - case UNLIMITED_ENERGY: - targetPlayer.getEnergyManager().setEnergyUsage(!enabled); - break; - default: - return false; - } - return true; - } - - private boolean setOpenState(Player targetPlayer, int state, int value) { - targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); - return true; - } - - private boolean unlockMap(Player targetPlayer) { - // Unlock. - GameData.getScenePointsPerScene() - .forEach( - (sceneId, scenePoints) -> { - // Unlock trans points. - targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); - - // Unlock map areas. - targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); - }); - - // Send notify. - int playerScene = targetPlayer.getSceneId(); - targetPlayer.sendPacket( - new PacketScenePointUnlockNotify( - playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); - targetPlayer.sendPacket( - new PacketSceneAreaUnlockNotify( - playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); - return true; - } - - enum PseudoProp { - NONE, - WORLD_LEVEL, - TOWER_LEVEL, - BP_LEVEL, - GOD_MODE, - UNLIMITED_STAMINA, - UNLIMITED_ENERGY, - SET_OPENSTATE, - UNSET_OPENSTATE, - UNLOCK_MAP - } - - static class Prop { - String name; - PlayerProperty prop; - PseudoProp pseudoProp; - - public Prop(PlayerProperty prop) { - this(prop.toString(), prop, PseudoProp.NONE); - } - - public Prop(String name) { - this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); - } - - public Prop(String name, PseudoProp pseudoProp) { - this(name, PlayerProperty.PROP_NONE, pseudoProp); - } - - public Prop(String name, PlayerProperty prop) { - this(name, prop, PseudoProp.NONE); - } - - public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { - this.name = name; - this.prop = prop; - this.pseudoProp = pseudoProp; - } - } -} +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.tower.TowerLevelRecord; +import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; +import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Command( + label = "setProp", + aliases = {"prop"}, + usage = {" "}, + permission = "player.setprop", + permissionTargeted = "player.setprop.others") +public final class SetPropCommand implements CommandHandler { + // List of map areas. Unfortunately, there is no readily available source for them in excels or + // bins. + private static final List sceneAreas = + List.of( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403); + Map props; + + public SetPropCommand() { + this.props = new HashMap<>(); + // Full PlayerProperty enum that won't be advertised but can be used by devs + for (PlayerProperty prop : PlayerProperty.values()) { + String name = prop.toString().substring(5); // PROP_EXP -> EXP + String key = name.toLowerCase(); // EXP -> exp + this.props.put(key, new Prop(name, prop)); + } + // Add special props + Prop worldlevel = + new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); + this.props.put("worldlevel", worldlevel); + this.props.put("wl", worldlevel); + + Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); + this.props.put("abyss", abyss); + this.props.put("abyssfloor", abyss); + this.props.put("ut", abyss); + this.props.put("tower", abyss); + this.props.put("towerlevel", abyss); + this.props.put("unlocktower", abyss); + + Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); + this.props.put("bplevel", bplevel); + this.props.put("bp", bplevel); + this.props.put("battlepass", bplevel); + + Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); + this.props.put("godmode", godmode); + this.props.put("god", godmode); + + Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); + this.props.put("unlimitedstamina", nostamina); + this.props.put("us", nostamina); + this.props.put("nostamina", nostamina); + this.props.put("nostam", nostamina); + this.props.put("ns", nostamina); + + Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); + this.props.put("unlimitedenergy", unlimitedenergy); + this.props.put("ue", unlimitedenergy); + + Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); + this.props.put("setopenstate", setopenstate); + this.props.put("so", setopenstate); + + Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE); + this.props.put("unsetopenstate", unsetopenstate); + this.props.put("uo", unsetopenstate); + + Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP); + this.props.put("unlockmap", unlockmap); + this.props.put("um", unlockmap); + } + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (args.size() != 2) { + sendUsageMessage(sender); + return; + } + String propStr = args.get(0).toLowerCase(); + String valueStr = args.get(1).toLowerCase(); + int value; + + if (!props.containsKey(propStr)) { + sendUsageMessage(sender); + return; + } + try { + value = + switch (valueStr.toLowerCase()) { + case "on", "true" -> 1; + case "off", "false" -> 0; + case "toggle" -> -1; + default -> Integer.parseInt(valueStr); + }; + } catch (NumberFormatException ignored) { + CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); + return; + } + + boolean success = false; + Prop prop = props.get(propStr); + + success = + switch (prop.pseudoProp) { + case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); + case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); + case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); + case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool( + sender, targetPlayer, prop.pseudoProp, value); + case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); + case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); + case UNLOCK_MAP -> unlockMap(targetPlayer); + default -> targetPlayer.setProperty(prop.prop, value); + }; + + if (success) { + if (targetPlayer == sender) { + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.set_to", prop.name, valueStr); + } else { + String uidStr = targetPlayer.getAccount().getId(); + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); + } + } else { + if (prop.prop + != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages + int min = targetPlayer.getPropertyMin(prop.prop); + int max = targetPlayer.getPropertyMax(prop.prop); + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.invalid.value_between", prop.name, min, max); + } + } + } + + private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { + List floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); + if (topFloor < 0 || topFloor > floorIds.size()) { + CommandHandler.sendTranslatedMessage( + sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); + return false; + } + + Map recordMap = targetPlayer.getTowerManager().getRecordMap(); + // Add records for each unlocked floor + for (int floor : floorIds.subList(0, topFloor)) { + if (!recordMap.containsKey(floor)) { + recordMap.put(floor, new TowerLevelRecord(floor)); + } + } + // Remove records for each floor past our target + for (int floor : floorIds.subList(topFloor, floorIds.size())) { + recordMap.remove(floor); + } + // Six stars required on Floor 8 to unlock Floor 9+ + if (topFloor > 8) { + recordMap + .get(floorIds.get(7)) + .setLevelStars( + 0, + 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at + // all + } + return true; + } + + private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { + boolean enabled = + switch (pseudoProp) { + case GOD_MODE -> targetPlayer.isInGodMode(); + case UNLIMITED_STAMINA -> targetPlayer.isUnlimitedStamina(); + case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().isEnergyUsage(); + default -> false; + }; + enabled = + switch (value) { + case -1 -> !enabled; + case 0 -> false; + default -> true; + }; + + switch (pseudoProp) { + case GOD_MODE: + targetPlayer.setInGodMode(enabled); + break; + case UNLIMITED_STAMINA: + targetPlayer.setUnlimitedStamina(enabled); + break; + case UNLIMITED_ENERGY: + targetPlayer.getEnergyManager().setEnergyUsage(!enabled); + break; + default: + return false; + } + return true; + } + + private boolean setOpenState(Player targetPlayer, int state, int value) { + targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); + return true; + } + + private boolean unlockMap(Player targetPlayer) { + // Unlock. + GameData.getScenePointsPerScene() + .forEach( + (sceneId, scenePoints) -> { + // Unlock trans points. + targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); + + // Unlock map areas. + targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); + }); + + // Send notify. + int playerScene = targetPlayer.getSceneId(); + targetPlayer.sendPacket( + new PacketScenePointUnlockNotify( + playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); + targetPlayer.sendPacket( + new PacketSceneAreaUnlockNotify( + playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); + return true; + } + + enum PseudoProp { + NONE, + WORLD_LEVEL, + TOWER_LEVEL, + BP_LEVEL, + GOD_MODE, + UNLIMITED_STAMINA, + UNLIMITED_ENERGY, + SET_OPENSTATE, + UNSET_OPENSTATE, + UNLOCK_MAP + } + + static class Prop { + String name; + PlayerProperty prop; + PseudoProp pseudoProp; + + public Prop(PlayerProperty prop) { + this(prop.toString(), prop, PseudoProp.NONE); + } + + public Prop(String name) { + this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); + } + + public Prop(String name, PseudoProp pseudoProp) { + this(name, PlayerProperty.PROP_NONE, pseudoProp); + } + + public Prop(String name, PlayerProperty prop) { + this(name, prop, PseudoProp.NONE); + } + + public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { + this.name = name; + this.prop = prop; + this.pseudoProp = pseudoProp; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index e35cd33da..5b581d077 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -1,606 +1,618 @@ -package emu.grasscutter.data; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.binout.*; -import emu.grasscutter.data.binout.config.*; -import emu.grasscutter.data.binout.routes.*; -import emu.grasscutter.data.custom.*; -import emu.grasscutter.data.excels.*; -import emu.grasscutter.data.excels.achievement.*; -import emu.grasscutter.data.excels.activity.*; -import emu.grasscutter.data.excels.avatar.*; -import emu.grasscutter.data.excels.codex.*; -import emu.grasscutter.data.excels.dungeon.*; -import emu.grasscutter.data.excels.monster.*; -import emu.grasscutter.data.excels.reliquary.*; -import emu.grasscutter.data.excels.tower.*; -import emu.grasscutter.data.excels.trial.*; -import emu.grasscutter.data.excels.weapon.*; -import emu.grasscutter.data.excels.world.*; -import emu.grasscutter.data.server.*; -import emu.grasscutter.game.dungeons.*; -import emu.grasscutter.game.quest.*; -import emu.grasscutter.game.world.*; -import emu.grasscutter.utils.Utils; -import it.unimi.dsi.fastutil.ints.*; -import java.lang.reflect.Field; -import java.util.*; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.val; - -@SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) -public final class GameData { - @Getter private static final Map abilityDataMap = new HashMap<>(); - - @Getter - private static final Int2ObjectMap scenePointEntryMap = - new Int2ObjectOpenHashMap<>(); - - // BinOutputs - @Getter - private static final Int2ObjectMap homeworldDefaultSaveData = - new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2ObjectMap abilityHashes = new Int2ObjectOpenHashMap<>(); - - @Deprecated(forRemoval = true) - @Getter - private static final Map abilityModifiers = new HashMap<>(); - - @Getter private static final Map avatarConfigData = new HashMap<>(); - @Getter private static final Map gadgetConfigData = new HashMap<>(); - @Getter private static final Map monsterConfigData = new HashMap<>(); - - @Getter private static final Map openConfigEntries = new HashMap<>(); - - private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap questsKeys = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap npcBornData = new Int2ObjectOpenHashMap<>(); - private static final Map abilityEmbryos = new HashMap<>(); - - // ExcelConfigs - @Getter - private static final Int2ObjectMap activityCondExcelConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap dungeonPassConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap dungeonChallengeConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap> sceneRouteData = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final ArrayList codexReliquaryArrayList = new ArrayList<>(); - - private static final Int2ObjectMap achievementDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap achievementGoalDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap activityDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap activityShopDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap activityWatcherDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarCostumeDataItemIdMap = - new Int2ObjectLinkedOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarCostumeDataMap = - new Int2ObjectLinkedOpenHashMap<>(); - - @Getter private static final Int2ObjectMap avatarReplaceCostumeDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarCurveDataMap = - new Int2ObjectLinkedOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarFetterLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarFlycloakDataMap = - new Int2ObjectLinkedOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarSkillDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarSkillDepotDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap avatarTalentDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap battlePassMissionDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap battlePassRewardDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap - blossomRefreshExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2ObjectMap buffDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap chapterDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2ObjectMap cityDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap codexAnimalDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap codexMaterialDataIdMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap codexQuestDataIdMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap codexReliquaryDataIdMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap codexWeaponDataIdMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap cookBonusDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap cookRecipeDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap compoundDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap dailyDungeonDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap dungeonEntryDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap envAnimalGatherConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap equipAffixDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap fetterCharacterCardDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap forgeDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap furnitureMakeConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap gadgetDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap gatherDataMap = new Int2ObjectOpenHashMap<>(); - @Getter @Deprecated // This is to prevent people from using this map. This is for the resource loader only! - private static final Int2ObjectMap guideTriggerDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap homeWorldBgmDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap homeWorldLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap investigationMonsterDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2ObjectMap itemDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap monsterCurveDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap monsterDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap monsterDescribeDataMap = - new Int2ObjectOpenHashMap<>(); - @Getter private static final Int2ObjectMap monsterSpecialNameDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap musicGameBasicDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2ObjectMap npcDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap openStateDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap personalLineDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap playerLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap proudSkillDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap questDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap reliquaryAffixDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap reliquaryMainPropDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap reliquarySetDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap rewardPreviewDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap towerFloorDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap towerLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap towerScheduleDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap triggerExcelConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap trialAvatarDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap trialAvatarActivityDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap trialAvatarActivityDataDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap trialAvatarTemplateDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap trialReliquaryDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap weaponCurveDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap weaponLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap weaponPromoteDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap weatherDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap worldAreaDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap worldLevelDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap rewindDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap teleportDataMap = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap refreshPolicyExcelConfigDataMap = - new Int2ObjectOpenHashMap<>(); - - private static final Int2ObjectMap avatarPromoteDataMap = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap reliquaryLevelDataMap = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap shopGoodsDataMap = - new Int2ObjectOpenHashMap<>(); - - // The following are accessed via getMapByResourceDef, and will show as unused - private static final Int2ObjectMap codexMaterialDataMap = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap codexQuestDataMap = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap codexReliquaryDataMap = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap codexWeaponDataMap = - new Int2ObjectOpenHashMap<>(); - - // Custom community server resources - @Getter - private static final Int2ObjectMap> dungeonDropDataMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap gadgetMappingMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap activityCondGroupMap = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Int2ObjectMap groupReplacements = - new Int2ObjectOpenHashMap<>(); - - // Cache - @Getter private static final IntList scenePointIdList = new IntArrayList(); - @Getter private static final List openStateList = new ArrayList<>(); - @Getter private static final Map> scenePointsPerScene = new HashMap<>(); - @Getter private static final Map scriptSceneDataMap = new HashMap<>(); - @Getter private static final Map guideTriggerDataStringMap = new HashMap<>(); - @Getter private static final Map configLevelEntityDataMap = new HashMap<>(); - - @Getter - private static final Int2ObjectMap proudSkillGroupLevels = new Int2ObjectOpenHashMap<>(); - - @Getter private static final Int2IntMap proudSkillGroupMaxLevels = new Int2IntOpenHashMap(); - - @Getter - private static final Int2ObjectMap avatarSkillLevels = new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Map> beginCondQuestMap = - new HashMap<>(); // cache filled by QuestData - - @Getter private static final Map questTalkMap = new HashMap<>(); - - @Getter - private static final Int2ObjectMap trialAvatarCustomData = - new Int2ObjectOpenHashMap<>(); - - @Getter - private static final Map trialAvatarActivityCustomData = - new HashMap<>(); - - @Getter - private static final Map trialAvatarActivityDataCustomData = - new HashMap<>(); - - @Getter - private static final Int2IntMap trialAvatarIndexIdTrialActivityDataDataMap = - new Int2IntOpenHashMap(); - - private static final Map> fetters = new HashMap<>(); - private static final Map> shopGoods = new HashMap<>(); - - // Getters with different names that stay for now - public static Int2ObjectMap getMainQuestDataMap() { - return mainQuestData; - } - - public static Int2ObjectMap getMainQuestEncryptionMap() { - return questsKeys; - } - - public static Int2ObjectMap getSceneNpcBornData() { - return npcBornData; - } - - public static Map getAbilityEmbryoInfo() { - return abilityEmbryos; - } - - // Getters that get values rather than containers. If Lombok ever gets syntactic sugar for this, - // we should adopt that. - public static AbilityData getAbilityData(String abilityName) { - return abilityDataMap.get(abilityName); - } - - public static IntSet getAvatarSkillLevels(int avatarSkillId) { - return avatarSkillLevels.get(avatarSkillId); - } - - public static IntSet getProudSkillGroupLevels(int proudSkillGroupId) { - return proudSkillGroupLevels.get(proudSkillGroupId); - } - - public static int getProudSkillGroupMaxLevel(int proudSkillGroupId) { - return proudSkillGroupMaxLevels.getOrDefault(proudSkillGroupId, 0); - } - - // Multi-keyed getters - public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) { - return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel); - } - - public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) { - return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel); - } - - public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) { - return reliquaryLevelDataMap.get((rankLevel << 8) + level); - } - - public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { - return scenePointEntryMap.get((sceneId << 16) + pointId); - } - - // Non-nullable value getters - public static int getAvatarLevelExpRequired(int level) { - return Optional.ofNullable(avatarLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); - } - - public static int getAvatarFetterLevelExpRequired(int level) { - return Optional.ofNullable(avatarFetterLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); - } - - public static int getRelicExpRequired(int rankLevel, int level) { - return Optional.ofNullable(getRelicLevelData(rankLevel, level)).map(d -> d.getExp()).orElse(0); - } - - // Generic getter - public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { - Int2ObjectMap map = null; - - try { - Field field = - GameData.class.getDeclaredField( - Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map"); - - field.setAccessible(true); - map = (Int2ObjectMap) field.get(null); - field.setAccessible(false); - } catch (Exception e) { - Grasscutter.getLogger() - .error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e); - } - - return map; - } - - public static int getWeaponExpRequired(int rankLevel, int level) { - WeaponLevelData levelData = weaponLevelDataMap.get(level); - if (levelData == null) { - return 0; - } - try { - return levelData.getRequiredExps()[rankLevel - 1]; - } catch (Exception e) { - return 0; - } - } - - public static Map> getFetterDataEntries() { - if (fetters.isEmpty()) { - fetterDataMap.forEach( - (k, v) -> { - if (!fetters.containsKey(v.getAvatarId())) { - fetters.put(v.getAvatarId(), new ArrayList<>()); - } - fetters.get(v.getAvatarId()).add(k); - }); - } - - return fetters; - } - - public static Map> getShopGoodsDataEntries() { - if (shopGoods.isEmpty()) { - shopGoodsDataMap.forEach( - (k, v) -> { - if (!shopGoods.containsKey(v.getShopType())) - shopGoods.put(v.getShopType(), new ArrayList<>()); - shopGoods.get(v.getShopType()).add(v); - }); - } - - return shopGoods; - } - - /** - * Fetches the route data for a scene by ID. - * - * @param sceneId The ID of the scene to fetch the route data for. - * @return The route data for the scene, or an empty map if the scene has no route data. - */ - public static Int2ObjectMap getSceneRoutes(int sceneId) { - return sceneRouteData.computeIfAbsent(sceneId, k -> new Int2ObjectOpenHashMap<>()); - } - - /** - * Fetches the trial data - * - * @param trialAvatarIndexId - * @return - */ - @Nullable public static TrialAvatarActivityDataData getTrialAvatarActivityDataByAvatarIndex( - int trialAvatarIndexId) { - // prefer custom data over official data - val dataId = trialAvatarIndexIdTrialActivityDataDataMap.get(trialAvatarIndexId); - val datamap = - GameData.getTrialAvatarActivityDataCustomData().isEmpty() - ? GameData.getTrialAvatarActivityDataDataMap() - : GameData.getTrialAvatarActivityDataCustomData(); - return datamap.get(dataId); - } - - public static Int2ObjectMap getAchievementDataMap() { - AchievementData.divideIntoGroups(); - return achievementDataMap; - } -} +package emu.grasscutter.data; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.*; +import emu.grasscutter.data.binout.config.*; +import emu.grasscutter.data.binout.routes.*; +import emu.grasscutter.data.custom.*; +import emu.grasscutter.data.excels.*; +import emu.grasscutter.data.excels.achievement.*; +import emu.grasscutter.data.excels.activity.*; +import emu.grasscutter.data.excels.avatar.*; +import emu.grasscutter.data.excels.codex.*; +import emu.grasscutter.data.excels.dungeon.*; +import emu.grasscutter.data.excels.monster.*; +import emu.grasscutter.data.excels.reliquary.*; +import emu.grasscutter.data.excels.tower.*; +import emu.grasscutter.data.excels.trial.*; +import emu.grasscutter.data.excels.weapon.*; +import emu.grasscutter.data.excels.world.*; +import emu.grasscutter.data.server.*; +import emu.grasscutter.game.dungeons.*; +import emu.grasscutter.game.quest.*; +import emu.grasscutter.game.world.*; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.*; +import java.lang.reflect.Field; +import java.util.*; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.val; + +@SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) +public final class GameData { + @Getter private static final Map abilityDataMap = new HashMap<>(); + + @Getter + private static final Int2ObjectMap scenePointEntryMap = + new Int2ObjectOpenHashMap<>(); + + // BinOutputs + @Getter + private static final Int2ObjectMap homeworldDefaultSaveData = + new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2ObjectMap abilityHashes = new Int2ObjectOpenHashMap<>(); + + @Deprecated(forRemoval = true) + @Getter + private static final Map abilityModifiers = new HashMap<>(); + + @Getter private static final Map avatarConfigData = new HashMap<>(); + @Getter private static final Map gadgetConfigData = new HashMap<>(); + @Getter private static final Map monsterConfigData = new HashMap<>(); + + @Getter private static final Map openConfigEntries = new HashMap<>(); + + private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap questsKeys = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap npcBornData = new Int2ObjectOpenHashMap<>(); + private static final Map abilityEmbryos = new HashMap<>(); + + // ExcelConfigs + @Getter + private static final Int2ObjectMap activityCondExcelConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap dungeonPassConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap dungeonChallengeConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap> sceneRouteData = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final ArrayList codexReliquaryArrayList = new ArrayList<>(); + + private static final Int2ObjectMap achievementDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap achievementGoalDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap activityDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap activityShopDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap activityWatcherDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarCostumeDataItemIdMap = + new Int2ObjectLinkedOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarCostumeDataMap = + new Int2ObjectLinkedOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarReplaceCostumeDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarCurveDataMap = + new Int2ObjectLinkedOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarFetterLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarFlycloakDataMap = + new Int2ObjectLinkedOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarSkillDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarSkillDepotDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap avatarTalentDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap battlePassMissionDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap battlePassRewardDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap + blossomRefreshExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2ObjectMap buffDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap chapterDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2ObjectMap cityDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap codexAnimalDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap codexMaterialDataIdMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap codexQuestDataIdMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap codexReliquaryDataIdMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap codexWeaponDataIdMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap cookBonusDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap cookRecipeDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap compoundDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap dailyDungeonDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap dungeonEntryDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap envAnimalGatherConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap equipAffixDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap fetterCharacterCardDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap forgeDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap furnitureMakeConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap gadgetDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap gatherDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + @Deprecated // This is to prevent people from using this map. This is for the resource loader + // only! + private static final Int2ObjectMap guideTriggerDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap homeWorldBgmDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap homeWorldLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap investigationMonsterDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2ObjectMap itemDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap monsterCurveDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap monsterDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap monsterDescribeDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap monsterSpecialNameDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap musicGameBasicDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2ObjectMap npcDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap openStateDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap personalLineDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap playerLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap proudSkillDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap questDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap reliquaryAffixDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap reliquaryMainPropDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap reliquarySetDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap rewardPreviewDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap towerFloorDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap towerLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap towerScheduleDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap triggerExcelConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap trialAvatarDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap trialAvatarActivityDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap trialAvatarActivityDataDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap trialAvatarTemplateDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap trialReliquaryDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap weaponCurveDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap weaponLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap weaponPromoteDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap weatherDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap worldAreaDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap worldLevelDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap rewindDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap teleportDataMap = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap refreshPolicyExcelConfigDataMap = + new Int2ObjectOpenHashMap<>(); + + private static final Int2ObjectMap avatarPromoteDataMap = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap reliquaryLevelDataMap = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap shopGoodsDataMap = + new Int2ObjectOpenHashMap<>(); + + // The following are accessed via getMapByResourceDef, and will show as unused + private static final Int2ObjectMap codexMaterialDataMap = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexQuestDataMap = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexReliquaryDataMap = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexWeaponDataMap = + new Int2ObjectOpenHashMap<>(); + + // Custom community server resources + @Getter + private static final Int2ObjectMap> dungeonDropDataMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap gadgetMappingMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap activityCondGroupMap = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Int2ObjectMap groupReplacements = + new Int2ObjectOpenHashMap<>(); + + // Cache + @Getter private static final IntList scenePointIdList = new IntArrayList(); + @Getter private static final List openStateList = new ArrayList<>(); + @Getter private static final Map> scenePointsPerScene = new HashMap<>(); + @Getter private static final Map scriptSceneDataMap = new HashMap<>(); + + @Getter + private static final Map guideTriggerDataStringMap = new HashMap<>(); + + @Getter + private static final Map configLevelEntityDataMap = new HashMap<>(); + + @Getter + private static final Int2ObjectMap proudSkillGroupLevels = new Int2ObjectOpenHashMap<>(); + + @Getter private static final Int2IntMap proudSkillGroupMaxLevels = new Int2IntOpenHashMap(); + + @Getter + private static final Int2ObjectMap avatarSkillLevels = new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Map> beginCondQuestMap = + new HashMap<>(); // cache filled by QuestData + + @Getter private static final Map questTalkMap = new HashMap<>(); + + @Getter + private static final Int2ObjectMap trialAvatarCustomData = + new Int2ObjectOpenHashMap<>(); + + @Getter + private static final Map trialAvatarActivityCustomData = + new HashMap<>(); + + @Getter + private static final Map trialAvatarActivityDataCustomData = + new HashMap<>(); + + @Getter + private static final Int2IntMap trialAvatarIndexIdTrialActivityDataDataMap = + new Int2IntOpenHashMap(); + + private static final Map> fetters = new HashMap<>(); + private static final Map> shopGoods = new HashMap<>(); + + // Getters with different names that stay for now + public static Int2ObjectMap getMainQuestDataMap() { + return mainQuestData; + } + + public static Int2ObjectMap getMainQuestEncryptionMap() { + return questsKeys; + } + + public static Int2ObjectMap getSceneNpcBornData() { + return npcBornData; + } + + public static Map getAbilityEmbryoInfo() { + return abilityEmbryos; + } + + // Getters that get values rather than containers. If Lombok ever gets syntactic sugar for this, + // we should adopt that. + public static AbilityData getAbilityData(String abilityName) { + return abilityDataMap.get(abilityName); + } + + public static IntSet getAvatarSkillLevels(int avatarSkillId) { + return avatarSkillLevels.get(avatarSkillId); + } + + public static IntSet getProudSkillGroupLevels(int proudSkillGroupId) { + return proudSkillGroupLevels.get(proudSkillGroupId); + } + + public static int getProudSkillGroupMaxLevel(int proudSkillGroupId) { + return proudSkillGroupMaxLevels.getOrDefault(proudSkillGroupId, 0); + } + + // Multi-keyed getters + public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) { + return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel); + } + + public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) { + return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel); + } + + public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) { + return reliquaryLevelDataMap.get((rankLevel << 8) + level); + } + + public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { + return scenePointEntryMap.get((sceneId << 16) + pointId); + } + + // Non-nullable value getters + public static int getAvatarLevelExpRequired(int level) { + return Optional.ofNullable(avatarLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); + } + + public static int getAvatarFetterLevelExpRequired(int level) { + return Optional.ofNullable(avatarFetterLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); + } + + public static int getRelicExpRequired(int rankLevel, int level) { + return Optional.ofNullable(getRelicLevelData(rankLevel, level)).map(d -> d.getExp()).orElse(0); + } + + // Generic getter + public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { + Int2ObjectMap map = null; + + try { + Field field = + GameData.class.getDeclaredField( + Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map"); + + field.setAccessible(true); + map = (Int2ObjectMap) field.get(null); + field.setAccessible(false); + } catch (Exception e) { + Grasscutter.getLogger() + .error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e); + } + + return map; + } + + public static int getWeaponExpRequired(int rankLevel, int level) { + WeaponLevelData levelData = weaponLevelDataMap.get(level); + if (levelData == null) { + return 0; + } + try { + return levelData.getRequiredExps()[rankLevel - 1]; + } catch (Exception e) { + return 0; + } + } + + public static Map> getFetterDataEntries() { + if (fetters.isEmpty()) { + fetterDataMap.forEach( + (k, v) -> { + if (!fetters.containsKey(v.getAvatarId())) { + fetters.put(v.getAvatarId(), new ArrayList<>()); + } + fetters.get(v.getAvatarId()).add(k); + }); + } + + return fetters; + } + + public static Map> getShopGoodsDataEntries() { + if (shopGoods.isEmpty()) { + shopGoodsDataMap.forEach( + (k, v) -> { + if (!shopGoods.containsKey(v.getShopType())) + shopGoods.put(v.getShopType(), new ArrayList<>()); + shopGoods.get(v.getShopType()).add(v); + }); + } + + return shopGoods; + } + + /** + * Fetches the route data for a scene by ID. + * + * @param sceneId The ID of the scene to fetch the route data for. + * @return The route data for the scene, or an empty map if the scene has no route data. + */ + public static Int2ObjectMap getSceneRoutes(int sceneId) { + return sceneRouteData.computeIfAbsent(sceneId, k -> new Int2ObjectOpenHashMap<>()); + } + + /** + * Fetches the trial data + * + * @param trialAvatarIndexId + * @return + */ + @Nullable public static TrialAvatarActivityDataData getTrialAvatarActivityDataByAvatarIndex( + int trialAvatarIndexId) { + // prefer custom data over official data + val dataId = trialAvatarIndexIdTrialActivityDataDataMap.get(trialAvatarIndexId); + val datamap = + GameData.getTrialAvatarActivityDataCustomData().isEmpty() + ? GameData.getTrialAvatarActivityDataDataMap() + : GameData.getTrialAvatarActivityDataCustomData(); + return datamap.get(dataId); + } + + public static Int2ObjectMap getAchievementDataMap() { + AchievementData.divideIntoGroups(); + return achievementDataMap; + } +} diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 837793bad..3535e6fd4 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -1,681 +1,691 @@ -package emu.grasscutter.data; - -import static emu.grasscutter.utils.FileUtils.getDataPath; -import static emu.grasscutter.utils.FileUtils.getResourcePath; -import static emu.grasscutter.utils.Language.translate; - -import com.google.gson.annotations.SerializedName; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.binout.*; -import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.data.binout.config.ConfigEntityAvatar; -import emu.grasscutter.data.binout.config.ConfigEntityBase; -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.data.binout.config.ConfigEntityMonster; -import emu.grasscutter.data.common.PointData; -import emu.grasscutter.game.managers.blossom.BlossomConfig; -import emu.grasscutter.game.quest.QuestEncryptionKey; -import emu.grasscutter.game.world.SpawnDataEntry; -import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId; -import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; -import emu.grasscutter.scripts.SceneIndexManager; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.JsonUtils; -import emu.grasscutter.utils.TsvUtils; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntArraySet; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.regex.Pattern; -import java.util.stream.Stream; -import lombok.val; -import org.reflections.Reflections; - -public class ResourceLoader { - - private static final Set loadedResources = new CopyOnWriteArraySet<>(); - private static boolean loadedAll = false; - - // Get a list of all resource classes, sorted by loadPriority - public static List> getResourceDefClasses() { - Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName()); - Set classes = reflections.getSubTypesOf(GameResource.class); - - List> classList = new ArrayList<>(classes.size()); - classes.forEach( - o -> { - Class c = (Class) o; - if (c.getAnnotation(ResourceType.class) != null) { - classList.add(c); - } - }); - - classList.sort( - (a, b) -> - b.getAnnotation(ResourceType.class).loadPriority().value() - - a.getAnnotation(ResourceType.class).loadPriority().value()); - - return classList; - } - - // Get a list containing sets of all resource classes, sorted by loadPriority - protected static List>> getResourceDefClassesPrioritySets() { - val reflections = new Reflections(ResourceLoader.class.getPackage().getName()); - val classes = reflections.getSubTypesOf(GameResource.class); - val priorities = ResourceType.LoadPriority.getInOrder(); - Grasscutter.getLogger().debug("Priorities are " + priorities); - val map = new LinkedHashMap>>(priorities.size()); - priorities.forEach(p -> map.put(p, new HashSet<>())); - - classes.forEach( - c -> { - // val c = (Class) o; - val annotation = c.getAnnotation(ResourceType.class); - if (annotation != null) { - map.get(annotation.loadPriority()).add(c); - } - }); - return List.copyOf(map.values()); - } - - public static void loadAll() { - if (loadedAll) return; - Grasscutter.getLogger().info(translate("messages.status.resources.loading")); - - loadConfigData(); - // Load ability lists - loadAbilityEmbryos(); - loadOpenConfig(); - loadAbilityModifiers(); - // Load resources - loadResources(true); - // Process into depots - GameDepot.load(); - // Load spawn data and quests - loadSpawnData(); - loadQuests(); - loadScriptSceneData(); - // Load scene points - must be done AFTER resources are loaded - loadScenePoints(); - // Load default home layout - loadHomeworldDefaultSaveData(); - loadNpcBornData(); - loadBlossomResources(); - cacheTalentLevelSets(); - - Grasscutter.getLogger().info(translate("messages.status.resources.finish")); - loadedAll = true; - } - - public static void loadResources() { - loadResources(false); - } - - public static void loadResources(boolean doReload) { - long startTime = System.nanoTime(); - val errors = - new ConcurrentLinkedQueue< - Pair>(); // Logger in a parallel stream will deadlock - - getResourceDefClassesPrioritySets() - .forEach( - classes -> { - classes.stream() - .parallel() - .unordered() - .forEach( - c -> { - val type = c.getAnnotation(ResourceType.class); - if (type == null) return; - - val map = GameData.getMapByResourceDef(c); - if (map == null) return; - - try { - loadFromResource(c, type, map, doReload); - } catch (Exception e) { - errors.add(Pair.of(Arrays.toString(type.name()), e)); - } - }); - }); - errors.forEach( - pair -> - Grasscutter.getLogger() - .error("Error loading resource file: " + pair.left(), pair.right())); - long endTime = System.nanoTime(); - long ns = (endTime - startTime); // divide by 1000000 to get milliseconds. - Grasscutter.getLogger().debug("Loading resources took " + ns + "ns == " + ns / 1000000 + "ms"); - } - - @SuppressWarnings("rawtypes") - protected static void loadFromResource( - Class c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception { - val simpleName = c.getSimpleName(); - if (doReload || !loadedResources.contains(simpleName)) { - for (String name : type.name()) { - loadFromResource(c, FileUtils.getExcelPath(name), map); - } - loadedResources.add(simpleName); - } - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - protected static void loadFromResource(Class c, Path filename, Int2ObjectMap map) - throws Exception { - val results = - switch (FileUtils.getFileExtension(filename)) { - case "json" -> JsonUtils.loadToList(filename, c); - case "tsj" -> TsvUtils.loadTsjToListSetField(filename, c); - case "tsv" -> TsvUtils.loadTsvToListSetField(filename, c); - default -> null; - }; - if (results == null) return; - results.forEach( - o -> { - GameResource res = (GameResource) o; - res.onLoad(); - map.put(res.getId(), res); - }); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) - throws Exception { - JsonUtils.loadToList(getResourcePath("ExcelBinOutput/" + fileName), c) - .forEach( - o -> { - GameResource res = (GameResource) o; - res.onLoad(); - map.put(res.getId(), res); - }); - } - - private static void loadScenePoints() { - val pattern = Pattern.compile("scene([0-9]+)_point\\.json"); - try { - Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json") - .forEach( - path -> { - val matcher = pattern.matcher(path.getFileName().toString()); - if (!matcher.find()) return; - int sceneId = Integer.parseInt(matcher.group(1)); - - ScenePointConfig config; - try { - config = JsonUtils.loadToClass(path, ScenePointConfig.class); - } catch (Exception e) { - e.printStackTrace(); - return; - } - - if (config.points == null) return; - - val scenePoints = new IntArrayList(); - config.points.forEach( - (pointId, pointData) -> { - val scenePoint = new ScenePointEntry(sceneId, pointData); - scenePoints.add(pointId); - pointData.setId(pointId); - - GameData.getScenePointIdList().add(pointId); - GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint); - - pointData.updateDailyDungeon(); - }); - GameData.getScenePointsPerScene().put(sceneId, scenePoints); - }); - } catch (IOException ignored) { - Grasscutter.getLogger() - .error("Scene point files cannot be found, you cannot use teleport waypoints!"); - } - } - - private static void cacheTalentLevelSets() { - // All known levels, keyed by proudSkillGroupId - GameData.getProudSkillDataMap() - .forEach( - (id, data) -> - GameData.getProudSkillGroupLevels() - .computeIfAbsent(data.getProudSkillGroupId(), i -> new IntArraySet()) - .add(data.getLevel())); - // All known levels, keyed by avatarSkillId - GameData.getAvatarSkillDataMap() - .forEach( - (id, data) -> - GameData.getAvatarSkillLevels() - .put( - (int) id, - GameData.getProudSkillGroupLevels().get(data.getProudSkillGroupId()))); - // Maximum known levels, keyed by proudSkillGroupId - GameData.getProudSkillGroupLevels() - .forEach( - (id, set) -> - GameData.getProudSkillGroupMaxLevels() - .put((int) id, set.intStream().max().orElse(-1))); - } - - private static void loadAbilityEmbryos() { - List embryoList = null; - - // Read from cached file if exists - try { - embryoList = - JsonUtils.loadToList(getDataPath("AbilityEmbryos.json"), AbilityEmbryoEntry.class); - } catch (Exception ignored) { - } - - if (embryoList == null) { - // Load from BinOutput - var pattern = Pattern.compile("ConfigAvatar_(.+?)\\.json"); - - var entries = new ArrayList(); - try (var stream = - Files.newDirectoryStream(getResourcePath("BinOutput/Avatar/"), "ConfigAvatar_*.json")) { - - stream.forEach( - path -> { - var matcher = pattern.matcher(path.getFileName().toString()); - if (!matcher.find()) return; - - var avatarName = matcher.group(1); - AvatarConfig config; - try { - config = JsonUtils.loadToClass(path, AvatarConfig.class); - } catch (Exception e) { - Grasscutter.getLogger().error("Error loading player ability embryos:", e); - return; - } - - if (config.abilities == null) return; - - entries.add( - new AbilityEmbryoEntry( - avatarName, - config.abilities.stream() - .map(Object::toString) - .toArray(size -> new String[config.abilities.size()]))); - }); - } catch (IOException e) { - Grasscutter.getLogger().error("Error loading ability embryos: no files found"); - return; - } - - embryoList = entries; - - try { - GameDepot.setPlayerAbilities( - JsonUtils.loadToMap( - getResourcePath( - "BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"), - String.class, - AvatarConfig.class)); - } catch (IOException e) { - Grasscutter.getLogger().error("Error loading player abilities:", e); - } - } - - if (embryoList == null || embryoList.isEmpty()) { - Grasscutter.getLogger().error("No embryos loaded!"); - return; - } - - for (AbilityEmbryoEntry entry : embryoList) { - GameData.getAbilityEmbryoInfo().put(entry.getName(), entry); - } - } - - private static void loadAbilityModifiers() { - // Load from BinOutput - try (Stream paths = Files.walk(getResourcePath("BinOutput/Ability/Temp/"))) { - paths - .filter(Files::isRegularFile) - .filter(path -> path.toString().endsWith(".json")) - .forEach(ResourceLoader::loadAbilityModifiers); - } catch (IOException e) { - Grasscutter.getLogger().error("Error loading ability modifiers: ", e); - } - // System.out.println("Loaded modifiers, found types:"); - // modifierActionTypes.stream().sorted().forEach(s -> System.out.printf("%s, ", s)); - // System.out.println("[End]"); - } - - private static void loadAbilityModifiers(Path path) { - try { - JsonUtils.loadToList(path, AbilityConfigData.class) - .forEach(data -> loadAbilityData(data.Default)); - } catch (IOException e) { - Grasscutter.getLogger() - .error("Error loading ability modifiers from path " + path.toString() + ": ", e); - } - } - - private static void loadAbilityData(AbilityData data) { - GameData.getAbilityDataMap().put(data.abilityName, data); - - val modifiers = data.modifiers; - if (modifiers == null || modifiers.size() == 0) return; - - String name = data.abilityName; - AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name); - modifiers.forEach( - (key, modifier) -> { - Stream.ofNullable(modifier.onAdded) - .flatMap(Stream::of) - // .map(action -> {modifierActionTypes.add(action.$type); return action;}) - .filter(action -> action.type == AbilityModifierAction.Type.HealHP) - .forEach(action -> modifierEntry.getOnAdded().add(action)); - Stream.ofNullable(modifier.onThinkInterval) - .flatMap(Stream::of) - // .map(action -> {modifierActionTypes.add(action.$type); return action;}) - .filter(action -> action.type == AbilityModifierAction.Type.HealHP) - .forEach(action -> modifierEntry.getOnThinkInterval().add(action)); - Stream.ofNullable(modifier.onRemoved) - .flatMap(Stream::of) - // .map(action -> {modifierActionTypes.add(action.$type); return action;}) - .filter(action -> action.type == AbilityModifierAction.Type.HealHP) - .forEach(action -> modifierEntry.getOnRemoved().add(action)); - }); - - GameData.getAbilityModifiers().put(name, modifierEntry); - } - - private static void loadSpawnData() { - String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"}; - ArrayList spawnEntryMap = new ArrayList<>(); - - for (String name : spawnDataNames) { - // Load spawn entries from file - try (InputStreamReader reader = DataLoader.loadReader(name)) { - // Add spawns to group if it already exists in our spawn group map - spawnEntryMap.addAll(JsonUtils.loadToList(reader, SpawnGroupEntry.class)); - } catch (Exception ignored) { - } - } - - if (spawnEntryMap.isEmpty()) { - Grasscutter.getLogger().error("No spawn data loaded!"); - return; - } - - HashMap> areaSort = new HashMap<>(); - // key = sceneId,x,z , value = ArrayList - for (SpawnGroupEntry entry : spawnEntryMap) { - entry - .getSpawns() - .forEach( - s -> { - s.setGroup(entry); - GridBlockId point = s.getBlockId(); - if (!areaSort.containsKey(point)) { - areaSort.put(point, new ArrayList<>()); - } - areaSort.get(point).add(s); - }); - } - GameDepot.addSpawnListById(areaSort); - } - - private static void loadOpenConfig() { - // Read from cached file if exists - List list = null; - - try { - list = JsonUtils.loadToList(getDataPath("OpenConfig.json"), OpenConfigEntry.class); - } catch (Exception ignored) { - } - - if (list == null) { - Map map = new TreeMap<>(); - String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; - - for (String folderName : folderNames) { - try { - Files.newDirectoryStream(getResourcePath(folderName), "*.json") - .forEach( - path -> { - try { - JsonUtils.loadToMap(path, String.class, OpenConfigData[].class) - .forEach((name, data) -> map.put(name, new OpenConfigEntry(name, data))); - } catch (Exception e) { - e.printStackTrace(); - } - }); - } catch (IOException e) { - Grasscutter.getLogger() - .error("Error loading open config: no files found in " + folderName); - return; - } - } - - list = new ArrayList<>(map.values()); - } - - if (list == null || list.isEmpty()) { - Grasscutter.getLogger().error("No openconfig entries loaded!"); - return; - } - - for (OpenConfigEntry entry : list) { - GameData.getOpenConfigEntries().put(entry.getName(), entry); - } - } - - private static void loadQuests() { - try { - Files.list(getResourcePath("BinOutput/Quest/")) - .forEach( - path -> { - try { - val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class); - GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); - } catch (IOException e) { - - } - }); - } catch (IOException e) { - Grasscutter.getLogger().error("Quest data missing"); - return; - } - - try { - val questEncryptionMap = GameData.getMainQuestEncryptionMap(); - String path = "QuestEncryptionKeys.json"; - try { - JsonUtils.loadToList(getResourcePath(path), QuestEncryptionKey.class) - .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); - } catch (IOException | NullPointerException ignored) { - } - try { - DataLoader.loadList(path, QuestEncryptionKey.class) - .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); - } catch (IOException | NullPointerException ignored) { - } - Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size()); - } catch (Exception e) { - Grasscutter.getLogger().error("Unable to load quest keys.", e); - } - - Grasscutter.getLogger() - .debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); - } - - public static void loadScriptSceneData() { - try { - Files.list(getResourcePath("ScriptSceneData/")) - .forEach( - path -> { - try { - GameData.getScriptSceneDataMap() - .put( - path.getFileName().toString(), - JsonUtils.loadToClass(path, ScriptSceneData.class)); - } catch (IOException e) { - e.printStackTrace(); - } - }); - Grasscutter.getLogger() - .debug("Loaded " + GameData.getScriptSceneDataMap().size() + " ScriptSceneDatas."); - } catch (IOException e) { - Grasscutter.getLogger().debug("ScriptSceneData folder missing or empty."); - } - } - - private static void loadHomeworldDefaultSaveData() { - val pattern = Pattern.compile("scene([0-9]+)_home_config\\.json"); - try { - Files.newDirectoryStream( - getResourcePath("BinOutput/HomeworldDefaultSave"), "scene*_home_config.json") - .forEach( - path -> { - val matcher = pattern.matcher(path.getFileName().toString()); - if (!matcher.find()) return; - - try { - val sceneId = Integer.parseInt(matcher.group(1)); - val data = JsonUtils.loadToClass(path, HomeworldDefaultSaveData.class); - GameData.getHomeworldDefaultSaveData().put(sceneId, data); - } catch (Exception ignored) { - } - }); - Grasscutter.getLogger() - .debug( - "Loaded " - + GameData.getHomeworldDefaultSaveData().size() - + " HomeworldDefaultSaveDatas."); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to load HomeworldDefaultSave folder."); - } - } - - private static void loadNpcBornData() { - try { - Files.newDirectoryStream(getResourcePath("BinOutput/Scene/SceneNpcBorn/"), "*.json") - .forEach( - path -> { - try { - val data = JsonUtils.loadToClass(path, SceneNpcBornData.class); - if (data.getBornPosList() == null || data.getBornPosList().size() == 0) { - return; - } - - data.setIndex( - SceneIndexManager.buildIndex( - 3, data.getBornPosList(), item -> item.getPos().toPoint())); - GameData.getSceneNpcBornData().put(data.getSceneId(), data); - } catch (IOException ignored) { - } - }); - Grasscutter.getLogger() - .debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas."); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to load SceneNpcBorn folder."); - } - } - - private static void loadConfigData(){ - loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class); - loadConfigData(GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class); - loadConfigDataMap(GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class); - } - - private static void loadConfigData(Map targetMap, String folderPath, Class configClass) { - val className = configClass.getName(); - try(val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) { - stream.forEach(path -> { - try { - val name = path.getFileName().toString().replace(".json", ""); - targetMap.put(name, JsonUtils.loadToClass(path, configClass)); - } catch (Exception e) { - Grasscutter.getLogger().error("failed to load {} entries for {}", className, path.toString(), e); - } - }); - - Grasscutter.getLogger().debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to load {} folder.", className); - } - } - - private static void loadConfigDataMap(Map targetMap, String folderPath, Class configClass) { - val className = configClass.getName(); - try(val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) { - stream.forEach(path -> { - try { - targetMap.putAll(JsonUtils.loadToMap(path, String.class, configClass)); - } catch (Exception e) { - Grasscutter.getLogger().error("failed to load {} entries for {}", className, path.toString(), e); - } - }); - - Grasscutter.getLogger().debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to load {} folder.", className); - } - } - - private static void loadBlossomResources() { - try { - GameDepot.setBlossomConfig(DataLoader.loadClass("BlossomConfig.json", BlossomConfig.class)); - Grasscutter.getLogger().debug("Loaded BlossomConfig."); - } catch (IOException e) { - Grasscutter.getLogger().warn("Failed to load BlossomConfig."); - } - } - - // private static HashSet modifierActionTypes = new HashSet<>(); - public static class AbilityConfigData { - public AbilityData Default; - } - - public static class AvatarConfig { - @SerializedName( - value = "abilities", - alternate = {"targetAbilities"}) - public ArrayList abilities; - } - - // BinOutput configs - - public static class AvatarConfigAbility { - public String abilityName; - - public String toString() { - return abilityName; - } - } - - private static class OpenConfig { - public OpenConfigData[] data; - } - - public static class OpenConfigData { - public String $type; - public String abilityName; - - @SerializedName( - value = "talentIndex", - alternate = {"OJOFFKLNAHN"}) - public int talentIndex; - - @SerializedName( - value = "skillID", - alternate = {"overtime"}) - public int skillID; - - @SerializedName( - value = "pointDelta", - alternate = {"IGEBKIHPOIF"}) - public int pointDelta; - } - - public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints() - public Map points; - } -} +package emu.grasscutter.data; + +import static emu.grasscutter.utils.FileUtils.getDataPath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; +import static emu.grasscutter.utils.Language.translate; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.*; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.data.binout.config.ConfigEntityAvatar; +import emu.grasscutter.data.binout.config.ConfigEntityBase; +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.data.binout.config.ConfigEntityMonster; +import emu.grasscutter.data.common.PointData; +import emu.grasscutter.game.managers.blossom.BlossomConfig; +import emu.grasscutter.game.quest.QuestEncryptionKey; +import emu.grasscutter.game.world.SpawnDataEntry; +import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId; +import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; +import emu.grasscutter.scripts.SceneIndexManager; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.JsonUtils; +import emu.grasscutter.utils.TsvUtils; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import lombok.val; +import org.reflections.Reflections; + +public class ResourceLoader { + + private static final Set loadedResources = new CopyOnWriteArraySet<>(); + private static boolean loadedAll = false; + + // Get a list of all resource classes, sorted by loadPriority + public static List> getResourceDefClasses() { + Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName()); + Set classes = reflections.getSubTypesOf(GameResource.class); + + List> classList = new ArrayList<>(classes.size()); + classes.forEach( + o -> { + Class c = (Class) o; + if (c.getAnnotation(ResourceType.class) != null) { + classList.add(c); + } + }); + + classList.sort( + (a, b) -> + b.getAnnotation(ResourceType.class).loadPriority().value() + - a.getAnnotation(ResourceType.class).loadPriority().value()); + + return classList; + } + + // Get a list containing sets of all resource classes, sorted by loadPriority + protected static List>> getResourceDefClassesPrioritySets() { + val reflections = new Reflections(ResourceLoader.class.getPackage().getName()); + val classes = reflections.getSubTypesOf(GameResource.class); + val priorities = ResourceType.LoadPriority.getInOrder(); + Grasscutter.getLogger().debug("Priorities are " + priorities); + val map = new LinkedHashMap>>(priorities.size()); + priorities.forEach(p -> map.put(p, new HashSet<>())); + + classes.forEach( + c -> { + // val c = (Class) o; + val annotation = c.getAnnotation(ResourceType.class); + if (annotation != null) { + map.get(annotation.loadPriority()).add(c); + } + }); + return List.copyOf(map.values()); + } + + public static void loadAll() { + if (loadedAll) return; + Grasscutter.getLogger().info(translate("messages.status.resources.loading")); + + loadConfigData(); + // Load ability lists + loadAbilityEmbryos(); + loadOpenConfig(); + loadAbilityModifiers(); + // Load resources + loadResources(true); + // Process into depots + GameDepot.load(); + // Load spawn data and quests + loadSpawnData(); + loadQuests(); + loadScriptSceneData(); + // Load scene points - must be done AFTER resources are loaded + loadScenePoints(); + // Load default home layout + loadHomeworldDefaultSaveData(); + loadNpcBornData(); + loadBlossomResources(); + cacheTalentLevelSets(); + + Grasscutter.getLogger().info(translate("messages.status.resources.finish")); + loadedAll = true; + } + + public static void loadResources() { + loadResources(false); + } + + public static void loadResources(boolean doReload) { + long startTime = System.nanoTime(); + val errors = + new ConcurrentLinkedQueue< + Pair>(); // Logger in a parallel stream will deadlock + + getResourceDefClassesPrioritySets() + .forEach( + classes -> { + classes.stream() + .parallel() + .unordered() + .forEach( + c -> { + val type = c.getAnnotation(ResourceType.class); + if (type == null) return; + + val map = GameData.getMapByResourceDef(c); + if (map == null) return; + + try { + loadFromResource(c, type, map, doReload); + } catch (Exception e) { + errors.add(Pair.of(Arrays.toString(type.name()), e)); + } + }); + }); + errors.forEach( + pair -> + Grasscutter.getLogger() + .error("Error loading resource file: " + pair.left(), pair.right())); + long endTime = System.nanoTime(); + long ns = (endTime - startTime); // divide by 1000000 to get milliseconds. + Grasscutter.getLogger().debug("Loading resources took " + ns + "ns == " + ns / 1000000 + "ms"); + } + + @SuppressWarnings("rawtypes") + protected static void loadFromResource( + Class c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception { + val simpleName = c.getSimpleName(); + if (doReload || !loadedResources.contains(simpleName)) { + for (String name : type.name()) { + loadFromResource(c, FileUtils.getExcelPath(name), map); + } + loadedResources.add(simpleName); + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + protected static void loadFromResource(Class c, Path filename, Int2ObjectMap map) + throws Exception { + val results = + switch (FileUtils.getFileExtension(filename)) { + case "json" -> JsonUtils.loadToList(filename, c); + case "tsj" -> TsvUtils.loadTsjToListSetField(filename, c); + case "tsv" -> TsvUtils.loadTsvToListSetField(filename, c); + default -> null; + }; + if (results == null) return; + results.forEach( + o -> { + GameResource res = (GameResource) o; + res.onLoad(); + map.put(res.getId(), res); + }); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) + throws Exception { + JsonUtils.loadToList(getResourcePath("ExcelBinOutput/" + fileName), c) + .forEach( + o -> { + GameResource res = (GameResource) o; + res.onLoad(); + map.put(res.getId(), res); + }); + } + + private static void loadScenePoints() { + val pattern = Pattern.compile("scene([0-9]+)_point\\.json"); + try { + Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json") + .forEach( + path -> { + val matcher = pattern.matcher(path.getFileName().toString()); + if (!matcher.find()) return; + int sceneId = Integer.parseInt(matcher.group(1)); + + ScenePointConfig config; + try { + config = JsonUtils.loadToClass(path, ScenePointConfig.class); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + if (config.points == null) return; + + val scenePoints = new IntArrayList(); + config.points.forEach( + (pointId, pointData) -> { + val scenePoint = new ScenePointEntry(sceneId, pointData); + scenePoints.add(pointId); + pointData.setId(pointId); + + GameData.getScenePointIdList().add(pointId); + GameData.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint); + + pointData.updateDailyDungeon(); + }); + GameData.getScenePointsPerScene().put(sceneId, scenePoints); + }); + } catch (IOException ignored) { + Grasscutter.getLogger() + .error("Scene point files cannot be found, you cannot use teleport waypoints!"); + } + } + + private static void cacheTalentLevelSets() { + // All known levels, keyed by proudSkillGroupId + GameData.getProudSkillDataMap() + .forEach( + (id, data) -> + GameData.getProudSkillGroupLevels() + .computeIfAbsent(data.getProudSkillGroupId(), i -> new IntArraySet()) + .add(data.getLevel())); + // All known levels, keyed by avatarSkillId + GameData.getAvatarSkillDataMap() + .forEach( + (id, data) -> + GameData.getAvatarSkillLevels() + .put( + (int) id, + GameData.getProudSkillGroupLevels().get(data.getProudSkillGroupId()))); + // Maximum known levels, keyed by proudSkillGroupId + GameData.getProudSkillGroupLevels() + .forEach( + (id, set) -> + GameData.getProudSkillGroupMaxLevels() + .put((int) id, set.intStream().max().orElse(-1))); + } + + private static void loadAbilityEmbryos() { + List embryoList = null; + + // Read from cached file if exists + try { + embryoList = + JsonUtils.loadToList(getDataPath("AbilityEmbryos.json"), AbilityEmbryoEntry.class); + } catch (Exception ignored) { + } + + if (embryoList == null) { + // Load from BinOutput + var pattern = Pattern.compile("ConfigAvatar_(.+?)\\.json"); + + var entries = new ArrayList(); + try (var stream = + Files.newDirectoryStream(getResourcePath("BinOutput/Avatar/"), "ConfigAvatar_*.json")) { + + stream.forEach( + path -> { + var matcher = pattern.matcher(path.getFileName().toString()); + if (!matcher.find()) return; + + var avatarName = matcher.group(1); + AvatarConfig config; + try { + config = JsonUtils.loadToClass(path, AvatarConfig.class); + } catch (Exception e) { + Grasscutter.getLogger().error("Error loading player ability embryos:", e); + return; + } + + if (config.abilities == null) return; + + entries.add( + new AbilityEmbryoEntry( + avatarName, + config.abilities.stream() + .map(Object::toString) + .toArray(size -> new String[config.abilities.size()]))); + }); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading ability embryos: no files found"); + return; + } + + embryoList = entries; + + try { + GameDepot.setPlayerAbilities( + JsonUtils.loadToMap( + getResourcePath( + "BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"), + String.class, + AvatarConfig.class)); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading player abilities:", e); + } + } + + if (embryoList == null || embryoList.isEmpty()) { + Grasscutter.getLogger().error("No embryos loaded!"); + return; + } + + for (AbilityEmbryoEntry entry : embryoList) { + GameData.getAbilityEmbryoInfo().put(entry.getName(), entry); + } + } + + private static void loadAbilityModifiers() { + // Load from BinOutput + try (Stream paths = Files.walk(getResourcePath("BinOutput/Ability/Temp/"))) { + paths + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".json")) + .forEach(ResourceLoader::loadAbilityModifiers); + } catch (IOException e) { + Grasscutter.getLogger().error("Error loading ability modifiers: ", e); + } + // System.out.println("Loaded modifiers, found types:"); + // modifierActionTypes.stream().sorted().forEach(s -> System.out.printf("%s, ", s)); + // System.out.println("[End]"); + } + + private static void loadAbilityModifiers(Path path) { + try { + JsonUtils.loadToList(path, AbilityConfigData.class) + .forEach(data -> loadAbilityData(data.Default)); + } catch (IOException e) { + Grasscutter.getLogger() + .error("Error loading ability modifiers from path " + path.toString() + ": ", e); + } + } + + private static void loadAbilityData(AbilityData data) { + GameData.getAbilityDataMap().put(data.abilityName, data); + + val modifiers = data.modifiers; + if (modifiers == null || modifiers.size() == 0) return; + + String name = data.abilityName; + AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name); + modifiers.forEach( + (key, modifier) -> { + Stream.ofNullable(modifier.onAdded) + .flatMap(Stream::of) + // .map(action -> {modifierActionTypes.add(action.$type); return action;}) + .filter(action -> action.type == AbilityModifierAction.Type.HealHP) + .forEach(action -> modifierEntry.getOnAdded().add(action)); + Stream.ofNullable(modifier.onThinkInterval) + .flatMap(Stream::of) + // .map(action -> {modifierActionTypes.add(action.$type); return action;}) + .filter(action -> action.type == AbilityModifierAction.Type.HealHP) + .forEach(action -> modifierEntry.getOnThinkInterval().add(action)); + Stream.ofNullable(modifier.onRemoved) + .flatMap(Stream::of) + // .map(action -> {modifierActionTypes.add(action.$type); return action;}) + .filter(action -> action.type == AbilityModifierAction.Type.HealHP) + .forEach(action -> modifierEntry.getOnRemoved().add(action)); + }); + + GameData.getAbilityModifiers().put(name, modifierEntry); + } + + private static void loadSpawnData() { + String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"}; + ArrayList spawnEntryMap = new ArrayList<>(); + + for (String name : spawnDataNames) { + // Load spawn entries from file + try (InputStreamReader reader = DataLoader.loadReader(name)) { + // Add spawns to group if it already exists in our spawn group map + spawnEntryMap.addAll(JsonUtils.loadToList(reader, SpawnGroupEntry.class)); + } catch (Exception ignored) { + } + } + + if (spawnEntryMap.isEmpty()) { + Grasscutter.getLogger().error("No spawn data loaded!"); + return; + } + + HashMap> areaSort = new HashMap<>(); + // key = sceneId,x,z , value = ArrayList + for (SpawnGroupEntry entry : spawnEntryMap) { + entry + .getSpawns() + .forEach( + s -> { + s.setGroup(entry); + GridBlockId point = s.getBlockId(); + if (!areaSort.containsKey(point)) { + areaSort.put(point, new ArrayList<>()); + } + areaSort.get(point).add(s); + }); + } + GameDepot.addSpawnListById(areaSort); + } + + private static void loadOpenConfig() { + // Read from cached file if exists + List list = null; + + try { + list = JsonUtils.loadToList(getDataPath("OpenConfig.json"), OpenConfigEntry.class); + } catch (Exception ignored) { + } + + if (list == null) { + Map map = new TreeMap<>(); + String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; + + for (String folderName : folderNames) { + try { + Files.newDirectoryStream(getResourcePath(folderName), "*.json") + .forEach( + path -> { + try { + JsonUtils.loadToMap(path, String.class, OpenConfigData[].class) + .forEach((name, data) -> map.put(name, new OpenConfigEntry(name, data))); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } catch (IOException e) { + Grasscutter.getLogger() + .error("Error loading open config: no files found in " + folderName); + return; + } + } + + list = new ArrayList<>(map.values()); + } + + if (list == null || list.isEmpty()) { + Grasscutter.getLogger().error("No openconfig entries loaded!"); + return; + } + + for (OpenConfigEntry entry : list) { + GameData.getOpenConfigEntries().put(entry.getName(), entry); + } + } + + private static void loadQuests() { + try { + Files.list(getResourcePath("BinOutput/Quest/")) + .forEach( + path -> { + try { + val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class); + GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); + } catch (IOException e) { + + } + }); + } catch (IOException e) { + Grasscutter.getLogger().error("Quest data missing"); + return; + } + + try { + val questEncryptionMap = GameData.getMainQuestEncryptionMap(); + String path = "QuestEncryptionKeys.json"; + try { + JsonUtils.loadToList(getResourcePath(path), QuestEncryptionKey.class) + .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); + } catch (IOException | NullPointerException ignored) { + } + try { + DataLoader.loadList(path, QuestEncryptionKey.class) + .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); + } catch (IOException | NullPointerException ignored) { + } + Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size()); + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load quest keys.", e); + } + + Grasscutter.getLogger() + .debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); + } + + public static void loadScriptSceneData() { + try { + Files.list(getResourcePath("ScriptSceneData/")) + .forEach( + path -> { + try { + GameData.getScriptSceneDataMap() + .put( + path.getFileName().toString(), + JsonUtils.loadToClass(path, ScriptSceneData.class)); + } catch (IOException e) { + e.printStackTrace(); + } + }); + Grasscutter.getLogger() + .debug("Loaded " + GameData.getScriptSceneDataMap().size() + " ScriptSceneDatas."); + } catch (IOException e) { + Grasscutter.getLogger().debug("ScriptSceneData folder missing or empty."); + } + } + + private static void loadHomeworldDefaultSaveData() { + val pattern = Pattern.compile("scene([0-9]+)_home_config\\.json"); + try { + Files.newDirectoryStream( + getResourcePath("BinOutput/HomeworldDefaultSave"), "scene*_home_config.json") + .forEach( + path -> { + val matcher = pattern.matcher(path.getFileName().toString()); + if (!matcher.find()) return; + + try { + val sceneId = Integer.parseInt(matcher.group(1)); + val data = JsonUtils.loadToClass(path, HomeworldDefaultSaveData.class); + GameData.getHomeworldDefaultSaveData().put(sceneId, data); + } catch (Exception ignored) { + } + }); + Grasscutter.getLogger() + .debug( + "Loaded " + + GameData.getHomeworldDefaultSaveData().size() + + " HomeworldDefaultSaveDatas."); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to load HomeworldDefaultSave folder."); + } + } + + private static void loadNpcBornData() { + try { + Files.newDirectoryStream(getResourcePath("BinOutput/Scene/SceneNpcBorn/"), "*.json") + .forEach( + path -> { + try { + val data = JsonUtils.loadToClass(path, SceneNpcBornData.class); + if (data.getBornPosList() == null || data.getBornPosList().size() == 0) { + return; + } + + data.setIndex( + SceneIndexManager.buildIndex( + 3, data.getBornPosList(), item -> item.getPos().toPoint())); + GameData.getSceneNpcBornData().put(data.getSceneId(), data); + } catch (IOException ignored) { + } + }); + Grasscutter.getLogger() + .debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas."); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to load SceneNpcBorn folder."); + } + } + + private static void loadConfigData() { + loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class); + loadConfigData( + GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class); + loadConfigDataMap( + GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class); + } + + private static void loadConfigData( + Map targetMap, String folderPath, Class configClass) { + val className = configClass.getName(); + try (val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) { + stream.forEach( + path -> { + try { + val name = path.getFileName().toString().replace(".json", ""); + targetMap.put(name, JsonUtils.loadToClass(path, configClass)); + } catch (Exception e) { + Grasscutter.getLogger() + .error("failed to load {} entries for {}", className, path.toString(), e); + } + }); + + Grasscutter.getLogger() + .debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to load {} folder.", className); + } + } + + private static void loadConfigDataMap( + Map targetMap, String folderPath, Class configClass) { + val className = configClass.getName(); + try (val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) { + stream.forEach( + path -> { + try { + targetMap.putAll(JsonUtils.loadToMap(path, String.class, configClass)); + } catch (Exception e) { + Grasscutter.getLogger() + .error("failed to load {} entries for {}", className, path.toString(), e); + } + }); + + Grasscutter.getLogger() + .debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to load {} folder.", className); + } + } + + private static void loadBlossomResources() { + try { + GameDepot.setBlossomConfig(DataLoader.loadClass("BlossomConfig.json", BlossomConfig.class)); + Grasscutter.getLogger().debug("Loaded BlossomConfig."); + } catch (IOException e) { + Grasscutter.getLogger().warn("Failed to load BlossomConfig."); + } + } + + // private static HashSet modifierActionTypes = new HashSet<>(); + public static class AbilityConfigData { + public AbilityData Default; + } + + public static class AvatarConfig { + @SerializedName( + value = "abilities", + alternate = {"targetAbilities"}) + public ArrayList abilities; + } + + // BinOutput configs + + public static class AvatarConfigAbility { + public String abilityName; + + public String toString() { + return abilityName; + } + } + + private static class OpenConfig { + public OpenConfigData[] data; + } + + public static class OpenConfigData { + public String $type; + public String abilityName; + + @SerializedName( + value = "talentIndex", + alternate = {"OJOFFKLNAHN"}) + public int talentIndex; + + @SerializedName( + value = "skillID", + alternate = {"overtime"}) + public int skillID; + + @SerializedName( + value = "pointDelta", + alternate = {"IGEBKIHPOIF"}) + public int pointDelta; + } + + public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints() + public Map points; + } +} diff --git a/src/main/java/emu/grasscutter/data/common/PointData.java b/src/main/java/emu/grasscutter/data/common/PointData.java index 220ef9d6b..5645c6375 100644 --- a/src/main/java/emu/grasscutter/data/common/PointData.java +++ b/src/main/java/emu/grasscutter/data/common/PointData.java @@ -1,58 +1,70 @@ -package emu.grasscutter.data.common; - -import com.google.gson.annotations.SerializedName; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.dungeon.DailyDungeonData; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import lombok.Getter; -import lombok.Setter; - -public final class PointData { - @Getter @Setter private int id; - private String $type; - @Getter private Position tranPos; - @Getter private Position pos; - @Getter private Position rot; - @Getter private Position size; - - @SerializedName(value="dungeonIds", alternate={"JHHFPGJNMIN"}) - @Getter private int[] dungeonIds; - - @SerializedName(value="dungeonRandomList", alternate={"OIBKFJNBLHO"}) - @Getter private int[] dungeonRandomList; - - @SerializedName(value="groupIDs", alternate={"HFOBOOHKBGF"}) - @Getter private int[] groupIDs; - - @SerializedName(value="tranSceneId", alternate={"JHBICGBAPIH"}) - @Getter @Setter private int tranSceneId; - - public String getType() { - return $type; - } - - public void updateDailyDungeon() { - if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) { - return; - } - - IntList newDungeons = new IntArrayList(); - int day = Grasscutter.getCurrentDayOfWeek(); - - for (int randomId : this.dungeonRandomList) { - DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId); - - if (data != null) { - for (int d : data.getDungeonsByDay(day)) { - newDungeons.add(d); - } - } - } - - this.dungeonIds = newDungeons.toIntArray(); - } -} +package emu.grasscutter.data.common; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.dungeon.DailyDungeonData; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; +import lombok.Setter; + +public final class PointData { + @Getter @Setter private int id; + private String $type; + @Getter private Position tranPos; + @Getter private Position pos; + @Getter private Position rot; + @Getter private Position size; + + @SerializedName( + value = "dungeonIds", + alternate = {"JHHFPGJNMIN"}) + @Getter + private int[] dungeonIds; + + @SerializedName( + value = "dungeonRandomList", + alternate = {"OIBKFJNBLHO"}) + @Getter + private int[] dungeonRandomList; + + @SerializedName( + value = "groupIDs", + alternate = {"HFOBOOHKBGF"}) + @Getter + private int[] groupIDs; + + @SerializedName( + value = "tranSceneId", + alternate = {"JHBICGBAPIH"}) + @Getter + @Setter + private int tranSceneId; + + public String getType() { + return $type; + } + + public void updateDailyDungeon() { + if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) { + return; + } + + IntList newDungeons = new IntArrayList(); + int day = Grasscutter.getCurrentDayOfWeek(); + + for (int randomId : this.dungeonRandomList) { + DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId); + + if (data != null) { + for (int d : data.getDungeonsByDay(day)) { + newDungeons.add(d); + } + } + } + + this.dungeonIds = newDungeons.toIntArray(); + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/GadgetData.java b/src/main/java/emu/grasscutter/data/excels/GadgetData.java index 33d3c2ee6..3b1e4e7ad 100644 --- a/src/main/java/emu/grasscutter/data/excels/GadgetData.java +++ b/src/main/java/emu/grasscutter/data/excels/GadgetData.java @@ -1,22 +1,22 @@ -package emu.grasscutter.data.excels; - -import emu.grasscutter.data.GameResource; -import emu.grasscutter.data.ResourceType; -import emu.grasscutter.game.props.EntityType; -import lombok.Getter; - -@ResourceType(name = "GadgetExcelConfigData.json") -@Getter -public final class GadgetData extends GameResource { - @Getter(onMethod_ = @Override) - private int id; - - private EntityType type; - private String jsonName; - private boolean isInteractive; - private String[] tags; - private String itemJsonName; - private long nameTextMapHash; - private int campId; - private String visionLevel; -} +package emu.grasscutter.data.excels; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.game.props.EntityType; +import lombok.Getter; + +@ResourceType(name = "GadgetExcelConfigData.json") +@Getter +public final class GadgetData extends GameResource { + @Getter(onMethod_ = @Override) + private int id; + + private EntityType type; + private String jsonName; + private boolean isInteractive; + private String[] tags; + private String itemJsonName; + private long nameTextMapHash; + private int campId; + private String visionLevel; +} diff --git a/src/main/java/emu/grasscutter/data/excels/monster/MonsterData.java b/src/main/java/emu/grasscutter/data/excels/monster/MonsterData.java index 9945bde0e..e9d6cbf31 100644 --- a/src/main/java/emu/grasscutter/data/excels/monster/MonsterData.java +++ b/src/main/java/emu/grasscutter/data/excels/monster/MonsterData.java @@ -1,120 +1,133 @@ -package emu.grasscutter.data.excels.monster; - -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - -import com.google.gson.annotations.SerializedName; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.GameResource; -import emu.grasscutter.data.ResourceType; -import emu.grasscutter.data.ResourceType.LoadPriority; -import emu.grasscutter.data.common.PropGrowCurve; -import emu.grasscutter.data.excels.GadgetData; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.MonsterType; -import lombok.Getter; - -@ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW) -@Getter -public class MonsterData extends GameResource { - static public Set definedFightProperties = Set.of(FightProperty.FIGHT_PROP_BASE_HP, FightProperty.FIGHT_PROP_BASE_ATTACK, FightProperty.FIGHT_PROP_BASE_DEFENSE, FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FightProperty.FIGHT_PROP_ELEC_SUB_HURT, FightProperty.FIGHT_PROP_WATER_SUB_HURT, FightProperty.FIGHT_PROP_GRASS_SUB_HURT, FightProperty.FIGHT_PROP_WIND_SUB_HURT, FightProperty.FIGHT_PROP_ROCK_SUB_HURT, FightProperty.FIGHT_PROP_ICE_SUB_HURT); - - @Getter(onMethod_ = @Override) - private int id; - - private String monsterName; - private MonsterType type; - private String serverScript; - private List affix; - private String ai; - private int[] equips; - private List hpDrops; - private int killDropId; - private String excludeWeathers; - private int featureTagGroupID; - private int mpPropID; - private String skin; - private int describeId; - private int combatBGMLevel; - private int entityBudgetLevel; - - @SerializedName("hpBase") - private float baseHp; - @SerializedName("attackBase") - private float baseAttack; - @SerializedName("defenseBase") - private float baseDefense; - - private float fireSubHurt; - private float elecSubHurt; - private float grassSubHurt; - private float waterSubHurt; - private float windSubHurt; - private float rockSubHurt; - private float iceSubHurt; - private float physicalSubHurt; - private List propGrowCurves; - private long nameTextMapHash; - private int campID; - - // Transient - private int weaponId; - private MonsterDescribeData describeData; - - private int specialNameId; // will only be set if describe data is available - - @Override - public void onLoad() { - for (int id : this.equips) { - if (id == 0) { - continue; - } - - GadgetData gadget = GameData.getGadgetDataMap().get(id); - if (gadget == null) { - continue; - } - - if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) { - this.weaponId = id; - } - } - - this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId()); - - if (this.describeData == null){ - return; - } - for(Entry entry: GameData.getMonsterSpecialNameDataMap().entrySet()) { - if (entry.getValue().getSpecialNameLabId() == this.getDescribeData().getSpecialNameLabId()){ - this.specialNameId = entry.getKey(); - break; - } - } - } - - public float getFightProperty(FightProperty prop) { - return switch (prop) { - case FIGHT_PROP_BASE_HP -> this.baseHp; - case FIGHT_PROP_BASE_ATTACK -> this.baseAttack; - case FIGHT_PROP_BASE_DEFENSE -> this.baseDefense; - case FIGHT_PROP_PHYSICAL_SUB_HURT -> this.physicalSubHurt; - case FIGHT_PROP_FIRE_SUB_HURT -> this.fireSubHurt; - case FIGHT_PROP_ELEC_SUB_HURT -> this.elecSubHurt; - case FIGHT_PROP_WATER_SUB_HURT -> this.waterSubHurt; - case FIGHT_PROP_GRASS_SUB_HURT -> this.grassSubHurt; - case FIGHT_PROP_WIND_SUB_HURT -> this.windSubHurt; - case FIGHT_PROP_ROCK_SUB_HURT -> this.rockSubHurt; - case FIGHT_PROP_ICE_SUB_HURT -> this.iceSubHurt; - default -> 0f; - }; - } - - @Getter - public class HpDrops { - private int DropId; - private int HpPercent; - } -} +package emu.grasscutter.data.excels.monster; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.ResourceType.LoadPriority; +import emu.grasscutter.data.common.PropGrowCurve; +import emu.grasscutter.data.excels.GadgetData; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.MonsterType; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import lombok.Getter; + +@ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW) +@Getter +public class MonsterData extends GameResource { + public static Set definedFightProperties = + Set.of( + FightProperty.FIGHT_PROP_BASE_HP, + FightProperty.FIGHT_PROP_BASE_ATTACK, + FightProperty.FIGHT_PROP_BASE_DEFENSE, + FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, + FightProperty.FIGHT_PROP_FIRE_SUB_HURT, + FightProperty.FIGHT_PROP_ELEC_SUB_HURT, + FightProperty.FIGHT_PROP_WATER_SUB_HURT, + FightProperty.FIGHT_PROP_GRASS_SUB_HURT, + FightProperty.FIGHT_PROP_WIND_SUB_HURT, + FightProperty.FIGHT_PROP_ROCK_SUB_HURT, + FightProperty.FIGHT_PROP_ICE_SUB_HURT); + + @Getter(onMethod_ = @Override) + private int id; + + private String monsterName; + private MonsterType type; + private String serverScript; + private List affix; + private String ai; + private int[] equips; + private List hpDrops; + private int killDropId; + private String excludeWeathers; + private int featureTagGroupID; + private int mpPropID; + private String skin; + private int describeId; + private int combatBGMLevel; + private int entityBudgetLevel; + + @SerializedName("hpBase") + private float baseHp; + + @SerializedName("attackBase") + private float baseAttack; + + @SerializedName("defenseBase") + private float baseDefense; + + private float fireSubHurt; + private float elecSubHurt; + private float grassSubHurt; + private float waterSubHurt; + private float windSubHurt; + private float rockSubHurt; + private float iceSubHurt; + private float physicalSubHurt; + private List propGrowCurves; + private long nameTextMapHash; + private int campID; + + // Transient + private int weaponId; + private MonsterDescribeData describeData; + + private int specialNameId; // will only be set if describe data is available + + @Override + public void onLoad() { + for (int id : this.equips) { + if (id == 0) { + continue; + } + + GadgetData gadget = GameData.getGadgetDataMap().get(id); + if (gadget == null) { + continue; + } + + if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) { + this.weaponId = id; + } + } + + this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId()); + + if (this.describeData == null) { + return; + } + for (Entry entry : + GameData.getMonsterSpecialNameDataMap().entrySet()) { + if (entry.getValue().getSpecialNameLabId() == this.getDescribeData().getSpecialNameLabId()) { + this.specialNameId = entry.getKey(); + break; + } + } + } + + public float getFightProperty(FightProperty prop) { + return switch (prop) { + case FIGHT_PROP_BASE_HP -> this.baseHp; + case FIGHT_PROP_BASE_ATTACK -> this.baseAttack; + case FIGHT_PROP_BASE_DEFENSE -> this.baseDefense; + case FIGHT_PROP_PHYSICAL_SUB_HURT -> this.physicalSubHurt; + case FIGHT_PROP_FIRE_SUB_HURT -> this.fireSubHurt; + case FIGHT_PROP_ELEC_SUB_HURT -> this.elecSubHurt; + case FIGHT_PROP_WATER_SUB_HURT -> this.waterSubHurt; + case FIGHT_PROP_GRASS_SUB_HURT -> this.grassSubHurt; + case FIGHT_PROP_WIND_SUB_HURT -> this.windSubHurt; + case FIGHT_PROP_ROCK_SUB_HURT -> this.rockSubHurt; + case FIGHT_PROP_ICE_SUB_HURT -> this.iceSubHurt; + default -> 0f; + }; + } + + @Getter + public class HpDrops { + private int DropId; + private int HpPercent; + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/monster/MonsterDescribeData.java b/src/main/java/emu/grasscutter/data/excels/monster/MonsterDescribeData.java index 9111d1de5..fe310b2eb 100644 --- a/src/main/java/emu/grasscutter/data/excels/monster/MonsterDescribeData.java +++ b/src/main/java/emu/grasscutter/data/excels/monster/MonsterDescribeData.java @@ -1,21 +1,28 @@ -package emu.grasscutter.data.excels.monster; - -import com.google.gson.annotations.SerializedName; -import emu.grasscutter.data.GameResource; -import emu.grasscutter.data.ResourceType; -import emu.grasscutter.data.ResourceType.LoadPriority; -import lombok.Getter; - -@ResourceType(name = "MonsterDescribeExcelConfigData.json", loadPriority = LoadPriority.HIGH) -@Getter -public class MonsterDescribeData extends GameResource { - @Getter(onMethod_ = @Override) - private int id; - - private long nameTextMapHash; - @SerializedName(value = "titleId", alternate={"titleID"}) - private int titleId; - @SerializedName(value = "specialNameLabId", alternate={"specialNameLabID"}) - private int specialNameLabId; - private MonsterSpecialNameData specialNameData; -} +package emu.grasscutter.data.excels.monster; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.ResourceType.LoadPriority; +import lombok.Getter; + +@ResourceType(name = "MonsterDescribeExcelConfigData.json", loadPriority = LoadPriority.HIGH) +@Getter +public class MonsterDescribeData extends GameResource { + @Getter(onMethod_ = @Override) + private int id; + + private long nameTextMapHash; + + @SerializedName( + value = "titleId", + alternate = {"titleID"}) + private int titleId; + + @SerializedName( + value = "specialNameLabId", + alternate = {"specialNameLabID"}) + private int specialNameLabId; + + private MonsterSpecialNameData specialNameData; +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index e3e965db5..214df5c38 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -1,464 +1,464 @@ -package emu.grasscutter.database; - -import static com.mongodb.client.model.Filters.eq; - -import com.mongodb.client.result.DeleteResult; -import dev.morphia.query.FindOptions; -import dev.morphia.query.Sort; -import dev.morphia.query.experimental.filters.Filters; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.Account; -import emu.grasscutter.game.achievement.Achievements; -import emu.grasscutter.game.activity.PlayerActivityData; -import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.battlepass.BattlePassManager; -import emu.grasscutter.game.friends.Friendship; -import emu.grasscutter.game.gacha.GachaRecord; -import emu.grasscutter.game.home.GameHome; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.mail.Mail; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.GameMainQuest; -import emu.grasscutter.game.world.SceneGroupInstance; - -import java.util.List; -import java.util.stream.Stream; - -public final class DatabaseHelper { - public static Account createAccount(String username) { - return createAccountWithUid(username, 0); - } - - public static Account createAccountWithUid(String username, int reservedUid) { - // Unique names only - if (DatabaseHelper.checkIfAccountExists(username)) { - return null; - } - - // Make sure there are no id collisions - if (reservedUid > 0) { - // Cannot make account with the same uid as the server console - if (reservedUid == GameConstants.SERVER_CONSOLE_UID) { - return null; - } - - if (DatabaseHelper.checkIfAccountExists(reservedUid)) { - return null; - } - - // Make sure no existing player already has this id. - if (DatabaseHelper.checkIfPlayerExists(reservedUid)) { - return null; - } - } - - // Account - Account account = new Account(); - account.setUsername(username); - account.setId(Integer.toString(DatabaseManager.getNextId(account))); - - if (reservedUid > 0) { - account.setReservedPlayerUid(reservedUid); - } - - DatabaseHelper.saveAccount(account); - return account; - } - - @Deprecated - public static Account createAccountWithPassword(String username, String password) { - // Unique names only - Account exists = DatabaseHelper.getAccountByName(username); - if (exists != null) { - return null; - } - - // Account - Account account = new Account(); - account.setId(Integer.toString(DatabaseManager.getNextId(account))); - account.setUsername(username); - account.setPassword(password); - DatabaseHelper.saveAccount(account); - return account; - } - - public static void saveAccount(Account account) { - DatabaseManager.getAccountDatastore().save(account); - } - - public static Account getAccountByName(String username) { - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("username", username)) - .first(); - } - - public static Account getAccountByToken(String token) { - if (token == null) return null; - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("token", token)) - .first(); - } - - public static Account getAccountBySessionKey(String sessionKey) { - if (sessionKey == null) return null; - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("sessionKey", sessionKey)) - .first(); - } - - public static Account getAccountById(String uid) { - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("_id", uid)) - .first(); - } - - public static Account getAccountByPlayerId(int playerId) { - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("reservedPlayerId", playerId)) - .first(); - } - - public static boolean checkIfAccountExists(String name) { - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("username", name)) - .count() - > 0; - } - - public static boolean checkIfAccountExists(int reservedUid) { - return DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("reservedPlayerId", reservedUid)) - .count() - > 0; - } - - public static synchronized void deleteAccount(Account target) { - // To delete an account, we need to also delete all the other documents in the database that - // reference the account. - // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way - // does not leave the - // database in an inconsistent state, but unfortunately Mongo only supports that when we have a - // replica set ... - - Player player = Grasscutter.getGameServer().getPlayerByAccountId(target.getId()); - - // Close session first - if (player != null) { - player.getSession().close(); - } else { - player = getPlayerByAccount(target); - if (player == null) return; - } - int uid = player.getUid(); - // Delete data from collections - DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid)); - DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid)); - DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid)); - DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid)); - DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid)); - DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid)); - DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid)); - DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid)); - DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid)); - - // Delete friendships. - // Here, we need to make sure to not only delete the deleted account's friendships, - // but also all friendship entries for that account's friends. - DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid)); - DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid)); - - // Delete the player last. - DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete(); - - // Finally, delete the account itself. - DatabaseManager.getAccountDatastore() - .find(Account.class) - .filter(Filters.eq("id", target.getId())) - .delete(); - } - - public static Stream getByGameClass(Class classType) { - return DatabaseManager.getGameDatastore().find(classType).stream(); - } - - @Deprecated(forRemoval = true) - public static List getAllPlayers() { - return DatabaseManager.getGameDatastore().find(Player.class).stream().toList(); - } - - public static Player getPlayerByUid(int id) { - return DatabaseManager.getGameDatastore() - .find(Player.class) - .filter(Filters.eq("_id", id)) - .first(); - } - - @Deprecated - public static Player getPlayerByAccount(Account account) { - return DatabaseManager.getGameDatastore() - .find(Player.class) - .filter(Filters.eq("accountId", account.getId())) - .first(); - } - - public static Player getPlayerByAccount(Account account, Class playerClass) { - return DatabaseManager.getGameDatastore() - .find(playerClass) - .filter(Filters.eq("accountId", account.getId())) - .first(); - } - - public static boolean checkIfPlayerExists(int uid) { - return DatabaseManager.getGameDatastore() - .find(Player.class) - .filter(Filters.eq("_id", uid)) - .count() - > 0; - } - - public static synchronized Player generatePlayerUid(Player character, int reservedId) { - // Check if reserved id - int id; - if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { - id = reservedId; - character.setUid(id); - } else { - do { - id = DatabaseManager.getNextId(character); - } while (checkIfPlayerExists(id)); - character.setUid(id); - } - // Save to database - DatabaseManager.getGameDatastore().save(character); - return character; - } - - public static synchronized int getNextPlayerId(int reservedId) { - // Check if reserved id - int id; - if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { - id = reservedId; - } else { - do { - id = DatabaseManager.getNextId(Player.class); - } while (checkIfPlayerExists(id)); - } - return id; - } - - public static void savePlayer(Player character) { - DatabaseManager.getGameDatastore().save(character); - } - - public static void saveAvatar(Avatar avatar) { - DatabaseManager.getGameDatastore().save(avatar); - } - - public static List getAvatars(Player player) { - return DatabaseManager.getGameDatastore() - .find(Avatar.class) - .filter(Filters.eq("ownerId", player.getUid())) - .stream() - .toList(); - } - - public static void saveItem(GameItem item) { - DatabaseManager.getGameDatastore().save(item); - } - - public static boolean deleteItem(GameItem item) { - DeleteResult result = DatabaseManager.getGameDatastore().delete(item); - return result.wasAcknowledged(); - } - - public static List getInventoryItems(Player player) { - return DatabaseManager.getGameDatastore() - .find(GameItem.class) - .filter(Filters.eq("ownerId", player.getUid())) - .stream() - .toList(); - } - - public static List getFriends(Player player) { - return DatabaseManager.getGameDatastore() - .find(Friendship.class) - .filter(Filters.eq("ownerId", player.getUid())) - .stream() - .toList(); - } - - public static List getReverseFriends(Player player) { - return DatabaseManager.getGameDatastore() - .find(Friendship.class) - .filter(Filters.eq("friendId", player.getUid())) - .stream() - .toList(); - } - - public static void saveFriendship(Friendship friendship) { - DatabaseManager.getGameDatastore().save(friendship); - } - - public static void deleteFriendship(Friendship friendship) { - DatabaseManager.getGameDatastore().delete(friendship); - } - - public static Friendship getReverseFriendship(Friendship friendship) { - return DatabaseManager.getGameDatastore() - .find(Friendship.class) - .filter( - Filters.and( - Filters.eq("ownerId", friendship.getFriendId()), - Filters.eq("friendId", friendship.getOwnerId()))) - .first(); - } - - public static List getGachaRecords(int ownerId, int page, int gachaType) { - return getGachaRecords(ownerId, page, gachaType, 10); - } - - public static List getGachaRecords( - int ownerId, int page, int gachaType, int pageSize) { - return DatabaseManager.getGameDatastore() - .find(GachaRecord.class) - .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) - .iterator( - new FindOptions() - .sort(Sort.descending("transactionDate")) - .skip(pageSize * page) - .limit(pageSize)) - .toList(); - } - - public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType) { - return getGachaRecordsMaxPage(ownerId, page, gachaType, 10); - } - - public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize) { - long count = - DatabaseManager.getGameDatastore() - .find(GachaRecord.class) - .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) - .count(); - return count / 10 + (count % 10 > 0 ? 1 : 0); - } - - public static void saveGachaRecord(GachaRecord gachaRecord) { - DatabaseManager.getGameDatastore().save(gachaRecord); - } - - public static List getAllMail(Player player) { - return DatabaseManager.getGameDatastore() - .find(Mail.class) - .filter(Filters.eq("ownerUid", player.getUid())) - .stream() - .toList(); - } - - public static void saveMail(Mail mail) { - DatabaseManager.getGameDatastore().save(mail); - } - - public static boolean deleteMail(Mail mail) { - DeleteResult result = DatabaseManager.getGameDatastore().delete(mail); - return result.wasAcknowledged(); - } - - public static List getAllQuests(Player player) { - return DatabaseManager.getGameDatastore() - .find(GameMainQuest.class) - .filter(Filters.eq("ownerUid", player.getUid())) - .stream() - .toList(); - } - - public static void saveQuest(GameMainQuest quest) { - DatabaseManager.getGameDatastore().save(quest); - } - - public static boolean deleteQuest(GameMainQuest quest) { - return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged(); - } - - public static GameHome getHomeByUid(int id) { - return DatabaseManager.getGameDatastore() - .find(GameHome.class) - .filter(Filters.eq("ownerUid", id)) - .first(); - } - - public static void saveHome(GameHome gameHome) { - DatabaseManager.getGameDatastore().save(gameHome); - } - - public static BattlePassManager loadBattlePass(Player player) { - BattlePassManager manager = - DatabaseManager.getGameDatastore() - .find(BattlePassManager.class) - .filter(Filters.eq("ownerUid", player.getUid())) - .first(); - if (manager == null) { - manager = new BattlePassManager(player); - manager.save(); - } else { - manager.setPlayer(player); - } - return manager; - } - - public static void saveBattlePass(BattlePassManager manager) { - DatabaseManager.getGameDatastore().save(manager); - } - - public static PlayerActivityData getPlayerActivityData(int uid, int activityId) { - return DatabaseManager.getGameDatastore() - .find(PlayerActivityData.class) - .filter(Filters.and(Filters.eq("uid", uid), Filters.eq("activityId", activityId))) - .first(); - } - - public static void savePlayerActivityData(PlayerActivityData playerActivityData) { - DatabaseManager.getGameDatastore().save(playerActivityData); - } - - public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) { - return DatabaseManager.getGameDatastore() - .find(MusicGameBeatmap.class) - .filter(Filters.eq("musicShareId", musicShareId)) - .first(); - } - - public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) { - DatabaseManager.getGameDatastore().save(musicGameBeatmap); - } - - public static Achievements getAchievementData(int uid) { - return DatabaseManager.getGameDatastore() - .find(Achievements.class) - .filter(Filters.and(Filters.eq("uid", uid))) - .first(); - } - - public static void saveAchievementData(Achievements achievements) { - DatabaseManager.getGameDatastore().save(achievements); - } - - public static void saveGroupInstance(SceneGroupInstance instance) { - DatabaseManager.getGameDatastore().save(instance); - } - - public static SceneGroupInstance loadGroupInstance(int groupId, Player owner) { - return DatabaseManager.getGameDatastore().find(SceneGroupInstance.class) - .filter(Filters.and(Filters.eq("ownerUid", owner.getUid()), - Filters.eq("groupId", groupId))).first(); - } -} +package emu.grasscutter.database; + +import static com.mongodb.client.model.Filters.eq; + +import com.mongodb.client.result.DeleteResult; +import dev.morphia.query.FindOptions; +import dev.morphia.query.Sort; +import dev.morphia.query.experimental.filters.Filters; +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.achievement.Achievements; +import emu.grasscutter.game.activity.PlayerActivityData; +import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.battlepass.BattlePassManager; +import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.game.gacha.GachaRecord; +import emu.grasscutter.game.home.GameHome; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.mail.Mail; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.world.SceneGroupInstance; +import java.util.List; +import java.util.stream.Stream; + +public final class DatabaseHelper { + public static Account createAccount(String username) { + return createAccountWithUid(username, 0); + } + + public static Account createAccountWithUid(String username, int reservedUid) { + // Unique names only + if (DatabaseHelper.checkIfAccountExists(username)) { + return null; + } + + // Make sure there are no id collisions + if (reservedUid > 0) { + // Cannot make account with the same uid as the server console + if (reservedUid == GameConstants.SERVER_CONSOLE_UID) { + return null; + } + + if (DatabaseHelper.checkIfAccountExists(reservedUid)) { + return null; + } + + // Make sure no existing player already has this id. + if (DatabaseHelper.checkIfPlayerExists(reservedUid)) { + return null; + } + } + + // Account + Account account = new Account(); + account.setUsername(username); + account.setId(Integer.toString(DatabaseManager.getNextId(account))); + + if (reservedUid > 0) { + account.setReservedPlayerUid(reservedUid); + } + + DatabaseHelper.saveAccount(account); + return account; + } + + @Deprecated + public static Account createAccountWithPassword(String username, String password) { + // Unique names only + Account exists = DatabaseHelper.getAccountByName(username); + if (exists != null) { + return null; + } + + // Account + Account account = new Account(); + account.setId(Integer.toString(DatabaseManager.getNextId(account))); + account.setUsername(username); + account.setPassword(password); + DatabaseHelper.saveAccount(account); + return account; + } + + public static void saveAccount(Account account) { + DatabaseManager.getAccountDatastore().save(account); + } + + public static Account getAccountByName(String username) { + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("username", username)) + .first(); + } + + public static Account getAccountByToken(String token) { + if (token == null) return null; + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("token", token)) + .first(); + } + + public static Account getAccountBySessionKey(String sessionKey) { + if (sessionKey == null) return null; + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("sessionKey", sessionKey)) + .first(); + } + + public static Account getAccountById(String uid) { + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("_id", uid)) + .first(); + } + + public static Account getAccountByPlayerId(int playerId) { + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("reservedPlayerId", playerId)) + .first(); + } + + public static boolean checkIfAccountExists(String name) { + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("username", name)) + .count() + > 0; + } + + public static boolean checkIfAccountExists(int reservedUid) { + return DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("reservedPlayerId", reservedUid)) + .count() + > 0; + } + + public static synchronized void deleteAccount(Account target) { + // To delete an account, we need to also delete all the other documents in the database that + // reference the account. + // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way + // does not leave the + // database in an inconsistent state, but unfortunately Mongo only supports that when we have a + // replica set ... + + Player player = Grasscutter.getGameServer().getPlayerByAccountId(target.getId()); + + // Close session first + if (player != null) { + player.getSession().close(); + } else { + player = getPlayerByAccount(target); + if (player == null) return; + } + int uid = player.getUid(); + // Delete data from collections + DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid)); + DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid)); + DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid)); + DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid)); + DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid)); + DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid)); + DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid)); + DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid)); + DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid)); + + // Delete friendships. + // Here, we need to make sure to not only delete the deleted account's friendships, + // but also all friendship entries for that account's friends. + DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid)); + DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid)); + + // Delete the player last. + DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete(); + + // Finally, delete the account itself. + DatabaseManager.getAccountDatastore() + .find(Account.class) + .filter(Filters.eq("id", target.getId())) + .delete(); + } + + public static Stream getByGameClass(Class classType) { + return DatabaseManager.getGameDatastore().find(classType).stream(); + } + + @Deprecated(forRemoval = true) + public static List getAllPlayers() { + return DatabaseManager.getGameDatastore().find(Player.class).stream().toList(); + } + + public static Player getPlayerByUid(int id) { + return DatabaseManager.getGameDatastore() + .find(Player.class) + .filter(Filters.eq("_id", id)) + .first(); + } + + @Deprecated + public static Player getPlayerByAccount(Account account) { + return DatabaseManager.getGameDatastore() + .find(Player.class) + .filter(Filters.eq("accountId", account.getId())) + .first(); + } + + public static Player getPlayerByAccount(Account account, Class playerClass) { + return DatabaseManager.getGameDatastore() + .find(playerClass) + .filter(Filters.eq("accountId", account.getId())) + .first(); + } + + public static boolean checkIfPlayerExists(int uid) { + return DatabaseManager.getGameDatastore() + .find(Player.class) + .filter(Filters.eq("_id", uid)) + .count() + > 0; + } + + public static synchronized Player generatePlayerUid(Player character, int reservedId) { + // Check if reserved id + int id; + if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { + id = reservedId; + character.setUid(id); + } else { + do { + id = DatabaseManager.getNextId(character); + } while (checkIfPlayerExists(id)); + character.setUid(id); + } + // Save to database + DatabaseManager.getGameDatastore().save(character); + return character; + } + + public static synchronized int getNextPlayerId(int reservedId) { + // Check if reserved id + int id; + if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { + id = reservedId; + } else { + do { + id = DatabaseManager.getNextId(Player.class); + } while (checkIfPlayerExists(id)); + } + return id; + } + + public static void savePlayer(Player character) { + DatabaseManager.getGameDatastore().save(character); + } + + public static void saveAvatar(Avatar avatar) { + DatabaseManager.getGameDatastore().save(avatar); + } + + public static List getAvatars(Player player) { + return DatabaseManager.getGameDatastore() + .find(Avatar.class) + .filter(Filters.eq("ownerId", player.getUid())) + .stream() + .toList(); + } + + public static void saveItem(GameItem item) { + DatabaseManager.getGameDatastore().save(item); + } + + public static boolean deleteItem(GameItem item) { + DeleteResult result = DatabaseManager.getGameDatastore().delete(item); + return result.wasAcknowledged(); + } + + public static List getInventoryItems(Player player) { + return DatabaseManager.getGameDatastore() + .find(GameItem.class) + .filter(Filters.eq("ownerId", player.getUid())) + .stream() + .toList(); + } + + public static List getFriends(Player player) { + return DatabaseManager.getGameDatastore() + .find(Friendship.class) + .filter(Filters.eq("ownerId", player.getUid())) + .stream() + .toList(); + } + + public static List getReverseFriends(Player player) { + return DatabaseManager.getGameDatastore() + .find(Friendship.class) + .filter(Filters.eq("friendId", player.getUid())) + .stream() + .toList(); + } + + public static void saveFriendship(Friendship friendship) { + DatabaseManager.getGameDatastore().save(friendship); + } + + public static void deleteFriendship(Friendship friendship) { + DatabaseManager.getGameDatastore().delete(friendship); + } + + public static Friendship getReverseFriendship(Friendship friendship) { + return DatabaseManager.getGameDatastore() + .find(Friendship.class) + .filter( + Filters.and( + Filters.eq("ownerId", friendship.getFriendId()), + Filters.eq("friendId", friendship.getOwnerId()))) + .first(); + } + + public static List getGachaRecords(int ownerId, int page, int gachaType) { + return getGachaRecords(ownerId, page, gachaType, 10); + } + + public static List getGachaRecords( + int ownerId, int page, int gachaType, int pageSize) { + return DatabaseManager.getGameDatastore() + .find(GachaRecord.class) + .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) + .iterator( + new FindOptions() + .sort(Sort.descending("transactionDate")) + .skip(pageSize * page) + .limit(pageSize)) + .toList(); + } + + public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType) { + return getGachaRecordsMaxPage(ownerId, page, gachaType, 10); + } + + public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize) { + long count = + DatabaseManager.getGameDatastore() + .find(GachaRecord.class) + .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) + .count(); + return count / 10 + (count % 10 > 0 ? 1 : 0); + } + + public static void saveGachaRecord(GachaRecord gachaRecord) { + DatabaseManager.getGameDatastore().save(gachaRecord); + } + + public static List getAllMail(Player player) { + return DatabaseManager.getGameDatastore() + .find(Mail.class) + .filter(Filters.eq("ownerUid", player.getUid())) + .stream() + .toList(); + } + + public static void saveMail(Mail mail) { + DatabaseManager.getGameDatastore().save(mail); + } + + public static boolean deleteMail(Mail mail) { + DeleteResult result = DatabaseManager.getGameDatastore().delete(mail); + return result.wasAcknowledged(); + } + + public static List getAllQuests(Player player) { + return DatabaseManager.getGameDatastore() + .find(GameMainQuest.class) + .filter(Filters.eq("ownerUid", player.getUid())) + .stream() + .toList(); + } + + public static void saveQuest(GameMainQuest quest) { + DatabaseManager.getGameDatastore().save(quest); + } + + public static boolean deleteQuest(GameMainQuest quest) { + return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged(); + } + + public static GameHome getHomeByUid(int id) { + return DatabaseManager.getGameDatastore() + .find(GameHome.class) + .filter(Filters.eq("ownerUid", id)) + .first(); + } + + public static void saveHome(GameHome gameHome) { + DatabaseManager.getGameDatastore().save(gameHome); + } + + public static BattlePassManager loadBattlePass(Player player) { + BattlePassManager manager = + DatabaseManager.getGameDatastore() + .find(BattlePassManager.class) + .filter(Filters.eq("ownerUid", player.getUid())) + .first(); + if (manager == null) { + manager = new BattlePassManager(player); + manager.save(); + } else { + manager.setPlayer(player); + } + return manager; + } + + public static void saveBattlePass(BattlePassManager manager) { + DatabaseManager.getGameDatastore().save(manager); + } + + public static PlayerActivityData getPlayerActivityData(int uid, int activityId) { + return DatabaseManager.getGameDatastore() + .find(PlayerActivityData.class) + .filter(Filters.and(Filters.eq("uid", uid), Filters.eq("activityId", activityId))) + .first(); + } + + public static void savePlayerActivityData(PlayerActivityData playerActivityData) { + DatabaseManager.getGameDatastore().save(playerActivityData); + } + + public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) { + return DatabaseManager.getGameDatastore() + .find(MusicGameBeatmap.class) + .filter(Filters.eq("musicShareId", musicShareId)) + .first(); + } + + public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) { + DatabaseManager.getGameDatastore().save(musicGameBeatmap); + } + + public static Achievements getAchievementData(int uid) { + return DatabaseManager.getGameDatastore() + .find(Achievements.class) + .filter(Filters.and(Filters.eq("uid", uid))) + .first(); + } + + public static void saveAchievementData(Achievements achievements) { + DatabaseManager.getGameDatastore().save(achievements); + } + + public static void saveGroupInstance(SceneGroupInstance instance) { + DatabaseManager.getGameDatastore().save(instance); + } + + public static SceneGroupInstance loadGroupInstance(int groupId, Player owner) { + return DatabaseManager.getGameDatastore() + .find(SceneGroupInstance.class) + .filter(Filters.and(Filters.eq("ownerUid", owner.getUid()), Filters.eq("groupId", groupId))) + .first(); + } +} diff --git a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java index ba82bb696..9d2da3686 100644 --- a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java +++ b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java @@ -1,134 +1,134 @@ -package emu.grasscutter.game.activity; - -import dev.morphia.annotations.Entity; -import dev.morphia.annotations.Id; -import dev.morphia.annotations.Transient; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.excels.activity.ActivityWatcherData; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass; -import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify; -import emu.grasscutter.utils.JsonUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; -import lombok.experimental.FieldDefaults; - -@Entity("activities") -@Data -@FieldDefaults(level = AccessLevel.PRIVATE) -@Builder(builderMethodName = "of") -public class PlayerActivityData { - @Id String id; - int uid; - int activityId; - Map watcherInfoMap; - /** the detail data of each type of activity (Json format) */ - String detail; - - @Transient Player player; - @Transient ActivityHandler activityHandler; - - public static PlayerActivityData getByPlayer(Player player, int activityId) { - return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId); - } - - public void save() { - DatabaseHelper.savePlayerActivityData(this); - } - - public synchronized void addWatcherProgress(int watcherId) { - var watcherInfo = watcherInfoMap.get(watcherId); - if (watcherInfo == null) { - return; - } - - if (watcherInfo.curProgress >= watcherInfo.totalProgress) { - return; - } - - watcherInfo.curProgress++; - getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo)); - } - - public List getAllWatcherInfoList() { - return watcherInfoMap.values().stream().map(WatcherInfo::toProto).toList(); - } - - public void setDetail(Object detail) { - this.detail = JsonUtils.encode(detail); - } - - public void takeWatcherReward(int watcherId) { - var watcher = watcherInfoMap.get(watcherId); - if (watcher == null || watcher.isTakenReward()) { - return; - } - - var reward = - Optional.of(watcher) - .map(WatcherInfo::getMetadata) - .map(ActivityWatcherData::getRewardID) - .map(id -> GameData.getRewardDataMap().get(id.intValue())); - - if (reward.isEmpty()) { - return; - } - - List rewards = new ArrayList<>(); - for (ItemParamData param : reward.get().getRewardItemList()) { - rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); - } - - player.getInventory().addItems(rewards, ActionReason.ActivityWatcher); - watcher.setTakenReward(true); - save(); - } - - @Entity - @Data - @FieldDefaults(level = AccessLevel.PRIVATE) - @Builder(builderMethodName = "of") - public static class WatcherInfo { - int watcherId; - int totalProgress; - int curProgress; - boolean isTakenReward; - - /** - * @return True when the progress of this watcher has reached the total progress. - */ - public boolean isFinished(){ - return this.curProgress >= this.totalProgress; - } - - public static WatcherInfo init(ActivityWatcher watcher) { - return WatcherInfo.of() - .watcherId(watcher.getWatcherId()) - .totalProgress(watcher.getActivityWatcherData().getProgress()) - .isTakenReward(false) - .build(); - } - - public ActivityWatcherData getMetadata() { - return GameData.getActivityWatcherDataMap().get(watcherId); - } - - public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() { - return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder() - .setWatcherId(watcherId) - .setCurProgress(curProgress) - .setTotalProgress(totalProgress) - .setIsTakenReward(isTakenReward) - .build(); - } - } -} +package emu.grasscutter.game.activity; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Transient; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.excels.activity.ActivityWatcherData; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass; +import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify; +import emu.grasscutter.utils.JsonUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Entity("activities") +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder(builderMethodName = "of") +public class PlayerActivityData { + @Id String id; + int uid; + int activityId; + Map watcherInfoMap; + /** the detail data of each type of activity (Json format) */ + String detail; + + @Transient Player player; + @Transient ActivityHandler activityHandler; + + public static PlayerActivityData getByPlayer(Player player, int activityId) { + return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId); + } + + public void save() { + DatabaseHelper.savePlayerActivityData(this); + } + + public synchronized void addWatcherProgress(int watcherId) { + var watcherInfo = watcherInfoMap.get(watcherId); + if (watcherInfo == null) { + return; + } + + if (watcherInfo.curProgress >= watcherInfo.totalProgress) { + return; + } + + watcherInfo.curProgress++; + getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo)); + } + + public List getAllWatcherInfoList() { + return watcherInfoMap.values().stream().map(WatcherInfo::toProto).toList(); + } + + public void setDetail(Object detail) { + this.detail = JsonUtils.encode(detail); + } + + public void takeWatcherReward(int watcherId) { + var watcher = watcherInfoMap.get(watcherId); + if (watcher == null || watcher.isTakenReward()) { + return; + } + + var reward = + Optional.of(watcher) + .map(WatcherInfo::getMetadata) + .map(ActivityWatcherData::getRewardID) + .map(id -> GameData.getRewardDataMap().get(id.intValue())); + + if (reward.isEmpty()) { + return; + } + + List rewards = new ArrayList<>(); + for (ItemParamData param : reward.get().getRewardItemList()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + + player.getInventory().addItems(rewards, ActionReason.ActivityWatcher); + watcher.setTakenReward(true); + save(); + } + + @Entity + @Data + @FieldDefaults(level = AccessLevel.PRIVATE) + @Builder(builderMethodName = "of") + public static class WatcherInfo { + int watcherId; + int totalProgress; + int curProgress; + boolean isTakenReward; + + /** + * @return True when the progress of this watcher has reached the total progress. + */ + public boolean isFinished() { + return this.curProgress >= this.totalProgress; + } + + public static WatcherInfo init(ActivityWatcher watcher) { + return WatcherInfo.of() + .watcherId(watcher.getWatcherId()) + .totalProgress(watcher.getActivityWatcherData().getProgress()) + .isTakenReward(false) + .build(); + } + + public ActivityWatcherData getMetadata() { + return GameData.getActivityWatcherDataMap().get(watcherId); + } + + public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() { + return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder() + .setWatcherId(watcherId) + .setCurProgress(curProgress) + .setTotalProgress(totalProgress) + .setIsTakenReward(isTakenReward) + .build(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index 2597b1ae3..234e8c5a4 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -1,1271 +1,1269 @@ -package emu.grasscutter.game.avatar; - -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - -import dev.morphia.annotations.*; -import emu.grasscutter.GameConstants; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.OpenConfigEntry; -import emu.grasscutter.data.binout.OpenConfigEntry.SkillPointModifier; -import emu.grasscutter.data.common.FightPropData; -import emu.grasscutter.data.excels.*; -import emu.grasscutter.data.excels.ItemData.WeaponProperty; -import emu.grasscutter.data.excels.avatar.AvatarData; -import emu.grasscutter.data.excels.avatar.AvatarSkillData; -import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; -import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData.InherentProudSkillOpens; -import emu.grasscutter.data.excels.avatar.AvatarTalentData; -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.trial.TrialAvatarTemplateData; -import emu.grasscutter.data.excels.weapon.WeaponCurveData; -import emu.grasscutter.data.excels.weapon.WeaponPromoteData; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.inventory.EquipType; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.inventory.ItemType; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.*; -import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo; -import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo; -import emu.grasscutter.net.proto.AvatarSkillInfoOuterClass.AvatarSkillInfo; -import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData; -import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass; -import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo; -import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip; -import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord; -import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo; -import emu.grasscutter.server.packet.send.*; -import emu.grasscutter.utils.ProtoHelper; -import it.unimi.dsi.fastutil.ints.*; -import java.util.*; -import java.util.stream.Stream; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import lombok.val; -import org.bson.types.ObjectId; - -import javax.annotation.Nonnull; - -@Entity(value = "avatars", useDiscriminator = false) -public class Avatar { - @Transient @Getter private final Int2ObjectMap equips; - @Transient @Getter private final Int2FloatOpenHashMap fightProperties; - @Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides; - @Id private ObjectId id; - @Indexed @Getter private int ownerId; // Id of player that this avatar belongs to - @Transient private Player owner; - @Transient @Getter private AvatarData avatarData; - @Transient @Getter private AvatarSkillDepotData skillDepot; - @Transient @Getter private long guid; // Player unique id - @Getter private int avatarId; // Id of avatar - @Getter @Setter private int level = 1; - @Getter @Setter private int exp; - @Getter @Setter private int promoteLevel; - @Getter @Setter private int satiation; // Fullness - @Getter @Setter private int satiationPenalty; // When eating too much - @Getter @Setter private float currentHp; - private float currentEnergy; - @Transient @Getter private Set extraAbilityEmbryos; - - private List fetters; - - private final Map skillLevelMap = new Int2IntArrayMap(7); // Talent levels - - @Transient @Getter - private final Map skillExtraChargeMap = new Int2IntArrayMap(2); // Charges - - @Transient - private final Map proudSkillBonusMap = - new Int2IntArrayMap(2); // Talent bonus levels (from const) - - @Getter private int skillDepotId; - private Set talentIdList; // Constellation id list - @Getter private Set proudSkillList; // Character passives - - @Getter @Setter private int flyCloak; - @Getter @Setter private int costume; - @Getter private int bornTime; - - @Getter @Setter private int fetterLevel = 1; - @Getter @Setter private int fetterExp; - - @Getter @Setter private int nameCardRewardId; - @Getter @Setter private int nameCardId; - - // trial avatar property - @Getter @Setter private int trialAvatarId = 0; - // cannot store to db if grant reason is not integer - @Getter @Setter - private int grantReason = TrialAvatarGrantRecord.GrantReason.GRANT_REASON_INVALID.getNumber(); - - @Getter @Setter private int fromParentQuestId = 0; - // so far no outer class or prop value has information of this, but from packet: - // 1 = normal, 2 = trial avatar - @Getter @Setter private int avatarType = Type.NORMAL.getNumber(); - - @Deprecated // Do not use. Morhpia only! - public Avatar() { - this.equips = new Int2ObjectOpenHashMap<>(); - this.fightProperties = new Int2FloatOpenHashMap(); - this.fightPropOverrides = new Int2FloatOpenHashMap(); - this.extraAbilityEmbryos = new HashSet<>(); - this.fetters = new ArrayList<>(); // TODO Move to avatar - } - - // On creation - public Avatar(int avatarId) { - this(GameData.getAvatarDataMap().get(avatarId)); - } - - public Avatar(AvatarData data) { - this(); - this.avatarId = data.getId(); - this.nameCardRewardId = data.getNameCardRewardId(); - this.nameCardId = data.getNameCardId(); - this.avatarData = data; - this.bornTime = (int) (System.currentTimeMillis() / 1000); - this.flyCloak = 140001; - - this.talentIdList = new HashSet<>(); - this.proudSkillList = new HashSet<>(); - - // Combat properties - Stream.of(FightProperty.values()) - .map(FightProperty::getId) - .filter(id -> (id > 0) && (id < 3000)) - .forEach(id -> this.setFightProperty(id, 0f)); - - // Skill depot - this.setSkillDepotData( - switch (this.avatarId) { - case GameConstants.MAIN_CHARACTER_MALE -> GameData.getAvatarSkillDepotDataMap() - .get(504); // Hack to start with anemo skills - case GameConstants.MAIN_CHARACTER_FEMALE -> GameData.getAvatarSkillDepotDataMap() - .get(704); - default -> data.getSkillDepot(); - }); - - // Set stats - this.recalcStats(); - this.currentHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.currentHp); - this.currentEnergy = 0f; - // Load handler - this.onLoad(); - } - - public static int getMinPromoteLevel(int level) { - if (level > 80) { - return 6; - } else if (level > 70) { - return 5; - } else if (level > 60) { - return 4; - } else if (level > 50) { - return 3; - } else if (level > 40) { - return 2; - } else if (level > 20) { - return 1; - } - return 0; - } - - public Player getPlayer() { - return this.owner; - } - - public ObjectId getObjectId() { - return this.id; - } - - protected void setAvatarData(AvatarData data) { - if (this.avatarData != null) return; - this.avatarData = data; // Used while loading this from the database - } - - public void setOwner(Player player) { - this.owner = player; - this.ownerId = player.getUid(); - this.guid = player.getNextGameGuid(); - } - - public boolean addSatiation(int value) { - if (this.satiation >= 10000) return false; - this.satiation += value; - return true; - } - - public float reduceSatiation(int value) { - if (this.satiation == 0) return 0; - this.satiation -= value; - if (this.satiation < 0) { - this.satiation = 0; - } - return this.satiation; - } - - public float reduceSatiationPenalty(int value) { - if (this.satiationPenalty == 0) return 0; - this.satiationPenalty -= value; - if (this.satiationPenalty < 0) { - this.satiationPenalty = 0; - } - return this.satiationPenalty; - } - - public GameItem getEquipBySlot(EquipType slot) { - return this.getEquips().get(slot.getValue()); - } - - private GameItem getEquipBySlot(int slotId) { - return this.getEquips().get(slotId); - } - - public GameItem getWeapon() { - return this.getEquipBySlot(EquipType.EQUIP_WEAPON); - } - - protected void setSkillDepot(AvatarSkillDepotData skillDepot) { - if (this.skillDepot != null) return; - this.skillDepot = skillDepot; // Used while loading this from the database - } - - /** - * Changes this avatar's skill depot. - * Does not notify the player of the change. - * - * @param skillDepot The new skill depot. - */ - public void setSkillDepotData(AvatarSkillDepotData skillDepot) { - this.setSkillDepotData(skillDepot, false); - } - - /** - * Changes this avatar's skill depot. - * - * @param skillDepot The new skill depot. - * @param notify Whether to notify the player of the change. - */ - public void setSkillDepotData(AvatarSkillDepotData skillDepot, boolean notify) { - // Set id and depot - this.skillDepotId = skillDepot.getId(); - this.skillDepot = skillDepot; - // Add any missing skills - this.skillDepot - .getSkillsAndEnergySkill() - .forEach(skillId -> this.skillLevelMap.putIfAbsent(skillId, 1)); - // Add proud skills - this.proudSkillList.clear(); - skillDepot.getInherentProudSkillOpens().stream() - .filter(openData -> openData.getProudSkillGroupId() > 0) - .filter(openData -> openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) - .mapToInt(openData -> (openData.getProudSkillGroupId() * 100) + 1) - .filter(proudSkillId -> GameData.getProudSkillDataMap().containsKey(proudSkillId)) - .forEach(proudSkillId -> this.proudSkillList.add(proudSkillId)); - this.recalcStats(); - - // Send the depot change notification. - if (notify) this.owner.sendPacket(new PacketAvatarSkillDepotChangeNotify(this)); - } - - /** - * Changes the avatar's element to the target element, if the character has values for it set in the candSkillDepot - * - * @param elementTypeToChange element to change to - * @return false if failed or already using that element, true if it actually changed - */ - public boolean changeElement(@Nonnull ElementType elementTypeToChange) { - var candSkillDepotIdsList = this.avatarData.getCandSkillDepotIds(); - var candSkillDepotIndex = elementTypeToChange.getDepotIndex(); - - // if no candidate skill to change or index out of bound - if (candSkillDepotIdsList == null || - candSkillDepotIndex >= candSkillDepotIdsList.size()) { - return false; - } - - var candSkillDepotId = candSkillDepotIdsList.get(candSkillDepotIndex); - - // Sanity checks for skill depots - val skillDepot = GameData.getAvatarSkillDepotDataMap().get(candSkillDepotId); - if (skillDepot == null || skillDepot.getId() == skillDepotId) { - return false; - } - - // Set skill depot - setSkillDepotData(skillDepot, true); - return true; - } - - public List getFetterList() { - return fetters; - } - - public void setFetterList(List fetterList) { - this.fetters = fetterList; - } - - public void setCurrentEnergy() { - if (GAME_OPTIONS.energyUsage) { - this.setCurrentEnergy(this.currentEnergy); - } - } - - public void setCurrentEnergy(float currentEnergy) { - var depot = this.skillDepot; - if (depot != null && depot.getEnergySkillData() != null) { - ElementType element = depot.getElementType(); - var maxEnergy = depot.getEnergySkillData().getCostElemVal(); - this.setFightProperty(element.getMaxEnergyProp(), maxEnergy); - this.setFightProperty( - element.getCurEnergyProp(), GAME_OPTIONS.energyUsage ? currentEnergy : maxEnergy); - } - } - - public void setCurrentEnergy(FightProperty curEnergyProp, float currentEnergy) { - if (GAME_OPTIONS.energyUsage) { - this.setFightProperty(curEnergyProp, currentEnergy); - this.currentEnergy = currentEnergy; - this.save(); - } - } - - public void setFightProperty(FightProperty prop, float value) { - this.getFightProperties().put(prop.getId(), value); - } - - private void setFightProperty(int id, float value) { - this.getFightProperties().put(id, value); - } - - public void addFightProperty(FightProperty prop, float value) { - this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value); - } - - public float getFightProperty(FightProperty prop) { - return getFightProperties().getOrDefault(prop.getId(), 0f); - } - - public Map - getSkillLevelMap() { // Returns a copy of the skill levels for the current skillDepot. - var map = new Int2IntOpenHashMap(); - this.skillDepot - .getSkillsAndEnergySkill() - .forEach( - skillId -> map.put(skillId, this.skillLevelMap.putIfAbsent(skillId, 1).intValue())); - return map; - } - - // Returns a copy of the skill bonus levels for the current skillDepot, capped to avoid invalid - // levels. - public Map getProudSkillBonusMap() { - var map = new Int2IntArrayMap(); - this.skillDepot - .getSkillsAndEnergySkill() - .forEach( - skillId -> { - val skillData = GameData.getAvatarSkillDataMap().get(skillId); - if (skillData == null) return; - int proudSkillGroupId = skillData.getProudSkillGroupId(); - int bonus = this.proudSkillBonusMap.getOrDefault(proudSkillGroupId, 0); - int maxLevel = GameData.getProudSkillGroupMaxLevel(proudSkillGroupId); - int curLevel = this.skillLevelMap.getOrDefault(skillId, 0); - if (maxLevel > 0) { - bonus = Math.min(bonus, maxLevel - curLevel); - } - map.put(proudSkillGroupId, bonus); - }); - return map; - } - - public IntSet getTalentIdList() { // Returns a copy of the unlocked constellations for the current - // skillDepot. - var talents = new IntOpenHashSet(this.getSkillDepot().getTalents()); - talents.removeIf(id -> !this.talentIdList.contains(id)); - return talents; - } - - public int getCoreProudSkillLevel() { - var lockedTalents = new IntOpenHashSet(this.getSkillDepot().getTalents()); - lockedTalents.removeAll(this.getTalentIdList()); - // One below the lowest locked talent, or 6 if there are no locked talents. - return lockedTalents.intStream().map(i -> i % 10).min().orElse(7) - 1; - } - - public boolean equipItem(GameItem item, boolean shouldRecalc) { - // Sanity check equip type - EquipType itemEquipType = item.getItemData().getEquipType(); - if (itemEquipType == EquipType.EQUIP_NONE) { - return false; - } - - // Check if other avatars have this item equipped - Avatar otherAvatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter()); - if (otherAvatar != null) { - // Unequip other avatar's item - if (otherAvatar.unequipItem(item.getItemData().getEquipType())) { - getPlayer() - .sendPacket( - new PacketAvatarEquipChangeNotify(otherAvatar, item.getItemData().getEquipType())); - } - // Swap with other avatar - if (getEquips().containsKey(itemEquipType.getValue())) { - GameItem toSwap = this.getEquipBySlot(itemEquipType); - otherAvatar.equipItem(toSwap, false); - } - // Recalc - otherAvatar.recalcStats(); - } else if (getEquips().containsKey(itemEquipType.getValue())) { - // Unequip item in current slot if it exists - unequipItem(itemEquipType); - } - - // Set equip - getEquips().put(itemEquipType.getValue(), item); - - if (itemEquipType == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) { - item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON)); - } - - item.setEquipCharacter(this.getAvatarId()); - item.save(); - - if (this.getPlayer().hasSentLoginPackets()) { - this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item)); - } - - if (shouldRecalc) { - this.recalcStats(); - } - - return true; - } - - public boolean unequipItem(EquipType slot) { - GameItem item = getEquips().remove(slot.getValue()); - - if (item != null) { - item.setEquipCharacter(0); - item.save(); - return true; - } - - return false; - } - - public void recalcStats() { - recalcStats(false); - } - - public void recalcStats(boolean forceSendAbilityChange) { - // Setup - var data = this.getAvatarData(); - var promoteData = - GameData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel()); - var setMap = new Int2IntOpenHashMap(); - - // Extra ability embryos - Set prevExtraAbilityEmbryos = this.getExtraAbilityEmbryos(); - this.extraAbilityEmbryos = new HashSet<>(); - - // Fetters - this.setFetterList(data.getFetters()); - this.setNameCardRewardId(data.getNameCardRewardId()); - this.setNameCardId(data.getNameCardId()); - - // Get hp percent, set to 100% if none - float hpPercent = - this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 - ? 1f - : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) - / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - - // Store current energy value for later - float currentEnergy = - (this.getSkillDepot() != null) - ? this.getFightProperty(this.getSkillDepot().getElementType().getCurEnergyProp()) - : 0f; - - // Clear properties - this.getFightProperties().clear(); - - // Base stats - this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp(this.getLevel())); - this.setFightProperty( - FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack(this.getLevel())); - this.setFightProperty( - FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense(this.getLevel())); - this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL, data.getBaseCritical()); - this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, data.getBaseCriticalHurt()); - this.setFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 1f); - - if (promoteData != null) { - for (FightPropData fightPropData : promoteData.getAddProps()) { - this.addFightProperty(fightPropData.getProp(), fightPropData.getValue()); - } - } - - // Set energy usage - setCurrentEnergy(currentEnergy); - - // Artifacts - for (int slotId = 1; slotId <= 5; slotId++) { - // Get artifact - GameItem equip = this.getEquipBySlot(slotId); - if (equip == null) { - continue; - } - // Artifact main stat - ReliquaryMainPropData mainPropData = - GameData.getReliquaryMainPropDataMap().get(equip.getMainPropId()); - if (mainPropData != null) { - ReliquaryLevelData levelData = - GameData.getRelicLevelData(equip.getItemData().getRankLevel(), equip.getLevel()); - if (levelData != null) { - this.addFightProperty( - mainPropData.getFightProp(), levelData.getPropValue(mainPropData.getFightProp())); - } - } - // Artifact sub stats - for (int appendPropId : equip.getAppendPropIdList()) { - ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId); - if (affixData != null) { - this.addFightProperty(affixData.getFightProp(), affixData.getPropValue()); - } - } - // Set bonus - if (equip.getItemData().getSetId() > 0) { - setMap.addTo(equip.getItemData().getSetId(), 1); - } - } - - // Set stuff - setMap.forEach( - (setId, amount) -> { - ReliquarySetData setData = GameData.getReliquarySetDataMap().get((int) setId); - if (setData == null) return; - - // Calculate how many items are from the set - // Add affix data from set bonus - val setNeedNum = setData.getSetNeedNum(); - for (int setIndex = 0; setIndex < setNeedNum.length; setIndex++) { - if (amount < setNeedNum[setIndex]) break; - - int affixId = (setData.getEquipAffixId() * 10) + setIndex; - EquipAffixData affix = GameData.getEquipAffixDataMap().get(affixId); - if (affix == null) { - continue; - } - - // Add properties from this affix to our avatar - for (FightPropData prop : affix.getAddProps()) { - this.addFightProperty(prop.getProp(), prop.getValue()); - } - - // Add any skill strings from this affix - this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); - } - }); - - // Weapon - GameItem weapon = this.getWeapon(); - if (weapon != null) { - // Add stats - WeaponCurveData curveData = GameData.getWeaponCurveDataMap().get(weapon.getLevel()); - if (curveData != null) { - for (WeaponProperty weaponProperty : weapon.getItemData().getWeaponProperties()) { - this.addFightProperty( - weaponProperty.getPropType(), - weaponProperty.getInitValue() * curveData.getMultByProp(weaponProperty.getType())); - } - } - // Weapon promotion stats - WeaponPromoteData wepPromoteData = - GameData.getWeaponPromoteData( - weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); - if (wepPromoteData != null) { - for (FightPropData prop : wepPromoteData.getAddProps()) { - if (prop.getValue() == 0f || prop.getProp() == null) { - continue; - } - this.addFightProperty(prop.getProp(), prop.getValue()); - } - } - // Add weapon skill from affixes - if (weapon.getAffixes() != null && weapon.getAffixes().size() > 0) { - // Weapons usually dont have more than one affix but just in case... - for (int af : weapon.getAffixes()) { - if (af == 0) { - continue; - } - // Calculate affix id - int affixId = (af * 10) + weapon.getRefinement(); - EquipAffixData affix = GameData.getEquipAffixDataMap().get(affixId); - if (affix == null) { - continue; - } - - // Add properties from this affix to our avatar - for (FightPropData prop : affix.getAddProps()) { - this.addFightProperty(prop.getProp(), prop.getValue()); - } - - // Add any skill strings from this affix - this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); - } - } - } - - // Add proud skills and unlock them if needed - AvatarSkillDepotData skillDepot = - GameData.getAvatarSkillDepotDataMap().get(this.getSkillDepotId()); - this.getProudSkillList().clear(); - for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { - if (openData.getProudSkillGroupId() == 0) { - continue; - } - if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) { - int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; - if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) { - this.getProudSkillList().add(proudSkillId); - } - } - } - - // Proud skills - for (int proudSkillId : this.getProudSkillList()) { - ProudSkillData proudSkillData = GameData.getProudSkillDataMap().get(proudSkillId); - if (proudSkillData == null) { - continue; - } - - // Add properties from this proud skill to our avatar - for (FightPropData prop : proudSkillData.getAddProps()) { - this.addFightProperty(prop.getProp(), prop.getValue()); - } - - // Add any embryos from this proud skill - this.addToExtraAbilityEmbryos(proudSkillData.getOpenConfig()); - } - - // Constellations - this.getTalentIdList() - .intStream() - .mapToObj(GameData.getAvatarTalentDataMap()::get) - .filter(Objects::nonNull) - .map(AvatarTalentData::getOpenConfig) - .filter(Objects::nonNull) - .forEach(this::addToExtraAbilityEmbryos); - // Add any skill strings from this constellation - - // Set % stats - FightProperty.forEachCompoundProperty( - c -> - this.setFightProperty( - c.getResult(), - this.getFightProperty(c.getFlat()) - + (this.getFightProperty(c.getBase()) - * (1f + this.getFightProperty(c.getPercent()))))); - - // Reapply all overrides - this.fightProperties.putAll(this.fightPropOverrides); - - // Set current hp - this.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent); - - // Packet - if (getPlayer() != null && getPlayer().hasSentLoginPackets()) { - // Update stats for client - getPlayer().sendPacket(new PacketAvatarFightPropNotify(this)); - // Update client abilities - EntityAvatar entity = this.getAsEntity(); - if (entity != null - && (!this.getExtraAbilityEmbryos().equals(prevExtraAbilityEmbryos) - || forceSendAbilityChange)) { - getPlayer().sendPacket(new PacketAbilityChangeNotify(entity)); - } - } - } - - public void addToExtraAbilityEmbryos(String openConfig) { - this.addToExtraAbilityEmbryos(openConfig, false); - } - - public void addToExtraAbilityEmbryos(String openConfig, boolean forceAdd) { - if (openConfig == null || openConfig.length() == 0) { - return; - } - - OpenConfigEntry entry = GameData.getOpenConfigEntries().get(openConfig); - if (entry == null) { - if (forceAdd) { - // Add config string to ability skill list anyways - this.getExtraAbilityEmbryos().add(openConfig); - } - return; - } - - if (entry.getAddAbilities() != null) { - for (String ability : entry.getAddAbilities()) { - this.getExtraAbilityEmbryos().add(ability); - } - } - } - - public void calcConstellation(OpenConfigEntry entry, boolean notifyClient) { - if (entry == null) return; - if (this.getPlayer() == null) notifyClient = false; - - // Check if new constellation adds +3 to a skill level - if (this.calcConstellationExtraLevels(entry) && notifyClient) { - // Packet - this.getPlayer() - .sendPacket(new PacketProudSkillExtraLevelNotify(this, entry.getExtraTalentIndex())); - } - // Check if new constellation adds skill charges - if (this.calcConstellationExtraCharges(entry) && notifyClient) { - // Packet - Stream.of(entry.getSkillPointModifiers()) - .mapToInt(SkillPointModifier::getSkillId) - .forEach( - skillId -> { - this.getPlayer() - .sendPacket( - new PacketAvatarSkillMaxChargeCountNotify( - this, skillId, this.getSkillExtraChargeMap().getOrDefault(skillId, 0))); - }); - } - } - - public void recalcConstellations() { - // Clear first - this.proudSkillBonusMap.clear(); - this.skillExtraChargeMap.clear(); - - // Sanity checks - if (this.avatarData == null || this.skillDepot == null) { - return; - } - - this.getTalentIdList() - .intStream() - .mapToObj(GameData.getAvatarTalentDataMap()::get) - .filter(Objects::nonNull) - .map(AvatarTalentData::getOpenConfig) - .filter(Objects::nonNull) - .filter(openConfig -> openConfig.length() > 0) - .map(GameData.getOpenConfigEntries()::get) - .filter(Objects::nonNull) - .forEach(e -> this.calcConstellation(e, false)); - } - - private boolean calcConstellationExtraCharges(OpenConfigEntry entry) { - var skillPointModifiers = entry.getSkillPointModifiers(); - if (skillPointModifiers == null) return false; - - for (var mod : skillPointModifiers) { - AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(mod.getSkillId()); - - if (skillData == null) continue; - - int charges = skillData.getMaxChargeNum() + mod.getDelta(); - - this.getSkillExtraChargeMap().put(mod.getSkillId(), charges); - } - return true; - } - - private boolean calcConstellationExtraLevels(OpenConfigEntry entry) { - int skillId = - switch (entry.getExtraTalentIndex()) { - case 9 -> this.skillDepot.getEnergySkill(); // Ult skill - case 2 -> (this.skillDepot.getSkills().size() >= 2) - ? this.skillDepot.getSkills().get(1) - : 0; // E skill - default -> 0; - }; - // Sanity check - if (skillId == 0) { - return false; - } - - // Get proud skill group id - AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId); - - if (skillData == null) { - return false; - } - - // Add to bonus list - this.addProudSkillLevelBonus(skillData.getProudSkillGroupId(), 3); - return true; - } - - private int addProudSkillLevelBonus(int proudSkillGroupId, int bonus) { - return this.proudSkillBonusMap.compute( - proudSkillGroupId, (k, v) -> (v == null) ? bonus : v + bonus); - } - - public boolean upgradeSkill(int skillId) { - AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId); - if (skillData == null) return false; - - // Get data for next skill level - int newLevel = this.skillLevelMap.getOrDefault(skillId, 0) + 1; - if (newLevel > 10) return false; - - // Proud skill data - int proudSkillId = (skillData.getProudSkillGroupId() * 100) + newLevel; - ProudSkillData proudSkill = GameData.getProudSkillDataMap().get(proudSkillId); - if (proudSkill == null) return false; - - // Make sure break level is correct - if (this.getPromoteLevel() < proudSkill.getBreakLevel()) return false; - - // Pay materials and mora if possible - if (!this.getPlayer().getInventory().payItems(proudSkill.getTotalCostItems())) return false; - - // Upgrade skill - this.setSkillLevel(skillId, newLevel); - return true; - } - - public boolean setSkillLevel(int skillId, int level) { - if (level < 0 || level > 15) return false; - var validLevels = GameData.getAvatarSkillLevels(skillId); - if (validLevels != null && !validLevels.contains(level)) return false; - int oldLevel = - this.skillLevelMap.getOrDefault( - skillId, 0); // just taking the return value of put would have null concerns - this.skillLevelMap.put(skillId, level); - this.save(); - - // Packet - val player = this.getPlayer(); - if (player != null) { - player.sendPacket(new PacketAvatarSkillChangeNotify(this, skillId, oldLevel, level)); - player.sendPacket(new PacketAvatarSkillUpgradeRsp(this, skillId, oldLevel, level)); - } - return true; - } - - public boolean unlockConstellation() { - return this.unlockConstellation(false); - } - - public boolean unlockConstellation(boolean skipPayment) { - int currentTalentLevel = this.getCoreProudSkillLevel(); - int talentId = this.skillDepot.getTalents().get(currentTalentLevel); - return this.unlockConstellation(talentId, skipPayment); - } - - public boolean unlockConstellation(int talentId) { - return unlockConstellation(talentId, false); - } - - public boolean unlockConstellation(int talentId, boolean skipPayment) { - // Get talent - AvatarTalentData talentData = GameData.getAvatarTalentDataMap().get(talentId); - if (talentData == null) return false; - var player = this.getPlayer(); - - // Pay constellation item if possible - if (!skipPayment - && (player != null) - && !player.getInventory().payItem(talentData.getMainCostItemId(), 1)) { - return false; - } - - // Apply + recalc - this.talentIdList.add(talentData.getId()); - - // Packet - if (player != null) { - player.sendPacket(new PacketAvatarUnlockTalentNotify(this, talentId)); - player.sendPacket(new PacketUnlockAvatarTalentRsp(this, talentId)); - } - - // Proud skill bonus map (Extra skills) - this.calcConstellation(GameData.getOpenConfigEntries().get(talentData.getOpenConfig()), true); - - // Recalc + save avatar - this.recalcStats(true); - this.save(); - return true; - } - - public void forceConstellationLevel(int level) { - if (level > 6) return; // Sanity check - - if (level < 0) { // Special case for resetConst to remove inactive depots too - this.talentIdList.clear(); - this.recalcStats(); - this.save(); - return; - } - this.talentIdList.removeAll( - this.getTalentIdList()); // Only remove constellations from active depot - for (int i = 0; i < level; i++) this.unlockConstellation(true); - this.recalcStats(); - this.save(); - } - - public boolean sendSkillExtraChargeMap() { - val map = this.getSkillExtraChargeMap(); - if (map.isEmpty()) return false; - this.getPlayer() - .sendPacket( - new PacketAvatarSkillInfoNotify( - this.guid, - new Int2IntArrayMap( - map))); // TODO: Remove this allocation when updating interfaces to FastUtils - // later - return true; - } - - public EntityAvatar getAsEntity() { - for (EntityAvatar entity : getPlayer().getTeamManager().getActiveTeam()) { - if (entity.getAvatar() == this) { - return entity; - } - } - return null; - } - - public int getEntityId() { - EntityAvatar entity = getAsEntity(); - return entity != null ? entity.getId() : 0; - } - - public void save() { - DatabaseHelper.saveAvatar(this); - } - - public AvatarInfo toProto() { - int fetterLevel = this.getFetterLevel(); - AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder().setExpLevel(fetterLevel); - - if (fetterLevel != 10) { - avatarFetter.setExpNumber(this.getFetterExp()); - } - - if (this.fetters != null) { - this.fetters.forEach( - fetterId -> - avatarFetter.addFetterList( - FetterData.newBuilder() - .setFetterId(fetterId) - .setFetterState(FetterState.FINISH.getValue()))); - } - - int cardId = this.getNameCardId(); - - if (this.getPlayer().getNameCardList().contains(cardId)) { - avatarFetter.addRewardedFetterLevelList(10); - } - - AvatarInfo.Builder avatarInfo = - AvatarInfo.newBuilder() - .setAvatarId(this.getAvatarId()) - .setGuid(this.getGuid()) - .setLifeState(1) - .addAllTalentIdList(this.getTalentIdList()) - .putAllFightPropMap(this.getFightProperties()) - .setSkillDepotId(this.getSkillDepotId()) - .setCoreProudSkillLevel(this.getCoreProudSkillLevel()) - .putAllSkillLevelMap(this.getSkillLevelMap()) - .addAllInherentProudSkillList(this.getProudSkillList()) - .putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap()) - .setAvatarType(1) - .setBornTime(this.getBornTime()) - .setFetterInfo(avatarFetter) - .setWearingFlycloakId(this.getFlyCloak()) - .setCostumeId(this.getCostume()); - - this.getSkillExtraChargeMap() - .forEach( - (skillId, count) -> - avatarInfo.putSkillMap( - skillId, AvatarSkillInfo.newBuilder().setMaxChargeCount(count).build())); - - this.getEquips().forEach((k, item) -> avatarInfo.addEquipGuidList(item.getGuid())); - - avatarInfo.putPropMap( - PlayerProperty.PROP_LEVEL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel())); - avatarInfo.putPropMap( - PlayerProperty.PROP_EXP.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp())); - avatarInfo.putPropMap( - PlayerProperty.PROP_BREAK_LEVEL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel())); - avatarInfo.putPropMap( - PlayerProperty.PROP_SATIATION_VAL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation())); - avatarInfo.putPropMap( - PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), - ProtoHelper.newPropValue( - PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty())); - - return avatarInfo.build(); - } - - // used only in character showcase - public ShowAvatarInfo toShowAvatarInfoProto() { - AvatarFetterInfo.Builder avatarFetter = - AvatarFetterInfo.newBuilder().setExpLevel(this.getFetterLevel()); - - ShowAvatarInfo.Builder showAvatarInfo = - ShowAvatarInfoOuterClass.ShowAvatarInfo.newBuilder() - .setAvatarId(avatarId) - .addAllTalentIdList(this.getTalentIdList()) - .putAllFightPropMap(this.getFightProperties()) - .setSkillDepotId(this.getSkillDepotId()) - .setCoreProudSkillLevel(this.getCoreProudSkillLevel()) - .addAllInherentProudSkillList(this.getProudSkillList()) - .putAllSkillLevelMap(this.getSkillLevelMap()) - .putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap()) - .setFetterInfo(avatarFetter) - .setCostumeId(this.getCostume()); - - showAvatarInfo.putPropMap( - PlayerProperty.PROP_LEVEL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel())); - showAvatarInfo.putPropMap( - PlayerProperty.PROP_EXP.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp())); - showAvatarInfo.putPropMap( - PlayerProperty.PROP_BREAK_LEVEL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel())); - showAvatarInfo.putPropMap( - PlayerProperty.PROP_SATIATION_VAL.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation())); - showAvatarInfo.putPropMap( - PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), - ProtoHelper.newPropValue( - PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty())); - int maxStamina = this.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - showAvatarInfo.putPropMap( - PlayerProperty.PROP_MAX_STAMINA.getId(), - ProtoHelper.newPropValue(PlayerProperty.PROP_MAX_STAMINA, maxStamina)); - - for (GameItem item : this.getEquips().values()) { - if (item.getItemType() == ItemType.ITEM_RELIQUARY) { - showAvatarInfo.addEquipList( - ShowEquip.newBuilder() - .setItemId(item.getItemId()) - .setReliquary(item.toReliquaryProto())); - } else if (item.getItemType() == ItemType.ITEM_WEAPON) { - showAvatarInfo.addEquipList( - ShowEquip.newBuilder().setItemId(item.getItemId()).setWeapon(item.toWeaponProto())); - } - } - - return showAvatarInfo.build(); - } - - /** - * Converts this avatar into a trial avatar. - * - * @param level The avatar's level. - * @param avatarId The ID of the avatar. - * @param grantReason The reason for granting the avatar. - * @param questId The ID of the quest that granted the avatar. - */ - public void setTrialAvatarInfo( - int level, int avatarId, TrialAvatarGrantRecord.GrantReason grantReason, int questId) { - this.setLevel(level); - this.setPromoteLevel(getMinPromoteLevel(level)); - this.setTrialAvatarId(avatarId); - this.setGrantReason(grantReason.getNumber()); - this.setFromParentQuestId(questId); - this.setAvatarType(Type.TRIAL.getNumber()); - this.applyTrialSkillLevels(); - this.applyTrialItems(); - } - - /** - * Gets the gear template based on the avatar's level. - * - * @return The avatar's template. - */ - private int getTrialTemplate() { - return this.getLevel() <= 9 - ? 1 - : (int) - (Math.floor(this.getLevel() / 10f) * 10); // round trial level to fit template levels - } - - /** - * @return The level to be used for the avatar's skills (talents). - */ - public int getTrialSkillLevel() { - // Use default data if custom data not available. - if (GameData.getTrialAvatarCustomData().isEmpty()) { - var template = getTrialTemplate(); // round trial level to fit template levels - - var templateData = GameData.getTrialAvatarTemplateDataMap().get(template); - return templateData == null ? 1 : templateData.getTrialAvatarSkillLevel(); - } - - // Use custom data. - var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); - if (trialData == null) return 1; - - return trialData.getCoreProudSkillLevel(); // enhanced version of weapon - } - - /** Applies the correct skill level for the trial avatar. */ - public void applyTrialSkillLevels() { - this.getSkillLevelMap() - .keySet() - .forEach(skill -> this.setSkillLevel(skill, this.getTrialSkillLevel())); - } - - /** - * @return The weapon to use with the avatar. - */ - public int getTrialWeaponId() { - // Use default data if custom data not available. - if (GameData.getTrialAvatarCustomData().isEmpty()) { - if (GameData.getTrialAvatarDataMap().get(this.getTrialAvatarId()) == null) - return this.getAvatarData().getInitialWeapon(); - - return GameData.getItemDataMap().get(this.getAvatarData().getInitialWeapon() + 100) == null - ? getAvatarData().getInitialWeapon() - : getAvatarData().getInitialWeapon() + 100; // enhanced version of weapon - } - - // Use custom data. - var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); - if (trialData == null) return 0; - - var trialCustomParams = trialData.getTrialAvatarParamList(); - return trialCustomParams.size() < 2 - ? getAvatarData().getInitialWeapon() - : Integer.parseInt(trialCustomParams.get(1).split(";")[0]); - } - - /** - * @return A list of artifact IDs to use with the avatar. - */ - public List getTrialReliquary() { - // Use default data if custom data not available. - if (GameData.getTrialAvatarCustomData().isEmpty()) { - int trialAvatarTemplateLevel = getTrialTemplate(); - - TrialAvatarTemplateData templateData = - GameData.getTrialAvatarTemplateDataMap().get(trialAvatarTemplateLevel); - return templateData == null ? List.of() : templateData.getTrialReliquaryList(); - } - - // Use custom data. - var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); - if (trialData == null) return List.of(); - - var trialCustomParams = - GameData.getTrialAvatarCustomData().get(getTrialAvatarId()).getTrialAvatarParamList(); - return trialCustomParams.size() < 3 - ? List.of() - : Stream.of(trialCustomParams.get(2).split(";")).map(Integer::parseInt).toList(); - } - - /** Applies the correct items for the trial avatar. */ - public void applyTrialItems() { - // Use an enhanced version of the weapon if available. - var weapon = new GameItem(this.getTrialWeaponId()); - weapon.setLevel(this.getLevel()); - weapon.setExp(0); - weapon.setPromoteLevel(getMinPromoteLevel(this.getLevel())); - this.getEquips().put(weapon.getEquipSlot(), weapon); - - // Add artifacts for the trial avatar. - this.getTrialReliquary() - .forEach( - id -> { - var reliquaryData = GameData.getTrialReliquaryDataMap().get((int) id); - if (reliquaryData == null) return; - - var relic = new GameItem(reliquaryData.getReliquaryId()); - relic.setLevel(reliquaryData.getLevel()); - relic.setMainPropId(reliquaryData.getMainPropId()); - relic.getAppendPropIdList().addAll(reliquaryData.getAppendPropList()); - this.getEquips().put(relic.getEquipSlot(), relic); - }); - - // Add costume if avatar has a costume. - GameData.getAvatarCostumeDataItemIdMap() - .values() - .forEach( - costumeData -> { - if (costumeData.getCharacterId() != this.getAvatarId()) return; - this.setCostume(costumeData.getId()); - }); - } - - /** Equips the items applied from {@link Avatar#applyTrialItems()}. */ - public void equipTrialItems() { - var player = this.getPlayer(); - - this.getEquips() - .values() - .forEach( - item -> { - item.setEquipCharacter(this.getAvatarId()); - item.setOwner(player); - if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { - item.setWeaponEntityId(player.getWorld().getNextEntityId(EntityIdType.WEAPON)); - player.sendPacket(new PacketAvatarEquipChangeNotify(this, item)); - } - }); - } - - /** - * Converts this (trial) avatar into a trial info protocol buffer. - * - * @return The trial info protocol buffer. - */ - public TrialAvatarInfo toTrialInfo() { - var trialAvatar = - TrialAvatarInfo.newBuilder() - .setTrialAvatarId(this.getTrialAvatarId()) - .setGrantRecord( - TrialAvatarGrantRecord.newBuilder() - .setGrantReason(this.getGrantReason()) - .setFromParentQuestId(this.getFromParentQuestId())); - - // Check if the avatar is a trial avatar. - if (this.getTrialAvatarId() > 0) { - // Add the artifacts & weapons for the avatar. - trialAvatar.addAllTrialEquipList( - this.getEquips().values().stream().map(GameItem::toProto).toList()); - } - - return trialAvatar.build(); - } - - @PostLoad - private void onLoad() {} - - @PrePersist - private void prePersist() { - this.currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - } - - @AllArgsConstructor - @Getter - enum Type { - NORMAL(0), - TRIAL(1); - - final int number; - } -} +package emu.grasscutter.game.avatar; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + +import dev.morphia.annotations.*; +import emu.grasscutter.GameConstants; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.OpenConfigEntry; +import emu.grasscutter.data.binout.OpenConfigEntry.SkillPointModifier; +import emu.grasscutter.data.common.FightPropData; +import emu.grasscutter.data.excels.*; +import emu.grasscutter.data.excels.ItemData.WeaponProperty; +import emu.grasscutter.data.excels.avatar.AvatarData; +import emu.grasscutter.data.excels.avatar.AvatarSkillData; +import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; +import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData.InherentProudSkillOpens; +import emu.grasscutter.data.excels.avatar.AvatarTalentData; +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.trial.TrialAvatarTemplateData; +import emu.grasscutter.data.excels.weapon.WeaponCurveData; +import emu.grasscutter.data.excels.weapon.WeaponPromoteData; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.inventory.EquipType; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.inventory.ItemType; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.*; +import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo; +import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo; +import emu.grasscutter.net.proto.AvatarSkillInfoOuterClass.AvatarSkillInfo; +import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData; +import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass; +import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo; +import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip; +import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord; +import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.ProtoHelper; +import it.unimi.dsi.fastutil.ints.*; +import java.util.*; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.val; +import org.bson.types.ObjectId; + +@Entity(value = "avatars", useDiscriminator = false) +public class Avatar { + @Transient @Getter private final Int2ObjectMap equips; + @Transient @Getter private final Int2FloatOpenHashMap fightProperties; + @Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides; + @Id private ObjectId id; + @Indexed @Getter private int ownerId; // Id of player that this avatar belongs to + @Transient private Player owner; + @Transient @Getter private AvatarData avatarData; + @Transient @Getter private AvatarSkillDepotData skillDepot; + @Transient @Getter private long guid; // Player unique id + @Getter private int avatarId; // Id of avatar + @Getter @Setter private int level = 1; + @Getter @Setter private int exp; + @Getter @Setter private int promoteLevel; + @Getter @Setter private int satiation; // Fullness + @Getter @Setter private int satiationPenalty; // When eating too much + @Getter @Setter private float currentHp; + private float currentEnergy; + @Transient @Getter private Set extraAbilityEmbryos; + + private List fetters; + + private final Map skillLevelMap = new Int2IntArrayMap(7); // Talent levels + + @Transient @Getter + private final Map skillExtraChargeMap = new Int2IntArrayMap(2); // Charges + + @Transient + private final Map proudSkillBonusMap = + new Int2IntArrayMap(2); // Talent bonus levels (from const) + + @Getter private int skillDepotId; + private Set talentIdList; // Constellation id list + @Getter private Set proudSkillList; // Character passives + + @Getter @Setter private int flyCloak; + @Getter @Setter private int costume; + @Getter private int bornTime; + + @Getter @Setter private int fetterLevel = 1; + @Getter @Setter private int fetterExp; + + @Getter @Setter private int nameCardRewardId; + @Getter @Setter private int nameCardId; + + // trial avatar property + @Getter @Setter private int trialAvatarId = 0; + // cannot store to db if grant reason is not integer + @Getter @Setter + private int grantReason = TrialAvatarGrantRecord.GrantReason.GRANT_REASON_INVALID.getNumber(); + + @Getter @Setter private int fromParentQuestId = 0; + // so far no outer class or prop value has information of this, but from packet: + // 1 = normal, 2 = trial avatar + @Getter @Setter private int avatarType = Type.NORMAL.getNumber(); + + @Deprecated // Do not use. Morhpia only! + public Avatar() { + this.equips = new Int2ObjectOpenHashMap<>(); + this.fightProperties = new Int2FloatOpenHashMap(); + this.fightPropOverrides = new Int2FloatOpenHashMap(); + this.extraAbilityEmbryos = new HashSet<>(); + this.fetters = new ArrayList<>(); // TODO Move to avatar + } + + // On creation + public Avatar(int avatarId) { + this(GameData.getAvatarDataMap().get(avatarId)); + } + + public Avatar(AvatarData data) { + this(); + this.avatarId = data.getId(); + this.nameCardRewardId = data.getNameCardRewardId(); + this.nameCardId = data.getNameCardId(); + this.avatarData = data; + this.bornTime = (int) (System.currentTimeMillis() / 1000); + this.flyCloak = 140001; + + this.talentIdList = new HashSet<>(); + this.proudSkillList = new HashSet<>(); + + // Combat properties + Stream.of(FightProperty.values()) + .map(FightProperty::getId) + .filter(id -> (id > 0) && (id < 3000)) + .forEach(id -> this.setFightProperty(id, 0f)); + + // Skill depot + this.setSkillDepotData( + switch (this.avatarId) { + case GameConstants.MAIN_CHARACTER_MALE -> GameData.getAvatarSkillDepotDataMap() + .get(504); // Hack to start with anemo skills + case GameConstants.MAIN_CHARACTER_FEMALE -> GameData.getAvatarSkillDepotDataMap() + .get(704); + default -> data.getSkillDepot(); + }); + + // Set stats + this.recalcStats(); + this.currentHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.currentHp); + this.currentEnergy = 0f; + // Load handler + this.onLoad(); + } + + public static int getMinPromoteLevel(int level) { + if (level > 80) { + return 6; + } else if (level > 70) { + return 5; + } else if (level > 60) { + return 4; + } else if (level > 50) { + return 3; + } else if (level > 40) { + return 2; + } else if (level > 20) { + return 1; + } + return 0; + } + + public Player getPlayer() { + return this.owner; + } + + public ObjectId getObjectId() { + return this.id; + } + + protected void setAvatarData(AvatarData data) { + if (this.avatarData != null) return; + this.avatarData = data; // Used while loading this from the database + } + + public void setOwner(Player player) { + this.owner = player; + this.ownerId = player.getUid(); + this.guid = player.getNextGameGuid(); + } + + public boolean addSatiation(int value) { + if (this.satiation >= 10000) return false; + this.satiation += value; + return true; + } + + public float reduceSatiation(int value) { + if (this.satiation == 0) return 0; + this.satiation -= value; + if (this.satiation < 0) { + this.satiation = 0; + } + return this.satiation; + } + + public float reduceSatiationPenalty(int value) { + if (this.satiationPenalty == 0) return 0; + this.satiationPenalty -= value; + if (this.satiationPenalty < 0) { + this.satiationPenalty = 0; + } + return this.satiationPenalty; + } + + public GameItem getEquipBySlot(EquipType slot) { + return this.getEquips().get(slot.getValue()); + } + + private GameItem getEquipBySlot(int slotId) { + return this.getEquips().get(slotId); + } + + public GameItem getWeapon() { + return this.getEquipBySlot(EquipType.EQUIP_WEAPON); + } + + protected void setSkillDepot(AvatarSkillDepotData skillDepot) { + if (this.skillDepot != null) return; + this.skillDepot = skillDepot; // Used while loading this from the database + } + + /** + * Changes this avatar's skill depot. Does not notify the player of the change. + * + * @param skillDepot The new skill depot. + */ + public void setSkillDepotData(AvatarSkillDepotData skillDepot) { + this.setSkillDepotData(skillDepot, false); + } + + /** + * Changes this avatar's skill depot. + * + * @param skillDepot The new skill depot. + * @param notify Whether to notify the player of the change. + */ + public void setSkillDepotData(AvatarSkillDepotData skillDepot, boolean notify) { + // Set id and depot + this.skillDepotId = skillDepot.getId(); + this.skillDepot = skillDepot; + // Add any missing skills + this.skillDepot + .getSkillsAndEnergySkill() + .forEach(skillId -> this.skillLevelMap.putIfAbsent(skillId, 1)); + // Add proud skills + this.proudSkillList.clear(); + skillDepot.getInherentProudSkillOpens().stream() + .filter(openData -> openData.getProudSkillGroupId() > 0) + .filter(openData -> openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) + .mapToInt(openData -> (openData.getProudSkillGroupId() * 100) + 1) + .filter(proudSkillId -> GameData.getProudSkillDataMap().containsKey(proudSkillId)) + .forEach(proudSkillId -> this.proudSkillList.add(proudSkillId)); + this.recalcStats(); + + // Send the depot change notification. + if (notify) this.owner.sendPacket(new PacketAvatarSkillDepotChangeNotify(this)); + } + + /** + * Changes the avatar's element to the target element, if the character has values for it set in + * the candSkillDepot + * + * @param elementTypeToChange element to change to + * @return false if failed or already using that element, true if it actually changed + */ + public boolean changeElement(@Nonnull ElementType elementTypeToChange) { + var candSkillDepotIdsList = this.avatarData.getCandSkillDepotIds(); + var candSkillDepotIndex = elementTypeToChange.getDepotIndex(); + + // if no candidate skill to change or index out of bound + if (candSkillDepotIdsList == null || candSkillDepotIndex >= candSkillDepotIdsList.size()) { + return false; + } + + var candSkillDepotId = candSkillDepotIdsList.get(candSkillDepotIndex); + + // Sanity checks for skill depots + val skillDepot = GameData.getAvatarSkillDepotDataMap().get(candSkillDepotId); + if (skillDepot == null || skillDepot.getId() == skillDepotId) { + return false; + } + + // Set skill depot + setSkillDepotData(skillDepot, true); + return true; + } + + public List getFetterList() { + return fetters; + } + + public void setFetterList(List fetterList) { + this.fetters = fetterList; + } + + public void setCurrentEnergy() { + if (GAME_OPTIONS.energyUsage) { + this.setCurrentEnergy(this.currentEnergy); + } + } + + public void setCurrentEnergy(float currentEnergy) { + var depot = this.skillDepot; + if (depot != null && depot.getEnergySkillData() != null) { + ElementType element = depot.getElementType(); + var maxEnergy = depot.getEnergySkillData().getCostElemVal(); + this.setFightProperty(element.getMaxEnergyProp(), maxEnergy); + this.setFightProperty( + element.getCurEnergyProp(), GAME_OPTIONS.energyUsage ? currentEnergy : maxEnergy); + } + } + + public void setCurrentEnergy(FightProperty curEnergyProp, float currentEnergy) { + if (GAME_OPTIONS.energyUsage) { + this.setFightProperty(curEnergyProp, currentEnergy); + this.currentEnergy = currentEnergy; + this.save(); + } + } + + public void setFightProperty(FightProperty prop, float value) { + this.getFightProperties().put(prop.getId(), value); + } + + private void setFightProperty(int id, float value) { + this.getFightProperties().put(id, value); + } + + public void addFightProperty(FightProperty prop, float value) { + this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value); + } + + public float getFightProperty(FightProperty prop) { + return getFightProperties().getOrDefault(prop.getId(), 0f); + } + + public Map + getSkillLevelMap() { // Returns a copy of the skill levels for the current skillDepot. + var map = new Int2IntOpenHashMap(); + this.skillDepot + .getSkillsAndEnergySkill() + .forEach( + skillId -> map.put(skillId, this.skillLevelMap.putIfAbsent(skillId, 1).intValue())); + return map; + } + + // Returns a copy of the skill bonus levels for the current skillDepot, capped to avoid invalid + // levels. + public Map getProudSkillBonusMap() { + var map = new Int2IntArrayMap(); + this.skillDepot + .getSkillsAndEnergySkill() + .forEach( + skillId -> { + val skillData = GameData.getAvatarSkillDataMap().get(skillId); + if (skillData == null) return; + int proudSkillGroupId = skillData.getProudSkillGroupId(); + int bonus = this.proudSkillBonusMap.getOrDefault(proudSkillGroupId, 0); + int maxLevel = GameData.getProudSkillGroupMaxLevel(proudSkillGroupId); + int curLevel = this.skillLevelMap.getOrDefault(skillId, 0); + if (maxLevel > 0) { + bonus = Math.min(bonus, maxLevel - curLevel); + } + map.put(proudSkillGroupId, bonus); + }); + return map; + } + + public IntSet getTalentIdList() { // Returns a copy of the unlocked constellations for the current + // skillDepot. + var talents = new IntOpenHashSet(this.getSkillDepot().getTalents()); + talents.removeIf(id -> !this.talentIdList.contains(id)); + return talents; + } + + public int getCoreProudSkillLevel() { + var lockedTalents = new IntOpenHashSet(this.getSkillDepot().getTalents()); + lockedTalents.removeAll(this.getTalentIdList()); + // One below the lowest locked talent, or 6 if there are no locked talents. + return lockedTalents.intStream().map(i -> i % 10).min().orElse(7) - 1; + } + + public boolean equipItem(GameItem item, boolean shouldRecalc) { + // Sanity check equip type + EquipType itemEquipType = item.getItemData().getEquipType(); + if (itemEquipType == EquipType.EQUIP_NONE) { + return false; + } + + // Check if other avatars have this item equipped + Avatar otherAvatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter()); + if (otherAvatar != null) { + // Unequip other avatar's item + if (otherAvatar.unequipItem(item.getItemData().getEquipType())) { + getPlayer() + .sendPacket( + new PacketAvatarEquipChangeNotify(otherAvatar, item.getItemData().getEquipType())); + } + // Swap with other avatar + if (getEquips().containsKey(itemEquipType.getValue())) { + GameItem toSwap = this.getEquipBySlot(itemEquipType); + otherAvatar.equipItem(toSwap, false); + } + // Recalc + otherAvatar.recalcStats(); + } else if (getEquips().containsKey(itemEquipType.getValue())) { + // Unequip item in current slot if it exists + unequipItem(itemEquipType); + } + + // Set equip + getEquips().put(itemEquipType.getValue(), item); + + if (itemEquipType == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) { + item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON)); + } + + item.setEquipCharacter(this.getAvatarId()); + item.save(); + + if (this.getPlayer().hasSentLoginPackets()) { + this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item)); + } + + if (shouldRecalc) { + this.recalcStats(); + } + + return true; + } + + public boolean unequipItem(EquipType slot) { + GameItem item = getEquips().remove(slot.getValue()); + + if (item != null) { + item.setEquipCharacter(0); + item.save(); + return true; + } + + return false; + } + + public void recalcStats() { + recalcStats(false); + } + + public void recalcStats(boolean forceSendAbilityChange) { + // Setup + var data = this.getAvatarData(); + var promoteData = + GameData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel()); + var setMap = new Int2IntOpenHashMap(); + + // Extra ability embryos + Set prevExtraAbilityEmbryos = this.getExtraAbilityEmbryos(); + this.extraAbilityEmbryos = new HashSet<>(); + + // Fetters + this.setFetterList(data.getFetters()); + this.setNameCardRewardId(data.getNameCardRewardId()); + this.setNameCardId(data.getNameCardId()); + + // Get hp percent, set to 100% if none + float hpPercent = + this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 + ? 1f + : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) + / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + + // Store current energy value for later + float currentEnergy = + (this.getSkillDepot() != null) + ? this.getFightProperty(this.getSkillDepot().getElementType().getCurEnergyProp()) + : 0f; + + // Clear properties + this.getFightProperties().clear(); + + // Base stats + this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp(this.getLevel())); + this.setFightProperty( + FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack(this.getLevel())); + this.setFightProperty( + FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense(this.getLevel())); + this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL, data.getBaseCritical()); + this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, data.getBaseCriticalHurt()); + this.setFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 1f); + + if (promoteData != null) { + for (FightPropData fightPropData : promoteData.getAddProps()) { + this.addFightProperty(fightPropData.getProp(), fightPropData.getValue()); + } + } + + // Set energy usage + setCurrentEnergy(currentEnergy); + + // Artifacts + for (int slotId = 1; slotId <= 5; slotId++) { + // Get artifact + GameItem equip = this.getEquipBySlot(slotId); + if (equip == null) { + continue; + } + // Artifact main stat + ReliquaryMainPropData mainPropData = + GameData.getReliquaryMainPropDataMap().get(equip.getMainPropId()); + if (mainPropData != null) { + ReliquaryLevelData levelData = + GameData.getRelicLevelData(equip.getItemData().getRankLevel(), equip.getLevel()); + if (levelData != null) { + this.addFightProperty( + mainPropData.getFightProp(), levelData.getPropValue(mainPropData.getFightProp())); + } + } + // Artifact sub stats + for (int appendPropId : equip.getAppendPropIdList()) { + ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId); + if (affixData != null) { + this.addFightProperty(affixData.getFightProp(), affixData.getPropValue()); + } + } + // Set bonus + if (equip.getItemData().getSetId() > 0) { + setMap.addTo(equip.getItemData().getSetId(), 1); + } + } + + // Set stuff + setMap.forEach( + (setId, amount) -> { + ReliquarySetData setData = GameData.getReliquarySetDataMap().get((int) setId); + if (setData == null) return; + + // Calculate how many items are from the set + // Add affix data from set bonus + val setNeedNum = setData.getSetNeedNum(); + for (int setIndex = 0; setIndex < setNeedNum.length; setIndex++) { + if (amount < setNeedNum[setIndex]) break; + + int affixId = (setData.getEquipAffixId() * 10) + setIndex; + EquipAffixData affix = GameData.getEquipAffixDataMap().get(affixId); + if (affix == null) { + continue; + } + + // Add properties from this affix to our avatar + for (FightPropData prop : affix.getAddProps()) { + this.addFightProperty(prop.getProp(), prop.getValue()); + } + + // Add any skill strings from this affix + this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); + } + }); + + // Weapon + GameItem weapon = this.getWeapon(); + if (weapon != null) { + // Add stats + WeaponCurveData curveData = GameData.getWeaponCurveDataMap().get(weapon.getLevel()); + if (curveData != null) { + for (WeaponProperty weaponProperty : weapon.getItemData().getWeaponProperties()) { + this.addFightProperty( + weaponProperty.getPropType(), + weaponProperty.getInitValue() * curveData.getMultByProp(weaponProperty.getType())); + } + } + // Weapon promotion stats + WeaponPromoteData wepPromoteData = + GameData.getWeaponPromoteData( + weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel()); + if (wepPromoteData != null) { + for (FightPropData prop : wepPromoteData.getAddProps()) { + if (prop.getValue() == 0f || prop.getProp() == null) { + continue; + } + this.addFightProperty(prop.getProp(), prop.getValue()); + } + } + // Add weapon skill from affixes + if (weapon.getAffixes() != null && weapon.getAffixes().size() > 0) { + // Weapons usually dont have more than one affix but just in case... + for (int af : weapon.getAffixes()) { + if (af == 0) { + continue; + } + // Calculate affix id + int affixId = (af * 10) + weapon.getRefinement(); + EquipAffixData affix = GameData.getEquipAffixDataMap().get(affixId); + if (affix == null) { + continue; + } + + // Add properties from this affix to our avatar + for (FightPropData prop : affix.getAddProps()) { + this.addFightProperty(prop.getProp(), prop.getValue()); + } + + // Add any skill strings from this affix + this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); + } + } + } + + // Add proud skills and unlock them if needed + AvatarSkillDepotData skillDepot = + GameData.getAvatarSkillDepotDataMap().get(this.getSkillDepotId()); + this.getProudSkillList().clear(); + for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { + if (openData.getProudSkillGroupId() == 0) { + continue; + } + if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) { + int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; + if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) { + this.getProudSkillList().add(proudSkillId); + } + } + } + + // Proud skills + for (int proudSkillId : this.getProudSkillList()) { + ProudSkillData proudSkillData = GameData.getProudSkillDataMap().get(proudSkillId); + if (proudSkillData == null) { + continue; + } + + // Add properties from this proud skill to our avatar + for (FightPropData prop : proudSkillData.getAddProps()) { + this.addFightProperty(prop.getProp(), prop.getValue()); + } + + // Add any embryos from this proud skill + this.addToExtraAbilityEmbryos(proudSkillData.getOpenConfig()); + } + + // Constellations + this.getTalentIdList() + .intStream() + .mapToObj(GameData.getAvatarTalentDataMap()::get) + .filter(Objects::nonNull) + .map(AvatarTalentData::getOpenConfig) + .filter(Objects::nonNull) + .forEach(this::addToExtraAbilityEmbryos); + // Add any skill strings from this constellation + + // Set % stats + FightProperty.forEachCompoundProperty( + c -> + this.setFightProperty( + c.getResult(), + this.getFightProperty(c.getFlat()) + + (this.getFightProperty(c.getBase()) + * (1f + this.getFightProperty(c.getPercent()))))); + + // Reapply all overrides + this.fightProperties.putAll(this.fightPropOverrides); + + // Set current hp + this.setFightProperty( + FightProperty.FIGHT_PROP_CUR_HP, + this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent); + + // Packet + if (getPlayer() != null && getPlayer().hasSentLoginPackets()) { + // Update stats for client + getPlayer().sendPacket(new PacketAvatarFightPropNotify(this)); + // Update client abilities + EntityAvatar entity = this.getAsEntity(); + if (entity != null + && (!this.getExtraAbilityEmbryos().equals(prevExtraAbilityEmbryos) + || forceSendAbilityChange)) { + getPlayer().sendPacket(new PacketAbilityChangeNotify(entity)); + } + } + } + + public void addToExtraAbilityEmbryos(String openConfig) { + this.addToExtraAbilityEmbryos(openConfig, false); + } + + public void addToExtraAbilityEmbryos(String openConfig, boolean forceAdd) { + if (openConfig == null || openConfig.length() == 0) { + return; + } + + OpenConfigEntry entry = GameData.getOpenConfigEntries().get(openConfig); + if (entry == null) { + if (forceAdd) { + // Add config string to ability skill list anyways + this.getExtraAbilityEmbryos().add(openConfig); + } + return; + } + + if (entry.getAddAbilities() != null) { + for (String ability : entry.getAddAbilities()) { + this.getExtraAbilityEmbryos().add(ability); + } + } + } + + public void calcConstellation(OpenConfigEntry entry, boolean notifyClient) { + if (entry == null) return; + if (this.getPlayer() == null) notifyClient = false; + + // Check if new constellation adds +3 to a skill level + if (this.calcConstellationExtraLevels(entry) && notifyClient) { + // Packet + this.getPlayer() + .sendPacket(new PacketProudSkillExtraLevelNotify(this, entry.getExtraTalentIndex())); + } + // Check if new constellation adds skill charges + if (this.calcConstellationExtraCharges(entry) && notifyClient) { + // Packet + Stream.of(entry.getSkillPointModifiers()) + .mapToInt(SkillPointModifier::getSkillId) + .forEach( + skillId -> { + this.getPlayer() + .sendPacket( + new PacketAvatarSkillMaxChargeCountNotify( + this, skillId, this.getSkillExtraChargeMap().getOrDefault(skillId, 0))); + }); + } + } + + public void recalcConstellations() { + // Clear first + this.proudSkillBonusMap.clear(); + this.skillExtraChargeMap.clear(); + + // Sanity checks + if (this.avatarData == null || this.skillDepot == null) { + return; + } + + this.getTalentIdList() + .intStream() + .mapToObj(GameData.getAvatarTalentDataMap()::get) + .filter(Objects::nonNull) + .map(AvatarTalentData::getOpenConfig) + .filter(Objects::nonNull) + .filter(openConfig -> openConfig.length() > 0) + .map(GameData.getOpenConfigEntries()::get) + .filter(Objects::nonNull) + .forEach(e -> this.calcConstellation(e, false)); + } + + private boolean calcConstellationExtraCharges(OpenConfigEntry entry) { + var skillPointModifiers = entry.getSkillPointModifiers(); + if (skillPointModifiers == null) return false; + + for (var mod : skillPointModifiers) { + AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(mod.getSkillId()); + + if (skillData == null) continue; + + int charges = skillData.getMaxChargeNum() + mod.getDelta(); + + this.getSkillExtraChargeMap().put(mod.getSkillId(), charges); + } + return true; + } + + private boolean calcConstellationExtraLevels(OpenConfigEntry entry) { + int skillId = + switch (entry.getExtraTalentIndex()) { + case 9 -> this.skillDepot.getEnergySkill(); // Ult skill + case 2 -> (this.skillDepot.getSkills().size() >= 2) + ? this.skillDepot.getSkills().get(1) + : 0; // E skill + default -> 0; + }; + // Sanity check + if (skillId == 0) { + return false; + } + + // Get proud skill group id + AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId); + + if (skillData == null) { + return false; + } + + // Add to bonus list + this.addProudSkillLevelBonus(skillData.getProudSkillGroupId(), 3); + return true; + } + + private int addProudSkillLevelBonus(int proudSkillGroupId, int bonus) { + return this.proudSkillBonusMap.compute( + proudSkillGroupId, (k, v) -> (v == null) ? bonus : v + bonus); + } + + public boolean upgradeSkill(int skillId) { + AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId); + if (skillData == null) return false; + + // Get data for next skill level + int newLevel = this.skillLevelMap.getOrDefault(skillId, 0) + 1; + if (newLevel > 10) return false; + + // Proud skill data + int proudSkillId = (skillData.getProudSkillGroupId() * 100) + newLevel; + ProudSkillData proudSkill = GameData.getProudSkillDataMap().get(proudSkillId); + if (proudSkill == null) return false; + + // Make sure break level is correct + if (this.getPromoteLevel() < proudSkill.getBreakLevel()) return false; + + // Pay materials and mora if possible + if (!this.getPlayer().getInventory().payItems(proudSkill.getTotalCostItems())) return false; + + // Upgrade skill + this.setSkillLevel(skillId, newLevel); + return true; + } + + public boolean setSkillLevel(int skillId, int level) { + if (level < 0 || level > 15) return false; + var validLevels = GameData.getAvatarSkillLevels(skillId); + if (validLevels != null && !validLevels.contains(level)) return false; + int oldLevel = + this.skillLevelMap.getOrDefault( + skillId, 0); // just taking the return value of put would have null concerns + this.skillLevelMap.put(skillId, level); + this.save(); + + // Packet + val player = this.getPlayer(); + if (player != null) { + player.sendPacket(new PacketAvatarSkillChangeNotify(this, skillId, oldLevel, level)); + player.sendPacket(new PacketAvatarSkillUpgradeRsp(this, skillId, oldLevel, level)); + } + return true; + } + + public boolean unlockConstellation() { + return this.unlockConstellation(false); + } + + public boolean unlockConstellation(boolean skipPayment) { + int currentTalentLevel = this.getCoreProudSkillLevel(); + int talentId = this.skillDepot.getTalents().get(currentTalentLevel); + return this.unlockConstellation(talentId, skipPayment); + } + + public boolean unlockConstellation(int talentId) { + return unlockConstellation(talentId, false); + } + + public boolean unlockConstellation(int talentId, boolean skipPayment) { + // Get talent + AvatarTalentData talentData = GameData.getAvatarTalentDataMap().get(talentId); + if (talentData == null) return false; + var player = this.getPlayer(); + + // Pay constellation item if possible + if (!skipPayment + && (player != null) + && !player.getInventory().payItem(talentData.getMainCostItemId(), 1)) { + return false; + } + + // Apply + recalc + this.talentIdList.add(talentData.getId()); + + // Packet + if (player != null) { + player.sendPacket(new PacketAvatarUnlockTalentNotify(this, talentId)); + player.sendPacket(new PacketUnlockAvatarTalentRsp(this, talentId)); + } + + // Proud skill bonus map (Extra skills) + this.calcConstellation(GameData.getOpenConfigEntries().get(talentData.getOpenConfig()), true); + + // Recalc + save avatar + this.recalcStats(true); + this.save(); + return true; + } + + public void forceConstellationLevel(int level) { + if (level > 6) return; // Sanity check + + if (level < 0) { // Special case for resetConst to remove inactive depots too + this.talentIdList.clear(); + this.recalcStats(); + this.save(); + return; + } + this.talentIdList.removeAll( + this.getTalentIdList()); // Only remove constellations from active depot + for (int i = 0; i < level; i++) this.unlockConstellation(true); + this.recalcStats(); + this.save(); + } + + public boolean sendSkillExtraChargeMap() { + val map = this.getSkillExtraChargeMap(); + if (map.isEmpty()) return false; + this.getPlayer() + .sendPacket( + new PacketAvatarSkillInfoNotify( + this.guid, + new Int2IntArrayMap( + map))); // TODO: Remove this allocation when updating interfaces to FastUtils + // later + return true; + } + + public EntityAvatar getAsEntity() { + for (EntityAvatar entity : getPlayer().getTeamManager().getActiveTeam()) { + if (entity.getAvatar() == this) { + return entity; + } + } + return null; + } + + public int getEntityId() { + EntityAvatar entity = getAsEntity(); + return entity != null ? entity.getId() : 0; + } + + public void save() { + DatabaseHelper.saveAvatar(this); + } + + public AvatarInfo toProto() { + int fetterLevel = this.getFetterLevel(); + AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder().setExpLevel(fetterLevel); + + if (fetterLevel != 10) { + avatarFetter.setExpNumber(this.getFetterExp()); + } + + if (this.fetters != null) { + this.fetters.forEach( + fetterId -> + avatarFetter.addFetterList( + FetterData.newBuilder() + .setFetterId(fetterId) + .setFetterState(FetterState.FINISH.getValue()))); + } + + int cardId = this.getNameCardId(); + + if (this.getPlayer().getNameCardList().contains(cardId)) { + avatarFetter.addRewardedFetterLevelList(10); + } + + AvatarInfo.Builder avatarInfo = + AvatarInfo.newBuilder() + .setAvatarId(this.getAvatarId()) + .setGuid(this.getGuid()) + .setLifeState(1) + .addAllTalentIdList(this.getTalentIdList()) + .putAllFightPropMap(this.getFightProperties()) + .setSkillDepotId(this.getSkillDepotId()) + .setCoreProudSkillLevel(this.getCoreProudSkillLevel()) + .putAllSkillLevelMap(this.getSkillLevelMap()) + .addAllInherentProudSkillList(this.getProudSkillList()) + .putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap()) + .setAvatarType(1) + .setBornTime(this.getBornTime()) + .setFetterInfo(avatarFetter) + .setWearingFlycloakId(this.getFlyCloak()) + .setCostumeId(this.getCostume()); + + this.getSkillExtraChargeMap() + .forEach( + (skillId, count) -> + avatarInfo.putSkillMap( + skillId, AvatarSkillInfo.newBuilder().setMaxChargeCount(count).build())); + + this.getEquips().forEach((k, item) -> avatarInfo.addEquipGuidList(item.getGuid())); + + avatarInfo.putPropMap( + PlayerProperty.PROP_LEVEL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel())); + avatarInfo.putPropMap( + PlayerProperty.PROP_EXP.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp())); + avatarInfo.putPropMap( + PlayerProperty.PROP_BREAK_LEVEL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel())); + avatarInfo.putPropMap( + PlayerProperty.PROP_SATIATION_VAL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation())); + avatarInfo.putPropMap( + PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), + ProtoHelper.newPropValue( + PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty())); + + return avatarInfo.build(); + } + + // used only in character showcase + public ShowAvatarInfo toShowAvatarInfoProto() { + AvatarFetterInfo.Builder avatarFetter = + AvatarFetterInfo.newBuilder().setExpLevel(this.getFetterLevel()); + + ShowAvatarInfo.Builder showAvatarInfo = + ShowAvatarInfoOuterClass.ShowAvatarInfo.newBuilder() + .setAvatarId(avatarId) + .addAllTalentIdList(this.getTalentIdList()) + .putAllFightPropMap(this.getFightProperties()) + .setSkillDepotId(this.getSkillDepotId()) + .setCoreProudSkillLevel(this.getCoreProudSkillLevel()) + .addAllInherentProudSkillList(this.getProudSkillList()) + .putAllSkillLevelMap(this.getSkillLevelMap()) + .putAllProudSkillExtraLevelMap(this.getProudSkillBonusMap()) + .setFetterInfo(avatarFetter) + .setCostumeId(this.getCostume()); + + showAvatarInfo.putPropMap( + PlayerProperty.PROP_LEVEL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel())); + showAvatarInfo.putPropMap( + PlayerProperty.PROP_EXP.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp())); + showAvatarInfo.putPropMap( + PlayerProperty.PROP_BREAK_LEVEL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel())); + showAvatarInfo.putPropMap( + PlayerProperty.PROP_SATIATION_VAL.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation())); + showAvatarInfo.putPropMap( + PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), + ProtoHelper.newPropValue( + PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty())); + int maxStamina = this.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); + showAvatarInfo.putPropMap( + PlayerProperty.PROP_MAX_STAMINA.getId(), + ProtoHelper.newPropValue(PlayerProperty.PROP_MAX_STAMINA, maxStamina)); + + for (GameItem item : this.getEquips().values()) { + if (item.getItemType() == ItemType.ITEM_RELIQUARY) { + showAvatarInfo.addEquipList( + ShowEquip.newBuilder() + .setItemId(item.getItemId()) + .setReliquary(item.toReliquaryProto())); + } else if (item.getItemType() == ItemType.ITEM_WEAPON) { + showAvatarInfo.addEquipList( + ShowEquip.newBuilder().setItemId(item.getItemId()).setWeapon(item.toWeaponProto())); + } + } + + return showAvatarInfo.build(); + } + + /** + * Converts this avatar into a trial avatar. + * + * @param level The avatar's level. + * @param avatarId The ID of the avatar. + * @param grantReason The reason for granting the avatar. + * @param questId The ID of the quest that granted the avatar. + */ + public void setTrialAvatarInfo( + int level, int avatarId, TrialAvatarGrantRecord.GrantReason grantReason, int questId) { + this.setLevel(level); + this.setPromoteLevel(getMinPromoteLevel(level)); + this.setTrialAvatarId(avatarId); + this.setGrantReason(grantReason.getNumber()); + this.setFromParentQuestId(questId); + this.setAvatarType(Type.TRIAL.getNumber()); + this.applyTrialSkillLevels(); + this.applyTrialItems(); + } + + /** + * Gets the gear template based on the avatar's level. + * + * @return The avatar's template. + */ + private int getTrialTemplate() { + return this.getLevel() <= 9 + ? 1 + : (int) + (Math.floor(this.getLevel() / 10f) * 10); // round trial level to fit template levels + } + + /** + * @return The level to be used for the avatar's skills (talents). + */ + public int getTrialSkillLevel() { + // Use default data if custom data not available. + if (GameData.getTrialAvatarCustomData().isEmpty()) { + var template = getTrialTemplate(); // round trial level to fit template levels + + var templateData = GameData.getTrialAvatarTemplateDataMap().get(template); + return templateData == null ? 1 : templateData.getTrialAvatarSkillLevel(); + } + + // Use custom data. + var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); + if (trialData == null) return 1; + + return trialData.getCoreProudSkillLevel(); // enhanced version of weapon + } + + /** Applies the correct skill level for the trial avatar. */ + public void applyTrialSkillLevels() { + this.getSkillLevelMap() + .keySet() + .forEach(skill -> this.setSkillLevel(skill, this.getTrialSkillLevel())); + } + + /** + * @return The weapon to use with the avatar. + */ + public int getTrialWeaponId() { + // Use default data if custom data not available. + if (GameData.getTrialAvatarCustomData().isEmpty()) { + if (GameData.getTrialAvatarDataMap().get(this.getTrialAvatarId()) == null) + return this.getAvatarData().getInitialWeapon(); + + return GameData.getItemDataMap().get(this.getAvatarData().getInitialWeapon() + 100) == null + ? getAvatarData().getInitialWeapon() + : getAvatarData().getInitialWeapon() + 100; // enhanced version of weapon + } + + // Use custom data. + var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); + if (trialData == null) return 0; + + var trialCustomParams = trialData.getTrialAvatarParamList(); + return trialCustomParams.size() < 2 + ? getAvatarData().getInitialWeapon() + : Integer.parseInt(trialCustomParams.get(1).split(";")[0]); + } + + /** + * @return A list of artifact IDs to use with the avatar. + */ + public List getTrialReliquary() { + // Use default data if custom data not available. + if (GameData.getTrialAvatarCustomData().isEmpty()) { + int trialAvatarTemplateLevel = getTrialTemplate(); + + TrialAvatarTemplateData templateData = + GameData.getTrialAvatarTemplateDataMap().get(trialAvatarTemplateLevel); + return templateData == null ? List.of() : templateData.getTrialReliquaryList(); + } + + // Use custom data. + var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId()); + if (trialData == null) return List.of(); + + var trialCustomParams = + GameData.getTrialAvatarCustomData().get(getTrialAvatarId()).getTrialAvatarParamList(); + return trialCustomParams.size() < 3 + ? List.of() + : Stream.of(trialCustomParams.get(2).split(";")).map(Integer::parseInt).toList(); + } + + /** Applies the correct items for the trial avatar. */ + public void applyTrialItems() { + // Use an enhanced version of the weapon if available. + var weapon = new GameItem(this.getTrialWeaponId()); + weapon.setLevel(this.getLevel()); + weapon.setExp(0); + weapon.setPromoteLevel(getMinPromoteLevel(this.getLevel())); + this.getEquips().put(weapon.getEquipSlot(), weapon); + + // Add artifacts for the trial avatar. + this.getTrialReliquary() + .forEach( + id -> { + var reliquaryData = GameData.getTrialReliquaryDataMap().get((int) id); + if (reliquaryData == null) return; + + var relic = new GameItem(reliquaryData.getReliquaryId()); + relic.setLevel(reliquaryData.getLevel()); + relic.setMainPropId(reliquaryData.getMainPropId()); + relic.getAppendPropIdList().addAll(reliquaryData.getAppendPropList()); + this.getEquips().put(relic.getEquipSlot(), relic); + }); + + // Add costume if avatar has a costume. + GameData.getAvatarCostumeDataItemIdMap() + .values() + .forEach( + costumeData -> { + if (costumeData.getCharacterId() != this.getAvatarId()) return; + this.setCostume(costumeData.getId()); + }); + } + + /** Equips the items applied from {@link Avatar#applyTrialItems()}. */ + public void equipTrialItems() { + var player = this.getPlayer(); + + this.getEquips() + .values() + .forEach( + item -> { + item.setEquipCharacter(this.getAvatarId()); + item.setOwner(player); + if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { + item.setWeaponEntityId(player.getWorld().getNextEntityId(EntityIdType.WEAPON)); + player.sendPacket(new PacketAvatarEquipChangeNotify(this, item)); + } + }); + } + + /** + * Converts this (trial) avatar into a trial info protocol buffer. + * + * @return The trial info protocol buffer. + */ + public TrialAvatarInfo toTrialInfo() { + var trialAvatar = + TrialAvatarInfo.newBuilder() + .setTrialAvatarId(this.getTrialAvatarId()) + .setGrantRecord( + TrialAvatarGrantRecord.newBuilder() + .setGrantReason(this.getGrantReason()) + .setFromParentQuestId(this.getFromParentQuestId())); + + // Check if the avatar is a trial avatar. + if (this.getTrialAvatarId() > 0) { + // Add the artifacts & weapons for the avatar. + trialAvatar.addAllTrialEquipList( + this.getEquips().values().stream().map(GameItem::toProto).toList()); + } + + return trialAvatar.build(); + } + + @PostLoad + private void onLoad() {} + + @PrePersist + private void prePersist() { + this.currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + } + + @AllArgsConstructor + @Getter + enum Type { + NORMAL(0), + TRIAL(1); + + final int number; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java index 24e65d841..61de1ae8b 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java @@ -1,18 +1,19 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; -import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; - -public class BasicDungeonSettleListener implements DungeonSettleListener { - - @Override - public void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) { - var scene = dungeonManager.getScene(); - var dungeonData = dungeonManager.getDungeonData(); - var time = scene.getSceneTimeSeconds() - dungeonManager.getStartSceneTime() ; - // TODO time taken and chests handling - DungeonEndStats stats = new DungeonEndStats(scene.getKilledMonsterCount(), time, 0, endReason); - - scene.broadcastPacket(new PacketDungeonSettleNotify(new BaseDungeonResult(dungeonData, stats))); - } -} +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; +import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; + +public class BasicDungeonSettleListener implements DungeonSettleListener { + + @Override + public void onDungeonSettle( + DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) { + var scene = dungeonManager.getScene(); + var dungeonData = dungeonManager.getDungeonData(); + var time = scene.getSceneTimeSeconds() - dungeonManager.getStartSceneTime(); + // TODO time taken and chests handling + DungeonEndStats stats = new DungeonEndStats(scene.getKilledMonsterCount(), time, 0, endReason); + + scene.broadcastPacket(new PacketDungeonSettleNotify(new BaseDungeonResult(dungeonData, stats))); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 96b5e3cef..308a62c41 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -1,314 +1,331 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.excels.dungeon.DungeonData; -import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData; -import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler; -import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; -import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.props.ActivityType; -import emu.grasscutter.game.props.WatcherTriggerType; -import emu.grasscutter.game.quest.enums.LogicType; -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify; -import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.Utils; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import lombok.Getter; -import lombok.NonNull; -import lombok.val; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * TODO handle time limits - * TODO handle respawn points - * TODO handle team wipes and respawns - * TODO check monster level and levelConfigMap - */ -public final class DungeonManager { - @Getter private final Scene scene; - @Getter private final DungeonData dungeonData; - @Getter private final DungeonPassConfigData passConfigData; - - @Getter private final int[] finishedConditions; - private final IntSet rewardedPlayers = new IntOpenHashSet(); - private final Set activeDungeonWayPoints = new HashSet<>(); - private boolean ended = false; - private int newestWayPoint = 0; - @Getter private int startSceneTime = 0; - - DungeonTrialTeam trialTeam = null; - - public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) { - this.scene = scene; - this.dungeonData = dungeonData; - this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond()); - this.finishedConditions = new int[passConfigData.getConds().size()]; - this.scene.setDungeonManager(this); - } - - public void triggerEvent(DungeonPassConditionType conditionType, int... params) { - if (ended) { - return; - } - for (int i = 0; i < passConfigData.getConds().size(); i++) { - var cond = passConfigData.getConds().get(i); - if (conditionType == cond.getCondType()) { - if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) { - finishedConditions[i] = 1; - } - - } - } - - if (isFinishedSuccessfully()) { - finishDungeon(); - } - - } - - public boolean isFinishedSuccessfully() { - return LogicType.calculate(passConfigData.getLogicType(), finishedConditions); - } - - public int getLevelForMonster(int id) { - //TODO should use levelConfigMap? and how? - return dungeonData.getShowLevel(); - } - - public boolean activateRespawnPoint(int pointId) { - val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId); - - if (respawnPoint == null) { - Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId); - return false; - } - - scene.broadcastPacket(new PacketDungeonWayPointNotify(activeDungeonWayPoints.add(pointId), activeDungeonWayPoints)); - newestWayPoint = pointId; - - Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId); - return true; - } - - @Nullable - public Position getRespawnLocation() { - if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid - return null; - } - var pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); - return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos(); - } - - public Position getRespawnRotation() { - if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid - return null; - } - val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); - return pointData.getRot() != null ? pointData.getRot() : null; - } - - public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) { - if (!isFinishedSuccessfully() || dungeonData.getRewardPreviewData() == null || dungeonData.getRewardPreviewData().getPreviewItems().length == 0) { - return false; - } - - // Already rewarded - if (rewardedPlayers.contains(player.getUid())) { - return false; - } - - - if (!handleCost(player, useCondensed)) { - return false; - } - - // Get and roll rewards. - List rewards = new ArrayList<>(this.rollRewards(useCondensed)); - // Add rewards to player and send notification. - player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); - player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); - - rewardedPlayers.add(player.getUid()); - - scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET)); - return true; - } - - public boolean handleCost(Player player, boolean useCondensed) { - int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20; - if (resinCost == 0) { - return true; - } - if (useCondensed) { - // Check if condensed resin is usable here. - // For this, we use the following logic for now: - // The normal resin cost of the dungeon has to be 20. - if (resinCost != 20) { - return false; - } - - // Spend the condensed resin and only proceed if the transaction succeeds. - return player.getResinManager().useCondensedResin(1); - } else if (dungeonData.getStatueCostID() == 106) { - // Spend the resin and only proceed if the transaction succeeds. - return player.getResinManager().useResin(resinCost); - } - return true; - } - - private List rollRewards(boolean useCondensed) { - List rewards = new ArrayList<>(); - int dungeonId = this.dungeonData.getId(); - // If we have specific drop data for this dungeon, we use it. - if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) { - List dropEntries = GameData.getDungeonDropDataMap().get(dungeonId); - - // Roll for each drop group. - for (var entry : dropEntries) { - // Determine the number of drops we get for this entry. - int start = entry.getCounts().get(0); - int end = entry.getCounts().get(entry.getCounts().size() - 1); - var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList()); - - int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); - - if (useCondensed) { - amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); - } - - // Double rewards in multiplay mode, if specified. - if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) { - amount *= 2; - } - - // Roll items for this group. - // Here, we have to handle stacking, or the client will not display results correctly. - // For now, we use the following logic: If the possible drop item are a list of multiple items, - // we roll them separately. If not, we stack them. This should work out in practice, at least - // for the currently existing set of dungeons. - if (entry.getItems().size() == 1) { - rewards.add(new GameItem(entry.getItems().get(0), amount)); - } else { - for (int i = 0; i < amount; i++) { - // int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size()); - // int itemId = entry.getItems().get(itemIndex); - int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities()); - rewards.add(new GameItem(itemId, 1)); - } - } - } - } - // Otherwise, we fall back to the preview data. - else { - Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId); - for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) { - rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); - } - } - - return rewards; - } - - public void applyTrialTeam(Player player) { - if (getDungeonData() == null) return; - - switch (getDungeonData().getType()) { - // case DUNGEON_PLOT is handled by quest execs - case DUNGEON_ACTIVITY -> { - switch (getDungeonData().getPlayType()) { - case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> { - val activityHandler = player.getActivityManager() - .getActivityHandlerAs(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class); - activityHandler.ifPresent(trialAvatarActivityHandler -> - this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam()); - } - } - } - case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO - } - - if (this.trialTeam != null) { - player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds); - } - } - - public void unsetTrialTeam(Player player){ - if (this.trialTeam == null) return; - - player.getTeamManager().removeTrialAvatar(); - this.trialTeam = null; - } - - public void startDungeon() { - this.startSceneTime = scene.getSceneTimeSeconds(); - scene.getPlayers().forEach(p -> { - p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId()); - applyTrialTeam(p); - }); - } - - public void finishDungeon() { - notifyEndDungeon(true); - endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED); - } - - public void notifyEndDungeon(boolean successfully) { - scene.getPlayers().forEach(p -> { - // Quest trigger - p.getQuestManager().queueEvent(successfully ? - QuestContent.QUEST_CONTENT_FINISH_DUNGEON : QuestContent.QUEST_CONTENT_FAIL_DUNGEON, - dungeonData.getId()); - - // Battle pass trigger - if (dungeonData.getType().isCountsToBattlepass() && successfully) { - p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); - } - }); - scene.getScriptManager().callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); - } - - public void quitDungeon() { - notifyEndDungeon(false); - endDungeon(BaseDungeonResult.DungeonEndReason.QUIT); - } - - public void failDungeon() { - notifyEndDungeon(false); - endDungeon(BaseDungeonResult.DungeonEndReason.FAILED); - } - - public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) { - if (scene.getDungeonSettleListeners() != null) { - scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason)); - } - ended = true; - } - - public void restartDungeon() { - this.scene.setKilledMonsterCount(0); - this.rewardedPlayers.clear(); - Arrays.fill(finishedConditions, 0); - this.ended = false; - this.activeDungeonWayPoints.clear(); - } - - public void cleanUpScene() { - this.scene.setDungeonManager(null); - this.scene.setKilledMonsterCount(0); - } -} +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; +import emu.grasscutter.data.excels.dungeon.DungeonData; +import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData; +import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler; +import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; +import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.ActivityType; +import emu.grasscutter.game.props.WatcherTriggerType; +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify; +import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import lombok.val; + +/** + * TODO handle time limits TODO handle respawn points TODO handle team wipes and respawns TODO check + * monster level and levelConfigMap + */ +public final class DungeonManager { + @Getter private final Scene scene; + @Getter private final DungeonData dungeonData; + @Getter private final DungeonPassConfigData passConfigData; + + @Getter private final int[] finishedConditions; + private final IntSet rewardedPlayers = new IntOpenHashSet(); + private final Set activeDungeonWayPoints = new HashSet<>(); + private boolean ended = false; + private int newestWayPoint = 0; + @Getter private int startSceneTime = 0; + + DungeonTrialTeam trialTeam = null; + + public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) { + this.scene = scene; + this.dungeonData = dungeonData; + this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond()); + this.finishedConditions = new int[passConfigData.getConds().size()]; + this.scene.setDungeonManager(this); + } + + public void triggerEvent(DungeonPassConditionType conditionType, int... params) { + if (ended) { + return; + } + for (int i = 0; i < passConfigData.getConds().size(); i++) { + var cond = passConfigData.getConds().get(i); + if (conditionType == cond.getCondType()) { + if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) { + finishedConditions[i] = 1; + } + } + } + + if (isFinishedSuccessfully()) { + finishDungeon(); + } + } + + public boolean isFinishedSuccessfully() { + return LogicType.calculate(passConfigData.getLogicType(), finishedConditions); + } + + public int getLevelForMonster(int id) { + // TODO should use levelConfigMap? and how? + return dungeonData.getShowLevel(); + } + + public boolean activateRespawnPoint(int pointId) { + val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId); + + if (respawnPoint == null) { + Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId); + return false; + } + + scene.broadcastPacket( + new PacketDungeonWayPointNotify( + activeDungeonWayPoints.add(pointId), activeDungeonWayPoints)); + newestWayPoint = pointId; + + Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId); + return true; + } + + @Nullable public Position getRespawnLocation() { + if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid + return null; + } + var pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); + return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos(); + } + + public Position getRespawnRotation() { + if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid + return null; + } + val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); + return pointData.getRot() != null ? pointData.getRot() : null; + } + + public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) { + if (!isFinishedSuccessfully() + || dungeonData.getRewardPreviewData() == null + || dungeonData.getRewardPreviewData().getPreviewItems().length == 0) { + return false; + } + + // Already rewarded + if (rewardedPlayers.contains(player.getUid())) { + return false; + } + + if (!handleCost(player, useCondensed)) { + return false; + } + + // Get and roll rewards. + List rewards = new ArrayList<>(this.rollRewards(useCondensed)); + // Add rewards to player and send notification. + player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); + player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); + + rewardedPlayers.add(player.getUid()); + + scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET)); + return true; + } + + public boolean handleCost(Player player, boolean useCondensed) { + int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20; + if (resinCost == 0) { + return true; + } + if (useCondensed) { + // Check if condensed resin is usable here. + // For this, we use the following logic for now: + // The normal resin cost of the dungeon has to be 20. + if (resinCost != 20) { + return false; + } + + // Spend the condensed resin and only proceed if the transaction succeeds. + return player.getResinManager().useCondensedResin(1); + } else if (dungeonData.getStatueCostID() == 106) { + // Spend the resin and only proceed if the transaction succeeds. + return player.getResinManager().useResin(resinCost); + } + return true; + } + + private List rollRewards(boolean useCondensed) { + List rewards = new ArrayList<>(); + int dungeonId = this.dungeonData.getId(); + // If we have specific drop data for this dungeon, we use it. + if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) { + List dropEntries = GameData.getDungeonDropDataMap().get(dungeonId); + + // Roll for each drop group. + for (var entry : dropEntries) { + // Determine the number of drops we get for this entry. + int start = entry.getCounts().get(0); + int end = entry.getCounts().get(entry.getCounts().size() - 1); + var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList()); + + int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); + + if (useCondensed) { + amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); + } + + // Double rewards in multiplay mode, if specified. + if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) { + amount *= 2; + } + + // Roll items for this group. + // Here, we have to handle stacking, or the client will not display results correctly. + // For now, we use the following logic: If the possible drop item are a list of multiple + // items, + // we roll them separately. If not, we stack them. This should work out in practice, at + // least + // for the currently existing set of dungeons. + if (entry.getItems().size() == 1) { + rewards.add(new GameItem(entry.getItems().get(0), amount)); + } else { + for (int i = 0; i < amount; i++) { + // int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size()); + // int itemId = entry.getItems().get(itemIndex); + int itemId = + Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities()); + rewards.add(new GameItem(itemId, 1)); + } + } + } + } + // Otherwise, we fall back to the preview data. + else { + Grasscutter.getLogger() + .info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId); + for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) { + rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); + } + } + + return rewards; + } + + public void applyTrialTeam(Player player) { + if (getDungeonData() == null) return; + + switch (getDungeonData().getType()) { + // case DUNGEON_PLOT is handled by quest execs + case DUNGEON_ACTIVITY -> { + switch (getDungeonData().getPlayType()) { + case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> { + val activityHandler = + player + .getActivityManager() + .getActivityHandlerAs( + ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class); + activityHandler.ifPresent( + trialAvatarActivityHandler -> + this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam()); + } + } + } + case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO + } + + if (this.trialTeam != null) { + player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds); + } + } + + public void unsetTrialTeam(Player player) { + if (this.trialTeam == null) return; + + player.getTeamManager().removeTrialAvatar(); + this.trialTeam = null; + } + + public void startDungeon() { + this.startSceneTime = scene.getSceneTimeSeconds(); + scene + .getPlayers() + .forEach( + p -> { + p.getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId()); + applyTrialTeam(p); + }); + } + + public void finishDungeon() { + notifyEndDungeon(true); + endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED); + } + + public void notifyEndDungeon(boolean successfully) { + scene + .getPlayers() + .forEach( + p -> { + // Quest trigger + p.getQuestManager() + .queueEvent( + successfully + ? QuestContent.QUEST_CONTENT_FINISH_DUNGEON + : QuestContent.QUEST_CONTENT_FAIL_DUNGEON, + dungeonData.getId()); + + // Battle pass trigger + if (dungeonData.getType().isCountsToBattlepass() && successfully) { + p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); + } + }); + scene + .getScriptManager() + .callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); + } + + public void quitDungeon() { + notifyEndDungeon(false); + endDungeon(BaseDungeonResult.DungeonEndReason.QUIT); + } + + public void failDungeon() { + notifyEndDungeon(false); + endDungeon(BaseDungeonResult.DungeonEndReason.FAILED); + } + + public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) { + if (scene.getDungeonSettleListeners() != null) { + scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason)); + } + ended = true; + } + + public void restartDungeon() { + this.scene.setKilledMonsterCount(0); + this.rewardedPlayers.clear(); + Arrays.fill(finishedConditions, 0); + this.ended = false; + this.activeDungeonWayPoints.clear(); + } + + public void cleanUpScene() { + this.scene.setDungeonManager(null); + this.scene.setKilledMonsterCount(0); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java index e49e6f58d..073262716 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java @@ -1,7 +1,7 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; - -public interface DungeonSettleListener { - void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason); -} +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; + +public interface DungeonSettleListener { + void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason); +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java index ac7076590..c76d8748d 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java @@ -1,157 +1,170 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.ScenePointEntry; -import emu.grasscutter.data.excels.dungeon.DungeonData; -import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData; -import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.SceneType; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.server.game.BaseGameSystem; -import emu.grasscutter.server.game.GameServer; -import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.val; -import org.reflections.Reflections; - -import java.util.List; - -public class DungeonSystem extends BaseGameSystem { - private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener(); - private final Int2ObjectMap passCondHandlers; - - public DungeonSystem(GameServer server) { - super(server); - this.passCondHandlers = new Int2ObjectOpenHashMap<>(); - registerHandlers(); - } - - public void registerHandlers() { - this.registerHandlers(this.passCondHandlers, "emu.grasscutter.game.dungeons.pass_condition", DungeonBaseHandler.class); - } - - public void registerHandlers(Int2ObjectMap map, String packageName, Class clazz) { - Reflections reflections = new Reflections(packageName); - var handlerClasses = reflections.getSubTypesOf(clazz); - - for (var obj : handlerClasses) { - this.registerPacketHandler(map, obj); - } - } - - public void registerPacketHandler(Int2ObjectMap map, Class handlerClass) { - try { - DungeonValue opcode = handlerClass.getAnnotation(DungeonValue.class); - - if (opcode == null || opcode.value() == null) { - return; - } - - map.put(opcode.value().ordinal(), handlerClass.getDeclaredConstructor().newInstance()); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void getEntryInfo(Player player, int pointId) { - ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId); - - if (entry == null) { - // Error - player.sendPacket(new PacketDungeonEntryInfoRsp()); - return; - } - - player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData())); - } - - public boolean triggerCondition(DungeonPassConfigData.DungeonPassCondition condition, int... params) { - var handler = passCondHandlers.get(condition.getCondType().ordinal()); - - if (handler == null) { - Grasscutter.getLogger().debug("Could not trigger condition {} at {}", condition.getCondType(), params); - return false; - } - - return handler.execute(condition, params); - } - - public boolean enterDungeon(Player player, int pointId, int dungeonId) { - DungeonData data = GameData.getDungeonDataMap().get(dungeonId); - - if (data == null) { - return false; - } - Grasscutter.getLogger().info("{}({}) is trying to enter dungeon {}" ,player.getNickname(),player.getUid(),dungeonId); - - int sceneId = data.getSceneId(); - var scene = player.getScene(); - scene.setPrevScene(sceneId); - - if (player.getWorld().transferPlayerToScene(player, sceneId, data)) { - scene = player.getScene(); - scene.addDungeonSettleObserver(basicDungeonSettleObserver); - } - - scene.setPrevScenePoint(pointId); - return true; - } - - /** - * used in tower dungeons handoff - */ - public boolean handoffDungeon(Player player, int dungeonId, List dungeonSettleListeners) { - DungeonData data = GameData.getDungeonDataMap().get(dungeonId); - - if (data == null) { - return false; - } - Grasscutter.getLogger().info("{}({}) is trying to enter tower dungeon {}" ,player.getNickname(),player.getUid(),dungeonId); - - if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) { - dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver); - } - return true; - } - - public void exitDungeon(Player player) { - Scene scene = player.getScene(); - - if (scene==null || scene.getSceneType() != SceneType.SCENE_DUNGEON) { - return; - } - - // Get previous scene - int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3; - - // Get previous position - val dungeonManager = scene.getDungeonManager(); - DungeonData dungeonData = dungeonManager != null ? dungeonManager.getDungeonData() : null; - Position prevPos = new Position(GameConstants.START_POSITION); - - if (dungeonData != null) { - ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint()); - - if (entry != null) { - prevPos.set(entry.getPointData().getTranPos()); - } - if(!dungeonManager.isFinishedSuccessfully()){ - dungeonManager.quitDungeon(); - } - - dungeonManager.unsetTrialTeam(player); - } - // clean temp team if it has - player.getTeamManager().cleanTemporaryTeam(); - player.getTowerManager().clearEntry(); - - - // Transfer player back to world - player.getWorld().transferPlayerToScene(player, prevScene, prevPos); - } -} +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.ScenePointEntry; +import emu.grasscutter.data.excels.dungeon.DungeonData; +import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData; +import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.server.game.BaseGameSystem; +import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.List; +import lombok.val; +import org.reflections.Reflections; + +public class DungeonSystem extends BaseGameSystem { + private static final BasicDungeonSettleListener basicDungeonSettleObserver = + new BasicDungeonSettleListener(); + private final Int2ObjectMap passCondHandlers; + + public DungeonSystem(GameServer server) { + super(server); + this.passCondHandlers = new Int2ObjectOpenHashMap<>(); + registerHandlers(); + } + + public void registerHandlers() { + this.registerHandlers( + this.passCondHandlers, + "emu.grasscutter.game.dungeons.pass_condition", + DungeonBaseHandler.class); + } + + public void registerHandlers(Int2ObjectMap map, String packageName, Class clazz) { + Reflections reflections = new Reflections(packageName); + var handlerClasses = reflections.getSubTypesOf(clazz); + + for (var obj : handlerClasses) { + this.registerPacketHandler(map, obj); + } + } + + public void registerPacketHandler(Int2ObjectMap map, Class handlerClass) { + try { + DungeonValue opcode = handlerClass.getAnnotation(DungeonValue.class); + + if (opcode == null || opcode.value() == null) { + return; + } + + map.put(opcode.value().ordinal(), handlerClass.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void getEntryInfo(Player player, int pointId) { + ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId); + + if (entry == null) { + // Error + player.sendPacket(new PacketDungeonEntryInfoRsp()); + return; + } + + player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData())); + } + + public boolean triggerCondition( + DungeonPassConfigData.DungeonPassCondition condition, int... params) { + var handler = passCondHandlers.get(condition.getCondType().ordinal()); + + if (handler == null) { + Grasscutter.getLogger() + .debug("Could not trigger condition {} at {}", condition.getCondType(), params); + return false; + } + + return handler.execute(condition, params); + } + + public boolean enterDungeon(Player player, int pointId, int dungeonId) { + DungeonData data = GameData.getDungeonDataMap().get(dungeonId); + + if (data == null) { + return false; + } + Grasscutter.getLogger() + .info( + "{}({}) is trying to enter dungeon {}", + player.getNickname(), + player.getUid(), + dungeonId); + + int sceneId = data.getSceneId(); + var scene = player.getScene(); + scene.setPrevScene(sceneId); + + if (player.getWorld().transferPlayerToScene(player, sceneId, data)) { + scene = player.getScene(); + scene.addDungeonSettleObserver(basicDungeonSettleObserver); + } + + scene.setPrevScenePoint(pointId); + return true; + } + + /** used in tower dungeons handoff */ + public boolean handoffDungeon( + Player player, int dungeonId, List dungeonSettleListeners) { + DungeonData data = GameData.getDungeonDataMap().get(dungeonId); + + if (data == null) { + return false; + } + Grasscutter.getLogger() + .info( + "{}({}) is trying to enter tower dungeon {}", + player.getNickname(), + player.getUid(), + dungeonId); + + if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) { + dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver); + } + return true; + } + + public void exitDungeon(Player player) { + Scene scene = player.getScene(); + + if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) { + return; + } + + // Get previous scene + int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3; + + // Get previous position + val dungeonManager = scene.getDungeonManager(); + DungeonData dungeonData = dungeonManager != null ? dungeonManager.getDungeonData() : null; + Position prevPos = new Position(GameConstants.START_POSITION); + + if (dungeonData != null) { + ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint()); + + if (entry != null) { + prevPos.set(entry.getPointData().getTranPos()); + } + if (!dungeonManager.isFinishedSuccessfully()) { + dungeonManager.quitDungeon(); + } + + dungeonManager.unsetTrialTeam(player); + } + // clean temp team if it has + player.getTeamManager().cleanTemporaryTeam(); + player.getTowerManager().clearEntry(); + + // Transfer player back to world + player.getWorld().transferPlayerToScene(player, prevScene, prevPos); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index 9b98311a6..b1dc39f56 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -1,38 +1,40 @@ -package emu.grasscutter.game.dungeons; - -import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason; -import emu.grasscutter.game.world.SceneGroupInstance; -import emu.grasscutter.game.dungeons.dungeon_results.TowerResult; -import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; -import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify; - -public class TowerDungeonSettleListener implements DungeonSettleListener { - - @Override - public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) { - var scene = dungeonManager.getScene(); - var dungeonData = dungeonManager.getDungeonData(); - if (scene.getLoadedGroups().stream().anyMatch(g -> { - var variables = scene.getScriptManager().getVariables(g.id); - return variables != null && variables.containsKey("stage") && variables.get("stage") == 1; - })) { - return; - } - - var towerManager = scene.getPlayers().get(0).getTowerManager(); - - towerManager.notifyCurLevelRecordChangeWhenDone(3); - scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify( - towerManager.getCurrentFloorId(), - 3, - towerManager.canEnterScheduleFloor() - )); - - var challenge = scene.getChallenge(); - var dungeonStats = new DungeonEndStats(scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason); - var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge); - - scene.broadcastPacket(new PacketDungeonSettleNotify(result)); - - } -} +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason; +import emu.grasscutter.game.dungeons.dungeon_results.TowerResult; +import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; +import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify; + +public class TowerDungeonSettleListener implements DungeonSettleListener { + + @Override + public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) { + var scene = dungeonManager.getScene(); + var dungeonData = dungeonManager.getDungeonData(); + if (scene.getLoadedGroups().stream() + .anyMatch( + g -> { + var variables = scene.getScriptManager().getVariables(g.id); + return variables != null + && variables.containsKey("stage") + && variables.get("stage") == 1; + })) { + return; + } + + var towerManager = scene.getPlayers().get(0).getTowerManager(); + + towerManager.notifyCurLevelRecordChangeWhenDone(3); + scene.broadcastPacket( + new PacketTowerFloorRecordChangeNotify( + towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor())); + + var challenge = scene.getChallenge(); + var dungeonStats = + new DungeonEndStats( + scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason); + var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge); + + scene.broadcastPacket(new PacketDungeonSettleNotify(result)); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java index 26bce996c..2ecfdc1ea 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java @@ -1,167 +1,178 @@ -package emu.grasscutter.game.dungeons.challenge; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; -import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.props.WatcherTriggerType; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneTrigger; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; -import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import lombok.Getter; -import lombok.Setter; -import lombok.val; - -@Getter -@Setter -public class WorldChallenge { - private final Scene scene; - private final SceneGroup group; - private final int challengeId; - private final int challengeIndex; - private final List paramList; - private final int timeLimit; - private final List challengeTriggers; - private final int goal; - private final AtomicInteger score; - private boolean progress; - private boolean success; - private long startedAt; - private int finishedTime; - - public WorldChallenge( - Scene scene, - SceneGroup group, - int challengeId, - int challengeIndex, - List paramList, - int timeLimit, - int goal, - List challengeTriggers) { - this.scene = scene; - this.group = group; - this.challengeId = challengeId; - this.challengeIndex = challengeIndex; - this.paramList = paramList; - this.timeLimit = timeLimit; - this.challengeTriggers = challengeTriggers; - this.goal = goal; - this.score = new AtomicInteger(0); - } - - public boolean inProgress() { - return this.progress; - } - - public void onCheckTimeOut() { - if (!inProgress()) { - return; - } - if (timeLimit <= 0) { - return; - } - challengeTriggers.forEach(t -> t.onCheckTimeout(this)); - } - - public void start() { - if (inProgress()) { - Grasscutter.getLogger().info("Could not start a in progress challenge."); - return; - } - this.progress = true; - this.startedAt = System.currentTimeMillis(); - getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); - challengeTriggers.forEach(t -> t.onBegin(this)); - } - - public void done() { - if (!this.inProgress()) return; - this.finish(true); - - var scene = this.getScene(); - var dungeonManager = scene.getDungeonManager(); - if (dungeonManager != null && dungeonManager.getDungeonData() != null) { - scene.getPlayers().forEach(p -> p.getActivityManager().triggerWatcher( - WatcherTriggerType.TRIGGER_FINISH_CHALLENGE, - String.valueOf(dungeonManager.getDungeonData().getId()), - String.valueOf(this.getGroup().id), - String.valueOf(this.getChallengeId()) - )); - } - - scene.getScriptManager().callEvent( - // TODO record the time in PARAM2 and used in action - new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS).setParam2(finishedTime)); - this.getScene().triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE, getChallengeId(), getChallengeIndex()); - - this.challengeTriggers.forEach(t -> t.onFinish(this)); - } - - public void fail(){ - if (!this.inProgress()) return; - this.finish(true); - - this.getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL)); - challengeTriggers.forEach(t -> t.onFinish(this)); - } - - private void finish(boolean success) { - this.progress = false; - this.success = success; - this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L); - getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); - } - - public int increaseScore() { - return score.incrementAndGet(); - } - - public void onMonsterDeath(EntityMonster monster) { - if (!inProgress()) { - return; - } - if (monster.getGroupId() != getGroup().id) { - return; - } - this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster)); - } - - public void onGadgetDeath(EntityGadget gadget) { - if (!inProgress()) { - return; - } - if (gadget.getGroupId() != getGroup().id) { - return; - } - this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget)); - } - - public void onGroupTriggerDeath(SceneTrigger trigger) { - if(!this.inProgress()) return; - - var triggerGroup = trigger.getCurrentGroup(); - if (triggerGroup == null || - triggerGroup.id != getGroup().id) { - return; - } - - this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger)); - } - - public void onGadgetDamage(EntityGadget gadget) { - if (!inProgress()) { - return; - } - if (gadget.getGroupId() != getGroup().id) { - return; - } - this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget)); - } -} +package emu.grasscutter.game.dungeons.challenge; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.props.WatcherTriggerType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneTrigger; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; +import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class WorldChallenge { + private final Scene scene; + private final SceneGroup group; + private final int challengeId; + private final int challengeIndex; + private final List paramList; + private final int timeLimit; + private final List challengeTriggers; + private final int goal; + private final AtomicInteger score; + private boolean progress; + private boolean success; + private long startedAt; + private int finishedTime; + + public WorldChallenge( + Scene scene, + SceneGroup group, + int challengeId, + int challengeIndex, + List paramList, + int timeLimit, + int goal, + List challengeTriggers) { + this.scene = scene; + this.group = group; + this.challengeId = challengeId; + this.challengeIndex = challengeIndex; + this.paramList = paramList; + this.timeLimit = timeLimit; + this.challengeTriggers = challengeTriggers; + this.goal = goal; + this.score = new AtomicInteger(0); + } + + public boolean inProgress() { + return this.progress; + } + + public void onCheckTimeOut() { + if (!inProgress()) { + return; + } + if (timeLimit <= 0) { + return; + } + challengeTriggers.forEach(t -> t.onCheckTimeout(this)); + } + + public void start() { + if (inProgress()) { + Grasscutter.getLogger().info("Could not start a in progress challenge."); + return; + } + this.progress = true; + this.startedAt = System.currentTimeMillis(); + getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); + challengeTriggers.forEach(t -> t.onBegin(this)); + } + + public void done() { + if (!this.inProgress()) return; + this.finish(true); + + var scene = this.getScene(); + var dungeonManager = scene.getDungeonManager(); + if (dungeonManager != null && dungeonManager.getDungeonData() != null) { + scene + .getPlayers() + .forEach( + p -> + p.getActivityManager() + .triggerWatcher( + WatcherTriggerType.TRIGGER_FINISH_CHALLENGE, + String.valueOf(dungeonManager.getDungeonData().getId()), + String.valueOf(this.getGroup().id), + String.valueOf(this.getChallengeId()))); + } + + scene + .getScriptManager() + .callEvent( + // TODO record the time in PARAM2 and used in action + new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS) + .setParam2(finishedTime)); + this.getScene() + .triggerDungeonEvent( + DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE, + getChallengeId(), + getChallengeIndex()); + + this.challengeTriggers.forEach(t -> t.onFinish(this)); + } + + public void fail() { + if (!this.inProgress()) return; + this.finish(true); + + this.getScene() + .getScriptManager() + .callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL)); + challengeTriggers.forEach(t -> t.onFinish(this)); + } + + private void finish(boolean success) { + this.progress = false; + this.success = success; + this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L); + getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); + } + + public int increaseScore() { + return score.incrementAndGet(); + } + + public void onMonsterDeath(EntityMonster monster) { + if (!inProgress()) { + return; + } + if (monster.getGroupId() != getGroup().id) { + return; + } + this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster)); + } + + public void onGadgetDeath(EntityGadget gadget) { + if (!inProgress()) { + return; + } + if (gadget.getGroupId() != getGroup().id) { + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget)); + } + + public void onGroupTriggerDeath(SceneTrigger trigger) { + if (!this.inProgress()) return; + + var triggerGroup = trigger.getCurrentGroup(); + if (triggerGroup == null || triggerGroup.id != getGroup().id) { + return; + } + + this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger)); + } + + public void onGadgetDamage(EntityGadget gadget) { + if (!inProgress()) { + return; + } + if (gadget.getGroupId() != getGroup().id) { + return; + } + this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget)); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java index a5649a334..ad6879d38 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactory.java @@ -1,36 +1,44 @@ -package emu.grasscutter.game.dungeons.challenge.factory; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.data.SceneGroup; -import lombok.val; - -import java.util.ArrayList; -import java.util.List; - -public abstract class ChallengeFactory { - private static final List challengeFactoryHandlers = new ArrayList<>(); - - static { - challengeFactoryHandlers.add(new KillAndGuardChallengeFactoryHandler()); - challengeFactoryHandlers.add(new KillMonsterCountChallengeFactoryHandler()); - challengeFactoryHandlers.add(new KillMonsterInTimeChallengeFactoryHandler()); - challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler()); - challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler()); - challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler()); - } - - public static WorldChallenge getChallenge(int localChallengeId, int challengeDataId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group){ - val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeDataId); - val challengeType = challengeData.getChallengeType(); - - for(var handler : challengeFactoryHandlers){ - if(!handler.isThisType(challengeType)){ - continue; - } - return handler.build(localChallengeId, challengeDataId, param3, param4, param5, param6, scene, group); - } - return null; - } -} +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; +import java.util.ArrayList; +import java.util.List; +import lombok.val; + +public abstract class ChallengeFactory { + private static final List challengeFactoryHandlers = new ArrayList<>(); + + static { + challengeFactoryHandlers.add(new KillAndGuardChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterCountChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterInTimeChallengeFactoryHandler()); + challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler()); + challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler()); + challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler()); + } + + public static WorldChallenge getChallenge( + int localChallengeId, + int challengeDataId, + int param3, + int param4, + int param5, + int param6, + Scene scene, + SceneGroup group) { + val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeDataId); + val challengeType = challengeData.getChallengeType(); + + for (var handler : challengeFactoryHandlers) { + if (!handler.isThisType(challengeType)) { + continue; + } + return handler.build( + localChallengeId, challengeDataId, param3, param4, param5, param6, scene, group); + } + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java index ae77fd989..f20da12c1 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/ChallengeFactoryHandler.java @@ -7,5 +7,14 @@ import emu.grasscutter.scripts.data.SceneGroup; public interface ChallengeFactoryHandler { boolean isThisType(ChallengeType challengeType); - WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group); + + WorldChallenge build( + int challengeIndex, + int challengeId, + int param3, + int param4, + int param5, + int param6, + Scene scene, + SceneGroup group); } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillAndGuardChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillAndGuardChallengeFactoryHandler.java index 2547c2fa1..c311128ee 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillAndGuardChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillAndGuardChallengeFactoryHandler.java @@ -1,18 +1,17 @@ package emu.grasscutter.game.dungeons.challenge.factory; +import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP; + import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGroup; +import java.util.List; import lombok.val; -import java.util.List; - -import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP; - -public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler{ +public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler { @Override public boolean isThisType(ChallengeType challengeType) { // ActiveChallenge with 1,188,234101003,12,3030,0 @@ -20,15 +19,24 @@ public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHand } @Override /*TODO check param4 == monstesToKill*/ - public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int monstersToKill, int gadgetCFGId, int unused, Scene scene, SceneGroup group) { + public WorldChallenge build( + int challengeIndex, + int challengeId, + int groupId, + int monstersToKill, + int gadgetCFGId, + int unused, + Scene scene, + SceneGroup group) { val realGroup = scene.getScriptManager().getGroupById(groupId); return new WorldChallenge( - scene, realGroup, + scene, + realGroup, challengeId, // Id challengeIndex, // Index List.of(monstersToKill, 0), 0, // Limit - monstersToKill, // Goal + monstersToKill, // Goal List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId))); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountChallengeFactoryHandler.java index a21c42bbc..fc5ba5add 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterCountChallengeFactoryHandler.java @@ -5,11 +5,10 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGroup; +import java.util.List; import lombok.val; -import java.util.List; - -public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler{ +public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler { @Override public boolean isThisType(ChallengeType challengeType) { // ActiveChallenge with 1, 1, 241033003, 15, 0, 0 @@ -17,16 +16,24 @@ public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactory } @Override - public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int goal, int param5, int param6, Scene scene, SceneGroup group) { + public WorldChallenge build( + int challengeIndex, + int challengeId, + int groupId, + int goal, + int param5, + int param6, + Scene scene, + SceneGroup group) { val realGroup = scene.getScriptManager().getGroupById(groupId); return new WorldChallenge( - scene, realGroup, + scene, + realGroup, challengeId, // Id challengeIndex, // Index List.of(goal, groupId), 0, // Limit - goal, // Goal - List.of(new KillMonsterCountTrigger()) - ); + goal, // Goal + List.of(new KillMonsterCountTrigger())); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterInTimeChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterInTimeChallengeFactoryHandler.java index 5adddb0db..36ebef1af 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterInTimeChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterInTimeChallengeFactoryHandler.java @@ -1,33 +1,40 @@ -package emu.grasscutter.game.dungeons.challenge.factory; - -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; -import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; -import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.data.SceneGroup; -import lombok.val; - -import java.util.List; - -public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler{ - @Override - public boolean isThisType(ChallengeType challengeType) { - // ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0 - return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME; - } - - @Override - public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCfgId, int param6, Scene scene, SceneGroup group) { - val realGroup = scene.getScriptManager().getGroupById(groupId); - return new WorldChallenge( - scene, realGroup, - challengeId, // Id - challengeIndex, // Index - List.of(timeLimit), - timeLimit, // Limit - 0, // Goal - List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger()) - ); - } -} +package emu.grasscutter.game.dungeons.challenge.factory; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; +import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneGroup; +import java.util.List; +import lombok.val; + +public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler { + @Override + public boolean isThisType(ChallengeType challengeType) { + // ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0 + return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME; + } + + @Override + public WorldChallenge build( + int challengeIndex, + int challengeId, + int timeLimit, + int groupId, + int targetCfgId, + int param6, + Scene scene, + SceneGroup group) { + val realGroup = scene.getScriptManager().getGroupById(groupId); + return new WorldChallenge( + scene, + realGroup, + challengeId, // Id + challengeIndex, // Index + List.of(timeLimit), + timeLimit, // Limit + 0, // Goal + List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterTimeChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterTimeChallengeFactoryHandler.java index fdced7377..303a2d7bf 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterTimeChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/KillMonsterTimeChallengeFactoryHandler.java @@ -6,30 +6,37 @@ import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGroup; +import java.util.List; import lombok.val; -import java.util.List; - -public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler{ +public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler { @Override public boolean isThisType(ChallengeType challengeType) { // ActiveChallenge with 180,180,45,133108061,1,0 // ActiveChallenge Fast with 1001, 5, 15, 240004005, 10, 0 - return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME || - challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST; + return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME + || challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST; } @Override - public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCount, int param6, Scene scene, SceneGroup group) { + public WorldChallenge build( + int challengeIndex, + int challengeId, + int timeLimit, + int groupId, + int targetCount, + int param6, + Scene scene, + SceneGroup group) { val realGroup = scene.getScriptManager().getGroupById(groupId); return new WorldChallenge( - scene, realGroup, + scene, + realGroup, challengeId, // Id challengeIndex, // Index List.of(targetCount, timeLimit), timeLimit, // Limit - targetCount, // Goal - List.of(new KillMonsterCountTrigger(), new InTimeTrigger()) - ); + targetCount, // Goal + List.of(new KillMonsterCountTrigger(), new InTimeTrigger())); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/SurviveChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/SurviveChallengeFactoryHandler.java index ae2a1184e..9653f3ce9 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/SurviveChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/SurviveChallengeFactoryHandler.java @@ -1,15 +1,14 @@ package emu.grasscutter.game.dungeons.challenge.factory; +import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE; + import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.trigger.ForTimeTrigger; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGroup; - import java.util.List; -import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE; - public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler { @Override public boolean isThisType(ChallengeType challengeType) { @@ -19,15 +18,23 @@ public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler { } @Override - public WorldChallenge build(int challengeIndex, int challengeId, int timeToSurvive, int unused4, int unused5, int unused6, Scene scene, SceneGroup group) { + public WorldChallenge build( + int challengeIndex, + int challengeId, + int timeToSurvive, + int unused4, + int unused5, + int unused6, + Scene scene, + SceneGroup group) { return new WorldChallenge( - scene, group, - challengeId, // Id - challengeIndex, // Index - List.of(timeToSurvive), - timeToSurvive, // Limit - 0, // Goal - List.of(new ForTimeTrigger()) - ); + scene, + group, + challengeId, // Id + challengeIndex, // Index + List.of(timeToSurvive), + timeToSurvive, // Limit + 0, // Goal + List.of(new ForTimeTrigger())); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/TriggerInTimeChallengeFactoryHandler.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/TriggerInTimeChallengeFactoryHandler.java index 18b7877db..79d2d0bce 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/TriggerInTimeChallengeFactoryHandler.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/factory/TriggerInTimeChallengeFactoryHandler.java @@ -1,16 +1,15 @@ package emu.grasscutter.game.dungeons.challenge.factory; +import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME; + import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGroup; - import java.util.List; -import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME; - public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHandler { @Override public boolean isThisType(ChallengeType challengeType) { @@ -22,15 +21,23 @@ public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHan } @Override - public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int param4, int triggerTag, int triggerCount, Scene scene, SceneGroup group) { + public WorldChallenge build( + int challengeIndex, + int challengeId, + int timeLimit, + int param4, + int triggerTag, + int triggerCount, + Scene scene, + SceneGroup group) { return new WorldChallenge( - scene, group, + scene, + group, challengeId, // Id challengeIndex, // Index List.of(timeLimit, triggerCount), timeLimit, // Limit - triggerCount, // Goal - List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag))) - ); + triggerCount, // Goal + List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag)))); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java index 17aa4c20f..bd619207f 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/ChallengeTrigger.java @@ -1,16 +1,22 @@ -package emu.grasscutter.game.dungeons.challenge.trigger; - -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.scripts.data.SceneTrigger; - -public abstract class ChallengeTrigger { - public void onBegin(WorldChallenge challenge) { } - public void onFinish(WorldChallenge challenge) { } - public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { } - public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) { } - public void onCheckTimeout(WorldChallenge challenge) { } - public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { } - public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) { } -} +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.data.SceneTrigger; + +public abstract class ChallengeTrigger { + public void onBegin(WorldChallenge challenge) {} + + public void onFinish(WorldChallenge challenge) {} + + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {} + + public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {} + + public void onCheckTimeout(WorldChallenge challenge) {} + + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {} + + public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) {} +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java index a734bc04e..ad22d96df 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/GuardTrigger.java @@ -1,38 +1,38 @@ -package emu.grasscutter.game.dungeons.challenge.trigger; - -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; - -public class GuardTrigger extends ChallengeTrigger { - private final int entityToProtectCFGId; - private int lastSendPercent = 100; - public GuardTrigger(int entityToProtectCFGId){ - this.entityToProtectCFGId = entityToProtectCFGId; - } - - public void onBegin(WorldChallenge challenge) { - challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100)); - } - - @Override - public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { - if(gadget.getConfigId() != entityToProtectCFGId){ - return; - } - var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId()); - var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId()); - int percent = (int) (curHp / maxHp); - - if(percent!=lastSendPercent) { - challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent)); - lastSendPercent = percent; - } - - if(percent <= 0){ - challenge.fail(); - } - } -} +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; + +public class GuardTrigger extends ChallengeTrigger { + private final int entityToProtectCFGId; + private int lastSendPercent = 100; + + public GuardTrigger(int entityToProtectCFGId) { + this.entityToProtectCFGId = entityToProtectCFGId; + } + + public void onBegin(WorldChallenge challenge) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100)); + } + + @Override + public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { + if (gadget.getConfigId() != entityToProtectCFGId) { + return; + } + var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId()); + var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId()); + int percent = (int) (curHp / maxHp); + + if (percent != lastSendPercent) { + challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent)); + lastSendPercent = percent; + } + + if (percent <= 0) { + challenge.fail(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java index 8735824fe..c31c74b4b 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/KillMonsterTrigger.java @@ -1,22 +1,25 @@ -package emu.grasscutter.game.dungeons.challenge.trigger; - -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class KillMonsterTrigger extends ChallengeTrigger{ - private int monsterCfgId; - @Override - public void onBegin(WorldChallenge challenge) { - challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get())); - } - - @Override - public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { - if(monster.getConfigId() == monsterCfgId){ - challenge.done(); - } - } -} +package emu.grasscutter.game.dungeons.challenge.trigger; + +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class KillMonsterTrigger extends ChallengeTrigger { + private int monsterCfgId; + + @Override + public void onBegin(WorldChallenge challenge) { + challenge + .getScene() + .broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get())); + } + + @Override + public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { + if (monster.getConfigId() == monsterCfgId) { + challenge.done(); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 184fe380e..4a03ea6a6 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -1,370 +1,370 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.avatar.AvatarData; -import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.inventory.EquipType; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock; -import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; -import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; -import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; -import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; -import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; -import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; -import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; -import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; -import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; -import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; -import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; -import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; -import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; -import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; -import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; -import emu.grasscutter.net.proto.VectorOuterClass.Vector; -import emu.grasscutter.server.event.player.PlayerMoveEvent; -import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.ProtoHelper; -import emu.grasscutter.utils.Utils; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import lombok.Getter; -import lombok.val; - -public class EntityAvatar extends GameEntity { - @Getter private final Avatar avatar; - - @Getter private PlayerDieType killedType; - @Getter private int killedBy; - - public EntityAvatar(Avatar avatar) { - this(null, avatar); - } - - public EntityAvatar(Scene scene, Avatar avatar) { - super(scene); - - this.avatar = avatar; - this.avatar.setCurrentEnergy(); - - if (getScene() != null) { - this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); - - var weapon = getAvatar().getWeapon(); - if (weapon != null) { - weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON)); - } - } - } - - @Override - public int getEntityTypeId() { - return this.getAvatar().getAvatarId(); - } - - public Player getPlayer() { - return this.avatar.getPlayer(); - } - - @Override - public Position getPosition() { - return getPlayer().getPosition(); - } - - @Override - public Position getRotation() { - return getPlayer().getRotation(); - } - - @Override - public boolean isAlive() { - return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; - } - - @Override - public Int2FloatMap getFightProperties() { - return getAvatar().getFightProperties(); - } - - public int getWeaponEntityId() { - if (getAvatar().getWeapon() != null) { - return getAvatar().getWeapon().getWeaponEntityId(); - } - return 0; - } - - @Override - public void onDeath(int killerId) { - super.onDeath(killerId); // Invoke super class's onDeath() method. - - this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER; - this.killedBy = killerId; - clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); - } - - public void onDeath(PlayerDieType dieType, int killerId) { - super.onDeath(killerId); // Invoke super class's onDeath() method. - - this.killedType = dieType; - this.killedBy = killerId; - clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); - } - - @Override - public float heal(float amount) { - // Do not heal character if they are dead - if (!this.isAlive()) { - return 0f; - } - - float healed = super.heal(amount); - - if (healed > 0f) { - getScene() - .broadcastPacket( - new PacketEntityFightPropChangeReasonNotify( - this, - FightProperty.FIGHT_PROP_CUR_HP, - healed, - PropChangeReason.PROP_CHANGE_REASON_ABILITY, - ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY)); - } - - return healed; - } - - public void clearEnergy(ChangeEnergyReason reason) { - // Fight props. - val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); - float curEnergy = this.getFightProperty(curEnergyProp); - - // Set energy to zero. - this.avatar.setCurrentEnergy(curEnergyProp, 0); - - // Send packets. - this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); - - if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) { - this.getScene() - .broadcastPacket( - new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason)); - } - } - - /** - * Adds a fixed amount of energy to the current avatar. - * - * @param amount The amount of energy to add. - * @return True if the energy was added, false if the energy was not added. - */ - public boolean addEnergy(float amount) { - var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); - var curEnergy = this.getFightProperty(curEnergyProp); - if (curEnergy == amount) return false; - - this.getAvatar().setCurrentEnergy(curEnergyProp, amount); - this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); - return true; - } - - public void addEnergy(float amount, PropChangeReason reason) { - this.addEnergy(amount, reason, false); - } - - public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) { - // Get current and maximum energy for this avatar. - val elementType = this.getAvatar().getSkillDepot().getElementType(); - val curEnergyProp = elementType.getCurEnergyProp(); - val maxEnergyProp = elementType.getMaxEnergyProp(); - - float curEnergy = this.getFightProperty(curEnergyProp); - float maxEnergy = this.getFightProperty(maxEnergyProp); - - // Scale amount by energy recharge, if the amount is not flat. - if (!isFlat) { - amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); - } - - // Determine the new energy value. - float newEnergy = Math.min(curEnergy + amount, maxEnergy); - - // Set energy and notify. - if (newEnergy != curEnergy) { - this.avatar.setCurrentEnergy(curEnergyProp, newEnergy); - - this.getScene() - .broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp)); - this.getScene() - .broadcastPacket( - new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason)); - } - } - - public SceneAvatarInfo getSceneAvatarInfo() { - val avatar = this.getAvatar(); - val player = this.getPlayer(); - SceneAvatarInfo.Builder avatarInfo = - SceneAvatarInfo.newBuilder() - .setUid(player.getUid()) - .setAvatarId(avatar.getAvatarId()) - .setGuid(avatar.getGuid()) - .setPeerId(player.getPeerId()) - .addAllTalentIdList(avatar.getTalentIdList()) - .setCoreProudSkillLevel(avatar.getCoreProudSkillLevel()) - .putAllSkillLevelMap(avatar.getSkillLevelMap()) - .setSkillDepotId(avatar.getSkillDepotId()) - .addAllInherentProudSkillList(avatar.getProudSkillList()) - .putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap()) - .addAllTeamResonanceList(player.getTeamManager().getTeamResonances()) - .setWearingFlycloakId(avatar.getFlyCloak()) - .setCostumeId(avatar.getCostume()) - .setBornTime(avatar.getBornTime()); - - for (GameItem item : avatar.getEquips().values()) { - if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { - avatarInfo.setWeapon(item.createSceneWeaponInfo()); - } else { - avatarInfo.addReliquaryList(item.createSceneReliquaryInfo()); - } - avatarInfo.addEquipIdList(item.getItemId()); - } - - return avatarInfo.build(); - } - - @Override - public SceneEntityInfo toProto() { - EntityAuthorityInfo authority = - EntityAuthorityInfo.newBuilder() - .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) - .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) - .setAiInfo( - SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder())) - .setBornPos(Vector.newBuilder()) - .build(); - - SceneEntityInfo.Builder entityInfo = - SceneEntityInfo.newBuilder() - .setEntityId(getId()) - .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR) - .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) - .setEntityClientData(EntityClientData.newBuilder()) - .setEntityAuthorityInfo(authority) - .setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs()) - .setLastMoveReliableSeq(this.getLastMoveReliableSeq()) - .setLifeState(this.getLifeState().getValue()); - - if (this.getScene() != null) { - entityInfo.setMotionInfo(this.getMotionInfo()); - } - - this.addAllFightPropsToEntityInfo(entityInfo); - - PropPair pair = - PropPair.newBuilder() - .setType(PlayerProperty.PROP_LEVEL.getId()) - .setPropValue( - ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel())) - .build(); - entityInfo.addPropList(pair); - - entityInfo.setAvatar(this.getSceneAvatarInfo()); - - return entityInfo.build(); - } - - public AbilityControlBlock getAbilityControlBlock() { - AvatarData data = this.getAvatar().getAvatarData(); - AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder(); - int embryoId = 0; - - // Add avatar abilities - if (data.getAbilities() != null) { - for (int id : data.getAbilities()) { - AbilityEmbryo emb = - AbilityEmbryo.newBuilder() - .setAbilityId(++embryoId) - .setAbilityNameHash(id) - .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) - .build(); - abilityControlBlock.addAbilityEmbryoList(emb); - } - } - // Add default abilities - for (int id : GameConstants.DEFAULT_ABILITY_HASHES) { - AbilityEmbryo emb = - AbilityEmbryo.newBuilder() - .setAbilityId(++embryoId) - .setAbilityNameHash(id) - .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) - .build(); - abilityControlBlock.addAbilityEmbryoList(emb); - } - // Add team resonances - for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) { - AbilityEmbryo emb = - AbilityEmbryo.newBuilder() - .setAbilityId(++embryoId) - .setAbilityNameHash(id) - .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) - .build(); - abilityControlBlock.addAbilityEmbryoList(emb); - } - // Add skill depot abilities - AvatarSkillDepotData skillDepot = - GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId()); - if (skillDepot != null && skillDepot.getAbilities() != null) { - for (int id : skillDepot.getAbilities()) { - AbilityEmbryo emb = - AbilityEmbryo.newBuilder() - .setAbilityId(++embryoId) - .setAbilityNameHash(id) - .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) - .build(); - abilityControlBlock.addAbilityEmbryoList(emb); - } - } - // Add equip abilities - if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) { - for (String skill : this.getAvatar().getExtraAbilityEmbryos()) { - AbilityEmbryo emb = - AbilityEmbryo.newBuilder() - .setAbilityId(++embryoId) - .setAbilityNameHash(Utils.abilityHash(skill)) - .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) - .build(); - abilityControlBlock.addAbilityEmbryoList(emb); - } - } - - // - return abilityControlBlock.build(); - } - - /** - * Move this entity to a new position. Additionally invoke player move event. - * - * @param newPosition The new position. - * @param rotation The new rotation. - */ - @Override - public void move(Position newPosition, Position rotation) { - // Invoke player move event. - PlayerMoveEvent event = - new PlayerMoveEvent( - this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition); - event.call(); - - // Set position and rotation. - super.move(event.getDestination(), rotation); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.GameConstants; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.avatar.AvatarData; +import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.inventory.EquipType; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock; +import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; +import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; +import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; +import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; +import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; +import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; +import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; +import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; +import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.event.player.PlayerMoveEvent; +import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.ProtoHelper; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import lombok.Getter; +import lombok.val; + +public class EntityAvatar extends GameEntity { + @Getter private final Avatar avatar; + + @Getter private PlayerDieType killedType; + @Getter private int killedBy; + + public EntityAvatar(Avatar avatar) { + this(null, avatar); + } + + public EntityAvatar(Scene scene, Avatar avatar) { + super(scene); + + this.avatar = avatar; + this.avatar.setCurrentEnergy(); + + if (getScene() != null) { + this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); + + var weapon = getAvatar().getWeapon(); + if (weapon != null) { + weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON)); + } + } + } + + @Override + public int getEntityTypeId() { + return this.getAvatar().getAvatarId(); + } + + public Player getPlayer() { + return this.avatar.getPlayer(); + } + + @Override + public Position getPosition() { + return getPlayer().getPosition(); + } + + @Override + public Position getRotation() { + return getPlayer().getRotation(); + } + + @Override + public boolean isAlive() { + return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; + } + + @Override + public Int2FloatMap getFightProperties() { + return getAvatar().getFightProperties(); + } + + public int getWeaponEntityId() { + if (getAvatar().getWeapon() != null) { + return getAvatar().getWeapon().getWeaponEntityId(); + } + return 0; + } + + @Override + public void onDeath(int killerId) { + super.onDeath(killerId); // Invoke super class's onDeath() method. + + this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER; + this.killedBy = killerId; + clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); + } + + public void onDeath(PlayerDieType dieType, int killerId) { + super.onDeath(killerId); // Invoke super class's onDeath() method. + + this.killedType = dieType; + this.killedBy = killerId; + clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); + } + + @Override + public float heal(float amount) { + // Do not heal character if they are dead + if (!this.isAlive()) { + return 0f; + } + + float healed = super.heal(amount); + + if (healed > 0f) { + getScene() + .broadcastPacket( + new PacketEntityFightPropChangeReasonNotify( + this, + FightProperty.FIGHT_PROP_CUR_HP, + healed, + PropChangeReason.PROP_CHANGE_REASON_ABILITY, + ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY)); + } + + return healed; + } + + public void clearEnergy(ChangeEnergyReason reason) { + // Fight props. + val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); + float curEnergy = this.getFightProperty(curEnergyProp); + + // Set energy to zero. + this.avatar.setCurrentEnergy(curEnergyProp, 0); + + // Send packets. + this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); + + if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) { + this.getScene() + .broadcastPacket( + new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason)); + } + } + + /** + * Adds a fixed amount of energy to the current avatar. + * + * @param amount The amount of energy to add. + * @return True if the energy was added, false if the energy was not added. + */ + public boolean addEnergy(float amount) { + var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); + var curEnergy = this.getFightProperty(curEnergyProp); + if (curEnergy == amount) return false; + + this.getAvatar().setCurrentEnergy(curEnergyProp, amount); + this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); + return true; + } + + public void addEnergy(float amount, PropChangeReason reason) { + this.addEnergy(amount, reason, false); + } + + public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) { + // Get current and maximum energy for this avatar. + val elementType = this.getAvatar().getSkillDepot().getElementType(); + val curEnergyProp = elementType.getCurEnergyProp(); + val maxEnergyProp = elementType.getMaxEnergyProp(); + + float curEnergy = this.getFightProperty(curEnergyProp); + float maxEnergy = this.getFightProperty(maxEnergyProp); + + // Scale amount by energy recharge, if the amount is not flat. + if (!isFlat) { + amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); + } + + // Determine the new energy value. + float newEnergy = Math.min(curEnergy + amount, maxEnergy); + + // Set energy and notify. + if (newEnergy != curEnergy) { + this.avatar.setCurrentEnergy(curEnergyProp, newEnergy); + + this.getScene() + .broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp)); + this.getScene() + .broadcastPacket( + new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason)); + } + } + + public SceneAvatarInfo getSceneAvatarInfo() { + val avatar = this.getAvatar(); + val player = this.getPlayer(); + SceneAvatarInfo.Builder avatarInfo = + SceneAvatarInfo.newBuilder() + .setUid(player.getUid()) + .setAvatarId(avatar.getAvatarId()) + .setGuid(avatar.getGuid()) + .setPeerId(player.getPeerId()) + .addAllTalentIdList(avatar.getTalentIdList()) + .setCoreProudSkillLevel(avatar.getCoreProudSkillLevel()) + .putAllSkillLevelMap(avatar.getSkillLevelMap()) + .setSkillDepotId(avatar.getSkillDepotId()) + .addAllInherentProudSkillList(avatar.getProudSkillList()) + .putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap()) + .addAllTeamResonanceList(player.getTeamManager().getTeamResonances()) + .setWearingFlycloakId(avatar.getFlyCloak()) + .setCostumeId(avatar.getCostume()) + .setBornTime(avatar.getBornTime()); + + for (GameItem item : avatar.getEquips().values()) { + if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { + avatarInfo.setWeapon(item.createSceneWeaponInfo()); + } else { + avatarInfo.addReliquaryList(item.createSceneReliquaryInfo()); + } + avatarInfo.addEquipIdList(item.getItemId()); + } + + return avatarInfo.build(); + } + + @Override + public SceneEntityInfo toProto() { + EntityAuthorityInfo authority = + EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) + .setAiInfo( + SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder())) + .setBornPos(Vector.newBuilder()) + .build(); + + SceneEntityInfo.Builder entityInfo = + SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR) + .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) + .setEntityClientData(EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs()) + .setLastMoveReliableSeq(this.getLastMoveReliableSeq()) + .setLifeState(this.getLifeState().getValue()); + + if (this.getScene() != null) { + entityInfo.setMotionInfo(this.getMotionInfo()); + } + + this.addAllFightPropsToEntityInfo(entityInfo); + + PropPair pair = + PropPair.newBuilder() + .setType(PlayerProperty.PROP_LEVEL.getId()) + .setPropValue( + ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel())) + .build(); + entityInfo.addPropList(pair); + + entityInfo.setAvatar(this.getSceneAvatarInfo()); + + return entityInfo.build(); + } + + public AbilityControlBlock getAbilityControlBlock() { + AvatarData data = this.getAvatar().getAvatarData(); + AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder(); + int embryoId = 0; + + // Add avatar abilities + if (data.getAbilities() != null) { + for (int id : data.getAbilities()) { + AbilityEmbryo emb = + AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(id) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + } + // Add default abilities + for (int id : GameConstants.DEFAULT_ABILITY_HASHES) { + AbilityEmbryo emb = + AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(id) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + // Add team resonances + for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) { + AbilityEmbryo emb = + AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(id) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + // Add skill depot abilities + AvatarSkillDepotData skillDepot = + GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId()); + if (skillDepot != null && skillDepot.getAbilities() != null) { + for (int id : skillDepot.getAbilities()) { + AbilityEmbryo emb = + AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(id) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + } + // Add equip abilities + if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) { + for (String skill : this.getAvatar().getExtraAbilityEmbryos()) { + AbilityEmbryo emb = + AbilityEmbryo.newBuilder() + .setAbilityId(++embryoId) + .setAbilityNameHash(Utils.abilityHash(skill)) + .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) + .build(); + abilityControlBlock.addAbilityEmbryoList(emb); + } + } + + // + return abilityControlBlock.build(); + } + + /** + * Move this entity to a new position. Additionally invoke player move event. + * + * @param newPosition The new position. + * @param rotation The new rotation. + */ + @Override + public void move(Position newPosition, Position rotation) { + // Invoke player move event. + PlayerMoveEvent event = + new PlayerMoveEvent( + this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition); + event.call(); + + // Set position and rotation. + super.move(event.getDestination(), rotation); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java index 5b81c422e..6bbc9f777 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityBaseGadget.java @@ -1,63 +1,63 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.utils.Position; -import lombok.Getter; - -public abstract class EntityBaseGadget extends GameEntity { - @Getter(onMethod_ = @Override) - protected final Position position; - - @Getter(onMethod_ = @Override) - protected final Position rotation; - - public EntityBaseGadget(Scene scene) { - this(scene, null, null); - } - - public EntityBaseGadget(Scene scene, Position position, Position rotation) { - super(scene); - this.position = position != null ? position.clone() : new Position(); - this.rotation = rotation != null ? rotation.clone() : new Position(); - } - - public abstract int getGadgetId(); - - @Override - public int getEntityTypeId() { - return this.getGadgetId(); - } - - @Override - public void onDeath(int killerId) { - super.onDeath(killerId); // Invoke super class's onDeath() method. - } - - protected void fillFightProps(ConfigEntityGadget configGadget) { - if (configGadget == null || configGadget.getCombat() == null) { - return; - } - var combatData = configGadget.getCombat(); - var combatProperties = combatData.getProperty(); - - var targetHp = combatProperties.getHP(); - setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp); - setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp); - if (combatProperties.isInvincible()) { - targetHp = Float.POSITIVE_INFINITY; - } - setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp); - - var atk = combatProperties.getAttack(); - setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk); - setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk); - - var def = combatProperties.getDefence(); - setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def); - setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def); - - setLockHP(combatProperties.isLockHP()); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.utils.Position; +import lombok.Getter; + +public abstract class EntityBaseGadget extends GameEntity { + @Getter(onMethod_ = @Override) + protected final Position position; + + @Getter(onMethod_ = @Override) + protected final Position rotation; + + public EntityBaseGadget(Scene scene) { + this(scene, null, null); + } + + public EntityBaseGadget(Scene scene, Position position, Position rotation) { + super(scene); + this.position = position != null ? position.clone() : new Position(); + this.rotation = rotation != null ? rotation.clone() : new Position(); + } + + public abstract int getGadgetId(); + + @Override + public int getEntityTypeId() { + return this.getGadgetId(); + } + + @Override + public void onDeath(int killerId) { + super.onDeath(killerId); // Invoke super class's onDeath() method. + } + + protected void fillFightProps(ConfigEntityGadget configGadget) { + if (configGadget == null || configGadget.getCombat() == null) { + return; + } + var combatData = configGadget.getCombat(); + var combatProperties = combatData.getProperty(); + + var targetHp = combatProperties.getHP(); + setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp); + setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp); + if (combatProperties.isInvincible()) { + targetHp = Float.POSITIVE_INFINITY; + } + setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp); + + var atk = combatProperties.getAttack(); + setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk); + setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk); + + var def = combatProperties.getDefence(); + setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def); + setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def); + + setLockHP(combatProperties.isLockHP()); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java index 90d639c8c..e453cb435 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityGadget.java @@ -1,281 +1,308 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.data.excels.GadgetData; -import emu.grasscutter.game.entity.gadget.*; -import emu.grasscutter.game.entity.gadget.platform.BaseRoute; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.SceneGroupInstance; -import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; -import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; -import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; -import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; -import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; -import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; -import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; -import emu.grasscutter.net.proto.PlatformInfoOuterClass; -import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; -import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; -import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; -import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; -import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; -import emu.grasscutter.net.proto.VectorOuterClass.Vector; -import emu.grasscutter.net.proto.VisionTypeOuterClass; -import emu.grasscutter.scripts.EntityControllerScriptManager; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneGadget; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; -import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify; -import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify; -import emu.grasscutter.server.packet.send.PacketSceneTimeNotify; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.ProtoHelper; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - -@ToString(callSuper = true) -public class EntityGadget extends EntityBaseGadget { - @Getter private final GadgetData gadgetData; - @Getter(onMethod = @__(@Override)) @Setter - private int gadgetId; - @Getter private final Position bornPos; - @Getter private final Position bornRot; - @Getter @Setter private GameEntity owner = null; - @Getter @Setter private List children = new ArrayList<>(); - - @Getter private int state; - @Getter @Setter private int pointType; - @Getter private GadgetContent content; - @Getter(onMethod = @__(@Override), lazy = true) - private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap(); - @Getter @Setter private SceneGadget metaGadget; - @Nullable @Getter - private ConfigEntityGadget configGadget; - @Getter @Setter private BaseRoute routeConfig; - - @Getter @Setter private int stopValue = 0; //Controller related, inited to zero - @Getter @Setter private int startValue = 0; //Controller related, inited to zero - @Getter @Setter private int ticksSinceChange; - - - public EntityGadget(Scene scene, int gadgetId, Position pos) { - this(scene, gadgetId, pos, null, null); - } - - public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) { - this(scene, gadgetId, pos, rot, null); - } - - public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { - super(scene, pos, rot); - - this.gadgetData = GameData.getGadgetDataMap().get(gadgetId); - if (gadgetData != null && gadgetData.getJsonName() != null) { - this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName()); - } - - this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET); - this.gadgetId = gadgetId; - this.content = content; - this.bornPos = this.getPosition().clone(); - this.bornRot = this.getRotation().clone(); - this.fillFightProps(configGadget); - - if(GameData.getGadgetMappingMap().containsKey(gadgetId)) { - String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController(); - this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName)); - } - } - - public void setState(int state) { - this.state = state; - //Cache the gadget state - if(metaGadget != null && metaGadget.group != null) { - var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.group.id); - if(instance != null) instance.cacheGadgetState(metaGadget, state); - } - } - - public void updateState(int state) { - if(state == this.getState()) return; //Don't triggers events - - this.setState(state); - ticksSinceChange = getScene().getSceneTimeSeconds(); - this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); - getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_STATE_CHANGE, state, this.getConfigId())); - } - - @Deprecated(forRemoval = true) // Dont use! - public void setContent(GadgetContent content) { - this.content = this.content == null ? content : this.content; - } - - // TODO refactor - public void buildContent() { - if (this.getContent() != null || this.getGadgetData() == null || this.getGadgetData().getType() == null) { - return; - } - - this.content = switch (this.getGadgetData().getType()) { - case GatherPoint -> new GadgetGatherPoint(this); - case GatherObject -> new GadgetGatherObject(this); - case Worktop, SealGadget -> new GadgetWorktop(this); - case RewardStatue -> new GadgetRewardStatue(this); - case Chest -> new GadgetChest(this); - case Gadget -> new GadgetObject(this); - default -> null; - }; - } - - @Override - public void onInteract(Player player, GadgetInteractReq interactReq) { - if (this.getContent() == null) { - return; - } - - boolean shouldDelete = this.getContent().onInteract(player, interactReq); - - if (shouldDelete) { - this.getScene().killEntity(this); - } - } - - @Override - public void onCreate() { - // Lua event - getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_CREATE, this.getConfigId())); - } - - @Override - public void onRemoved() { - super.onRemoved(); - if(!children.isEmpty()) { - getScene().removeEntities(children, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE); - children.clear(); - } - } - - @Override - public void onDeath(int killerId) { - super.onDeath(killerId); // Invoke super class's onDeath() method. - - if (this.getSpawnEntry() != null) { - this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); - } - if (getScene().getChallenge() != null) { - getScene().getChallenge().onGadgetDeath(this); - } - getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_GADGET_DIE, this.getConfigId())); - - SceneGroupInstance groupInstance = getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId()); - if(groupInstance != null && metaGadget != null) - groupInstance.getDeadEntities().add(metaGadget.config_id); - } - - public boolean startPlatform(){ - if(routeConfig == null){ - return false; - } - - if(routeConfig.isStarted()){ - return true; - } - getScene().broadcastPacket(new PacketSceneTimeNotify(getScene())); - routeConfig.startRoute(getScene()); - getScene().broadcastPacket(new PacketPlatformStartRouteNotify(this)); - - return true; - } - - public boolean stopPlatform(){ - if(routeConfig == null){ - return false; - } - - if(!routeConfig.isStarted()){ - return true; - } - routeConfig.stopRoute(getScene()); - getScene().broadcastPacket(new PacketPlatformStopRouteNotify(this)); - - return true; - } - - @Override - public SceneEntityInfo toProto() { - EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() - .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) - .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) - .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(bornPos.toProto())) - .setBornPos(bornPos.toProto()) - .build(); - - SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() - .setEntityId(getId()) - .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) - .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) - .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) - .setEntityClientData(EntityClientData.newBuilder()) - .setEntityAuthorityInfo(authority) - .setLifeState(1); - - PropPair pair = PropPair.newBuilder() - .setType(PlayerProperty.PROP_LEVEL.getId()) - .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1)) - .build(); - entityInfo.addPropList(pair); - - // We do not use the getter to null check because the getter will create a fight prop map if it is null - if (this.fightProperties != null) { - addAllFightPropsToEntityInfo(entityInfo); - } - - SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() - .setGadgetId(this.getGadgetId()) - .setGroupId(this.getGroupId()) - .setConfigId(this.getConfigId()) - .setGadgetState(this.getState()) - .setIsEnableInteract(true) - .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); - - if (this.metaGadget != null) { - gadgetInfo.setDraftId(this.metaGadget.draft_id); - } - - if(owner != null){ - gadgetInfo.setOwnerEntityId(owner.getId()); - } - - if (this.getContent() != null) { - this.getContent().onBuildProto(gadgetInfo); - } - - if(routeConfig!=null){ - gadgetInfo.setPlatform(getPlatformInfo()); - } - - entityInfo.setGadget(gadgetInfo); - - return entityInfo.build(); - } - - public PlatformInfoOuterClass.PlatformInfo.Builder getPlatformInfo(){ - if(routeConfig != null){ - return routeConfig.toProto(); - } - - return PlatformInfoOuterClass.PlatformInfo.newBuilder(); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.data.excels.GadgetData; +import emu.grasscutter.game.entity.gadget.*; +import emu.grasscutter.game.entity.gadget.platform.BaseRoute; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.world.SceneGroupInstance; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; +import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; +import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.PlatformInfoOuterClass; +import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; +import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; +import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.VisionTypeOuterClass; +import emu.grasscutter.scripts.EntityControllerScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; +import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify; +import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify; +import emu.grasscutter.server.packet.send.PacketSceneTimeNotify; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.ProtoHelper; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@ToString(callSuper = true) +public class EntityGadget extends EntityBaseGadget { + @Getter private final GadgetData gadgetData; + + @Getter(onMethod = @__(@Override)) + @Setter + private int gadgetId; + + @Getter private final Position bornPos; + @Getter private final Position bornRot; + @Getter @Setter private GameEntity owner = null; + @Getter @Setter private List children = new ArrayList<>(); + + @Getter private int state; + @Getter @Setter private int pointType; + @Getter private GadgetContent content; + + @Getter(onMethod = @__(@Override), lazy = true) + private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap(); + + @Getter @Setter private SceneGadget metaGadget; + @Nullable @Getter private ConfigEntityGadget configGadget; + @Getter @Setter private BaseRoute routeConfig; + + @Getter @Setter private int stopValue = 0; // Controller related, inited to zero + @Getter @Setter private int startValue = 0; // Controller related, inited to zero + @Getter @Setter private int ticksSinceChange; + + public EntityGadget(Scene scene, int gadgetId, Position pos) { + this(scene, gadgetId, pos, null, null); + } + + public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) { + this(scene, gadgetId, pos, rot, null); + } + + public EntityGadget( + Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { + super(scene, pos, rot); + + this.gadgetData = GameData.getGadgetDataMap().get(gadgetId); + if (gadgetData != null && gadgetData.getJsonName() != null) { + this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName()); + } + + this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET); + this.gadgetId = gadgetId; + this.content = content; + this.bornPos = this.getPosition().clone(); + this.bornRot = this.getRotation().clone(); + this.fillFightProps(configGadget); + + if (GameData.getGadgetMappingMap().containsKey(gadgetId)) { + String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController(); + this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName)); + } + } + + public void setState(int state) { + this.state = state; + // Cache the gadget state + if (metaGadget != null && metaGadget.group != null) { + var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.group.id); + if (instance != null) instance.cacheGadgetState(metaGadget, state); + } + } + + public void updateState(int state) { + if (state == this.getState()) return; // Don't triggers events + + this.setState(state); + ticksSinceChange = getScene().getSceneTimeSeconds(); + this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); + getScene() + .getScriptManager() + .callEvent( + new ScriptArgs( + this.getGroupId(), EventType.EVENT_GADGET_STATE_CHANGE, state, this.getConfigId())); + } + + @Deprecated(forRemoval = true) // Dont use! + public void setContent(GadgetContent content) { + this.content = this.content == null ? content : this.content; + } + + // TODO refactor + public void buildContent() { + if (this.getContent() != null + || this.getGadgetData() == null + || this.getGadgetData().getType() == null) { + return; + } + + this.content = + switch (this.getGadgetData().getType()) { + case GatherPoint -> new GadgetGatherPoint(this); + case GatherObject -> new GadgetGatherObject(this); + case Worktop, SealGadget -> new GadgetWorktop(this); + case RewardStatue -> new GadgetRewardStatue(this); + case Chest -> new GadgetChest(this); + case Gadget -> new GadgetObject(this); + default -> null; + }; + } + + @Override + public void onInteract(Player player, GadgetInteractReq interactReq) { + if (this.getContent() == null) { + return; + } + + boolean shouldDelete = this.getContent().onInteract(player, interactReq); + + if (shouldDelete) { + this.getScene().killEntity(this); + } + } + + @Override + public void onCreate() { + // Lua event + getScene() + .getScriptManager() + .callEvent( + new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_CREATE, this.getConfigId())); + } + + @Override + public void onRemoved() { + super.onRemoved(); + if (!children.isEmpty()) { + getScene().removeEntities(children, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE); + children.clear(); + } + } + + @Override + public void onDeath(int killerId) { + super.onDeath(killerId); // Invoke super class's onDeath() method. + + if (this.getSpawnEntry() != null) { + this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); + } + if (getScene().getChallenge() != null) { + getScene().getChallenge().onGadgetDeath(this); + } + getScene() + .getScriptManager() + .callEvent( + new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_GADGET_DIE, this.getConfigId())); + + SceneGroupInstance groupInstance = + getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId()); + if (groupInstance != null && metaGadget != null) + groupInstance.getDeadEntities().add(metaGadget.config_id); + } + + public boolean startPlatform() { + if (routeConfig == null) { + return false; + } + + if (routeConfig.isStarted()) { + return true; + } + getScene().broadcastPacket(new PacketSceneTimeNotify(getScene())); + routeConfig.startRoute(getScene()); + getScene().broadcastPacket(new PacketPlatformStartRouteNotify(this)); + + return true; + } + + public boolean stopPlatform() { + if (routeConfig == null) { + return false; + } + + if (!routeConfig.isStarted()) { + return true; + } + routeConfig.stopRoute(getScene()); + getScene().broadcastPacket(new PacketPlatformStopRouteNotify(this)); + + return true; + } + + @Override + public SceneEntityInfo toProto() { + EntityAuthorityInfo authority = + EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) + .setAiInfo( + SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(bornPos.toProto())) + .setBornPos(bornPos.toProto()) + .build(); + + SceneEntityInfo.Builder entityInfo = + SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) + .setMotionInfo( + MotionInfo.newBuilder() + .setPos(getPosition().toProto()) + .setRot(getRotation().toProto()) + .setSpeed(Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) + .setEntityClientData(EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + PropPair pair = + PropPair.newBuilder() + .setType(PlayerProperty.PROP_LEVEL.getId()) + .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1)) + .build(); + entityInfo.addPropList(pair); + + // We do not use the getter to null check because the getter will create a fight prop map if it + // is null + if (this.fightProperties != null) { + addAllFightPropsToEntityInfo(entityInfo); + } + + SceneGadgetInfo.Builder gadgetInfo = + SceneGadgetInfo.newBuilder() + .setGadgetId(this.getGadgetId()) + .setGroupId(this.getGroupId()) + .setConfigId(this.getConfigId()) + .setGadgetState(this.getState()) + .setIsEnableInteract(true) + .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); + + if (this.metaGadget != null) { + gadgetInfo.setDraftId(this.metaGadget.draft_id); + } + + if (owner != null) { + gadgetInfo.setOwnerEntityId(owner.getId()); + } + + if (this.getContent() != null) { + this.getContent().onBuildProto(gadgetInfo); + } + + if (routeConfig != null) { + gadgetInfo.setPlatform(getPlatformInfo()); + } + + entityInfo.setGadget(gadgetInfo); + + return entityInfo.build(); + } + + public PlatformInfoOuterClass.PlatformInfo.Builder getPlatformInfo() { + if (routeConfig != null) { + return routeConfig.toProto(); + } + + return PlatformInfoOuterClass.PlatformInfo.newBuilder(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java index e9db22fa7..b47cefcc2 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityNPC.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityNPC.java @@ -1,82 +1,82 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.*; -import emu.grasscutter.scripts.data.SceneNPC; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import lombok.Getter; - -public class EntityNPC extends GameEntity { - @Getter(onMethod_ = @Override) - private final Position position; - - @Getter(onMethod_ = @Override) - private final Position rotation; - - private final SceneNPC metaNpc; - @Getter private final int suiteId; - - public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) { - super(scene); - this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC); - setConfigId(metaNPC.config_id); - setGroupId(metaNPC.group.id); - setBlockId(blockId); - this.suiteId = suiteId; - this.position = metaNPC.pos.clone(); - this.rotation = metaNPC.rot.clone(); - this.metaNpc = metaNPC; - } - - @Override - public int getEntityTypeId() { - return this.metaNpc.npc_id; - } - - @Override - public Int2FloatMap getFightProperties() { - return null; - } - - @Override - public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { - - EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = - EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder() - .setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder()) - .setRendererChangedInfo( - EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder()) - .setAiInfo( - SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder() - .setIsAiOpen(true) - .setBornPos(getPosition().toProto())) - .setBornPos(getPosition().toProto()) - .build(); - - SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = - SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder() - .setEntityId(getId()) - .setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC) - .setMotionInfo( - MotionInfoOuterClass.MotionInfo.newBuilder() - .setPos(getPosition().toProto()) - .setRot(getRotation().toProto()) - .setSpeed(VectorOuterClass.Vector.newBuilder())) - .addAnimatorParaList( - AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair - .newBuilder()) - .setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder()) - .setEntityAuthorityInfo(authority) - .setLifeState(1); - - entityInfo.setNpc( - SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder() - .setNpcId(metaNpc.npc_id) - .setBlockId(getBlockId()) - .build()); - - return entityInfo.build(); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.scripts.data.SceneNPC; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import lombok.Getter; + +public class EntityNPC extends GameEntity { + @Getter(onMethod_ = @Override) + private final Position position; + + @Getter(onMethod_ = @Override) + private final Position rotation; + + private final SceneNPC metaNpc; + @Getter private final int suiteId; + + public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) { + super(scene); + this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC); + setConfigId(metaNPC.config_id); + setGroupId(metaNPC.group.id); + setBlockId(blockId); + this.suiteId = suiteId; + this.position = metaNPC.pos.clone(); + this.rotation = metaNPC.rot.clone(); + this.metaNpc = metaNPC; + } + + @Override + public int getEntityTypeId() { + return this.metaNpc.npc_id; + } + + @Override + public Int2FloatMap getFightProperties() { + return null; + } + + @Override + public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { + + EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = + EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo( + EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder()) + .setAiInfo( + SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder() + .setIsAiOpen(true) + .setBornPos(getPosition().toProto())) + .setBornPos(getPosition().toProto()) + .build(); + + SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = + SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC) + .setMotionInfo( + MotionInfoOuterClass.MotionInfo.newBuilder() + .setPos(getPosition().toProto()) + .setRot(getRotation().toProto()) + .setSpeed(VectorOuterClass.Vector.newBuilder())) + .addAnimatorParaList( + AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair + .newBuilder()) + .setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder()) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + entityInfo.setNpc( + SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder() + .setNpcId(metaNpc.npc_id) + .setBlockId(getBlockId()) + .build()); + + return entityInfo.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java index 5fe55ce63..15bca8125 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java @@ -1,96 +1,96 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.SceneEntityInfoOuterClass; -import emu.grasscutter.scripts.data.SceneRegion; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import lombok.Getter; - -@Getter -public class EntityRegion extends GameEntity { - private final Position position; - private final Set entities; // Ids of entities inside this region - private final SceneRegion metaRegion; - private boolean hasNewEntities; - private boolean entityLeave; - - public EntityRegion(Scene scene, SceneRegion region) { - super(scene); - - this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION); - this.setGroupId(region.group.id); - this.setBlockId(region.group.block_id); - this.setConfigId(region.config_id); - this.position = region.pos.clone(); - this.entities = ConcurrentHashMap.newKeySet(); - this.metaRegion = region; - } - - @Override - public int getEntityTypeId() { - return this.metaRegion.config_id; - } - - public void addEntity(GameEntity entity) { - if (this.getEntities().contains(entity.getId())) { - return; - } - this.getEntities().add(entity.getId()); - this.hasNewEntities = true; - } - - public boolean hasNewEntities() { - return hasNewEntities; - } - - public void resetNewEntities() { - hasNewEntities = false; - } - - public void removeEntity(int entityId) { - this.getEntities().remove(entityId); - this.entityLeave = true; - } - - public void removeEntity(GameEntity entity) { - this.getEntities().remove(entity.getId()); - this.entityLeave = true; - } - - public boolean entityLeave() { - return this.entityLeave; - } - - public void resetEntityLeave() { - this.entityLeave = false; - } - - @Override - public Int2FloatMap getFightProperties() { - return null; - } - - @Override - public Position getPosition() { - return position; - } - - @Override - public Position getRotation() { - return null; - } - - @Override - public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { - /** The Region Entity would not be sent to client. */ - return null; - } - - public int getFirstEntityId() { - return entities.stream().findFirst().orElse(0); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass; +import emu.grasscutter.scripts.data.SceneRegion; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; + +@Getter +public class EntityRegion extends GameEntity { + private final Position position; + private final Set entities; // Ids of entities inside this region + private final SceneRegion metaRegion; + private boolean hasNewEntities; + private boolean entityLeave; + + public EntityRegion(Scene scene, SceneRegion region) { + super(scene); + + this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION); + this.setGroupId(region.group.id); + this.setBlockId(region.group.block_id); + this.setConfigId(region.config_id); + this.position = region.pos.clone(); + this.entities = ConcurrentHashMap.newKeySet(); + this.metaRegion = region; + } + + @Override + public int getEntityTypeId() { + return this.metaRegion.config_id; + } + + public void addEntity(GameEntity entity) { + if (this.getEntities().contains(entity.getId())) { + return; + } + this.getEntities().add(entity.getId()); + this.hasNewEntities = true; + } + + public boolean hasNewEntities() { + return hasNewEntities; + } + + public void resetNewEntities() { + hasNewEntities = false; + } + + public void removeEntity(int entityId) { + this.getEntities().remove(entityId); + this.entityLeave = true; + } + + public void removeEntity(GameEntity entity) { + this.getEntities().remove(entity.getId()); + this.entityLeave = true; + } + + public boolean entityLeave() { + return this.entityLeave; + } + + public void resetEntityLeave() { + this.entityLeave = false; + } + + @Override + public Int2FloatMap getFightProperties() { + return null; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public Position getRotation() { + return null; + } + + @Override + public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { + /** The Region Entity would not be sent to client. */ + return null; + } + + public int getFirstEntityId() { + return entities.stream().findFirst().orElse(0); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntitySolarIsotomaClientGadget.java b/src/main/java/emu/grasscutter/game/entity/EntitySolarIsotomaClientGadget.java index 79bb3bfe0..68aaf70f8 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntitySolarIsotomaClientGadget.java +++ b/src/main/java/emu/grasscutter/game/entity/EntitySolarIsotomaClientGadget.java @@ -1,30 +1,33 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass; -import lombok.Getter; - -public class EntitySolarIsotomaClientGadget extends EntityClientGadget { - public static final int GADGET_ID = 41038001; - public static final int ELEVATOR_GADGET_ID = 41038002; - @Getter private EntityGadget platformGadget; - - public EntitySolarIsotomaClientGadget(Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) { - super(scene, player, notify); - } - - @Override - public void onCreate() { - //Create solar isotoma elevator and send to all. - this.platformGadget = new EntitySolarIsotomaElevatorPlatform(this, getScene(), ELEVATOR_GADGET_ID, getPosition(), getRotation()); - getScene().addEntity(this.platformGadget); - } - - @Override - public void onRemoved() { - //Remove solar isotoma elevator entity. - getScene().removeEntity(this.platformGadget); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass; +import lombok.Getter; + +public class EntitySolarIsotomaClientGadget extends EntityClientGadget { + public static final int GADGET_ID = 41038001; + public static final int ELEVATOR_GADGET_ID = 41038002; + @Getter private EntityGadget platformGadget; + + public EntitySolarIsotomaClientGadget( + Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) { + super(scene, player, notify); + } + + @Override + public void onCreate() { + // Create solar isotoma elevator and send to all. + this.platformGadget = + new EntitySolarIsotomaElevatorPlatform( + this, getScene(), ELEVATOR_GADGET_ID, getPosition(), getRotation()); + getScene().addEntity(this.platformGadget); + } + + @Override + public void onRemoved() { + // Remove solar isotoma elevator entity. + getScene().removeEntity(this.platformGadget); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java index d99ad29b1..d8b0ecc67 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityVehicle.java @@ -1,112 +1,125 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.data.excels.GadgetData; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.EntityIdType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; -import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; -import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; -import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; -import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; -import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; -import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; -import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; -import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; -import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; -import emu.grasscutter.net.proto.VectorOuterClass.Vector; -import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo; -import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.ProtoHelper; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; -import lombok.Getter; -import lombok.Setter; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - -public class EntityVehicle extends EntityBaseGadget { - - @Getter private final Player owner; - @Getter(onMethod = @__(@Override)) - private final Int2FloatMap fightProperties; - - @Getter private final int pointId; - @Getter private final int gadgetId; - - @Getter @Setter private float curStamina; - @Getter private final List vehicleMembers; - @Nullable @Getter private ConfigEntityGadget configGadget; - - public EntityVehicle(Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) { - super(scene, pos, rot); - this.owner = player; - this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); - this.fightProperties = new Int2FloatOpenHashMap(); - this.gadgetId = gadgetId; - this.pointId = pointId; - this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP - this.vehicleMembers = new ArrayList<>(); - GadgetData data = GameData.getGadgetDataMap().get(gadgetId); - if (data != null && data.getJsonName() != null) { - this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName()); - } - - fillFightProps(configGadget); - } - - @Override - protected void fillFightProps(ConfigEntityGadget configGadget) { - super.fillFightProps(configGadget); - this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0); - this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0); - } - - @Override - public SceneEntityInfo toProto() { - - VehicleInfo vehicle = VehicleInfo.newBuilder() - .setOwnerUid(this.owner.getUid()) - .setCurStamina(getCurStamina()) - .build(); - - EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() - .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) - .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) - .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto())) - .setBornPos(getPosition().toProto()) - .build(); - - SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() - .setGadgetId(this.getGadgetId()) - .setAuthorityPeerId(this.getOwner().getPeerId()) - .setIsEnableInteract(true) - .setVehicleInfo(vehicle); - - SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() - .setEntityId(getId()) - .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) - .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) - .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) - .setGadget(gadgetInfo) - .setEntityAuthorityInfo(authority) - .setLifeState(1); - - PropPair pair = PropPair.newBuilder() - .setType(PlayerProperty.PROP_LEVEL.getId()) - .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47)) - .build(); - - this.addAllFightPropsToEntityInfo(entityInfo); - entityInfo.addPropList(pair); - - return entityInfo.build(); - } -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.data.excels.GadgetData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; +import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; +import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; +import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; +import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo; +import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.ProtoHelper; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.Setter; + +public class EntityVehicle extends EntityBaseGadget { + + @Getter private final Player owner; + + @Getter(onMethod = @__(@Override)) + private final Int2FloatMap fightProperties; + + @Getter private final int pointId; + @Getter private final int gadgetId; + + @Getter @Setter private float curStamina; + @Getter private final List vehicleMembers; + @Nullable @Getter private ConfigEntityGadget configGadget; + + public EntityVehicle( + Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) { + super(scene, pos, rot); + this.owner = player; + this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); + this.fightProperties = new Int2FloatOpenHashMap(); + this.gadgetId = gadgetId; + this.pointId = pointId; + this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP + this.vehicleMembers = new ArrayList<>(); + GadgetData data = GameData.getGadgetDataMap().get(gadgetId); + if (data != null && data.getJsonName() != null) { + this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName()); + } + + fillFightProps(configGadget); + } + + @Override + protected void fillFightProps(ConfigEntityGadget configGadget) { + super.fillFightProps(configGadget); + this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0); + this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0); + } + + @Override + public SceneEntityInfo toProto() { + + VehicleInfo vehicle = + VehicleInfo.newBuilder() + .setOwnerUid(this.owner.getUid()) + .setCurStamina(getCurStamina()) + .build(); + + EntityAuthorityInfo authority = + EntityAuthorityInfo.newBuilder() + .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) + .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) + .setAiInfo( + SceneEntityAiInfo.newBuilder() + .setIsAiOpen(true) + .setBornPos(getPosition().toProto())) + .setBornPos(getPosition().toProto()) + .build(); + + SceneGadgetInfo.Builder gadgetInfo = + SceneGadgetInfo.newBuilder() + .setGadgetId(this.getGadgetId()) + .setAuthorityPeerId(this.getOwner().getPeerId()) + .setIsEnableInteract(true) + .setVehicleInfo(vehicle); + + SceneEntityInfo.Builder entityInfo = + SceneEntityInfo.newBuilder() + .setEntityId(getId()) + .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) + .setMotionInfo( + MotionInfo.newBuilder() + .setPos(getPosition().toProto()) + .setRot(getRotation().toProto()) + .setSpeed(Vector.newBuilder())) + .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) + .setGadget(gadgetInfo) + .setEntityAuthorityInfo(authority) + .setLifeState(1); + + PropPair pair = + PropPair.newBuilder() + .setType(PlayerProperty.PROP_LEVEL.getId()) + .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47)) + .build(); + + this.addAllFightPropsToEntityInfo(entityInfo); + entityInfo.addPropList(pair); + + return entityInfo.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index b774219b6..7b0e87454 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -1,264 +1,264 @@ -package emu.grasscutter.game.entity; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.LifeState; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.SpawnDataEntry; -import emu.grasscutter.game.world.World; -import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; -import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; -import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; -import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; -import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; -import emu.grasscutter.net.proto.VectorOuterClass.Vector; -import emu.grasscutter.scripts.data.controller.EntityController; -import emu.grasscutter.server.event.entity.EntityDamageEvent; -import emu.grasscutter.server.event.entity.EntityDeathEvent; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2FloatMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2FloatMap; -import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; -import lombok.Getter; -import lombok.Setter; - -public abstract class GameEntity { - @Getter private final Scene scene; - @Getter protected int id; - @Getter @Setter private SpawnDataEntry spawnEntry; - - @Getter @Setter private int blockId; - @Getter @Setter private int configId; - @Getter @Setter private int groupId; - - @Getter @Setter private MotionState motionState; - @Getter @Setter private int lastMoveSceneTimeMs; - @Getter @Setter private int lastMoveReliableSeq; - - @Getter @Setter private boolean lockHP; - - // Lua controller for specific actions - @Getter @Setter private EntityController entityController; - @Getter private ElementType lastAttackType = ElementType.None; - - // Abilities - private Object2FloatMap metaOverrideMap; - private Int2ObjectMap metaModifiers; - - public GameEntity(Scene scene) { - this.scene = scene; - this.motionState = MotionState.MOTION_STATE_NONE; - } - - public int getEntityType() { - return this.getId() >> 24; - } - - public abstract int getEntityTypeId(); - - public World getWorld() { - return this.getScene().getWorld(); - } - - public boolean isAlive() { - return true; - } - - public LifeState getLifeState() { - return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; - } - - public Object2FloatMap getMetaOverrideMap() { - if (this.metaOverrideMap == null) { - this.metaOverrideMap = new Object2FloatOpenHashMap<>(); - } - return this.metaOverrideMap; - } - - public Int2ObjectMap getMetaModifiers() { - if (this.metaModifiers == null) { - this.metaModifiers = new Int2ObjectOpenHashMap<>(); - } - return this.metaModifiers; - } - - public abstract Int2FloatMap getFightProperties(); - - public abstract Position getPosition(); - - public abstract Position getRotation(); - - public void setFightProperty(FightProperty prop, float value) { - this.getFightProperties().put(prop.getId(), value); - } - - public void setFightProperty(int id, float value) { - this.getFightProperties().put(id, value); - } - - public void addFightProperty(FightProperty prop, float value) { - this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value); - } - - public float getFightProperty(FightProperty prop) { - return this.getFightProperties().getOrDefault(prop.getId(), 0f); - } - - public boolean hasFightProperty(FightProperty prop) { - return this.getFightProperties().containsKey(prop.getId()); - } - - public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) { - this.getFightProperties() - .forEach( - (key, value) -> { - if (key == 0) return; - entityInfo.addFightPropList( - FightPropPair.newBuilder().setPropType(key).setPropValue(value).build()); - }); - } - - protected MotionInfo getMotionInfo() { - return MotionInfo.newBuilder() - .setPos(this.getPosition().toProto()) - .setRot(this.getRotation().toProto()) - .setSpeed(Vector.newBuilder()) - .setState(this.getMotionState()) - .build(); - } - - public float heal(float amount) { - if (this.getFightProperties() == null) { - return 0f; - } - - float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - - if (curHp >= maxHp) { - return 0f; - } - - float healed = Math.min(maxHp - curHp, amount); - this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); - - this.getScene() - .broadcastPacket( - new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); - - return healed; - } - - public void damage(float amount) { - this.damage(amount, 0, ElementType.None); - } - - public void damage(float amount, int killerId, ElementType attackType) { - // Check if the entity has properties. - if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) { - return; - } - - // Invoke entity damage event. - EntityDamageEvent event = - new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId)); - event.call(); - if (event.isCanceled()) { - return; // If the event is canceled, do not damage the entity. - } - - float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) { - // Add negative HP to the current HP property. - this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage())); - } - - // Check if dead - boolean isDead = false; - if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { - this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); - isDead = true; - } - - // Packets - this.getScene() - .broadcastPacket( - new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); - - // Check if dead. - if (isDead) { - this.getScene().killEntity(this, killerId); - } - } - - /** - * Runs the Lua callbacks for {@link EntityDamageEvent}. - * - * @param event The damage event. - */ - public void runLuaCallbacks(EntityDamageEvent event) { - if (entityController != null) { - entityController.onBeHurt(this, event.getAttackElementType(), true);//todo is host handling - } - } - - /** - * Move this entity to a new position. - * - * @param position The new position. - * @param rotation The new rotation. - */ - public void move(Position position, Position rotation) { - // Set the position and rotation. - this.getPosition().set(position); - this.getRotation().set(rotation); - } - - /** - * Called when a player interacts with this entity - * - * @param player Player that is interacting with this entity - * @param interactReq Interact request protobuf data - */ - public void onInteract(Player player, GadgetInteractReq interactReq) {} - - /** Called when this entity is added to the world */ - public void onCreate() {} - - public void onRemoved() {} - - public void onTick(int sceneTime) { - if (entityController != null) { - entityController.onTimer(this, sceneTime); - } - } - - public int onClientExecuteRequest(int param1, int param2, int param3) { - if (entityController != null) { - return entityController.onClientExecuteRequest(this, param1, param2, param3); - } - return 0; - } - - /** - * Called when this entity dies - * - * @param killerId Entity id of the entity that killed this entity - */ - public void onDeath(int killerId) { - // Invoke entity death event. - EntityDeathEvent event = new EntityDeathEvent(this, killerId); - event.call(); - - // Run Lua callbacks. - if (entityController != null) { - entityController.onDie(this, getLastAttackType()); - } - } - - public abstract SceneEntityInfo toProto(); -} +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.world.SpawnDataEntry; +import emu.grasscutter.game.world.World; +import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.scripts.data.controller.EntityController; +import emu.grasscutter.server.event.entity.EntityDamageEvent; +import emu.grasscutter.server.event.entity.EntityDeathEvent; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import lombok.Getter; +import lombok.Setter; + +public abstract class GameEntity { + @Getter private final Scene scene; + @Getter protected int id; + @Getter @Setter private SpawnDataEntry spawnEntry; + + @Getter @Setter private int blockId; + @Getter @Setter private int configId; + @Getter @Setter private int groupId; + + @Getter @Setter private MotionState motionState; + @Getter @Setter private int lastMoveSceneTimeMs; + @Getter @Setter private int lastMoveReliableSeq; + + @Getter @Setter private boolean lockHP; + + // Lua controller for specific actions + @Getter @Setter private EntityController entityController; + @Getter private ElementType lastAttackType = ElementType.None; + + // Abilities + private Object2FloatMap metaOverrideMap; + private Int2ObjectMap metaModifiers; + + public GameEntity(Scene scene) { + this.scene = scene; + this.motionState = MotionState.MOTION_STATE_NONE; + } + + public int getEntityType() { + return this.getId() >> 24; + } + + public abstract int getEntityTypeId(); + + public World getWorld() { + return this.getScene().getWorld(); + } + + public boolean isAlive() { + return true; + } + + public LifeState getLifeState() { + return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; + } + + public Object2FloatMap getMetaOverrideMap() { + if (this.metaOverrideMap == null) { + this.metaOverrideMap = new Object2FloatOpenHashMap<>(); + } + return this.metaOverrideMap; + } + + public Int2ObjectMap getMetaModifiers() { + if (this.metaModifiers == null) { + this.metaModifiers = new Int2ObjectOpenHashMap<>(); + } + return this.metaModifiers; + } + + public abstract Int2FloatMap getFightProperties(); + + public abstract Position getPosition(); + + public abstract Position getRotation(); + + public void setFightProperty(FightProperty prop, float value) { + this.getFightProperties().put(prop.getId(), value); + } + + public void setFightProperty(int id, float value) { + this.getFightProperties().put(id, value); + } + + public void addFightProperty(FightProperty prop, float value) { + this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value); + } + + public float getFightProperty(FightProperty prop) { + return this.getFightProperties().getOrDefault(prop.getId(), 0f); + } + + public boolean hasFightProperty(FightProperty prop) { + return this.getFightProperties().containsKey(prop.getId()); + } + + public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) { + this.getFightProperties() + .forEach( + (key, value) -> { + if (key == 0) return; + entityInfo.addFightPropList( + FightPropPair.newBuilder().setPropType(key).setPropValue(value).build()); + }); + } + + protected MotionInfo getMotionInfo() { + return MotionInfo.newBuilder() + .setPos(this.getPosition().toProto()) + .setRot(this.getRotation().toProto()) + .setSpeed(Vector.newBuilder()) + .setState(this.getMotionState()) + .build(); + } + + public float heal(float amount) { + if (this.getFightProperties() == null) { + return 0f; + } + + float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + + if (curHp >= maxHp) { + return 0f; + } + + float healed = Math.min(maxHp - curHp, amount); + this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); + + this.getScene() + .broadcastPacket( + new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); + + return healed; + } + + public void damage(float amount) { + this.damage(amount, 0, ElementType.None); + } + + public void damage(float amount, int killerId, ElementType attackType) { + // Check if the entity has properties. + if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) { + return; + } + + // Invoke entity damage event. + EntityDamageEvent event = + new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId)); + event.call(); + if (event.isCanceled()) { + return; // If the event is canceled, do not damage the entity. + } + + float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) { + // Add negative HP to the current HP property. + this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage())); + } + + // Check if dead + boolean isDead = false; + if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { + this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); + isDead = true; + } + + // Packets + this.getScene() + .broadcastPacket( + new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); + + // Check if dead. + if (isDead) { + this.getScene().killEntity(this, killerId); + } + } + + /** + * Runs the Lua callbacks for {@link EntityDamageEvent}. + * + * @param event The damage event. + */ + public void runLuaCallbacks(EntityDamageEvent event) { + if (entityController != null) { + entityController.onBeHurt(this, event.getAttackElementType(), true); // todo is host handling + } + } + + /** + * Move this entity to a new position. + * + * @param position The new position. + * @param rotation The new rotation. + */ + public void move(Position position, Position rotation) { + // Set the position and rotation. + this.getPosition().set(position); + this.getRotation().set(rotation); + } + + /** + * Called when a player interacts with this entity + * + * @param player Player that is interacting with this entity + * @param interactReq Interact request protobuf data + */ + public void onInteract(Player player, GadgetInteractReq interactReq) {} + + /** Called when this entity is added to the world */ + public void onCreate() {} + + public void onRemoved() {} + + public void onTick(int sceneTime) { + if (entityController != null) { + entityController.onTimer(this, sceneTime); + } + } + + public int onClientExecuteRequest(int param1, int param2, int param3) { + if (entityController != null) { + return entityController.onClientExecuteRequest(this, param1, param2, param3); + } + return 0; + } + + /** + * Called when this entity dies + * + * @param killerId Entity id of the entity that killed this entity + */ + public void onDeath(int killerId) { + // Invoke entity death event. + EntityDeathEvent event = new EntityDeathEvent(this, killerId); + event.call(); + + // Run Lua callbacks. + if (entityController != null) { + entityController.onDie(this, getLastAttackType()); + } + } + + public abstract SceneEntityInfo toProto(); +} diff --git a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java index 1e26c13c9..8696ccdb9 100644 --- a/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java +++ b/src/main/java/emu/grasscutter/game/entity/gadget/GadgetRewardStatue.java @@ -1,33 +1,34 @@ -package emu.grasscutter.game.entity.gadget; - -import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; -import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; -import emu.grasscutter.net.proto.ResinCostTypeOuterClass; -import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; -import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; - -public final class GadgetRewardStatue extends GadgetContent { - - public GadgetRewardStatue(EntityGadget gadget) { - super(gadget); - } - - public boolean onInteract(Player player, GadgetInteractReq req) { - var dungeonManager = player.getScene().getDungeonManager(); - - if (player.getScene().getChallenge() instanceof DungeonChallenge) { - var useCondensed = req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE; - dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId()); - } - - player.sendPacket( - new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE)); - - return false; - } - - public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {} -} +package emu.grasscutter.game.entity.gadget; + +import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; +import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; +import emu.grasscutter.net.proto.ResinCostTypeOuterClass; +import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; +import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; + +public final class GadgetRewardStatue extends GadgetContent { + + public GadgetRewardStatue(EntityGadget gadget) { + super(gadget); + } + + public boolean onInteract(Player player, GadgetInteractReq req) { + var dungeonManager = player.getScene().getDungeonManager(); + + if (player.getScene().getChallenge() instanceof DungeonChallenge) { + var useCondensed = + req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE; + dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId()); + } + + player.sendPacket( + new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE)); + + return false; + } + + public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {} +} diff --git a/src/main/java/emu/grasscutter/game/entity/platform/EntitySolarIsotomaElevatorPlatform.java b/src/main/java/emu/grasscutter/game/entity/platform/EntitySolarIsotomaElevatorPlatform.java index 3efb29872..d12148007 100644 --- a/src/main/java/emu/grasscutter/game/entity/platform/EntitySolarIsotomaElevatorPlatform.java +++ b/src/main/java/emu/grasscutter/game/entity/platform/EntitySolarIsotomaElevatorPlatform.java @@ -1,40 +1,45 @@ -package emu.grasscutter.game.entity.platform; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.binout.config.ConfigEntityGadget; -import emu.grasscutter.game.entity.*; -import emu.grasscutter.game.entity.gadget.GadgetAbility; -import emu.grasscutter.game.entity.gadget.platform.AbilityRoute; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.utils.Position; - -public class EntitySolarIsotomaElevatorPlatform extends EntityGadget { - public EntitySolarIsotomaElevatorPlatform(EntitySolarIsotomaClientGadget isotoma, Scene scene, int gadgetId, Position pos, Position rot) { - super(scene, gadgetId, pos, rot); - setOwner(isotoma); - this.setRouteConfig(new AbilityRoute(rot, false, false, pos)); - this.setContent(new GadgetAbility(this, isotoma)); - } - - @Override - protected void fillFightProps(ConfigEntityGadget configGadget) { - if (configGadget == null || configGadget.getCombat() == null) { - return; - } - var combatData = configGadget.getCombat(); - var combatProperties = combatData.getProperty(); - - if (combatProperties.isUseCreatorProperty()) { - //If useCreatorProperty == true, use owner's property; - GameEntity ownerEntity = getOwner(); - if (ownerEntity != null) { - getFightProperties().putAll(ownerEntity.getFightProperties()); - return; - } else { - Grasscutter.getLogger().warn("Why gadget owner is null?"); - } - } - - super.fillFightProps(configGadget); - } -} +package emu.grasscutter.game.entity.platform; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.config.ConfigEntityGadget; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.entity.gadget.GadgetAbility; +import emu.grasscutter.game.entity.gadget.platform.AbilityRoute; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.utils.Position; + +public class EntitySolarIsotomaElevatorPlatform extends EntityGadget { + public EntitySolarIsotomaElevatorPlatform( + EntitySolarIsotomaClientGadget isotoma, + Scene scene, + int gadgetId, + Position pos, + Position rot) { + super(scene, gadgetId, pos, rot); + setOwner(isotoma); + this.setRouteConfig(new AbilityRoute(rot, false, false, pos)); + this.setContent(new GadgetAbility(this, isotoma)); + } + + @Override + protected void fillFightProps(ConfigEntityGadget configGadget) { + if (configGadget == null || configGadget.getCombat() == null) { + return; + } + var combatData = configGadget.getCombat(); + var combatProperties = combatData.getProperty(); + + if (combatProperties.isUseCreatorProperty()) { + // If useCreatorProperty == true, use owner's property; + GameEntity ownerEntity = getOwner(); + if (ownerEntity != null) { + getFightProperties().putAll(ownerEntity.getFightProperties()); + return; + } else { + Grasscutter.getLogger().warn("Why gadget owner is null?"); + } + } + + super.fillFightProps(configGadget); + } +} diff --git a/src/main/java/emu/grasscutter/game/mail/Mail.java b/src/main/java/emu/grasscutter/game/mail/Mail.java index 7c599f077..054324459 100644 --- a/src/main/java/emu/grasscutter/game/mail/Mail.java +++ b/src/main/java/emu/grasscutter/game/mail/Mail.java @@ -1,163 +1,173 @@ -package emu.grasscutter.game.mail; - -import dev.morphia.annotations.Entity; -import dev.morphia.annotations.Id; -import dev.morphia.annotations.Indexed; -import dev.morphia.annotations.Transient; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.proto.*; -import emu.grasscutter.net.proto.EquipParamOuterClass.EquipParam; -import emu.grasscutter.net.proto.MailCollectStateOuterClass.MailCollectState; -import emu.grasscutter.net.proto.MailTextContentOuterClass.MailTextContent; -import org.bson.types.ObjectId; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; - -import static emu.grasscutter.net.proto.MailItemOuterClass.MailItem.*; - -@Entity(value = "mail", useDiscriminator = false) -public final class Mail { - @Id private ObjectId id; - @Indexed private int ownerUid; - public MailContent mailContent; - public List itemList; - public long sendTime; - public long expireTime; - public int importance; - public boolean isRead; - public boolean isAttachmentGot; - public int stateValue; - @Transient private boolean shouldDelete; - - public Mail() { - this(new MailContent(), new ArrayList(), (int) Instant.now().getEpochSecond() + 604800); // TODO: add expire time to send mail command - } - - public Mail(MailContent mailContent, List itemList, long expireTime) { - this(mailContent, itemList, expireTime, 0); - } - - public Mail(MailContent mailContent, List itemList, long expireTime, int importance) { - this(mailContent, itemList, expireTime, importance, 1); - } - - public Mail(MailContent mailContent, List itemList, long expireTime, int importance, int state) { - this.mailContent = mailContent; - this.itemList = itemList; - this.sendTime = (int) Instant.now().getEpochSecond(); - this.expireTime = expireTime; - this.importance = importance; // Starred mail, 0 = No star, 1 = Star. - this.isRead = false; - this.isAttachmentGot = false; - this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box. - } - - public ObjectId getId() { - return id; - } - - public int getOwnerUid() { - return ownerUid; - } - - public void setOwnerUid(int ownerUid) { - this.ownerUid = ownerUid; - } - - public MailDataOuterClass.MailData toProto(Player player) { - return MailDataOuterClass.MailData.newBuilder() - .setMailId(player.getMailId(this)) - .setMailTextContent(this.mailContent.toProto()) - .addAllItemList(this.itemList.stream().map(MailItem::toProto).toList()) - .setSendTime((int) this.sendTime) - .setExpireTime((int) this.expireTime) - .setImportance(this.importance) - .setIsRead(this.isRead) - .setIsAttachmentGot(this.isAttachmentGot) - .setCollectState(MailCollectState.MAIL_COLLECT_STATE_NOT_COLLECTIBLE) - .build(); - } - - @Entity - public static class MailContent { - public String title; - public String content; - public String sender; - - public MailContent() { - this.title = ""; - this.content = "loading..."; - this.sender = "loading"; - } - - public MailContent(String title, String content) { - this(title, content, "Server"); - } - - public MailContent(String title, String content, Player sender) { - this(title, content, sender.getNickname()); - } - - public MailContent(String title, String content, String sender) { - this.title = title; - this.content = content; - this.sender = sender; - } - - public MailTextContent toProto() { - return MailTextContent.newBuilder() - .setTitle(this.title) - .setContent(this.content) - .setSender(this.sender) - .build(); - } - } - - @Entity - public static class MailItem { - public int itemId; - public int itemCount; - public int itemLevel; - - public MailItem() { - this.itemId = 11101; - this.itemCount = 1; - this.itemLevel = 1; - } - - public MailItem(int itemId) { - this(itemId, 1); - } - - public MailItem(int itemId, int itemCount) { - this(itemId, itemCount, 1); - } - - public MailItem(int itemId, int itemCount, int itemLevel) { - this.itemId = itemId; - this.itemCount = itemCount; - this.itemLevel = itemLevel; - } - - public MailItemOuterClass.MailItem toProto() { - return newBuilder().setEquipParam(EquipParam.newBuilder() - .setItemId(this.itemId) - .setItemNum(this.itemCount) - .setItemLevel(this.itemLevel) - .setPromoteLevel(0)//mock - .build()) - .build(); - } - } - - public void save() { - if (this.expireTime * 1000 < System.currentTimeMillis()) { - DatabaseHelper.deleteMail(this); - } else { - DatabaseHelper.saveMail(this); - } - } -} +package emu.grasscutter.game.mail; + +import static emu.grasscutter.net.proto.MailItemOuterClass.MailItem.*; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Indexed; +import dev.morphia.annotations.Transient; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.net.proto.EquipParamOuterClass.EquipParam; +import emu.grasscutter.net.proto.MailCollectStateOuterClass.MailCollectState; +import emu.grasscutter.net.proto.MailTextContentOuterClass.MailTextContent; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import org.bson.types.ObjectId; + +@Entity(value = "mail", useDiscriminator = false) +public final class Mail { + @Id private ObjectId id; + @Indexed private int ownerUid; + public MailContent mailContent; + public List itemList; + public long sendTime; + public long expireTime; + public int importance; + public boolean isRead; + public boolean isAttachmentGot; + public int stateValue; + @Transient private boolean shouldDelete; + + public Mail() { + this( + new MailContent(), + new ArrayList(), + (int) Instant.now().getEpochSecond() + + 604800); // TODO: add expire time to send mail command + } + + public Mail(MailContent mailContent, List itemList, long expireTime) { + this(mailContent, itemList, expireTime, 0); + } + + public Mail(MailContent mailContent, List itemList, long expireTime, int importance) { + this(mailContent, itemList, expireTime, importance, 1); + } + + public Mail( + MailContent mailContent, + List itemList, + long expireTime, + int importance, + int state) { + this.mailContent = mailContent; + this.itemList = itemList; + this.sendTime = (int) Instant.now().getEpochSecond(); + this.expireTime = expireTime; + this.importance = importance; // Starred mail, 0 = No star, 1 = Star. + this.isRead = false; + this.isAttachmentGot = false; + this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box. + } + + public ObjectId getId() { + return id; + } + + public int getOwnerUid() { + return ownerUid; + } + + public void setOwnerUid(int ownerUid) { + this.ownerUid = ownerUid; + } + + public MailDataOuterClass.MailData toProto(Player player) { + return MailDataOuterClass.MailData.newBuilder() + .setMailId(player.getMailId(this)) + .setMailTextContent(this.mailContent.toProto()) + .addAllItemList(this.itemList.stream().map(MailItem::toProto).toList()) + .setSendTime((int) this.sendTime) + .setExpireTime((int) this.expireTime) + .setImportance(this.importance) + .setIsRead(this.isRead) + .setIsAttachmentGot(this.isAttachmentGot) + .setCollectState(MailCollectState.MAIL_COLLECT_STATE_NOT_COLLECTIBLE) + .build(); + } + + @Entity + public static class MailContent { + public String title; + public String content; + public String sender; + + public MailContent() { + this.title = ""; + this.content = "loading..."; + this.sender = "loading"; + } + + public MailContent(String title, String content) { + this(title, content, "Server"); + } + + public MailContent(String title, String content, Player sender) { + this(title, content, sender.getNickname()); + } + + public MailContent(String title, String content, String sender) { + this.title = title; + this.content = content; + this.sender = sender; + } + + public MailTextContent toProto() { + return MailTextContent.newBuilder() + .setTitle(this.title) + .setContent(this.content) + .setSender(this.sender) + .build(); + } + } + + @Entity + public static class MailItem { + public int itemId; + public int itemCount; + public int itemLevel; + + public MailItem() { + this.itemId = 11101; + this.itemCount = 1; + this.itemLevel = 1; + } + + public MailItem(int itemId) { + this(itemId, 1); + } + + public MailItem(int itemId, int itemCount) { + this(itemId, itemCount, 1); + } + + public MailItem(int itemId, int itemCount, int itemLevel) { + this.itemId = itemId; + this.itemCount = itemCount; + this.itemLevel = itemLevel; + } + + public MailItemOuterClass.MailItem toProto() { + return newBuilder() + .setEquipParam( + EquipParam.newBuilder() + .setItemId(this.itemId) + .setItemNum(this.itemCount) + .setItemLevel(this.itemLevel) + .setPromoteLevel(0) // mock + .build()) + .build(); + } + } + + public void save() { + if (this.expireTime * 1000 < System.currentTimeMillis()) { + DatabaseHelper.deleteMail(this); + } else { + DatabaseHelper.saveMail(this); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/blossom/BlossomActivity.java b/src/main/java/emu/grasscutter/game/managers/blossom/BlossomActivity.java index 0f313a83b..74bd27c9a 100644 --- a/src/main/java/emu/grasscutter/game/managers/blossom/BlossomActivity.java +++ b/src/main/java/emu/grasscutter/game/managers/blossom/BlossomActivity.java @@ -1,133 +1,146 @@ -package emu.grasscutter.game.managers.blossom; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; -import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.scripts.data.SceneBossChest; -import emu.grasscutter.scripts.data.SceneGadget; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.Utils; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; - -public final class BlossomActivity { - - private final SceneGroup tempSceneGroup; - private final WorldChallenge challenge; - private final EntityGadget gadget; - private EntityGadget chest; - private int step; - private final int goal; - private int generatedCount; - private final int worldLevel; - private boolean pass=false; - private final List activeMonsters = new ArrayList<>(); - private final Queue candidateMonsters = new ArrayDeque<>(); - private static final int BLOOMING_GADGET_ID = 70210109; - public BlossomActivity(EntityGadget entityGadget, List monsters, int timeout, int worldLevel) { - this.tempSceneGroup = new SceneGroup(); - this.tempSceneGroup.id = entityGadget.getId(); - this.gadget=entityGadget; - this.step=0; - this.goal = monsters.size(); - this.candidateMonsters.addAll(monsters); - this.worldLevel = worldLevel; - ArrayList challengeTriggers = new ArrayList<>(); - this.challenge = new WorldChallenge(entityGadget.getScene(), - tempSceneGroup, - 1, - 1, - List.of(goal, timeout), - timeout, - goal, challengeTriggers); - challengeTriggers.add(new KillMonsterCountTrigger()); - //this.challengeTriggers.add(new InTimeTrigger()); - } - public WorldChallenge getChallenge() { - return this.challenge; - } - public void setMonsters(List monsters) { - this.activeMonsters.clear(); - this.activeMonsters.addAll(monsters); - for (EntityMonster monster : monsters) { - monster.setGroupId(this.tempSceneGroup.id); - } - } - public int getAliveMonstersCount() { - int count=0; - for (EntityMonster monster: activeMonsters) { - if (monster.isAlive()) { - count++; - } - } - return count; - } - public boolean getPass() { - return pass; - } - public void start() { - challenge.start(); - } - public void onTick() { - Scene scene = gadget.getScene(); - Position pos = gadget.getPosition(); - if (getAliveMonstersCount() <= 2) { - if (generatedCount newMonsters = new ArrayList<>(); - int willSpawn = Utils.randomRange(3,5); - if (generatedCount+willSpawn>goal) { - willSpawn = goal - generatedCount; - } - generatedCount+=willSpawn; - for (int i = 0; i < willSpawn; i++) { - var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll()); - int level = scene.getEntityLevel(1, worldLevelOverride); - EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level); - scene.addEntity(entity); - newMonsters.add(entity); - } - setMonsters(newMonsters); - }else { - if (getAliveMonstersCount() == 0) { - this.pass = true; - this.challenge.done(); - } - } - } - } - public EntityGadget getGadget() { - return gadget; - } - public EntityGadget getChest() { - if (chest==null) { - EntityGadget rewardGadget = new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition()); - SceneGadget metaGadget = new SceneGadget(); - metaGadget.boss_chest = new SceneBossChest(); - metaGadget.boss_chest.resin = 20; - rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY); - rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY); - rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY); - rewardGadget.setMetaGadget(metaGadget); - rewardGadget.buildContent(); - chest = rewardGadget; - } - return chest; - } -} +package emu.grasscutter.game.managers.blossom; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; +import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.scripts.data.SceneBossChest; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.Utils; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; + +public final class BlossomActivity { + + private final SceneGroup tempSceneGroup; + private final WorldChallenge challenge; + private final EntityGadget gadget; + private EntityGadget chest; + private int step; + private final int goal; + private int generatedCount; + private final int worldLevel; + private boolean pass = false; + private final List activeMonsters = new ArrayList<>(); + private final Queue candidateMonsters = new ArrayDeque<>(); + private static final int BLOOMING_GADGET_ID = 70210109; + + public BlossomActivity( + EntityGadget entityGadget, List monsters, int timeout, int worldLevel) { + this.tempSceneGroup = new SceneGroup(); + this.tempSceneGroup.id = entityGadget.getId(); + this.gadget = entityGadget; + this.step = 0; + this.goal = monsters.size(); + this.candidateMonsters.addAll(monsters); + this.worldLevel = worldLevel; + ArrayList challengeTriggers = new ArrayList<>(); + this.challenge = + new WorldChallenge( + entityGadget.getScene(), + tempSceneGroup, + 1, + 1, + List.of(goal, timeout), + timeout, + goal, + challengeTriggers); + challengeTriggers.add(new KillMonsterCountTrigger()); + // this.challengeTriggers.add(new InTimeTrigger()); + } + + public WorldChallenge getChallenge() { + return this.challenge; + } + + public void setMonsters(List monsters) { + this.activeMonsters.clear(); + this.activeMonsters.addAll(monsters); + for (EntityMonster monster : monsters) { + monster.setGroupId(this.tempSceneGroup.id); + } + } + + public int getAliveMonstersCount() { + int count = 0; + for (EntityMonster monster : activeMonsters) { + if (monster.isAlive()) { + count++; + } + } + return count; + } + + public boolean getPass() { + return pass; + } + + public void start() { + challenge.start(); + } + + public void onTick() { + Scene scene = gadget.getScene(); + Position pos = gadget.getPosition(); + if (getAliveMonstersCount() <= 2) { + if (generatedCount < goal) { + step++; + + var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel); + int worldLevelOverride = 0; + if (worldLevelData != null) { + worldLevelOverride = worldLevelData.getMonsterLevel(); + } + + List newMonsters = new ArrayList<>(); + int willSpawn = Utils.randomRange(3, 5); + if (generatedCount + willSpawn > goal) { + willSpawn = goal - generatedCount; + } + generatedCount += willSpawn; + for (int i = 0; i < willSpawn; i++) { + var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll()); + int level = scene.getEntityLevel(1, worldLevelOverride); + EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level); + scene.addEntity(entity); + newMonsters.add(entity); + } + setMonsters(newMonsters); + } else { + if (getAliveMonstersCount() == 0) { + this.pass = true; + this.challenge.done(); + } + } + } + } + + public EntityGadget getGadget() { + return gadget; + } + + public EntityGadget getChest() { + if (chest == null) { + EntityGadget rewardGadget = + new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition()); + SceneGadget metaGadget = new SceneGadget(); + metaGadget.boss_chest = new SceneBossChest(); + metaGadget.boss_chest.resin = 20; + rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY); + rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY); + rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY); + rewardGadget.setMetaGadget(metaGadget); + rewardGadget.buildContent(); + chest = rewardGadget; + } + return chest; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index 3ec0c117e..cd3dfeaf0 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -1,418 +1,420 @@ -package emu.grasscutter.game.managers.energy; - -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - -import com.google.protobuf.InvalidProtocolBufferException; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.DataLoader; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.excels.ItemData; -import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; -import emu.grasscutter.data.excels.monster.MonsterData.HpDrops; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.entity.*; -import emu.grasscutter.game.player.BasePlayerManager; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.MonsterType; -import emu.grasscutter.game.props.WeaponType; -import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; -import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier; -import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; -import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; -import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; -import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; -import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import lombok.Getter; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; - -public class EnergyManager extends BasePlayerManager { - private static final Int2ObjectMap> energyDropData = - new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap> - skillParticleGenerationData = new Int2ObjectOpenHashMap<>(); - private final Object2IntMap avatarNormalProbabilities; - @Getter private boolean energyUsage; // Should energy usage be enabled for this player? - - public EnergyManager(Player player) { - super(player); - this.avatarNormalProbabilities = new Object2IntOpenHashMap<>(); - this.energyUsage = GAME_OPTIONS.energyUsage; - } - - public static void initialize() { - // Read the data we need for monster energy drops. - try { - DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class) - .forEach( - entry -> { - energyDropData.put(entry.getDropId(), entry.getDropList()); - }); - - Grasscutter.getLogger().debug("Energy drop data successfully loaded."); - } catch (Exception ex) { - Grasscutter.getLogger().error("Unable to load energy drop data.", ex); - } - - // Read the data for particle generation from skills - try { - DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class) - .forEach( - entry -> { - skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList()); - }); - - Grasscutter.getLogger().debug("Skill particle generation data successfully loaded."); - } catch (Exception ex) { - Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex); - } - } - - /** Particle creation for elemental skills. */ - private int getBallCountForAvatar(int avatarId) { - // We default to two particles. - int count = 2; - - // If we don't have any data for this avatar, stop. - if (!skillParticleGenerationData.containsKey(avatarId)) { - Grasscutter.getLogger().warn("No particle generation data for avatarId {} found.", avatarId); - } - // If we do have data, roll for how many particles we should generate. - else { - int roll = ThreadLocalRandom.current().nextInt(0, 100); - int percentageStack = 0; - for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) { - int chance = info.getChance(); - percentageStack += chance; - if (roll < percentageStack) { - count = info.getValue(); - break; - } - } - } - - // Done. - return count; - } - - private int getBallIdForElement(ElementType element) { - // If we have no element, we default to an element-less particle. - if (element == null) { - return 2024; - } - - // Otherwise, we determine the particle's ID based on the element. - return switch (element) { - case Fire -> 2017; - case Water -> 2018; - case Grass -> 2019; - case Electric -> 2020; - case Wind -> 2021; - case Ice -> 2022; - case Rock -> 2023; - default -> 2024; - }; - } - - public void handleGenerateElemBall(AbilityInvokeEntry invoke) - throws InvalidProtocolBufferException { - // ToDo: - // This is also called when a weapon like Favonius Warbow etc. creates energy through its - // passive. - // We are not handling this correctly at the moment. - - // Get action info. - AbilityActionGenerateElemBall action = - AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData()); - if (action == null) { - return; - } - - // Default to an elementless particle. - int itemId = 2024; - - // Generate 2 particles by default. - int amount = 2; - - // Try to get the casting avatar from the player's party. - Optional avatarEntity = - this.getCastingAvatarEntityForEnergy(invoke.getEntityId()); - - // Bug: invokes twice sometimes, Ayato, Keqing - // ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin) - if (avatarEntity.isPresent()) { - Avatar avatar = avatarEntity.get().getAvatar(); - - if (avatar != null) { - int avatarId = avatar.getAvatarId(); - AvatarSkillDepotData skillDepotData = avatar.getSkillDepot(); - - // Determine how many particles we need to create for this avatar. - amount = this.getBallCountForAvatar(avatarId); - - // Determine the avatar's element, and based on that the ID of the - // particles we have to generate. - if (skillDepotData != null) { - ElementType element = skillDepotData.getElementType(); - itemId = this.getBallIdForElement(element); - } - } - } - - // Generate the particles. - var pos = new Position(action.getPos()); - for (int i = 0; i < amount; i++) { - this.generateElemBall(itemId, pos, 1); - } - } - - /** - * Energy generation for NAs/CAs. - * - * @param avatar The avatar. - */ - private void generateEnergyForNormalAndCharged(EntityAvatar avatar) { - // This logic is based on the descriptions given in - // https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks - // https://library.keqingmains.com/combat-mechanics/energy#auto-attacking - // Those descriptions are lacking in some information, so this implementation most likely - // does not fully replicate the behavior of the official server. Open questions: - // - Does the probability for a character reset after some time? - // - Does the probability for a character reset when switching them out? - // - Does this really count every individual hit separately? - - // Get the avatar's weapon type. - WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType(); - - // Check if we already have probability data for this avatar. If not, insert it. - if (!this.avatarNormalProbabilities.containsKey(avatar)) { - this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); - } - - // Roll for energy. - int currentProbability = this.avatarNormalProbabilities.getInt(avatar); - int roll = ThreadLocalRandom.current().nextInt(0, 100); - - // If the player wins the roll, we increase the avatar's energy and reset the probability. - if (roll < currentProbability) { - avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true); - this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); - } - // Otherwise, we increase the probability for the next hit. - else { - this.avatarNormalProbabilities.put( - avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability()); - } - } - - public void handleAttackHit(EvtBeingHitInfo hitInfo) { - // Get the attack result. - AttackResult attackRes = hitInfo.getAttackResult(); - - // Make sure the attack was performed by the currently active avatar. If not, we ignore the hit. - Optional attackerEntity = - this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId()); - if (attackerEntity.isEmpty() - || this.player.getTeamManager().getCurrentAvatarEntity().getId() - != attackerEntity.get().getId()) { - return; - } - - // Make sure the target is an actual enemy. - GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId()); - if (!(targetEntity instanceof EntityMonster targetMonster)) { - return; - } - - MonsterType targetType = targetMonster.getMonsterData().getType(); - if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) { - return; - } - - // Get the ability that caused this hit. - AbilityIdentifier ability = attackRes.getAbilityIdentifier(); - - // Make sure there is no actual "ability" associated with the hit. For now, this is how we - // identify normal and charged attacks. Note that this is not completely accurate: - // - Many character's charged attacks have an ability associated with them. This means that, - // for now, we don't identify charged attacks reliably. - // - There might also be some cases where we incorrectly identify something as a normal or - // charged attack that is not (Diluc's E?). - // - Catalyst normal attacks have an ability, so we don't handle those for now. - // ToDo: Fix all of that. - if (ability != AbilityIdentifier.getDefaultInstance()) { - return; - } - - // Handle the energy generation. - this.generateEnergyForNormalAndCharged(attackerEntity.get()); - } - - /* - * Energy logic related to using skills. - */ - - private void handleBurstCast(Avatar avatar, int skillId) { - // Don't do anything if energy usage is disabled. - if (!GAME_OPTIONS.energyUsage || !this.energyUsage) { - return; - } - - // If the cast skill was a burst, consume energy. - if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) { - avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START); - } - } - - public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { - // Determine the entity that has cast the skill. Cancel if we can't find that avatar. - Optional caster = - this.player.getTeamManager().getActiveTeam().stream() - .filter(character -> character.getId() == casterId) - .findFirst(); - - if (caster.isEmpty()) { - return; - } - - Avatar avatar = caster.get().getAvatar(); - - // Handle elemental burst. - this.handleBurstCast(avatar, skillId); - } - - /* - * Monster energy drops. - */ - - private void generateElemBallDrops(EntityMonster monster, int dropId) { - // Generate all drops specified for the given drop id. - if (!energyDropData.containsKey(dropId)) { - Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId); - return; - } - - for (EnergyDropInfo info : energyDropData.get(dropId)) { - this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount()); - } - } - - public void handleMonsterEnergyDrop( - EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) { - // Make sure this is actually a monster. - // Note that some wildlife also has that type, like boars or birds. - MonsterType type = monster.getMonsterData().getType(); - if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) { - return; - } - - // Calculate the HP thresholds for before and after the damage was taken. - float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float thresholdBefore = hpBeforeDamage / maxHp; - float thresholdAfter = hpAfterDamage / maxHp; - - // Determine the thresholds the monster has passed, and generate drops based on that. - for (HpDrops drop : monster.getMonsterData().getHpDrops()) { - if (drop.getDropId() == 0) { - continue; - } - - float threshold = drop.getHpPercent() / 100.0f; - if (threshold < thresholdBefore && threshold >= thresholdAfter) { - this.generateElemBallDrops(monster, drop.getDropId()); - } - } - - // Handle kill drops. - if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) { - this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId()); - } - } - - /* - * Utilities. - */ - - private void generateElemBall(int ballId, Position position, int count) { - // Generate a particle/orb with the specified parameters. - ItemData itemData = GameData.getItemDataMap().get(ballId); - if (itemData == null) { - return; - } - - EntityItem energyBall = - new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count); - this.getPlayer().getScene().addEntity(energyBall); - } - - private Optional getCastingAvatarEntityForEnergy(int invokeEntityId) { - // To determine the avatar that has cast the skill that caused the energy particle to be - // generated, - // we have to look at the entity that has invoked the ability. This can either be that avatar - // directly, - // or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar - // that cast the skill. - - // Try to get the invoking entity from the scene. - GameEntity entity = this.player.getScene().getEntityById(invokeEntityId); - - // Determine the ID of the entity that originally cast this skill. If the scene entity is null, - // or not an `EntityClientGadget`, we assume that we are directly looking at the casting avatar - // (the null case will happen if the avatar was switched out between casting the skill and the - // particle being generated). If the scene entity is an `EntityClientGadget`, we need to find - // the - // ID of the original owner of that gadget. - int avatarEntityId = - (!(entity instanceof EntityClientGadget)) - ? invokeEntityId - : ((EntityClientGadget) entity).getOriginalOwnerEntityId(); - - // Finally, find the avatar entity in the player's team. - return this.player.getTeamManager().getActiveTeam().stream() - .filter(character -> character.getId() == avatarEntityId) - .findFirst(); - } - - /** - * Refills the energy of the active avatar. - * - * @return True if the energy was refilled, false otherwise. - */ - public boolean refillActiveEnergy() { - var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity(); - return activeEntity.addEnergy(activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal()); - } - - /** - * Refills the energy of the entire team. - * - * @param changeReason The reason for the energy change. - * @param isFlat Whether the energy should be added as a flat value. - */ - public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) { - for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) { - // giving the exact amount read off the AvatarSkillData.json - entityAvatar.addEnergy(entityAvatar.getAvatar().getSkillDepot() - .getEnergySkillData().getCostElemVal(), changeReason, isFlat); - } - } - - public void setEnergyUsage(boolean energyUsage) { - this.energyUsage = energyUsage; - if (!energyUsage) { // Refill team energy if usage is disabled - for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) { - entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true); - } - } - } -} +package emu.grasscutter.game.managers.energy; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + +import com.google.protobuf.InvalidProtocolBufferException; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.DataLoader; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.ItemData; +import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; +import emu.grasscutter.data.excels.monster.MonsterData.HpDrops; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.player.BasePlayerManager; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.MonsterType; +import emu.grasscutter.game.props.WeaponType; +import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; +import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier; +import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; +import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; +import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; +import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import lombok.Getter; + +public class EnergyManager extends BasePlayerManager { + private static final Int2ObjectMap> energyDropData = + new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap> + skillParticleGenerationData = new Int2ObjectOpenHashMap<>(); + private final Object2IntMap avatarNormalProbabilities; + @Getter private boolean energyUsage; // Should energy usage be enabled for this player? + + public EnergyManager(Player player) { + super(player); + this.avatarNormalProbabilities = new Object2IntOpenHashMap<>(); + this.energyUsage = GAME_OPTIONS.energyUsage; + } + + public static void initialize() { + // Read the data we need for monster energy drops. + try { + DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class) + .forEach( + entry -> { + energyDropData.put(entry.getDropId(), entry.getDropList()); + }); + + Grasscutter.getLogger().debug("Energy drop data successfully loaded."); + } catch (Exception ex) { + Grasscutter.getLogger().error("Unable to load energy drop data.", ex); + } + + // Read the data for particle generation from skills + try { + DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class) + .forEach( + entry -> { + skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList()); + }); + + Grasscutter.getLogger().debug("Skill particle generation data successfully loaded."); + } catch (Exception ex) { + Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex); + } + } + + /** Particle creation for elemental skills. */ + private int getBallCountForAvatar(int avatarId) { + // We default to two particles. + int count = 2; + + // If we don't have any data for this avatar, stop. + if (!skillParticleGenerationData.containsKey(avatarId)) { + Grasscutter.getLogger().warn("No particle generation data for avatarId {} found.", avatarId); + } + // If we do have data, roll for how many particles we should generate. + else { + int roll = ThreadLocalRandom.current().nextInt(0, 100); + int percentageStack = 0; + for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) { + int chance = info.getChance(); + percentageStack += chance; + if (roll < percentageStack) { + count = info.getValue(); + break; + } + } + } + + // Done. + return count; + } + + private int getBallIdForElement(ElementType element) { + // If we have no element, we default to an element-less particle. + if (element == null) { + return 2024; + } + + // Otherwise, we determine the particle's ID based on the element. + return switch (element) { + case Fire -> 2017; + case Water -> 2018; + case Grass -> 2019; + case Electric -> 2020; + case Wind -> 2021; + case Ice -> 2022; + case Rock -> 2023; + default -> 2024; + }; + } + + public void handleGenerateElemBall(AbilityInvokeEntry invoke) + throws InvalidProtocolBufferException { + // ToDo: + // This is also called when a weapon like Favonius Warbow etc. creates energy through its + // passive. + // We are not handling this correctly at the moment. + + // Get action info. + AbilityActionGenerateElemBall action = + AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData()); + if (action == null) { + return; + } + + // Default to an elementless particle. + int itemId = 2024; + + // Generate 2 particles by default. + int amount = 2; + + // Try to get the casting avatar from the player's party. + Optional avatarEntity = + this.getCastingAvatarEntityForEnergy(invoke.getEntityId()); + + // Bug: invokes twice sometimes, Ayato, Keqing + // ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin) + if (avatarEntity.isPresent()) { + Avatar avatar = avatarEntity.get().getAvatar(); + + if (avatar != null) { + int avatarId = avatar.getAvatarId(); + AvatarSkillDepotData skillDepotData = avatar.getSkillDepot(); + + // Determine how many particles we need to create for this avatar. + amount = this.getBallCountForAvatar(avatarId); + + // Determine the avatar's element, and based on that the ID of the + // particles we have to generate. + if (skillDepotData != null) { + ElementType element = skillDepotData.getElementType(); + itemId = this.getBallIdForElement(element); + } + } + } + + // Generate the particles. + var pos = new Position(action.getPos()); + for (int i = 0; i < amount; i++) { + this.generateElemBall(itemId, pos, 1); + } + } + + /** + * Energy generation for NAs/CAs. + * + * @param avatar The avatar. + */ + private void generateEnergyForNormalAndCharged(EntityAvatar avatar) { + // This logic is based on the descriptions given in + // https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks + // https://library.keqingmains.com/combat-mechanics/energy#auto-attacking + // Those descriptions are lacking in some information, so this implementation most likely + // does not fully replicate the behavior of the official server. Open questions: + // - Does the probability for a character reset after some time? + // - Does the probability for a character reset when switching them out? + // - Does this really count every individual hit separately? + + // Get the avatar's weapon type. + WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType(); + + // Check if we already have probability data for this avatar. If not, insert it. + if (!this.avatarNormalProbabilities.containsKey(avatar)) { + this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); + } + + // Roll for energy. + int currentProbability = this.avatarNormalProbabilities.getInt(avatar); + int roll = ThreadLocalRandom.current().nextInt(0, 100); + + // If the player wins the roll, we increase the avatar's energy and reset the probability. + if (roll < currentProbability) { + avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true); + this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); + } + // Otherwise, we increase the probability for the next hit. + else { + this.avatarNormalProbabilities.put( + avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability()); + } + } + + public void handleAttackHit(EvtBeingHitInfo hitInfo) { + // Get the attack result. + AttackResult attackRes = hitInfo.getAttackResult(); + + // Make sure the attack was performed by the currently active avatar. If not, we ignore the hit. + Optional attackerEntity = + this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId()); + if (attackerEntity.isEmpty() + || this.player.getTeamManager().getCurrentAvatarEntity().getId() + != attackerEntity.get().getId()) { + return; + } + + // Make sure the target is an actual enemy. + GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId()); + if (!(targetEntity instanceof EntityMonster targetMonster)) { + return; + } + + MonsterType targetType = targetMonster.getMonsterData().getType(); + if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) { + return; + } + + // Get the ability that caused this hit. + AbilityIdentifier ability = attackRes.getAbilityIdentifier(); + + // Make sure there is no actual "ability" associated with the hit. For now, this is how we + // identify normal and charged attacks. Note that this is not completely accurate: + // - Many character's charged attacks have an ability associated with them. This means that, + // for now, we don't identify charged attacks reliably. + // - There might also be some cases where we incorrectly identify something as a normal or + // charged attack that is not (Diluc's E?). + // - Catalyst normal attacks have an ability, so we don't handle those for now. + // ToDo: Fix all of that. + if (ability != AbilityIdentifier.getDefaultInstance()) { + return; + } + + // Handle the energy generation. + this.generateEnergyForNormalAndCharged(attackerEntity.get()); + } + + /* + * Energy logic related to using skills. + */ + + private void handleBurstCast(Avatar avatar, int skillId) { + // Don't do anything if energy usage is disabled. + if (!GAME_OPTIONS.energyUsage || !this.energyUsage) { + return; + } + + // If the cast skill was a burst, consume energy. + if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) { + avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START); + } + } + + public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { + // Determine the entity that has cast the skill. Cancel if we can't find that avatar. + Optional caster = + this.player.getTeamManager().getActiveTeam().stream() + .filter(character -> character.getId() == casterId) + .findFirst(); + + if (caster.isEmpty()) { + return; + } + + Avatar avatar = caster.get().getAvatar(); + + // Handle elemental burst. + this.handleBurstCast(avatar, skillId); + } + + /* + * Monster energy drops. + */ + + private void generateElemBallDrops(EntityMonster monster, int dropId) { + // Generate all drops specified for the given drop id. + if (!energyDropData.containsKey(dropId)) { + Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId); + return; + } + + for (EnergyDropInfo info : energyDropData.get(dropId)) { + this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount()); + } + } + + public void handleMonsterEnergyDrop( + EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) { + // Make sure this is actually a monster. + // Note that some wildlife also has that type, like boars or birds. + MonsterType type = monster.getMonsterData().getType(); + if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) { + return; + } + + // Calculate the HP thresholds for before and after the damage was taken. + float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float thresholdBefore = hpBeforeDamage / maxHp; + float thresholdAfter = hpAfterDamage / maxHp; + + // Determine the thresholds the monster has passed, and generate drops based on that. + for (HpDrops drop : monster.getMonsterData().getHpDrops()) { + if (drop.getDropId() == 0) { + continue; + } + + float threshold = drop.getHpPercent() / 100.0f; + if (threshold < thresholdBefore && threshold >= thresholdAfter) { + this.generateElemBallDrops(monster, drop.getDropId()); + } + } + + // Handle kill drops. + if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) { + this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId()); + } + } + + /* + * Utilities. + */ + + private void generateElemBall(int ballId, Position position, int count) { + // Generate a particle/orb with the specified parameters. + ItemData itemData = GameData.getItemDataMap().get(ballId); + if (itemData == null) { + return; + } + + EntityItem energyBall = + new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count); + this.getPlayer().getScene().addEntity(energyBall); + } + + private Optional getCastingAvatarEntityForEnergy(int invokeEntityId) { + // To determine the avatar that has cast the skill that caused the energy particle to be + // generated, + // we have to look at the entity that has invoked the ability. This can either be that avatar + // directly, + // or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar + // that cast the skill. + + // Try to get the invoking entity from the scene. + GameEntity entity = this.player.getScene().getEntityById(invokeEntityId); + + // Determine the ID of the entity that originally cast this skill. If the scene entity is null, + // or not an `EntityClientGadget`, we assume that we are directly looking at the casting avatar + // (the null case will happen if the avatar was switched out between casting the skill and the + // particle being generated). If the scene entity is an `EntityClientGadget`, we need to find + // the + // ID of the original owner of that gadget. + int avatarEntityId = + (!(entity instanceof EntityClientGadget)) + ? invokeEntityId + : ((EntityClientGadget) entity).getOriginalOwnerEntityId(); + + // Finally, find the avatar entity in the player's team. + return this.player.getTeamManager().getActiveTeam().stream() + .filter(character -> character.getId() == avatarEntityId) + .findFirst(); + } + + /** + * Refills the energy of the active avatar. + * + * @return True if the energy was refilled, false otherwise. + */ + public boolean refillActiveEnergy() { + var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity(); + return activeEntity.addEnergy( + activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal()); + } + + /** + * Refills the energy of the entire team. + * + * @param changeReason The reason for the energy change. + * @param isFlat Whether the energy should be added as a flat value. + */ + public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) { + for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) { + // giving the exact amount read off the AvatarSkillData.json + entityAvatar.addEnergy( + entityAvatar.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal(), + changeReason, + isFlat); + } + } + + public void setEnergyUsage(boolean energyUsage) { + this.energyUsage = energyUsage; + if (!energyUsage) { // Refill team energy if usage is disabled + for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) { + entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/game/player/PlayerProgressManager.java b/src/main/java/emu/grasscutter/game/player/PlayerProgressManager.java index aa55a0d14..13a8630ca 100644 --- a/src/main/java/emu/grasscutter/game/player/PlayerProgressManager.java +++ b/src/main/java/emu/grasscutter/game/player/PlayerProgressManager.java @@ -1,280 +1,276 @@ -package emu.grasscutter.game.player; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.ScenePointEntry; -import emu.grasscutter.data.excels.OpenStateData; -import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.game.quest.enums.QuestCond; -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.game.quest.enums.QuestState; -import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; -import emu.grasscutter.server.packet.send.*; -import lombok.val; - -import java.util.Set; -import java.util.stream.Collectors; - -// @Entity -public final class PlayerProgressManager extends BasePlayerDataManager { - /****************************************************************************************************************** - ****************************************************************************************************************** - * OPEN STATES - ****************************************************************************************************************** - *****************************************************************************************************************/ - - // Set of open states that are never unlocked, whether they fulfill the conditions or not. - public static final Set BLACKLIST_OPEN_STATES = - Set.of( - 48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as - // soon as quest unlocks are fully implemented. - ); - // Set of open states that are set per default for all accounts. Can be overwritten by an entry in - // `map`. - public static final Set DEFAULT_OPEN_STATES = - GameData.getOpenStateList().stream() - .filter( - s -> - s.isDefaultState() // Actual default-opened states. - // All states whose unlock we don't handle correctly yet. - || (s.getCond().stream() - .filter( - c -> - c.getCondType() - == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) - .count() - == 0) - // Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a - // working chat. - || s.getId() == 1) - .filter( - s -> - !BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist. - .map(s -> s.getId()) - .collect(Collectors.toSet()); - - public PlayerProgressManager(Player player) { - super(player); - } - - /********** - * Handler for player login. - **********/ - public void onPlayerLogin() { - // Try unlocking open states on player login. This handles accounts where unlock conditions were - // already met before certain open state unlocks were implemented. - this.tryUnlockOpenStates(false); - - // Send notify to the client. - player.getSession().send(new PacketOpenStateUpdateNotify(this.player)); - - // Add statue quests if necessary. - this.addStatueQuestsOnLogin(); - - // Auto-unlock the first statue and map area, until we figure out how to make - // that particular statue interactable. - this.player.getUnlockedScenePoints(3).add(7); - this.player.getUnlockedSceneAreas(3).add(1); - } - - /********** - * Direct getters and setters for open states. - **********/ - public int getOpenState(int openState) { - return this.player.getOpenStates().getOrDefault(openState, 0); - } - - private void setOpenState(int openState, int value, boolean sendNotify) { - int previousValue = this.player.getOpenStates().getOrDefault(openState, 0); - - if (value != previousValue) { - this.player.getOpenStates().put(openState, value); - - if (sendNotify) { - player.getSession().send(new PacketOpenStateChangeNotify(openState, value)); - } - } - } - - private void setOpenState(int openState, int value) { - this.setOpenState(openState, value, true); - } - - /********** - * Condition checking for setting open states. - **********/ - private boolean areConditionsMet(OpenStateData openState) { - // Check all conditions and test if at least one of them is violated. - for (var condition : openState.getCond()) { - // For level conditions, check if the player has reached the necessary level. - if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) { - if (this.player.getLevel() < condition.getParam()) { - return false; - } - } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) { - // ToDo: Implement. - } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) { - // ToDo: Implement. - } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) { - // ToDo: Implement. - } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) { - // ToDo: Implement. - } - } - - // Done. If we didn't find any violations, all conditions are met. - return true; - } - - /********** - * Setting open states from the client (via `SetOpenStateReq`). - **********/ - public void setOpenStateFromClient(int openState, int value) { - // Get the data for this open state. - OpenStateData data = GameData.getOpenStateDataMap().get(openState); - if (data == null) { - this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); - return; - } - - // Make sure that this is an open state that the client is allowed to set, - // and that it doesn't have any further conditions attached. - if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) { - this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); - return; - } - - // Set. - this.setOpenState(openState, value); - this.player.sendPacket(new PacketSetOpenStateRsp(openState, value)); - } - - /** - * This force sets an open state, ignoring all conditions and permissions - */ - public void forceSetOpenState(int openState, int value) { - this.setOpenState(openState, value); - } - - /********** - * Triggered unlocking of open states (unlock states whose conditions have been met.) - **********/ - public void tryUnlockOpenStates(boolean sendNotify) { - // Get list of open states that are not yet unlocked. - var lockedStates = - GameData.getOpenStateList().stream() - .filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0) - .toList(); - - // Try unlocking all of them. - for (var state : lockedStates) { - // To auto-unlock a state, it has to meet three conditions: - // * it can not be a state that is unlocked by the client, - // * it has to meet all its unlock conditions, and - // * it can not be in the blacklist. - if (!state.isAllowClientOpen() - && this.areConditionsMet(state) - && !BLACKLIST_OPEN_STATES.contains(state.getId())) { - this.setOpenState(state.getId(), 1, sendNotify); - } - } - } - - public void tryUnlockOpenStates() { - this.tryUnlockOpenStates(true); - } - - /****************************************************************************************************************** - ****************************************************************************************************************** - * MAP AREAS AND POINTS - ****************************************************************************************************************** - *****************************************************************************************************************/ - private void addStatueQuestsOnLogin() { - // Get all currently existing subquests for the "unlock all statues" main quest. - var statueMainQuest = GameData.getMainQuestDataMap().get(303); - var statueSubQuests = statueMainQuest.getSubQuests(); - - // Add the main statue quest if it isn't active yet. - var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); - if (statueGameMainQuest == null) { - this.player.getQuestManager().addQuest(30302); - statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); - } - - // Set all subquests to active if they aren't already finished. - for (var subData : statueSubQuests) { - var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId()); - if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) { - this.player.getQuestManager().addQuest(subData.getSubId()); - } - } - } - - public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) { - // Check whether the unlocked point exists and whether it is still locked. - ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId); - - if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) { - return false; - } - - // Add the point to the list of unlocked points for its scene. - this.player.getUnlockedScenePoints(sceneId).add(pointId); - - // Give primogems and Adventure EXP for unlocking. - this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward); - this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward); - - // this.player.sendPacket(new - // PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP), - // PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP)); - - // Fire quest trigger for trans point unlock. - this.player - .getQuestManager() - .queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId); - - // Send packet. - this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId)); - return true; - } - - public void unlockSceneArea(int sceneId, int areaId) { - // Add the area to the list of unlocked areas in its scene. - this.player.getUnlockedSceneAreas(sceneId).add(areaId); - - // Send packet. - this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId)); - } - - /** - * Give replace costume to player (Amber, Jean, Mona, Rosaria) - */ - public void addReplaceCostumes(){ - var currentPlayerCostumes = player.getCostumeList(); - GameData.getAvatarReplaceCostumeDataMap().keySet().forEach(costumeId -> { - if (GameData.getAvatarCostumeDataMap().get(costumeId) == null || currentPlayerCostumes.contains(costumeId)){ - return; - } - this.player.addCostume(costumeId); - }); - } - - /** - * Quest progress - */ - public void addQuestProgress(int id, int count){ - var newCount = player.getPlayerProgress().addToCurrentProgress(id, count); - player.save(); - player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount); - } - - /** - * Item history - */ - public void addItemObtainedHistory(int id, int count){ - var newCount = player.getPlayerProgress().addToItemHistory(id, count); - player.save(); - player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount); - } -} +package emu.grasscutter.game.player; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.ScenePointEntry; +import emu.grasscutter.data.excels.OpenStateData; +import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.quest.enums.QuestCond; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; +import emu.grasscutter.server.packet.send.*; +import java.util.Set; +import java.util.stream.Collectors; + +// @Entity +public final class PlayerProgressManager extends BasePlayerDataManager { + /****************************************************************************************************************** + ****************************************************************************************************************** + * OPEN STATES + ****************************************************************************************************************** + *****************************************************************************************************************/ + + // Set of open states that are never unlocked, whether they fulfill the conditions or not. + public static final Set BLACKLIST_OPEN_STATES = + Set.of( + 48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as + // soon as quest unlocks are fully implemented. + ); + // Set of open states that are set per default for all accounts. Can be overwritten by an entry in + // `map`. + public static final Set DEFAULT_OPEN_STATES = + GameData.getOpenStateList().stream() + .filter( + s -> + s.isDefaultState() // Actual default-opened states. + // All states whose unlock we don't handle correctly yet. + || (s.getCond().stream() + .filter( + c -> + c.getCondType() + == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) + .count() + == 0) + // Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a + // working chat. + || s.getId() == 1) + .filter( + s -> + !BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist. + .map(s -> s.getId()) + .collect(Collectors.toSet()); + + public PlayerProgressManager(Player player) { + super(player); + } + + /********** + * Handler for player login. + **********/ + public void onPlayerLogin() { + // Try unlocking open states on player login. This handles accounts where unlock conditions were + // already met before certain open state unlocks were implemented. + this.tryUnlockOpenStates(false); + + // Send notify to the client. + player.getSession().send(new PacketOpenStateUpdateNotify(this.player)); + + // Add statue quests if necessary. + this.addStatueQuestsOnLogin(); + + // Auto-unlock the first statue and map area, until we figure out how to make + // that particular statue interactable. + this.player.getUnlockedScenePoints(3).add(7); + this.player.getUnlockedSceneAreas(3).add(1); + } + + /********** + * Direct getters and setters for open states. + **********/ + public int getOpenState(int openState) { + return this.player.getOpenStates().getOrDefault(openState, 0); + } + + private void setOpenState(int openState, int value, boolean sendNotify) { + int previousValue = this.player.getOpenStates().getOrDefault(openState, 0); + + if (value != previousValue) { + this.player.getOpenStates().put(openState, value); + + if (sendNotify) { + player.getSession().send(new PacketOpenStateChangeNotify(openState, value)); + } + } + } + + private void setOpenState(int openState, int value) { + this.setOpenState(openState, value, true); + } + + /********** + * Condition checking for setting open states. + **********/ + private boolean areConditionsMet(OpenStateData openState) { + // Check all conditions and test if at least one of them is violated. + for (var condition : openState.getCond()) { + // For level conditions, check if the player has reached the necessary level. + if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) { + if (this.player.getLevel() < condition.getParam()) { + return false; + } + } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) { + // ToDo: Implement. + } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) { + // ToDo: Implement. + } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) { + // ToDo: Implement. + } else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) { + // ToDo: Implement. + } + } + + // Done. If we didn't find any violations, all conditions are met. + return true; + } + + /********** + * Setting open states from the client (via `SetOpenStateReq`). + **********/ + public void setOpenStateFromClient(int openState, int value) { + // Get the data for this open state. + OpenStateData data = GameData.getOpenStateDataMap().get(openState); + if (data == null) { + this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); + return; + } + + // Make sure that this is an open state that the client is allowed to set, + // and that it doesn't have any further conditions attached. + if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) { + this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); + return; + } + + // Set. + this.setOpenState(openState, value); + this.player.sendPacket(new PacketSetOpenStateRsp(openState, value)); + } + + /** This force sets an open state, ignoring all conditions and permissions */ + public void forceSetOpenState(int openState, int value) { + this.setOpenState(openState, value); + } + + /********** + * Triggered unlocking of open states (unlock states whose conditions have been met.) + **********/ + public void tryUnlockOpenStates(boolean sendNotify) { + // Get list of open states that are not yet unlocked. + var lockedStates = + GameData.getOpenStateList().stream() + .filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0) + .toList(); + + // Try unlocking all of them. + for (var state : lockedStates) { + // To auto-unlock a state, it has to meet three conditions: + // * it can not be a state that is unlocked by the client, + // * it has to meet all its unlock conditions, and + // * it can not be in the blacklist. + if (!state.isAllowClientOpen() + && this.areConditionsMet(state) + && !BLACKLIST_OPEN_STATES.contains(state.getId())) { + this.setOpenState(state.getId(), 1, sendNotify); + } + } + } + + public void tryUnlockOpenStates() { + this.tryUnlockOpenStates(true); + } + + /****************************************************************************************************************** + ****************************************************************************************************************** + * MAP AREAS AND POINTS + ****************************************************************************************************************** + *****************************************************************************************************************/ + private void addStatueQuestsOnLogin() { + // Get all currently existing subquests for the "unlock all statues" main quest. + var statueMainQuest = GameData.getMainQuestDataMap().get(303); + var statueSubQuests = statueMainQuest.getSubQuests(); + + // Add the main statue quest if it isn't active yet. + var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); + if (statueGameMainQuest == null) { + this.player.getQuestManager().addQuest(30302); + statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); + } + + // Set all subquests to active if they aren't already finished. + for (var subData : statueSubQuests) { + var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId()); + if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) { + this.player.getQuestManager().addQuest(subData.getSubId()); + } + } + } + + public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) { + // Check whether the unlocked point exists and whether it is still locked. + ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId); + + if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) { + return false; + } + + // Add the point to the list of unlocked points for its scene. + this.player.getUnlockedScenePoints(sceneId).add(pointId); + + // Give primogems and Adventure EXP for unlocking. + this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward); + this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward); + + // this.player.sendPacket(new + // PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP), + // PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP)); + + // Fire quest trigger for trans point unlock. + this.player + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId); + + // Send packet. + this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId)); + return true; + } + + public void unlockSceneArea(int sceneId, int areaId) { + // Add the area to the list of unlocked areas in its scene. + this.player.getUnlockedSceneAreas(sceneId).add(areaId); + + // Send packet. + this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId)); + } + + /** Give replace costume to player (Amber, Jean, Mona, Rosaria) */ + public void addReplaceCostumes() { + var currentPlayerCostumes = player.getCostumeList(); + GameData.getAvatarReplaceCostumeDataMap() + .keySet() + .forEach( + costumeId -> { + if (GameData.getAvatarCostumeDataMap().get(costumeId) == null + || currentPlayerCostumes.contains(costumeId)) { + return; + } + this.player.addCostume(costumeId); + }); + } + + /** Quest progress */ + public void addQuestProgress(int id, int count) { + var newCount = player.getPlayerProgress().addToCurrentProgress(id, count); + player.save(); + player + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount); + } + + /** Item history */ + public void addItemObtainedHistory(int id, int count) { + var newCount = player.getPlayerProgress().addToItemHistory(id, count); + player.save(); + player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount); + } +} diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 7d2441c42..728503fcd 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -1,1080 +1,1082 @@ -package emu.grasscutter.game.player; - -import static emu.grasscutter.config.Configuration.GAME_OPTIONS; - -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.binout.config.fields.ConfigAbilityData; -import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.EntityBaseGadget; -import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.game.props.EnterReason; -import emu.grasscutter.game.props.FightProperty; -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.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.player.PlayerTeamDeathEvent; -import emu.grasscutter.server.packet.send.*; -import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.Utils; -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 java.util.*; -import java.util.stream.Stream; -import lombok.Getter; -import lombok.Setter; -import lombok.val; - -@Entity -public final class TeamManager extends BasePlayerDataManager { - @Transient private final List avatars; - @Transient @Getter private final Set gadgets; - @Transient @Getter private final IntSet teamResonances; - @Transient @Getter private final IntSet teamResonancesConfig; - // This needs to be a LinkedHashMap to guarantee insertion order. - @Getter private LinkedHashMap teams; - private int currentTeamIndex; - @Getter @Setter private int currentCharacterIndex; - @Transient @Getter @Setter private TeamInfo mpTeam; - @Transient @Getter @Setter private int entityId; - - @Transient private int useTemporarilyTeamIndex = -1; - @Transient private List temporaryTeam; // Temporary Team for tower - @Transient @Getter @Setter private boolean usingTrialTeam; - @Transient @Getter @Setter private TeamInfo trialAvatarTeam; - // hold trial avatars for later use in rebuilding active team - @Transient @Getter @Setter private Map trialAvatars; - - @Transient @Getter @Setter - private int previousIndex = -1; // index of character selection in team before adding trial avatar - - public TeamManager() { - this.mpTeam = new TeamInfo(); - this.avatars = new ArrayList<>(); - this.gadgets = new HashSet<>(); - this.teamResonances = new IntOpenHashSet(); - this.teamResonancesConfig = new IntOpenHashSet(); - } - - public TeamManager(Player player) { - this(); - this.setPlayer(player); - - this.teams = new LinkedHashMap<>(); - this.currentTeamIndex = 1; - for (int i = 1; i <= GameConstants.DEFAULT_TEAMS; i++) { - this.teams.put(i, new TeamInfo()); - } - } - - public World getWorld() { - return this.getPlayer().getWorld(); - } - - /** - * Search through all teams and if the team matches, return that index. Otherwise, return -1. No - * match could mean that the team does not currently belong to the player. - */ - public int getTeamId(TeamInfo team) { - for (int i = 1; i <= this.teams.size(); i++) { - if (this.teams.get(i).equals(team)) { - return i; - } - } - return -1; - } - - public int getCurrentTeamId() { - // Starts from 1 - return currentTeamIndex; - } - - private void setCurrentTeamId(int currentTeamIndex) { - this.currentTeamIndex = currentTeamIndex; - } - - public long getCurrentCharacterGuid() { - return this.getCurrentAvatarEntity().getAvatar().getGuid(); - } - - public TeamInfo getCurrentTeamInfo() { - if (useTemporarilyTeamIndex >= 0 && useTemporarilyTeamIndex < temporaryTeam.size()) { - return temporaryTeam.get(useTemporarilyTeamIndex); - } - if (this.getPlayer().isInMultiplayer()) { - return this.getMpTeam(); - } - return this.getTeams().get(this.currentTeamIndex); - } - - public TeamInfo getCurrentSinglePlayerTeamInfo() { - return this.getTeams().get(this.currentTeamIndex); - } - - public List getActiveTeam() { - return avatars; - } - - public EntityAvatar getCurrentAvatarEntity() { - return this.getActiveTeam().get(currentCharacterIndex); - } - - public boolean isSpawned() { - return this.getPlayer().getScene() != null - && this.getPlayer() - .getScene() - .getEntities() - .containsKey(this.getCurrentAvatarEntity().getId()); - } - - public int getMaxTeamSize() { - if (this.getPlayer().isInMultiplayer()) { - int max = GAME_OPTIONS.avatarLimits.multiplayerTeam; - if (this.getPlayer().getWorld().getHost() == this.getPlayer()) { - return Math.max(1, (int) Math.ceil(max / (double) this.getWorld().getPlayerCount())); - } - return Math.max(1, (int) Math.floor(max / (double) this.getWorld().getPlayerCount())); - } - - return GAME_OPTIONS.avatarLimits.singlePlayerTeam; - } - - // Methods - - /** Returns true if there is space to add the number of avatars to the team. */ - public boolean canAddAvatarsToTeam(TeamInfo team, int avatars) { - return team.size() + avatars <= this.getMaxTeamSize(); - } - - /** Returns true if there is space to add to the team. */ - public boolean canAddAvatarToTeam(TeamInfo team) { - return this.canAddAvatarsToTeam(team, 1); - } - - /** - * Returns true if there is space to add the number of avatars to the current team. If the current - * team is temporary, returns false. - */ - public boolean canAddAvatarsToCurrentTeam(int avatars) { - if (this.useTemporarilyTeamIndex != -1) { - return false; - } - return this.canAddAvatarsToTeam(this.getCurrentTeamInfo(), avatars); - } - - /** - * Returns true if there is space to add to the current team. If the current team is temporary, - * returns false. - */ - public boolean canAddAvatarToCurrentTeam() { - return this.canAddAvatarsToCurrentTeam(1); - } - - /** - * Try to add the collection of avatars to the team. Returns true if all were successfully added. - * If some can not be added, returns false and does not add any. - */ - public boolean addAvatarsToTeam(TeamInfo team, Collection avatars) { - if (!this.canAddAvatarsToTeam(team, avatars.size())) { - return false; - } - - // Convert avatars into a collection of avatar IDs, then add - team.getAvatars().addAll(avatars.stream().map(a -> a.getAvatarId()).toList()); - - // Update team - if (this.getPlayer().isInMultiplayer()) { - if (team.equals(this.getMpTeam())) { - // MP team Packet - this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(this.getPlayer(), team)); - } - } else { - // SP team update packet - this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(this.getPlayer())); - - int teamId = this.getTeamId(team); - if (teamId != -1) { - // This is one of the player's teams - // Update entites - if (teamId == this.getCurrentTeamId()) { - this.updateTeamEntities(new PacketSetUpAvatarTeamRsp(this.getPlayer(), teamId, team)); - } else { - this.getPlayer().sendPacket(new PacketSetUpAvatarTeamRsp(this.getPlayer(), teamId, team)); - } - } - } - - return true; - } - - /** Try to add an avatar to a team. Returns true if successful. */ - public boolean addAvatarToTeam(TeamInfo team, Avatar avatar) { - return this.addAvatarsToTeam(team, Collections.singleton(avatar)); - } - - /** - * Try to add the collection of avatars to the current team. Will not modify a temporary team. - * Returns true if all were successfully added. If some can not be added, returns false and does - * not add any. - */ - public boolean addAvatarsToCurrentTeam(Collection avatars) { - if (this.useTemporarilyTeamIndex != -1) { - return false; - } - return this.addAvatarsToTeam(this.getCurrentTeamInfo(), avatars); - } - - /** - * Try to add an avatar to the current team. Will not modify a temporary team. Returns true if - * successful. - */ - public boolean addAvatarToCurrentTeam(Avatar avatar) { - return this.addAvatarsToCurrentTeam(Collections.singleton(avatar)); - } - - private void updateTeamResonances() { - this.getTeamResonances().clear(); - this.getTeamResonancesConfig().clear(); - // Official resonances require a full party - if (this.avatars.size() < 4) return; - - // TODO: make this actually read from TeamResonanceExcelConfigData.json for the real resonances - // and conditions - // Currently we just hardcode these conditions, but this won't work for modded resources or - // future changes - var elementCounts = new Object2IntOpenHashMap(); - this.getActiveTeam().stream() - .map(EntityAvatar::getAvatar) - .filter(Objects::nonNull) - .map(Avatar::getSkillDepot) - .filter(Objects::nonNull) - .map(AvatarSkillDepotData::getElementType) - .filter(Objects::nonNull) - .forEach(elementType -> elementCounts.addTo(elementType, 1)); - - // Dual element resonances - elementCounts.object2IntEntrySet().stream() - .filter(e -> e.getIntValue() >= 2) - .map(e -> e.getKey()) - .filter(elementType -> elementType.getTeamResonanceId() != 0) - .forEach( - elementType -> { - this.teamResonances.add(elementType.getTeamResonanceId()); - this.teamResonancesConfig.add(elementType.getConfigHash()); - }); - - // Four element resonance - if (elementCounts.size() >= 4) { - this.teamResonances.add(ElementType.Default.getTeamResonanceId()); - this.teamResonancesConfig.add(ElementType.Default.getConfigHash()); - } - } - - /** 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. - - // Skill charges packet - Yes, this is official server behavior as of 2.6.0 - this.getActiveTeam().stream() - .map(EntityAvatar::getAvatar) - .forEach(Avatar::sendSkillExtraChargeMap); - } - - public void updateTeamEntities(BasePacket responsePacket) { - // Sanity check - Should never happen - if (this.getCurrentTeamInfo().getAvatars().size() <= 0) { - return; - } - - // If current team has changed - var currentEntity = this.getCurrentAvatarEntity(); - var existingAvatars = new Int2ObjectOpenHashMap(); - var prevSelectedAvatarIndex = -1; - - for (EntityAvatar entity : this.getActiveTeam()) { - existingAvatars.put(entity.getAvatar().getAvatarId(), entity); - } - - // Clear active team entity list - this.getActiveTeam().clear(); - - // Add back entities into team - for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) { - var avatarId = (int) this.getCurrentTeamInfo().getAvatars().get(i); - EntityAvatar entity; - if (existingAvatars.containsKey(avatarId)) { - entity = existingAvatars.get(avatarId); - existingAvatars.remove(avatarId); - if (entity == currentEntity) { - prevSelectedAvatarIndex = i; - } - } else { - entity = - new EntityAvatar( - this.getPlayer().getScene(), this.getPlayer().getAvatars().getAvatarById(avatarId)); - } - - this.getActiveTeam().add(entity); - } - - // Unload removed entities - for (var entity : existingAvatars.values()) { - this.getPlayer().getScene().removeEntity(entity); - entity.getAvatar().save(); - } - - // Set new selected character index - if (prevSelectedAvatarIndex == -1) { - // Previous selected avatar is not in the same spot, we will select the current one in the - // prev slot - prevSelectedAvatarIndex = - Math.min(this.currentCharacterIndex, this.getActiveTeam().size() - 1); - } - this.currentCharacterIndex = prevSelectedAvatarIndex; - - // Update properties. - // Notify player. - this.updateTeamProperties(); - - // Send response packet. - if (responsePacket != null) { - this.getPlayer().sendPacket(responsePacket); - } - - // Check if character changed - if (currentEntity != this.getCurrentAvatarEntity()) { - // Remove and Add - this.getPlayer().getScene().replaceEntity(currentEntity, this.getCurrentAvatarEntity()); - } - } - - public synchronized void setupAvatarTeam(int teamId, List list) { - // Sanity checks - if (list.size() == 0 - || list.size() > this.getMaxTeamSize() - || this.getPlayer().isInMultiplayer()) { - return; - } - - // Get team - TeamInfo teamInfo = this.getTeams().get(teamId); - if (teamInfo == null) { - return; - } - - // Set team data - LinkedHashSet newTeam = new LinkedHashSet<>(); - for (Long aLong : list) { - Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); - if (avatar == null || newTeam.contains(avatar)) { - // Should never happen - return; - } - newTeam.add(avatar); - } - - // Clear current team info and add avatars from our new team - teamInfo.getAvatars().clear(); - this.addAvatarsToTeam(teamInfo, newTeam); - } - - public void setupMpTeam(List list) { - // Sanity checks - if (list.size() == 0 - || list.size() > this.getMaxTeamSize() - || !this.getPlayer().isInMultiplayer()) { - return; - } - - TeamInfo teamInfo = this.getMpTeam(); - - // Set team data - LinkedHashSet newTeam = new LinkedHashSet<>(); - for (Long aLong : list) { - Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); - if (avatar == null || newTeam.contains(avatar)) { - // Should never happen - return; - } - newTeam.add(avatar); - } - - // Clear current team info and add avatars from our new team - teamInfo.getAvatars().clear(); - this.addAvatarsToTeam(teamInfo, newTeam); - } - - /** - * Setup avatars for a trial avatar team. - * - * @param save Should the original team be saved? - */ - public void setupTrialAvatars(boolean save) { - this.setPreviousIndex(this.getCurrentCharacterIndex()); - - if (save) { - var originalTeam = getCurrentTeamInfo(); - this.getTrialAvatarTeam().copyFrom(originalTeam); - } else this.getActiveTeam().clear(); - - this.usingTrialTeam = true; - } - - /** Displays the trial avatars. Picks the last avatar in the team. */ - public void trialAvatarTeamPostUpdate() { - this.trialAvatarTeamPostUpdate(this.getActiveTeam().size() - 1); - } - - /** - * Displays the trial avatars. - * - * @param newCharacterIndex The avatar to equip. - */ - public void trialAvatarTeamPostUpdate(int newCharacterIndex) { - this.setCurrentCharacterIndex(Math.min(newCharacterIndex, this.getActiveTeam().size() - 1)); - - this.updateTeamProperties(); - this.getPlayer().getScene().addEntity(this.getCurrentAvatarEntity()); - } - - /** - * Adds an avatar to the trial team. - * - * @param trialAvatar The avatar to add. - */ - public void addAvatarToTrialTeam(Avatar trialAvatar) { - // Remove the existing team's avatars. - this.getActiveTeam() - .forEach( - x -> - this.getPlayer() - .getScene() - .removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE)); - // Remove the existing avatar from the teams if it exists. - this.getActiveTeam().removeIf(x -> x.getAvatar().getAvatarId() == trialAvatar.getAvatarId()); - this.getCurrentTeamInfo().getAvatars().removeIf(x -> x == trialAvatar.getAvatarId()); - // Add the avatar to the teams. - this.getActiveTeam().add(new EntityAvatar(this.getPlayer().getScene(), trialAvatar)); - this.getCurrentTeamInfo().addAvatar(trialAvatar); - this.getTrialAvatars().put(trialAvatar.getAvatarId(), trialAvatar); - } - - /** - * Get the GUID of a trial avatar. - * - * @param avatarId The avatar ID. - * @return The GUID of the avatar. - */ - public long getTrialAvatarGuid(int avatarId) { - return getTrialAvatars().values().stream() - .filter(avatar -> avatar.getTrialAvatarId() == avatarId) - .map(avatar -> avatar.getGuid()) - .findFirst() - .orElse(0L); - } - - /** Rollback changes from using a trial avatar team. */ - public void unsetTrialAvatarTeam() { - this.trialAvatarTeamPostUpdate(this.getPreviousIndex()); - this.setPreviousIndex(-1); - } - - /** Removes all avatars from the trial avatar team. */ - public void removeTrialAvatarTeam() { - this.removeTrialAvatarTeam( - this.getActiveTeam().stream().map(avatar -> avatar.getAvatar().getAvatarId()).toList()); - } - - /** - * Removes one avatar from the trial avatar team. - * - * @param avatarId The avatar ID to remove. - */ - public void removeTrialAvatarTeam(int avatarId) { - this.removeTrialAvatarTeam(List.of(avatarId)); - } - - /** - * Removes a collection of avatars from the trial avatar team. - * - * @param avatarIds The avatar IDs to remove. - */ - public void removeTrialAvatarTeam(List avatarIds) { - var player = this.getPlayer(); - - // Disable the trial team. - this.usingTrialTeam = false; - this.trialAvatarTeam = new TeamInfo(); - - // Remove the avatars from the team. - avatarIds.forEach( - avatarId -> { - this.getActiveTeam() - .forEach( - x -> - player - .getScene() - .removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE)); - this.getActiveTeam().removeIf(x -> x.getAvatar().getTrialAvatarId() == avatarId); - this.getTrialAvatars().values().removeIf(x -> x.getTrialAvatarId() == avatarId); - }); - - // Re-add the avatars to the team. - var index = 0; - for (var avatar : this.getCurrentTeamInfo().getAvatars()) { - if (this.getActiveTeam().stream() - .map(entity -> entity.getAvatar().getAvatarId()) - .toList() - .contains(avatar)) return; - - this.getActiveTeam() - .add( - index++, - new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatar))); - } - - this.unsetTrialAvatarTeam(); - } - - public void setupTemporaryTeam(List> guidList) { - this.temporaryTeam = - guidList.stream() - .map( - list -> { - // Sanity checks - if (list.size() == 0 || list.size() > this.getMaxTeamSize()) { - return null; - } - - // Set team data - LinkedHashSet newTeam = new LinkedHashSet<>(); - for (Long aLong : list) { - Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); - if (avatar == null || newTeam.contains(avatar)) { - // Should never happen - return null; - } - newTeam.add(avatar); - } - - // convert to avatar ids - return newTeam.stream().map(Avatar::getAvatarId).toList(); - }) - .filter(Objects::nonNull) - .map(TeamInfo::new) - .toList(); - } - - public void useTemporaryTeam(int index) { - this.useTemporarilyTeamIndex = index; - this.updateTeamEntities(null); - } - - public void cleanTemporaryTeam() { - // check if using temporary team - if (useTemporarilyTeamIndex < 0) { - return; - } - - this.useTemporarilyTeamIndex = -1; - this.temporaryTeam = null; - this.updateTeamEntities(null); - } - - public synchronized void setCurrentTeam(int teamId) { - // - if (this.getPlayer().isInMultiplayer()) { - return; - } - - // Get team - TeamInfo teamInfo = this.getTeams().get(teamId); - if (teamInfo == null || teamInfo.getAvatars().size() == 0) { - return; - } - - // Set - this.setCurrentTeamId(teamId); - this.updateTeamEntities(new PacketChooseCurAvatarTeamRsp(teamId)); - } - - public synchronized void setTeamName(int teamId, String teamName) { - // Get team - TeamInfo teamInfo = this.getTeams().get(teamId); - if (teamInfo == null) { - return; - } - - teamInfo.setName(teamName); - - // Packet - this.getPlayer().sendPacket(new PacketChangeTeamNameRsp(teamId, teamName)); - } - - public synchronized void changeAvatar(long guid) { - EntityAvatar oldEntity = this.getCurrentAvatarEntity(); - - if (guid == oldEntity.getAvatar().getGuid()) { - return; - } - - EntityAvatar newEntity = null; - int index = -1; - for (int i = 0; i < this.getActiveTeam().size(); i++) { - if (guid == this.getActiveTeam().get(i).getAvatar().getGuid()) { - index = i; - newEntity = this.getActiveTeam().get(i); - } - } - - if (index < 0 || newEntity == oldEntity) { - return; - } - - // Set index - this.setCurrentCharacterIndex(index); - - // Old entity motion state - oldEntity.setMotionState(MotionState.MOTION_STATE_STANDBY); - - // Remove and Add - this.getPlayer().getScene().replaceEntity(oldEntity, newEntity); - this.getPlayer().sendPacket(new PacketChangeAvatarRsp(guid)); - } - - /** - * Applies 10% of the avatar's max HP as damage. - * This occurs when the avatar is killed by the void. - */ - public void applyVoidDamage() { - this.getActiveTeam().forEach(entity -> { - entity.damage(entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f); - player.sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - }); - } - - public void onAvatarDie(long dieGuid) { - EntityAvatar deadAvatar = this.getCurrentAvatarEntity(); - - if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) { - return; - } - - PlayerDieType dieType = deadAvatar.getKilledType(); - int killedBy = deadAvatar.getKilledBy(); - - if (dieType == PlayerDieType.PLAYER_DIE_TYPE_DRAWN) { - // Died in water. Do not replace - // The official server has skipped this notify and will just respawn the team immediately - // after the animation. - // TODO: Perhaps find a way to get vanilla experience? - this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); - } else { - // Replacement avatar - EntityAvatar replacement = null; - int replaceIndex = -1; - - for (int i = 0; i < this.getActiveTeam().size(); i++) { - EntityAvatar entity = this.getActiveTeam().get(i); - if (entity.isAlive()) { - replaceIndex = i; - replacement = entity; - break; - } - } - - if (replacement == null) { - // No more living team members... - this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); - // Invoke player team death event. - PlayerTeamDeathEvent event = - new PlayerTeamDeathEvent( - this.getPlayer(), this.getActiveTeam().get(this.getCurrentCharacterIndex())); - event.call(); - } else { - // Set index and spawn replacement member - this.setCurrentCharacterIndex(replaceIndex); - this.getPlayer().getScene().addEntity(replacement); - } - } - - // Response packet - this.getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0)); - } - - public boolean reviveAvatar(Avatar avatar) { - for (EntityAvatar entity : this.getActiveTeam()) { - if (entity.getAvatar() == avatar) { - if (entity.isAlive()) { - return false; - } - - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f); - // Satiation is reset when reviving an avatar - player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000); - this.getPlayer() - .sendPacket( - new PacketAvatarFightPropUpdateNotify( - entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); - this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - return true; - } - } - - return false; - } - - public boolean healAvatar(Avatar avatar, int healRate, int healAmount) { - for (EntityAvatar entity : this.getActiveTeam()) { - if (entity.getAvatar() == avatar) { - if (!entity.isAlive()) { - return false; - } - - entity.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - (float) - Math.min( - (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) - + entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) - * (float) healRate - / 100.0 - + (float) healAmount / 100.0), - entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP))); - this.getPlayer() - .sendPacket( - new PacketAvatarFightPropUpdateNotify( - entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); - this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - return true; - } - } - return false; - } - - public void respawnTeam() { - // Make sure all team members are dead - // Drowning needs revive when there may be other team members still alive. - // for (EntityAvatar entity : getActiveTeam()) { - // if (entity.isAlive()) { - // return; - // } - // } - player - .getStaminaManager() - .stopSustainedStaminaHandler(); // prevent drowning immediately after respawn - - // Revive all team members - for (EntityAvatar entity : this.getActiveTeam()) { - entity.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .4f); - player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000); - this.getPlayer() - .sendPacket( - new PacketAvatarFightPropUpdateNotify( - entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); - this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - } - - // Teleport player and set player position - try { - this.getPlayer() - .sendPacket( - new PacketPlayerEnterSceneNotify( - this.getPlayer(), - EnterType.ENTER_TYPE_SELF, - EnterReason.Revival, - player.getSceneId(), - getRespawnPosition())); - player.getPosition().set(getRespawnPosition()); - } catch (Exception e) { - this.getPlayer() - .sendPacket( - new PacketPlayerEnterSceneNotify( - this.getPlayer(), - EnterType.ENTER_TYPE_SELF, - EnterReason.Revival, - 3, - GameConstants.START_POSITION)); - player - .getPosition() - .set(GameConstants.START_POSITION); // If something goes wrong, the resurrection is here - } - - // Packets - this.getPlayer().sendPacket(new BasePacket(PacketOpcodes.WorldPlayerReviveRsp)); - } - - public Position getRespawnPosition() { - var deathPos = this.getPlayer().getPosition(); - int sceneId = this.getPlayer().getSceneId(); - - // Get the closest trans point to where the player died. - var respawnPoint = - this.getPlayer().getUnlockedScenePoints(sceneId).stream() - .map(pointId -> GameData.getScenePointEntryById(sceneId, pointId)) - .filter(point -> point.getPointData().getType().equals("SceneTransPoint")) - .min( - (Comparator.comparingDouble( - pos -> Utils.getDist(pos.getPointData().getTranPos(), deathPos)))); - - return respawnPoint.get().getPointData().getTranPos(); - } - - public void saveAvatars() { - // Save all avatars from active team - for (EntityAvatar entity : this.getActiveTeam()) { - entity.getAvatar().save(); - } - } - - public void onPlayerLogin() { // Hack for now to fix resonances on login - this.updateTeamResonances(); - } - - public synchronized void addNewCustomTeam() { - // Sanity check - max number of teams. - if (this.teams.size() == GameConstants.MAX_TEAMS) { - player.sendPacket(new PacketAddBackupAvatarTeamRsp(Retcode.RET_FAIL)); - return; - } - - // The id of the new custom team is the lowest id in [5,MAX_TEAMS] that is not yet taken. - int id = -1; - for (int i = 5; i <= GameConstants.MAX_TEAMS; i++) { - if (!this.teams.containsKey(i)) { - id = i; - break; - } - } - - // Create the new team. - this.teams.put(id, new TeamInfo()); - - // Send packets. - player.sendPacket(new PacketAvatarTeamAllDataNotify(player)); - player.sendPacket(new PacketAddBackupAvatarTeamRsp()); - } - - public synchronized void removeCustomTeam(int id) { - // Check if the target id exists. - if (!this.teams.containsKey(id)) { - player.sendPacket(new PacketDelBackupAvatarTeamRsp(Retcode.RET_FAIL, id)); - } - - // Remove team. - this.teams.remove(id); - - // Send packets. - player.sendPacket(new PacketAvatarTeamAllDataNotify(player)); - player.sendPacket(new PacketDelBackupAvatarTeamRsp(id)); - } - - /** - * Applies abilities for the currently selected team. These abilities are sourced from the scene. - * - * @param scene The scene with the abilities to apply. - */ - public void applyAbilities(Scene scene) { - try { - var levelEntityConfig = scene.getSceneData().getLevelEntityConfig(); - var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig); - if (config == null) return; - - var avatars = this.getPlayer().getAvatars(); - var avatarIds = scene.getSceneData().getSpecifiedAvatarList(); - var specifiedAvatarList = this.getActiveTeam(); - - if (avatarIds != null && avatarIds.size() > 0) { - // certain scene could limit specific avatars' entry - specifiedAvatarList.clear(); - for (int id : avatarIds) { - var avatar = avatars.getAvatarById(id); - if (avatar == null) continue; - - specifiedAvatarList.add(new EntityAvatar(scene, avatar)); - } - } - - for (var entityAvatar : specifiedAvatarList) { - var avatarData = entityAvatar.getAvatar().getAvatarData(); - if (avatarData == null) { - continue; - } - - avatarData.buildEmbryo(); // Create avatar abilities. - if (config.getAvatarAbilities() == null) { - continue; // continue and not break because has to rebuild ability for the next avatar if - // any - } - - for (ConfigAbilityData abilities : config.getAvatarAbilities()) { - avatarData.getAbilities().add(Utils.abilityHash(abilities.getAbilityName())); - } - } - } catch (Exception e) { - Grasscutter.getLogger() - .error( - "Error applying level entity config for scene {}", scene.getSceneData().getId(), e); - } - } - - public List getTrialAvatarParam(int trialAvatarId) { - if (GameData.getTrialAvatarCustomData() - .isEmpty()) { // use default data if custom data not available - if (GameData.getTrialAvatarDataMap().get(trialAvatarId) == null) return List.of(); - - return GameData.getTrialAvatarDataMap().get(trialAvatarId).getTrialAvatarParamList(); - } - // use custom data - if (GameData.getTrialAvatarCustomData().get(trialAvatarId) == null) return List.of(); - - val trialCustomParams = - GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList(); - return trialCustomParams.isEmpty() - ? List.of() - : Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList(); - } - - /** - * Adds a trial avatar to the player's team. - * - * @param avatarId The ID of the avatar. - * @param questMainId The quest ID associated with the quest. - * @param reason The reason for granting the avatar. - * @return True if the avatar was added, false otherwise. - */ - public boolean addTrialAvatar(int avatarId, int questMainId, GrantReason reason) { - List trialAvatarBasicParam = getTrialAvatarParam(avatarId); - if (trialAvatarBasicParam.isEmpty()) return false; - - var avatar = new Avatar(trialAvatarBasicParam.get(0)); - if (avatar.getAvatarData() == null || !this.getPlayer().hasSentLoginPackets()) return false; - - avatar.setOwner(this.getPlayer()); - // Add trial weapons and relics. - avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), avatarId, reason, questMainId); - avatar.equipTrialItems(); - // Re-calculate stats - avatar.recalcStats(); - - // Packet, mimic official server behaviour, add to player's bag but not saving to database. - this.getPlayer().sendPacket(new PacketAvatarAddNotify(avatar, false)); - // Add to avatar to the temporary trial team. - this.addAvatarToTrialTeam(avatar); - return true; - } - - /** - * Adds a trial avatar to the player's team. - * - * @param avatarId The ID of the avatar. - * @param questMainId The quest ID associated with the quest. - */ - public void addTrialAvatar(int avatarId, int questMainId) { - this.addTrialAvatars(List.of(avatarId), questMainId, true); - - // Packet, mimic official server behaviour, necessary to stop player from modifying team. - this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(this.getPlayer())); - } - - /** - * Adds a collection of trial avatars to the player's team. - * - * @param avatarIds List of trial avatar IDs. - */ - public void addTrialAvatars(List avatarIds) { - this.addTrialAvatars(avatarIds, 0, false); - } - - /** - * Adds a collection of trial avatars to the player's team. - * - * @param avatarIds List of trial avatar IDs. - * @param save Whether to retain the currently equipped avatars. - */ - public void addTrialAvatars(List avatarIds, boolean save) { - this.addTrialAvatars(avatarIds, 0, save); - } - - /** - * Adds a list of trial avatars to the player's team. - * - * @param avatarIds List of trial avatar IDs. - * @param questId The ID of the quest this trial team is associated with. - * @param save Whether to retain the currently equipped avatars. - */ - public void addTrialAvatars(List avatarIds, int questId, boolean save) { - this.setupTrialAvatars(save); // Perform initial setup. - - // Add the avatars to the team. - avatarIds.forEach( - avatarId -> { - var result = - this.addTrialAvatar( - avatarId, - questId, - questId == 0 - ? GrantReason.GRANT_REASON_BY_QUEST - : GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY); - - if (!result) throw new RuntimeException("Unable to add trial avatar to team."); - }); - - // Update the team. - this.trialAvatarTeamPostUpdate(questId == 0 ? getActiveTeam().size() - 1 : 0); - } - - /** Removes all trial avatars from the player's team. */ - public void removeTrialAvatar() { - this.removeTrialAvatar( - this.getActiveTeam().stream() - .map(EntityAvatar::getAvatar) - .map(Avatar::getAvatarId) - .toList()); - } - - /** - * Removes a trial avatar from the player's team. Additionally, unlocks the ability to change the - * team configuration. - * - * @param avatarId The ID of the avatar. - */ - public void removeTrialAvatar(int avatarId) { - this.removeTrialAvatar(List.of(avatarId)); - } - - /** - * Removes a collection of trial avatars from the player's team. - * - * @param avatarIds List of trial avatar IDs. - */ - public void removeTrialAvatar(List avatarIds) { - if (!this.isUsingTrialTeam()) throw new RuntimeException("Player is not using a trial team."); - - this.getPlayer() - .sendPacket( - new PacketAvatarDelNotify(avatarIds.stream().map(this::getTrialAvatarGuid).toList())); - this.removeTrialAvatarTeam(avatarIds); - - // Update the team. - if (avatarIds.size() == 1) this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify()); - } -} +package emu.grasscutter.game.player; + +import static emu.grasscutter.config.Configuration.GAME_OPTIONS; + +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.binout.config.fields.ConfigAbilityData; +import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.entity.EntityBaseGadget; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.EnterReason; +import emu.grasscutter.game.props.FightProperty; +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.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.player.PlayerTeamDeathEvent; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; +import emu.grasscutter.utils.Utils; +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 java.util.*; +import java.util.stream.Stream; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +@Entity +public final class TeamManager extends BasePlayerDataManager { + @Transient private final List avatars; + @Transient @Getter private final Set gadgets; + @Transient @Getter private final IntSet teamResonances; + @Transient @Getter private final IntSet teamResonancesConfig; + // This needs to be a LinkedHashMap to guarantee insertion order. + @Getter private LinkedHashMap teams; + private int currentTeamIndex; + @Getter @Setter private int currentCharacterIndex; + @Transient @Getter @Setter private TeamInfo mpTeam; + @Transient @Getter @Setter private int entityId; + + @Transient private int useTemporarilyTeamIndex = -1; + @Transient private List temporaryTeam; // Temporary Team for tower + @Transient @Getter @Setter private boolean usingTrialTeam; + @Transient @Getter @Setter private TeamInfo trialAvatarTeam; + // hold trial avatars for later use in rebuilding active team + @Transient @Getter @Setter private Map trialAvatars; + + @Transient @Getter @Setter + private int previousIndex = -1; // index of character selection in team before adding trial avatar + + public TeamManager() { + this.mpTeam = new TeamInfo(); + this.avatars = new ArrayList<>(); + this.gadgets = new HashSet<>(); + this.teamResonances = new IntOpenHashSet(); + this.teamResonancesConfig = new IntOpenHashSet(); + } + + public TeamManager(Player player) { + this(); + this.setPlayer(player); + + this.teams = new LinkedHashMap<>(); + this.currentTeamIndex = 1; + for (int i = 1; i <= GameConstants.DEFAULT_TEAMS; i++) { + this.teams.put(i, new TeamInfo()); + } + } + + public World getWorld() { + return this.getPlayer().getWorld(); + } + + /** + * Search through all teams and if the team matches, return that index. Otherwise, return -1. No + * match could mean that the team does not currently belong to the player. + */ + public int getTeamId(TeamInfo team) { + for (int i = 1; i <= this.teams.size(); i++) { + if (this.teams.get(i).equals(team)) { + return i; + } + } + return -1; + } + + public int getCurrentTeamId() { + // Starts from 1 + return currentTeamIndex; + } + + private void setCurrentTeamId(int currentTeamIndex) { + this.currentTeamIndex = currentTeamIndex; + } + + public long getCurrentCharacterGuid() { + return this.getCurrentAvatarEntity().getAvatar().getGuid(); + } + + public TeamInfo getCurrentTeamInfo() { + if (useTemporarilyTeamIndex >= 0 && useTemporarilyTeamIndex < temporaryTeam.size()) { + return temporaryTeam.get(useTemporarilyTeamIndex); + } + if (this.getPlayer().isInMultiplayer()) { + return this.getMpTeam(); + } + return this.getTeams().get(this.currentTeamIndex); + } + + public TeamInfo getCurrentSinglePlayerTeamInfo() { + return this.getTeams().get(this.currentTeamIndex); + } + + public List getActiveTeam() { + return avatars; + } + + public EntityAvatar getCurrentAvatarEntity() { + return this.getActiveTeam().get(currentCharacterIndex); + } + + public boolean isSpawned() { + return this.getPlayer().getScene() != null + && this.getPlayer() + .getScene() + .getEntities() + .containsKey(this.getCurrentAvatarEntity().getId()); + } + + public int getMaxTeamSize() { + if (this.getPlayer().isInMultiplayer()) { + int max = GAME_OPTIONS.avatarLimits.multiplayerTeam; + if (this.getPlayer().getWorld().getHost() == this.getPlayer()) { + return Math.max(1, (int) Math.ceil(max / (double) this.getWorld().getPlayerCount())); + } + return Math.max(1, (int) Math.floor(max / (double) this.getWorld().getPlayerCount())); + } + + return GAME_OPTIONS.avatarLimits.singlePlayerTeam; + } + + // Methods + + /** Returns true if there is space to add the number of avatars to the team. */ + public boolean canAddAvatarsToTeam(TeamInfo team, int avatars) { + return team.size() + avatars <= this.getMaxTeamSize(); + } + + /** Returns true if there is space to add to the team. */ + public boolean canAddAvatarToTeam(TeamInfo team) { + return this.canAddAvatarsToTeam(team, 1); + } + + /** + * Returns true if there is space to add the number of avatars to the current team. If the current + * team is temporary, returns false. + */ + public boolean canAddAvatarsToCurrentTeam(int avatars) { + if (this.useTemporarilyTeamIndex != -1) { + return false; + } + return this.canAddAvatarsToTeam(this.getCurrentTeamInfo(), avatars); + } + + /** + * Returns true if there is space to add to the current team. If the current team is temporary, + * returns false. + */ + public boolean canAddAvatarToCurrentTeam() { + return this.canAddAvatarsToCurrentTeam(1); + } + + /** + * Try to add the collection of avatars to the team. Returns true if all were successfully added. + * If some can not be added, returns false and does not add any. + */ + public boolean addAvatarsToTeam(TeamInfo team, Collection avatars) { + if (!this.canAddAvatarsToTeam(team, avatars.size())) { + return false; + } + + // Convert avatars into a collection of avatar IDs, then add + team.getAvatars().addAll(avatars.stream().map(a -> a.getAvatarId()).toList()); + + // Update team + if (this.getPlayer().isInMultiplayer()) { + if (team.equals(this.getMpTeam())) { + // MP team Packet + this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(this.getPlayer(), team)); + } + } else { + // SP team update packet + this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(this.getPlayer())); + + int teamId = this.getTeamId(team); + if (teamId != -1) { + // This is one of the player's teams + // Update entites + if (teamId == this.getCurrentTeamId()) { + this.updateTeamEntities(new PacketSetUpAvatarTeamRsp(this.getPlayer(), teamId, team)); + } else { + this.getPlayer().sendPacket(new PacketSetUpAvatarTeamRsp(this.getPlayer(), teamId, team)); + } + } + } + + return true; + } + + /** Try to add an avatar to a team. Returns true if successful. */ + public boolean addAvatarToTeam(TeamInfo team, Avatar avatar) { + return this.addAvatarsToTeam(team, Collections.singleton(avatar)); + } + + /** + * Try to add the collection of avatars to the current team. Will not modify a temporary team. + * Returns true if all were successfully added. If some can not be added, returns false and does + * not add any. + */ + public boolean addAvatarsToCurrentTeam(Collection avatars) { + if (this.useTemporarilyTeamIndex != -1) { + return false; + } + return this.addAvatarsToTeam(this.getCurrentTeamInfo(), avatars); + } + + /** + * Try to add an avatar to the current team. Will not modify a temporary team. Returns true if + * successful. + */ + public boolean addAvatarToCurrentTeam(Avatar avatar) { + return this.addAvatarsToCurrentTeam(Collections.singleton(avatar)); + } + + private void updateTeamResonances() { + this.getTeamResonances().clear(); + this.getTeamResonancesConfig().clear(); + // Official resonances require a full party + if (this.avatars.size() < 4) return; + + // TODO: make this actually read from TeamResonanceExcelConfigData.json for the real resonances + // and conditions + // Currently we just hardcode these conditions, but this won't work for modded resources or + // future changes + var elementCounts = new Object2IntOpenHashMap(); + this.getActiveTeam().stream() + .map(EntityAvatar::getAvatar) + .filter(Objects::nonNull) + .map(Avatar::getSkillDepot) + .filter(Objects::nonNull) + .map(AvatarSkillDepotData::getElementType) + .filter(Objects::nonNull) + .forEach(elementType -> elementCounts.addTo(elementType, 1)); + + // Dual element resonances + elementCounts.object2IntEntrySet().stream() + .filter(e -> e.getIntValue() >= 2) + .map(e -> e.getKey()) + .filter(elementType -> elementType.getTeamResonanceId() != 0) + .forEach( + elementType -> { + this.teamResonances.add(elementType.getTeamResonanceId()); + this.teamResonancesConfig.add(elementType.getConfigHash()); + }); + + // Four element resonance + if (elementCounts.size() >= 4) { + this.teamResonances.add(ElementType.Default.getTeamResonanceId()); + this.teamResonancesConfig.add(ElementType.Default.getConfigHash()); + } + } + + /** 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. + + // Skill charges packet - Yes, this is official server behavior as of 2.6.0 + this.getActiveTeam().stream() + .map(EntityAvatar::getAvatar) + .forEach(Avatar::sendSkillExtraChargeMap); + } + + public void updateTeamEntities(BasePacket responsePacket) { + // Sanity check - Should never happen + if (this.getCurrentTeamInfo().getAvatars().size() <= 0) { + return; + } + + // If current team has changed + var currentEntity = this.getCurrentAvatarEntity(); + var existingAvatars = new Int2ObjectOpenHashMap(); + var prevSelectedAvatarIndex = -1; + + for (EntityAvatar entity : this.getActiveTeam()) { + existingAvatars.put(entity.getAvatar().getAvatarId(), entity); + } + + // Clear active team entity list + this.getActiveTeam().clear(); + + // Add back entities into team + for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) { + var avatarId = (int) this.getCurrentTeamInfo().getAvatars().get(i); + EntityAvatar entity; + if (existingAvatars.containsKey(avatarId)) { + entity = existingAvatars.get(avatarId); + existingAvatars.remove(avatarId); + if (entity == currentEntity) { + prevSelectedAvatarIndex = i; + } + } else { + entity = + new EntityAvatar( + this.getPlayer().getScene(), this.getPlayer().getAvatars().getAvatarById(avatarId)); + } + + this.getActiveTeam().add(entity); + } + + // Unload removed entities + for (var entity : existingAvatars.values()) { + this.getPlayer().getScene().removeEntity(entity); + entity.getAvatar().save(); + } + + // Set new selected character index + if (prevSelectedAvatarIndex == -1) { + // Previous selected avatar is not in the same spot, we will select the current one in the + // prev slot + prevSelectedAvatarIndex = + Math.min(this.currentCharacterIndex, this.getActiveTeam().size() - 1); + } + this.currentCharacterIndex = prevSelectedAvatarIndex; + + // Update properties. + // Notify player. + this.updateTeamProperties(); + + // Send response packet. + if (responsePacket != null) { + this.getPlayer().sendPacket(responsePacket); + } + + // Check if character changed + if (currentEntity != this.getCurrentAvatarEntity()) { + // Remove and Add + this.getPlayer().getScene().replaceEntity(currentEntity, this.getCurrentAvatarEntity()); + } + } + + public synchronized void setupAvatarTeam(int teamId, List list) { + // Sanity checks + if (list.size() == 0 + || list.size() > this.getMaxTeamSize() + || this.getPlayer().isInMultiplayer()) { + return; + } + + // Get team + TeamInfo teamInfo = this.getTeams().get(teamId); + if (teamInfo == null) { + return; + } + + // Set team data + LinkedHashSet newTeam = new LinkedHashSet<>(); + for (Long aLong : list) { + Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); + if (avatar == null || newTeam.contains(avatar)) { + // Should never happen + return; + } + newTeam.add(avatar); + } + + // Clear current team info and add avatars from our new team + teamInfo.getAvatars().clear(); + this.addAvatarsToTeam(teamInfo, newTeam); + } + + public void setupMpTeam(List list) { + // Sanity checks + if (list.size() == 0 + || list.size() > this.getMaxTeamSize() + || !this.getPlayer().isInMultiplayer()) { + return; + } + + TeamInfo teamInfo = this.getMpTeam(); + + // Set team data + LinkedHashSet newTeam = new LinkedHashSet<>(); + for (Long aLong : list) { + Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); + if (avatar == null || newTeam.contains(avatar)) { + // Should never happen + return; + } + newTeam.add(avatar); + } + + // Clear current team info and add avatars from our new team + teamInfo.getAvatars().clear(); + this.addAvatarsToTeam(teamInfo, newTeam); + } + + /** + * Setup avatars for a trial avatar team. + * + * @param save Should the original team be saved? + */ + public void setupTrialAvatars(boolean save) { + this.setPreviousIndex(this.getCurrentCharacterIndex()); + + if (save) { + var originalTeam = getCurrentTeamInfo(); + this.getTrialAvatarTeam().copyFrom(originalTeam); + } else this.getActiveTeam().clear(); + + this.usingTrialTeam = true; + } + + /** Displays the trial avatars. Picks the last avatar in the team. */ + public void trialAvatarTeamPostUpdate() { + this.trialAvatarTeamPostUpdate(this.getActiveTeam().size() - 1); + } + + /** + * Displays the trial avatars. + * + * @param newCharacterIndex The avatar to equip. + */ + public void trialAvatarTeamPostUpdate(int newCharacterIndex) { + this.setCurrentCharacterIndex(Math.min(newCharacterIndex, this.getActiveTeam().size() - 1)); + + this.updateTeamProperties(); + this.getPlayer().getScene().addEntity(this.getCurrentAvatarEntity()); + } + + /** + * Adds an avatar to the trial team. + * + * @param trialAvatar The avatar to add. + */ + public void addAvatarToTrialTeam(Avatar trialAvatar) { + // Remove the existing team's avatars. + this.getActiveTeam() + .forEach( + x -> + this.getPlayer() + .getScene() + .removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE)); + // Remove the existing avatar from the teams if it exists. + this.getActiveTeam().removeIf(x -> x.getAvatar().getAvatarId() == trialAvatar.getAvatarId()); + this.getCurrentTeamInfo().getAvatars().removeIf(x -> x == trialAvatar.getAvatarId()); + // Add the avatar to the teams. + this.getActiveTeam().add(new EntityAvatar(this.getPlayer().getScene(), trialAvatar)); + this.getCurrentTeamInfo().addAvatar(trialAvatar); + this.getTrialAvatars().put(trialAvatar.getAvatarId(), trialAvatar); + } + + /** + * Get the GUID of a trial avatar. + * + * @param avatarId The avatar ID. + * @return The GUID of the avatar. + */ + public long getTrialAvatarGuid(int avatarId) { + return getTrialAvatars().values().stream() + .filter(avatar -> avatar.getTrialAvatarId() == avatarId) + .map(avatar -> avatar.getGuid()) + .findFirst() + .orElse(0L); + } + + /** Rollback changes from using a trial avatar team. */ + public void unsetTrialAvatarTeam() { + this.trialAvatarTeamPostUpdate(this.getPreviousIndex()); + this.setPreviousIndex(-1); + } + + /** Removes all avatars from the trial avatar team. */ + public void removeTrialAvatarTeam() { + this.removeTrialAvatarTeam( + this.getActiveTeam().stream().map(avatar -> avatar.getAvatar().getAvatarId()).toList()); + } + + /** + * Removes one avatar from the trial avatar team. + * + * @param avatarId The avatar ID to remove. + */ + public void removeTrialAvatarTeam(int avatarId) { + this.removeTrialAvatarTeam(List.of(avatarId)); + } + + /** + * Removes a collection of avatars from the trial avatar team. + * + * @param avatarIds The avatar IDs to remove. + */ + public void removeTrialAvatarTeam(List avatarIds) { + var player = this.getPlayer(); + + // Disable the trial team. + this.usingTrialTeam = false; + this.trialAvatarTeam = new TeamInfo(); + + // Remove the avatars from the team. + avatarIds.forEach( + avatarId -> { + this.getActiveTeam() + .forEach( + x -> + player + .getScene() + .removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE)); + this.getActiveTeam().removeIf(x -> x.getAvatar().getTrialAvatarId() == avatarId); + this.getTrialAvatars().values().removeIf(x -> x.getTrialAvatarId() == avatarId); + }); + + // Re-add the avatars to the team. + var index = 0; + for (var avatar : this.getCurrentTeamInfo().getAvatars()) { + if (this.getActiveTeam().stream() + .map(entity -> entity.getAvatar().getAvatarId()) + .toList() + .contains(avatar)) return; + + this.getActiveTeam() + .add( + index++, + new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatar))); + } + + this.unsetTrialAvatarTeam(); + } + + public void setupTemporaryTeam(List> guidList) { + this.temporaryTeam = + guidList.stream() + .map( + list -> { + // Sanity checks + if (list.size() == 0 || list.size() > this.getMaxTeamSize()) { + return null; + } + + // Set team data + LinkedHashSet newTeam = new LinkedHashSet<>(); + for (Long aLong : list) { + Avatar avatar = this.getPlayer().getAvatars().getAvatarByGuid(aLong); + if (avatar == null || newTeam.contains(avatar)) { + // Should never happen + return null; + } + newTeam.add(avatar); + } + + // convert to avatar ids + return newTeam.stream().map(Avatar::getAvatarId).toList(); + }) + .filter(Objects::nonNull) + .map(TeamInfo::new) + .toList(); + } + + public void useTemporaryTeam(int index) { + this.useTemporarilyTeamIndex = index; + this.updateTeamEntities(null); + } + + public void cleanTemporaryTeam() { + // check if using temporary team + if (useTemporarilyTeamIndex < 0) { + return; + } + + this.useTemporarilyTeamIndex = -1; + this.temporaryTeam = null; + this.updateTeamEntities(null); + } + + public synchronized void setCurrentTeam(int teamId) { + // + if (this.getPlayer().isInMultiplayer()) { + return; + } + + // Get team + TeamInfo teamInfo = this.getTeams().get(teamId); + if (teamInfo == null || teamInfo.getAvatars().size() == 0) { + return; + } + + // Set + this.setCurrentTeamId(teamId); + this.updateTeamEntities(new PacketChooseCurAvatarTeamRsp(teamId)); + } + + public synchronized void setTeamName(int teamId, String teamName) { + // Get team + TeamInfo teamInfo = this.getTeams().get(teamId); + if (teamInfo == null) { + return; + } + + teamInfo.setName(teamName); + + // Packet + this.getPlayer().sendPacket(new PacketChangeTeamNameRsp(teamId, teamName)); + } + + public synchronized void changeAvatar(long guid) { + EntityAvatar oldEntity = this.getCurrentAvatarEntity(); + + if (guid == oldEntity.getAvatar().getGuid()) { + return; + } + + EntityAvatar newEntity = null; + int index = -1; + for (int i = 0; i < this.getActiveTeam().size(); i++) { + if (guid == this.getActiveTeam().get(i).getAvatar().getGuid()) { + index = i; + newEntity = this.getActiveTeam().get(i); + } + } + + if (index < 0 || newEntity == oldEntity) { + return; + } + + // Set index + this.setCurrentCharacterIndex(index); + + // Old entity motion state + oldEntity.setMotionState(MotionState.MOTION_STATE_STANDBY); + + // Remove and Add + this.getPlayer().getScene().replaceEntity(oldEntity, newEntity); + this.getPlayer().sendPacket(new PacketChangeAvatarRsp(guid)); + } + + /** + * Applies 10% of the avatar's max HP as damage. This occurs when the avatar is killed by the + * void. + */ + public void applyVoidDamage() { + this.getActiveTeam() + .forEach( + entity -> { + entity.damage(entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f); + player.sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + }); + } + + public void onAvatarDie(long dieGuid) { + EntityAvatar deadAvatar = this.getCurrentAvatarEntity(); + + if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) { + return; + } + + PlayerDieType dieType = deadAvatar.getKilledType(); + int killedBy = deadAvatar.getKilledBy(); + + if (dieType == PlayerDieType.PLAYER_DIE_TYPE_DRAWN) { + // Died in water. Do not replace + // The official server has skipped this notify and will just respawn the team immediately + // after the animation. + // TODO: Perhaps find a way to get vanilla experience? + this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); + } else { + // Replacement avatar + EntityAvatar replacement = null; + int replaceIndex = -1; + + for (int i = 0; i < this.getActiveTeam().size(); i++) { + EntityAvatar entity = this.getActiveTeam().get(i); + if (entity.isAlive()) { + replaceIndex = i; + replacement = entity; + break; + } + } + + if (replacement == null) { + // No more living team members... + this.getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); + // Invoke player team death event. + PlayerTeamDeathEvent event = + new PlayerTeamDeathEvent( + this.getPlayer(), this.getActiveTeam().get(this.getCurrentCharacterIndex())); + event.call(); + } else { + // Set index and spawn replacement member + this.setCurrentCharacterIndex(replaceIndex); + this.getPlayer().getScene().addEntity(replacement); + } + } + + // Response packet + this.getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0)); + } + + public boolean reviveAvatar(Avatar avatar) { + for (EntityAvatar entity : this.getActiveTeam()) { + if (entity.getAvatar() == avatar) { + if (entity.isAlive()) { + return false; + } + + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f); + // Satiation is reset when reviving an avatar + player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000); + this.getPlayer() + .sendPacket( + new PacketAvatarFightPropUpdateNotify( + entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); + this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + return true; + } + } + + return false; + } + + public boolean healAvatar(Avatar avatar, int healRate, int healAmount) { + for (EntityAvatar entity : this.getActiveTeam()) { + if (entity.getAvatar() == avatar) { + if (!entity.isAlive()) { + return false; + } + + entity.setFightProperty( + FightProperty.FIGHT_PROP_CUR_HP, + (float) + Math.min( + (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) + + entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) + * (float) healRate + / 100.0 + + (float) healAmount / 100.0), + entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP))); + this.getPlayer() + .sendPacket( + new PacketAvatarFightPropUpdateNotify( + entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); + this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + return true; + } + } + return false; + } + + public void respawnTeam() { + // Make sure all team members are dead + // Drowning needs revive when there may be other team members still alive. + // for (EntityAvatar entity : getActiveTeam()) { + // if (entity.isAlive()) { + // return; + // } + // } + player + .getStaminaManager() + .stopSustainedStaminaHandler(); // prevent drowning immediately after respawn + + // Revive all team members + for (EntityAvatar entity : this.getActiveTeam()) { + entity.setFightProperty( + FightProperty.FIGHT_PROP_CUR_HP, + entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .4f); + player.getSatiationManager().removeSatiationDirectly(entity.getAvatar(), 15000); + this.getPlayer() + .sendPacket( + new PacketAvatarFightPropUpdateNotify( + entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); + this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + } + + // Teleport player and set player position + try { + this.getPlayer() + .sendPacket( + new PacketPlayerEnterSceneNotify( + this.getPlayer(), + EnterType.ENTER_TYPE_SELF, + EnterReason.Revival, + player.getSceneId(), + getRespawnPosition())); + player.getPosition().set(getRespawnPosition()); + } catch (Exception e) { + this.getPlayer() + .sendPacket( + new PacketPlayerEnterSceneNotify( + this.getPlayer(), + EnterType.ENTER_TYPE_SELF, + EnterReason.Revival, + 3, + GameConstants.START_POSITION)); + player + .getPosition() + .set(GameConstants.START_POSITION); // If something goes wrong, the resurrection is here + } + + // Packets + this.getPlayer().sendPacket(new BasePacket(PacketOpcodes.WorldPlayerReviveRsp)); + } + + public Position getRespawnPosition() { + var deathPos = this.getPlayer().getPosition(); + int sceneId = this.getPlayer().getSceneId(); + + // Get the closest trans point to where the player died. + var respawnPoint = + this.getPlayer().getUnlockedScenePoints(sceneId).stream() + .map(pointId -> GameData.getScenePointEntryById(sceneId, pointId)) + .filter(point -> point.getPointData().getType().equals("SceneTransPoint")) + .min( + (Comparator.comparingDouble( + pos -> Utils.getDist(pos.getPointData().getTranPos(), deathPos)))); + + return respawnPoint.get().getPointData().getTranPos(); + } + + public void saveAvatars() { + // Save all avatars from active team + for (EntityAvatar entity : this.getActiveTeam()) { + entity.getAvatar().save(); + } + } + + public void onPlayerLogin() { // Hack for now to fix resonances on login + this.updateTeamResonances(); + } + + public synchronized void addNewCustomTeam() { + // Sanity check - max number of teams. + if (this.teams.size() == GameConstants.MAX_TEAMS) { + player.sendPacket(new PacketAddBackupAvatarTeamRsp(Retcode.RET_FAIL)); + return; + } + + // The id of the new custom team is the lowest id in [5,MAX_TEAMS] that is not yet taken. + int id = -1; + for (int i = 5; i <= GameConstants.MAX_TEAMS; i++) { + if (!this.teams.containsKey(i)) { + id = i; + break; + } + } + + // Create the new team. + this.teams.put(id, new TeamInfo()); + + // Send packets. + player.sendPacket(new PacketAvatarTeamAllDataNotify(player)); + player.sendPacket(new PacketAddBackupAvatarTeamRsp()); + } + + public synchronized void removeCustomTeam(int id) { + // Check if the target id exists. + if (!this.teams.containsKey(id)) { + player.sendPacket(new PacketDelBackupAvatarTeamRsp(Retcode.RET_FAIL, id)); + } + + // Remove team. + this.teams.remove(id); + + // Send packets. + player.sendPacket(new PacketAvatarTeamAllDataNotify(player)); + player.sendPacket(new PacketDelBackupAvatarTeamRsp(id)); + } + + /** + * Applies abilities for the currently selected team. These abilities are sourced from the scene. + * + * @param scene The scene with the abilities to apply. + */ + public void applyAbilities(Scene scene) { + try { + var levelEntityConfig = scene.getSceneData().getLevelEntityConfig(); + var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig); + if (config == null) return; + + var avatars = this.getPlayer().getAvatars(); + var avatarIds = scene.getSceneData().getSpecifiedAvatarList(); + var specifiedAvatarList = this.getActiveTeam(); + + if (avatarIds != null && avatarIds.size() > 0) { + // certain scene could limit specific avatars' entry + specifiedAvatarList.clear(); + for (int id : avatarIds) { + var avatar = avatars.getAvatarById(id); + if (avatar == null) continue; + + specifiedAvatarList.add(new EntityAvatar(scene, avatar)); + } + } + + for (var entityAvatar : specifiedAvatarList) { + var avatarData = entityAvatar.getAvatar().getAvatarData(); + if (avatarData == null) { + continue; + } + + avatarData.buildEmbryo(); // Create avatar abilities. + if (config.getAvatarAbilities() == null) { + continue; // continue and not break because has to rebuild ability for the next avatar if + // any + } + + for (ConfigAbilityData abilities : config.getAvatarAbilities()) { + avatarData.getAbilities().add(Utils.abilityHash(abilities.getAbilityName())); + } + } + } catch (Exception e) { + Grasscutter.getLogger() + .error( + "Error applying level entity config for scene {}", scene.getSceneData().getId(), e); + } + } + + public List getTrialAvatarParam(int trialAvatarId) { + if (GameData.getTrialAvatarCustomData() + .isEmpty()) { // use default data if custom data not available + if (GameData.getTrialAvatarDataMap().get(trialAvatarId) == null) return List.of(); + + return GameData.getTrialAvatarDataMap().get(trialAvatarId).getTrialAvatarParamList(); + } + // use custom data + if (GameData.getTrialAvatarCustomData().get(trialAvatarId) == null) return List.of(); + + val trialCustomParams = + GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList(); + return trialCustomParams.isEmpty() + ? List.of() + : Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList(); + } + + /** + * Adds a trial avatar to the player's team. + * + * @param avatarId The ID of the avatar. + * @param questMainId The quest ID associated with the quest. + * @param reason The reason for granting the avatar. + * @return True if the avatar was added, false otherwise. + */ + public boolean addTrialAvatar(int avatarId, int questMainId, GrantReason reason) { + List trialAvatarBasicParam = getTrialAvatarParam(avatarId); + if (trialAvatarBasicParam.isEmpty()) return false; + + var avatar = new Avatar(trialAvatarBasicParam.get(0)); + if (avatar.getAvatarData() == null || !this.getPlayer().hasSentLoginPackets()) return false; + + avatar.setOwner(this.getPlayer()); + // Add trial weapons and relics. + avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), avatarId, reason, questMainId); + avatar.equipTrialItems(); + // Re-calculate stats + avatar.recalcStats(); + + // Packet, mimic official server behaviour, add to player's bag but not saving to database. + this.getPlayer().sendPacket(new PacketAvatarAddNotify(avatar, false)); + // Add to avatar to the temporary trial team. + this.addAvatarToTrialTeam(avatar); + return true; + } + + /** + * Adds a trial avatar to the player's team. + * + * @param avatarId The ID of the avatar. + * @param questMainId The quest ID associated with the quest. + */ + public void addTrialAvatar(int avatarId, int questMainId) { + this.addTrialAvatars(List.of(avatarId), questMainId, true); + + // Packet, mimic official server behaviour, necessary to stop player from modifying team. + this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(this.getPlayer())); + } + + /** + * Adds a collection of trial avatars to the player's team. + * + * @param avatarIds List of trial avatar IDs. + */ + public void addTrialAvatars(List avatarIds) { + this.addTrialAvatars(avatarIds, 0, false); + } + + /** + * Adds a collection of trial avatars to the player's team. + * + * @param avatarIds List of trial avatar IDs. + * @param save Whether to retain the currently equipped avatars. + */ + public void addTrialAvatars(List avatarIds, boolean save) { + this.addTrialAvatars(avatarIds, 0, save); + } + + /** + * Adds a list of trial avatars to the player's team. + * + * @param avatarIds List of trial avatar IDs. + * @param questId The ID of the quest this trial team is associated with. + * @param save Whether to retain the currently equipped avatars. + */ + public void addTrialAvatars(List avatarIds, int questId, boolean save) { + this.setupTrialAvatars(save); // Perform initial setup. + + // Add the avatars to the team. + avatarIds.forEach( + avatarId -> { + var result = + this.addTrialAvatar( + avatarId, + questId, + questId == 0 + ? GrantReason.GRANT_REASON_BY_QUEST + : GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY); + + if (!result) throw new RuntimeException("Unable to add trial avatar to team."); + }); + + // Update the team. + this.trialAvatarTeamPostUpdate(questId == 0 ? getActiveTeam().size() - 1 : 0); + } + + /** Removes all trial avatars from the player's team. */ + public void removeTrialAvatar() { + this.removeTrialAvatar( + this.getActiveTeam().stream() + .map(EntityAvatar::getAvatar) + .map(Avatar::getAvatarId) + .toList()); + } + + /** + * Removes a trial avatar from the player's team. Additionally, unlocks the ability to change the + * team configuration. + * + * @param avatarId The ID of the avatar. + */ + public void removeTrialAvatar(int avatarId) { + this.removeTrialAvatar(List.of(avatarId)); + } + + /** + * Removes a collection of trial avatars from the player's team. + * + * @param avatarIds List of trial avatar IDs. + */ + public void removeTrialAvatar(List avatarIds) { + if (!this.isUsingTrialTeam()) throw new RuntimeException("Player is not using a trial team."); + + this.getPlayer() + .sendPacket( + new PacketAvatarDelNotify(avatarIds.stream().map(this::getTrialAvatarGuid).toList())); + this.removeTrialAvatarTeam(avatarIds); + + // Update the team. + if (avatarIds.size() == 1) this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify()); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ElementType.java b/src/main/java/emu/grasscutter/game/props/ElementType.java index 26d942cfe..eda061c4a 100644 --- a/src/main/java/emu/grasscutter/game/props/ElementType.java +++ b/src/main/java/emu/grasscutter/game/props/ElementType.java @@ -1,72 +1,131 @@ -package emu.grasscutter.game.props; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; - -import emu.grasscutter.scripts.constants.IntValueEnum; -import emu.grasscutter.utils.Utils; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.Getter; - -public enum ElementType implements IntValueEnum { - None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), - Fire (1, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2", 1), - Water (2, FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2", 2), - Grass (3, FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, 10501, "TeamResonance_Grass_Lv2", 7), - Electric (4, FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2", 6), - Ice (5, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2", 4), - Frozen (6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), - Wind (7, FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2", 3), - Rock (8, FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2", 5), - AntiFire (9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), - Default (255, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent"); - - private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - private static final Map stringMap = new HashMap<>(); - - static { - // Create bindings for each value. - Stream.of(ElementType.values()).forEach(entry -> { - map.put(entry.getValue(), entry); - stringMap.put(entry.name(), entry); - }); - } - - @Getter private final int value; - @Getter private final int teamResonanceId; - @Getter private final FightProperty curEnergyProp; - @Getter private final FightProperty maxEnergyProp; - @Getter private final int depotIndex; - @Getter private final int configHash; - - ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) { - this(value, curEnergyProp, maxEnergyProp, 0, null, 1); - } - - ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName) { - this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1); - } - - ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName, int depotIndex) { - this.value = value; - this.curEnergyProp = curEnergyProp; - this.maxEnergyProp = maxEnergyProp; - this.teamResonanceId = teamResonanceId; - this.depotIndex = depotIndex; - if (configName != null) { - this.configHash = Utils.abilityHash(configName); - } else { - this.configHash = 0; - } - } - - public static ElementType getTypeByValue(int value) { - return map.getOrDefault(value, None); - } - - public static ElementType getTypeByName(String name) { - return stringMap.getOrDefault(name, None); - } -} +package emu.grasscutter.game.props; + +import emu.grasscutter.scripts.constants.IntValueEnum; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.HashMap; +import java.util.Map; +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), + Fire( + 1, + FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, + FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, + 10101, + "TeamResonance_Fire_Lv2", + 1), + Water( + 2, + FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, + FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, + 10201, + "TeamResonance_Water_Lv2", + 2), + Grass( + 3, + FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, + FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, + 10501, + "TeamResonance_Grass_Lv2", + 7), + Electric( + 4, + FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, + FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, + 10401, + "TeamResonance_Electric_Lv2", + 6), + Ice( + 5, + FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, + FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, + 10601, + "TeamResonance_Ice_Lv2", + 4), + Frozen(6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), + Wind( + 7, + FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, + FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, + 10301, + "TeamResonance_Wind_Lv2", + 3), + Rock( + 8, + FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, + FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, + 10701, + "TeamResonance_Rock_Lv2", + 5), + AntiFire(9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), + Default( + 255, + FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, + FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, + 10801, + "TeamResonance_AllDifferent"); + + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + // Create bindings for each value. + Stream.of(ElementType.values()) + .forEach( + entry -> { + map.put(entry.getValue(), entry); + stringMap.put(entry.name(), entry); + }); + } + + @Getter private final int value; + @Getter private final int teamResonanceId; + @Getter private final FightProperty curEnergyProp; + @Getter private final FightProperty maxEnergyProp; + @Getter private final int depotIndex; + @Getter private final int configHash; + + ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) { + this(value, curEnergyProp, maxEnergyProp, 0, null, 1); + } + + ElementType( + int value, + FightProperty curEnergyProp, + FightProperty maxEnergyProp, + int teamResonanceId, + String configName) { + this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1); + } + + ElementType( + int value, + FightProperty curEnergyProp, + FightProperty maxEnergyProp, + int teamResonanceId, + String configName, + int depotIndex) { + this.value = value; + this.curEnergyProp = curEnergyProp; + this.maxEnergyProp = maxEnergyProp; + this.teamResonanceId = teamResonanceId; + this.depotIndex = depotIndex; + if (configName != null) { + this.configHash = Utils.abilityHash(configName); + } else { + this.configHash = 0; + } + } + + public static ElementType getTypeByValue(int value) { + return map.getOrDefault(value, None); + } + + public static ElementType getTypeByName(String name) { + return stringMap.getOrDefault(name, None); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPersonalLineUnlock.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPersonalLineUnlock.java index c4f9ce10e..c6809a2f8 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPersonalLineUnlock.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPersonalLineUnlock.java @@ -1,23 +1,21 @@ -package emu.grasscutter.game.quest.conditions; - -import emu.grasscutter.data.excels.QuestData; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.QuestValueCond; -import emu.grasscutter.game.quest.enums.QuestCond; -import lombok.val; - -@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK) -public class ConditionPersonalLineUnlock extends BaseCondition { - - @Override - public boolean execute( - Player owner, - QuestData questData, - QuestData.QuestAcceptCondition condition, - String paramStr, - int... params - ) { - var personalLineId = condition.getParam()[0]; - return owner.getPersonalLineList().contains(personalLineId); - } -} +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.QuestValueCond; +import emu.grasscutter.game.quest.enums.QuestCond; + +@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK) +public class ConditionPersonalLineUnlock extends BaseCondition { + + @Override + public boolean execute( + Player owner, + QuestData questData, + QuestData.QuestAcceptCondition condition, + String paramStr, + int... params) { + var personalLineId = condition.getParam()[0]; + return owner.getPersonalLineList().contains(personalLineId); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecAddCurAvatarEnergy.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddCurAvatarEnergy.java index b8ba76adc..bd7ff7a15 100644 --- a/src/main/java/emu/grasscutter/game/quest/exec/ExecAddCurAvatarEnergy.java +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddCurAvatarEnergy.java @@ -1,17 +1,17 @@ -package emu.grasscutter.game.quest.exec; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.excels.QuestData; -import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.QuestValueExec; -import emu.grasscutter.game.quest.enums.QuestExec; -import emu.grasscutter.game.quest.handlers.QuestExecHandler; - -@QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY) -public class ExecAddCurAvatarEnergy extends QuestExecHandler { - @Override - public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { - Grasscutter.getLogger().info("Energy refilled"); - return quest.getOwner().getEnergyManager().refillActiveEnergy(); - } -} +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValueExec; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY) +public class ExecAddCurAvatarEnergy extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + Grasscutter.getLogger().info("Energy refilled"); + return quest.getOwner().getEnergyManager().refillActiveEnergy(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java index 76d3b45cd..3de637899 100644 --- a/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java @@ -1,21 +1,21 @@ -package emu.grasscutter.game.quest.exec; - -import emu.grasscutter.data.excels.QuestData; -import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.QuestValueExec; -import emu.grasscutter.game.quest.enums.QuestExec; -import emu.grasscutter.game.quest.handlers.QuestExecHandler; -import java.util.Arrays; - -@QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS) -public final class ExecAddQuestProgress extends QuestExecHandler { - @Override - public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { - var param = - Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray(); - - quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]); - - return true; - } -} +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValueExec; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; +import java.util.Arrays; + +@QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS) +public final class ExecAddQuestProgress extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + var param = + Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray(); + + quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]); + + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecSetOpenState.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetOpenState.java index 314ee310c..2998dbfab 100644 --- a/src/main/java/emu/grasscutter/game/quest/exec/ExecSetOpenState.java +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetOpenState.java @@ -1,20 +1,20 @@ -package emu.grasscutter.game.quest.exec; - -import emu.grasscutter.data.excels.QuestData; -import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.QuestValueExec; -import emu.grasscutter.game.quest.enums.QuestExec; -import emu.grasscutter.game.quest.handlers.QuestExecHandler; -import java.util.Arrays; - -@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE) -public class ExecSetOpenState extends QuestExecHandler { - @Override - public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { - var param = - Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray(); - - quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]); - return true; - } -} +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValueExec; +import emu.grasscutter.game.quest.enums.QuestExec; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; +import java.util.Arrays; + +@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE) +public class ExecSetOpenState extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + var param = + Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray(); + + quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 0cd5dccb2..edb6e356e 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -1,1145 +1,1158 @@ -package emu.grasscutter.game.world; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.GameDepot; -import emu.grasscutter.data.binout.SceneNpcBornEntry; -import emu.grasscutter.data.binout.routes.Route; -import emu.grasscutter.data.excels.*; -import emu.grasscutter.data.excels.codex.CodexAnimalData; -import emu.grasscutter.data.excels.monster.MonsterData; -import emu.grasscutter.data.excels.world.WorldLevelData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.dungeons.DungeonManager; -import emu.grasscutter.game.dungeons.DungeonSettleListener; -import emu.grasscutter.game.dungeons.challenge.WorldChallenge; -import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; -import emu.grasscutter.game.entity.*; -import emu.grasscutter.game.entity.gadget.GadgetWorktop; -import emu.grasscutter.game.managers.blossom.BlossomManager; -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.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.SceneIndexManager; -import emu.grasscutter.scripts.SceneScriptManager; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneBlock; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.server.event.player.PlayerTeleportEvent; -import emu.grasscutter.server.packet.send.*; -import emu.grasscutter.utils.KahnsSort; -import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; -import lombok.Getter; -import lombok.Setter; -import lombok.val; - -public final class Scene { - @Getter private final World world; - @Getter private final SceneData sceneData; - @Getter private final List players; - @Getter private final Map entities; - @Getter private final Set spawnedEntities; - @Getter private final Set deadSpawnedEntities; - @Getter private final Set loadedBlocks; - @Getter private final Set loadedGroups; - @Getter private final BlossomManager blossomManager; - private final HashSet unlockedForces; - private final List afterLoadedCallbacks = new ArrayList<>(); - private final long startWorldTime; - @Getter @Setter DungeonManager dungeonManager; - @Getter Int2ObjectMap sceneRoutes; - private Set loadedGridBlocks; - @Getter @Setter private boolean dontDestroyWhenEmpty; - @Getter private final SceneScriptManager scriptManager; - @Getter @Setter private WorldChallenge challenge; - @Getter private List dungeonSettleListeners; - @Getter @Setter private int prevScene; // Id of the previous scene - @Getter @Setter private int prevScenePoint; - @Getter @Setter private int killedMonsterCount; - private Set npcBornEntrySet; - @Getter private boolean finishedLoading = false; - @Getter private int tickCount = 0; - @Getter private boolean isPaused = false; - - public Scene(World world, SceneData sceneData) { - this.world = world; - this.sceneData = sceneData; - this.players = new CopyOnWriteArrayList<>(); - this.entities = new ConcurrentHashMap<>(); - - this.prevScene = 3; - this.sceneRoutes = GameData.getSceneRoutes(getId()); - - this.startWorldTime = world.getWorldTime(); - - this.spawnedEntities = ConcurrentHashMap.newKeySet(); - this.deadSpawnedEntities = ConcurrentHashMap.newKeySet(); - this.loadedBlocks = ConcurrentHashMap.newKeySet(); - this.loadedGroups = ConcurrentHashMap.newKeySet(); - this.loadedGridBlocks = new HashSet<>(); - this.npcBornEntrySet = ConcurrentHashMap.newKeySet(); - this.scriptManager = new SceneScriptManager(this); - this.blossomManager = new BlossomManager(this); - this.unlockedForces = new HashSet<>(); - } - - public int getId() { - return sceneData.getId(); - } - - public SceneType getSceneType() { - return getSceneData().getSceneType(); - } - - public int getPlayerCount() { - return this.getPlayers().size(); - } - - public GameEntity getEntityById(int id) { - return this.entities.get(id); - } - - public GameEntity getEntityByConfigId(int configId) { - return this.entities.values().stream() - .filter(x -> x.getConfigId() == configId) - .findFirst() - .orElse(null); - } - - public GameEntity getEntityByConfigId(int configId, int groupId) { - return this.entities.values().stream() - .filter(x -> x.getConfigId() == configId && x.getGroupId() == groupId) - .findFirst() - .orElse(null); - } - - /** - * Sets the scene's pause state. Sends the current scene's time to all players. - * - * @param paused The new pause state. - */ - public void setPaused(boolean paused) { - if (this.isPaused != paused) { - this.isPaused = paused; - this.broadcastPacket(new PacketSceneTimeNotify(this)); - } - } - - /** - * Gets the time in seconds since the scene started. - * - * @return The time in seconds since the scene started. - */ - public int getSceneTime() { - return (int) (this.getWorld().getWorldTime() - this.startWorldTime); - } - - /** - * Gets {@link Scene#getSceneTime()} in seconds. - * - * @return The time in seconds since the scene started. - */ - public int getSceneTimeSeconds() { - return this.getSceneTime() / 1000; - } - - public void addDungeonSettleObserver(DungeonSettleListener dungeonSettleListener) { - if (dungeonSettleListeners == null) { - dungeonSettleListeners = new ArrayList<>(); - } - - dungeonSettleListeners.add(dungeonSettleListener); - } - - /** - * Triggers an event in the dungeon manager. - * - * @param conditionType The condition type to trigger. - * @param params The parameters to pass to the event. - */ - public void triggerDungeonEvent(DungeonPassConditionType conditionType, int... params) { - if (this.dungeonManager == null) return; - this.dungeonManager.triggerEvent(conditionType, params); - } - - public boolean isInScene(GameEntity entity) { - return this.entities.containsKey(entity.getId()); - } - - public synchronized void addPlayer(Player player) { - // Check if player already in - if (getPlayers().contains(player)) { - return; - } - - // Remove player from prev scene - if (player.getScene() != null) { - player.getScene().removePlayer(player); - } - - // Add - getPlayers().add(player); - player.setSceneId(this.getId()); - player.setScene(this); - - this.setupPlayerAvatars(player); - } - - public synchronized void removePlayer(Player player) { - // Remove from challenge if leaving - if (this.getChallenge() != null && this.getChallenge().inProgress()) { - player.sendPacket(new PacketDungeonChallengeFinishNotify(this.getChallenge())); - } - - // Remove player from scene - getPlayers().remove(player); - player.setScene(null); - - // Remove player avatars - this.removePlayerAvatars(player); - - // Remove player gadgets - for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) { - this.removeEntity(gadget); - } - - // Deregister scene if not in use - if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty) { - this.getWorld().deregisterScene(this); - } - } - - private void setupPlayerAvatars(Player player) { - // Clear entities from old team - player.getTeamManager().getActiveTeam().clear(); - - // Add new entities for player - TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo(); - for (int avatarId : teamInfo.getAvatars()) { - EntityAvatar entity = - new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatarId)); - player.getTeamManager().getActiveTeam().add(entity); - } - - // Limit character index in case its out of bounds - if (player.getTeamManager().getCurrentCharacterIndex() - >= player.getTeamManager().getActiveTeam().size() - || player.getTeamManager().getCurrentCharacterIndex() < 0) { - player - .getTeamManager() - .setCurrentCharacterIndex(player.getTeamManager().getCurrentCharacterIndex() - 1); - } - } - - private synchronized void removePlayerAvatars(Player player) { - var team = player.getTeamManager().getActiveTeam(); - // removeEntities(team, VisionType.VISION_TYPE_REMOVE); // List isn't cool apparently - // :( - team.forEach(e -> removeEntity(e, VisionType.VISION_TYPE_REMOVE)); - team.clear(); - } - - public void spawnPlayer(Player player) { - var teamManager = player.getTeamManager(); - if (this.isInScene(teamManager.getCurrentAvatarEntity())) { - return; - } - - if (teamManager.getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) - <= 0f) { - teamManager.getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f); - } - - this.addEntity(teamManager.getCurrentAvatarEntity()); - - // Notify the client of any extra skill charges - teamManager.getActiveTeam().stream() - .map(EntityAvatar::getAvatar) - .forEach(Avatar::sendSkillExtraChargeMap); - } - - private void addEntityDirectly(GameEntity entity) { - getEntities().put(entity.getId(), entity); - entity.onCreate(); // Call entity create event - } - - public synchronized void addEntity(GameEntity entity) { - this.addEntityDirectly(entity); - this.broadcastPacket(new PacketSceneEntityAppearNotify(entity)); - } - - public synchronized void addEntityToSingleClient(Player player, GameEntity entity) { - this.addEntityDirectly(entity); - player.sendPacket(new PacketSceneEntityAppearNotify(entity)); - } - - public void addEntities(Collection entities) { - addEntities(entities, VisionType.VISION_TYPE_BORN); - } - - public synchronized void addEntities( - Collection entities, VisionType visionType) { - if (entities == null || entities.isEmpty()) { - return; - } - for (GameEntity entity : entities) { - this.addEntityDirectly(entity); - } - - this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType)); - } - - private GameEntity removeEntityDirectly(GameEntity entity) { - var removed = getEntities().remove(entity.getId()); - if (removed != null) { - removed.onRemoved(); // Call entity remove event - } - return removed; - } - - public void removeEntity(GameEntity entity) { - this.removeEntity(entity, VisionType.VISION_TYPE_DIE); - } - - public synchronized void removeEntity(GameEntity entity, VisionType visionType) { - GameEntity removed = this.removeEntityDirectly(entity); - if (removed != null) { - this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType)); - } - } - - public synchronized void removeEntities(List entity, VisionType visionType) { - var toRemove = entity.stream().map(this::removeEntityDirectly).toList(); - if (toRemove.size() > 0) { - this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType)); - } - } - - public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) { - this.removeEntityDirectly(oldEntity); - this.addEntityDirectly(newEntity); - this.broadcastPacket( - new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VISION_TYPE_REPLACE)); - this.broadcastPacket( - new PacketSceneEntityAppearNotify( - newEntity, VisionType.VISION_TYPE_REPLACE, oldEntity.getId())); - } - - public void showOtherEntities(Player player) { - GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity(); - List entities = - this.getEntities().values().stream().filter(entity -> entity != currentEntity).toList(); - - player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_MEET)); - } - - public void handleAttack(AttackResult result) { - // GameEntity attacker = getEntityById(result.getAttackerId()); - GameEntity target = getEntityById(result.getDefenseId()); - ElementType attackType = ElementType.getTypeByValue(result.getElementType()); - - if (target == null) { - return; - } - - // Godmode check - if (target instanceof EntityAvatar) { - if (((EntityAvatar) target).getPlayer().isInGodMode()) { - return; - } - } - - // Sanity check - target.damage(result.getDamage(), result.getAttackerId(), attackType); - } - - public void killEntity(GameEntity target) { - killEntity(target, 0); - } - - public void killEntity(GameEntity target, int attackerId) { - GameEntity attacker = null; - - if (attackerId > 0) { - attacker = getEntityById(attackerId); - } - - if (attacker != null) { - // Check codex - if (attacker instanceof EntityClientGadget gadgetAttacker) { - var clientGadgetOwner = getEntityById(gadgetAttacker.getOwnerEntityId()); - if (clientGadgetOwner instanceof EntityAvatar) { - ((EntityClientGadget) attacker) - .getOwner() - .getCodex() - .checkAnimal(target, CodexAnimalData.CountType.CODEX_COUNT_TYPE_KILL); - } - } else if (attacker instanceof EntityAvatar avatarAttacker) { - avatarAttacker - .getPlayer() - .getCodex() - .checkAnimal(target, CodexAnimalData.CountType.CODEX_COUNT_TYPE_KILL); - } - } - - // Packet - this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD)); - - // Reward drop - if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_DUNGEON) { - getWorld().getServer().getDropSystem().callDrop((EntityMonster) target); - } - - // Remove entity from world - this.removeEntity(target); - - // Death event - target.onDeath(attackerId); - } - - public void onTick() { - // Disable ticking for the player's home world. - if (this.getSceneType() == SceneType.SCENE_HOME_WORLD - || this.getSceneType() == SceneType.SCENE_HOME_ROOM) { - return; - } - - if (this.getScriptManager().isInit()) { - this.checkBlocks(); - } else { - // TEMPORARY - this.checkSpawns(); - } - - // Triggers - this.scriptManager.checkRegions(); - - if (challenge != null) { - challenge.onCheckTimeOut(); - } - - this.blossomManager.onTick(); - - checkNpcGroup(); - - this.finishLoading(); - this.checkPlayerRespawn(); - if (this.tickCount++ % 10 == 0) - broadcastPacket(new PacketSceneTimeNotify(this)); - } - - /** - * Validates a player's current position. - * Teleports the player if the player is out of bounds. - */ - private void checkPlayerRespawn() { - if (this.getScriptManager().getConfig() == null) return; - var diePos = this.getScriptManager().getConfig().die_y; - - // Check players in the scene. - this.players.forEach(player -> { - if (this.getScriptManager().getConfig() == null) return; - - // Check if we need a respawn - if (diePos >= player.getPosition().getY()) { - //Respawn the player. - this.respawnPlayer(player); - } - }); - - // Check entities in the scene. - this.getEntities().forEach((id, entity) -> { - if (diePos >= entity.getPosition().getY()){ - this.killEntity(entity); - } - }); - } - - /** - * @return The script's default location, or the player's location. - */ - public Position getDefaultLocation(Player player) { - val defaultPosition = getScriptManager().getConfig().born_pos; - return defaultPosition != null ? defaultPosition : player.getPosition(); - } - - /** - * @return The script's default rotation, or the player's rotation. - */ - private Position getDefaultRot(Player player) { - var defaultRotation = this.getScriptManager().getConfig().born_rot; - return defaultRotation != null ? defaultRotation : player.getRotation(); - } - - /** - * Gets the respawn position for the player. - * - * @param player The player to get the respawn position for. - * @return The respawn position for the player. - */ - private Position getRespawnLocation(Player player) { - // TODO: Get the last valid location the player stood on. - var lastCheckpointPos = dungeonManager != null ? dungeonManager.getRespawnLocation() : null; - return lastCheckpointPos != null ? lastCheckpointPos : getDefaultLocation(player); - } - - /** - * Gets the respawn rotation for the player. - * - * @param player The player to get the respawn rotation for. - * @return The respawn rotation for the player. - */ - private Position getRespawnRotation(Player player) { - var lastCheckpointRot = this.dungeonManager != null ? this.dungeonManager.getRespawnRotation() : null; - return lastCheckpointRot != null ? lastCheckpointRot : this.getDefaultRot(player); - } - - /** - * Teleports the player to the respawn location. - * - * @param player The player to respawn. - * @return true if the player was successfully respawned, false otherwise. - */ - public boolean respawnPlayer(Player player) { - // Apply void damage as a penalty. - player.getTeamManager().applyVoidDamage(); - - // TODO: Respawn the player at the last valid location. - var targetPos = getRespawnLocation(player); - var targetRot = getRespawnRotation(player); - var teleportProps = TeleportProperties.builder() - .sceneId(getId()) - .teleportTo(targetPos) - .teleportRot(targetRot) - .teleportType(PlayerTeleportEvent.TeleportType.INTERNAL) - .enterType(EnterTypeOuterClass.EnterType.ENTER_TYPE_GOTO) - .enterReason(dungeonManager != null ? EnterReason.DungeonReviveOnWaypoint : EnterReason.Revival); - - return this.getWorld().transferPlayerToScene(player, teleportProps.build()); - } - - /** - * Invoked when the scene finishes loading. - * Runs all callbacks that were added with {@link #runWhenFinished(Runnable)}. - */ - public void finishLoading() { - if (this.finishedLoading) return; - - this.finishedLoading = true; - this.afterLoadedCallbacks.forEach(Runnable::run); - this.afterLoadedCallbacks.clear(); - } - - /** - * Adds a callback to be executed when the scene is finished loading. - * If the scene is already finished loading, the callback will be executed immediately. - * - * @param runnable The callback to be executed. - */ - public void runWhenFinished(Runnable runnable) { - if (this.isFinishedLoading()) { - runnable.run();return; - } - - this.afterLoadedCallbacks.add(runnable); - } - - public int getEntityLevel(int baseLevel, int worldLevelOverride) { - int level = worldLevelOverride > 0 ? worldLevelOverride + baseLevel - 22 : baseLevel; - level = Math.min(level, 100); - level = level <= 0 ? 1 : level; - - return level; - } - - public void checkNpcGroup() { - Set npcBornEntries = ConcurrentHashMap.newKeySet(); - for (Player player : this.getPlayers()) { - npcBornEntries.addAll(loadNpcForPlayer(player)); - } - - // clear the unreachable group for client - var toUnload = - this.npcBornEntrySet.stream() - .filter(i -> !npcBornEntries.contains(i)) - .map(SceneNpcBornEntry::getGroupId) - .toList(); - - if (toUnload.size() > 0) { - broadcastPacket(new PacketGroupUnloadNotify(toUnload)); - Grasscutter.getLogger().debug("Unload NPC Group {}", toUnload); - } - // exchange the new npcBornEntry Set - this.npcBornEntrySet = npcBornEntries; - } - - public synchronized void checkSpawns() { - Set loadedGridBlocks = new HashSet<>(); - for (Player player : this.getPlayers()) { - Collections.addAll( - loadedGridBlocks, - SpawnDataEntry.GridBlockId.getAdjacentGridBlockIds( - player.getSceneId(), player.getPosition())); - } - if (this.loadedGridBlocks.containsAll( - loadedGridBlocks)) { // Don't recalculate static spawns if nothing has changed - return; - } - this.loadedGridBlocks = loadedGridBlocks; - var spawnLists = GameDepot.getSpawnLists(); - Set visible = new HashSet<>(); - for (var block : loadedGridBlocks) { - var spawns = spawnLists.get(block); - if (spawns != null) { - visible.addAll(spawns); - } - } - - // World level - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getWorld().getWorldLevel()); - int worldLevelOverride = 0; - - if (worldLevelData != null) { - worldLevelOverride = worldLevelData.getMonsterLevel(); - } - - // Todo - List toAdd = new ArrayList<>(); - List toRemove = new ArrayList<>(); - var spawnedEntities = this.getSpawnedEntities(); - for (SpawnDataEntry entry : visible) { - // If spawn entry is in our view and hasnt been spawned/killed yet, we should spawn it - if (!spawnedEntities.contains(entry) && !this.getDeadSpawnedEntities().contains(entry)) { - // Entity object holder - GameEntity entity = null; - - // Check if spawn entry is monster or gadget - if (entry.getMonsterId() > 0) { - MonsterData data = GameData.getMonsterDataMap().get(entry.getMonsterId()); - if (data == null) continue; - - int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride); - - EntityMonster monster = new EntityMonster(this, data, entry.getPos(), level); - monster.getRotation().set(entry.getRot()); - monster.setGroupId(entry.getGroup().getGroupId()); - monster.setPoseId(entry.getPoseId()); - monster.setConfigId(entry.getConfigId()); - monster.setSpawnEntry(entry); - - entity = monster; - } else if (entry.getGadgetId() > 0) { - EntityGadget gadget = - new EntityGadget(this, entry.getGadgetId(), entry.getPos(), entry.getRot()); - gadget.setGroupId(entry.getGroup().getGroupId()); - gadget.setConfigId(entry.getConfigId()); - gadget.setSpawnEntry(entry); - int state = entry.getGadgetState(); - if (state > 0) { - gadget.setState(state); - } - gadget.buildContent(); - - gadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY); - gadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY); - gadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY); - - entity = gadget; - blossomManager.initBlossom(gadget); - } - - if (entity == null) continue; - - // Add to scene and spawned list - toAdd.add(entity); - spawnedEntities.add(entry); - } - } - - for (GameEntity entity : this.getEntities().values()) { - var spawnEntry = entity.getSpawnEntry(); - if (spawnEntry != null && !visible.contains(spawnEntry)) { - toRemove.add(entity); - spawnedEntities.remove(spawnEntry); - } - } - - if (toAdd.size() > 0) { - toAdd.forEach(this::addEntityDirectly); - this.broadcastPacket(new PacketSceneEntityAppearNotify(toAdd, VisionType.VISION_TYPE_BORN)); - } - - if (toRemove.size() > 0) { - toRemove.forEach(this::removeEntityDirectly); - this.broadcastPacket( - new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); - blossomManager.recycleGadgetEntity(toRemove); - } - } - - public List getPlayerActiveBlocks(Player player) { - // consider the borders' entities of blocks, so we check if contains by index - return SceneIndexManager.queryNeighbors( - getScriptManager().getBlocksIndex(), - player.getPosition().toXZDoubleArray(), - Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); - } - - private boolean unloadBlockIfNotVisible(Collection visible, SceneBlock block) { - if (visible.contains(block)) return false; - this.onUnloadBlock(block); - return true; - } - - public synchronized boolean loadBlock(SceneBlock block) { - if (this.loadedBlocks.contains(block)) return false; - - this.onLoadBlock(block, this.players); - this.loadedBlocks.add(block); - return true; - } - - public synchronized void checkBlocks() { - Set visible = - this.players.stream() - .map(this::getPlayerActiveBlocks) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - - this.loadedBlocks.removeIf(block -> unloadBlockIfNotVisible(visible, block)); - visible.stream() - .filter(block -> !this.loadBlock(block)) - .forEach( - block -> { - // dynamic load the groups for players in a loaded block - var toLoad = - this.players.stream() - .filter(p -> block.contains(p.getPosition())) - .map(p -> this.playerMeetGroups(p, block)) - .flatMap(Collection::stream) - .toList(); - this.onLoadGroup(toLoad); - }); - } - - public List playerMeetGroups(Player player, SceneBlock block) { - List sceneGroups = - SceneIndexManager.queryNeighbors( - block.sceneGroupIndex, - player.getPosition().toDoubleArray(), - Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); - - List groups = - sceneGroups.stream() - .filter( - group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) - .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)) - .toList(); - - if (groups.size() == 0) { - return List.of(); - } - - return groups; - } - - public void onLoadBlock(SceneBlock block, List players) { - this.getScriptManager().loadBlockFromScript(block); - scriptManager.getLoadedGroupSetPerBlock().put(block.id, new HashSet<>()); - - // the groups form here is not added in current scene - var groups = - players.stream() - .filter(player -> block.contains(player.getPosition())) - .map(p -> playerMeetGroups(p, block)) - .flatMap(Collection::stream) - .toList(); - - onLoadGroup(groups); - Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); - } - - public int loadDynamicGroup(int group_id) { - SceneGroup group = getScriptManager().getGroupById(group_id); - if(group == null || getScriptManager().getGroupInstanceById(group_id) != null) return -1; //Group not found or already instanced - - onLoadGroup(new ArrayList<>(List.of(group))); - - if(GameData.getGroupReplacements().containsKey(group_id)) onRegisterGroups(); - - if (group.init_config == null) return -1; - return group.init_config.suite; - } - - public boolean unregisterDynamicGroup(int groupId){ - var group = getScriptManager().getGroupById(groupId); - if (group == null) return false; - - var block = getScriptManager().getBlocks().get(group.block_id); - this.unloadGroup(block, groupId); - return true; - } - - public void onRegisterGroups() { - var sceneGroups = this.loadedGroups; - var sceneGroupMap = sceneGroups.stream() - .collect(Collectors.toMap(item -> item.id, item -> item)); - var sceneGroupsIds = sceneGroups.stream() - .map(group -> group.id) - .toList(); - var dynamicGroups = sceneGroups.stream() - .filter(group -> group.dynamic_load) - .map(group -> group.id) - .toList(); - - //Create the graph - var nodes = new ArrayList(); - var groupList = new ArrayList(); - GameData.getGroupReplacements().values().stream().filter(replacement -> dynamicGroups.contains(replacement.id)).forEach(replacement -> { - Grasscutter.getLogger().info("Graph ordering replacement {}", replacement); - replacement.replace_groups.forEach(group -> { - nodes.add(new KahnsSort.Node(replacement.id, group)); - if (!groupList.contains(group)) - groupList.add(group); - }); - - if (!groupList.contains(replacement.id)) - groupList.add(replacement.id); - }); - - KahnsSort.Graph graph = new KahnsSort.Graph(nodes, groupList); - List dynamicGroupsOrdered = KahnsSort.doSort(graph); - if (dynamicGroupsOrdered == null) throw new RuntimeException("Invalid group replacement graph"); - - // Now we can start unloading and loading groups :D - dynamicGroupsOrdered.forEach(group -> { - if (GameData.getGroupReplacements().containsKey((int)group)) { //isGroupJoinReplacement - var data = GameData.getGroupReplacements().get((int)group); - var sceneGroupReplacement = this.loadedGroups.stream().filter(g -> g.id == group).findFirst().orElseThrow(); - if (sceneGroupReplacement.is_replaceable != null) { - var it = data.replace_groups.iterator(); - while (it.hasNext()) { - var replace_group = it.next(); - if (!sceneGroupsIds.contains(replace_group)) continue; - - // Check if we can replace this group - SceneGroup sceneGroup = sceneGroupMap.get(replace_group); - if (sceneGroup != null && sceneGroup.is_replaceable != null && - ((sceneGroup.is_replaceable.value && - sceneGroup.is_replaceable.version <= sceneGroupReplacement.is_replaceable.version) || - sceneGroup.is_replaceable.new_bin_only)) { - this.unloadGroup(scriptManager.getBlocks().get(sceneGroup.block_id), replace_group); - it.remove(); - Grasscutter.getLogger().info("Graph ordering: unloaded {}", replace_group); - } - } - } - } - }); - } - - public void loadTriggerFromGroup(SceneGroup group, String triggerName) { - // Load triggers and regions - this.getScriptManager().registerTrigger(group.triggers.values().stream() - .filter(p -> p.getName().contains(triggerName)).toList()); - group.regions.values().stream() - .filter(q -> q.config_id == Integer.parseInt(triggerName.substring(13))) - .map(region -> new EntityRegion(this, region)) - .forEach(getScriptManager()::registerRegion); - } - - public void onLoadGroup(List groups) { - if (groups == null || groups.isEmpty()) { - return; - } - - for (var group : groups) { - if(this.loadedGroups.contains(group)) continue; - - // We load the script files for the groups here - this.getScriptManager().loadGroupFromScript(group); - if (!this.scriptManager.getLoadedGroupSetPerBlock().containsKey(group.block_id)) - this.onLoadBlock(scriptManager.getBlocks().get(group.block_id), players); - this.scriptManager.getLoadedGroupSetPerBlock().get(group.block_id).add(group); - } - - // Spawn gadgets AFTER triggers are added - // TODO - var entities = new ArrayList(); - for (var group : groups) { - if(this.loadedGroups.contains(group)) continue; - - if (group.init_config == null) { - continue; - } - - var groupInstance = this.getScriptManager().getGroupInstanceById(group.id); - var cachedInstance = this.getScriptManager().getCachedGroupInstanceById(group.id); - if (cachedInstance != null) { - cachedInstance.setLuaGroup(group); - groupInstance = cachedInstance; - } - - // Load garbages - var garbageGadgets = group.getGarbageGadgets(); - - if (garbageGadgets != null) { - entities.addAll(garbageGadgets.stream() - .map(g -> scriptManager.createGadget(group.id, group.block_id, g)) - .filter(Objects::nonNull).toList()); - } - - // Load suites - //int suite = group.findInitSuiteIndex(0); - this.getScriptManager().refreshGroup(groupInstance, 0, false); //This is what the official server does - - this.loadedGroups.add(group); - } - - this.scriptManager.meetEntities(entities); - groups.forEach(g -> scriptManager.callEvent(new ScriptArgs(g.id, EventType.EVENT_GROUP_LOAD, g.id))); - - Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); - } - - public void onUnloadBlock(SceneBlock block) { - List toRemove = - this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList(); - - if (toRemove.size() > 0) { - toRemove.forEach(this::removeEntityDirectly); - this.broadcastPacket( - new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); - } - - for (SceneGroup group : block.groups.values()) { - if (group.triggers != null) { - group.triggers.values().forEach(getScriptManager()::deregisterTrigger); - } - if (group.regions != null) { - group.regions.values().forEach(getScriptManager()::deregisterRegion); - } - } - scriptManager.getLoadedGroupSetPerBlock().remove(block.id); - Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); - } - - /** - * Unloads a Lua group. - * - * @param block The block that contains the group. - * @param groupId The group ID. - */ - public void unloadGroup(SceneBlock block, int groupId) { - var toRemove = this.getEntities().values().stream() - .filter(e -> e != null && ( - e.getBlockId() == block.id && - e.getGroupId() == groupId) - ).toList(); - - if (toRemove.size() > 0) { - toRemove.forEach(this::removeEntityDirectly); - this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); - } - - var group = block.groups.get(groupId); - if (group.triggers != null) { - group.triggers.values().forEach( - this.getScriptManager()::deregisterTrigger); - } - if (group.regions != null) { - group.regions.values().forEach( - this.getScriptManager()::deregisterRegion); - } - - this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group); - this.loadedGroups.remove(group); - - if (this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).isEmpty()) { - this.scriptManager.getLoadedGroupSetPerBlock().remove(block.id); - Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); - } - - this.broadcastPacket(new PacketGroupUnloadNotify(List.of(groupId))); - this.scriptManager.unregisterGroup(group); - } - - // Gadgets - - public void onPlayerCreateGadget(EntityClientGadget gadget) { - // Directly add - this.addEntityDirectly(gadget); - - // Add to owner's gadget list - gadget.getOwner().getTeamManager().getGadgets().add(gadget); - - // Optimization - if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) { - return; - } - - this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityAppearNotify(gadget)); - } - - public void onPlayerDestroyGadget(int entityId) { - GameEntity entity = getEntities().get(entityId); - - if (!(entity instanceof EntityClientGadget gadget)) { - return; - } - - // Get and remove entity - this.removeEntityDirectly(gadget); - - // Remove from owner's gadget list - gadget.getOwner().getTeamManager().getGadgets().remove(gadget); - - // Optimization - if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) { - return; - } - - this.broadcastPacketToOthers( - gadget.getOwner(), - new PacketSceneEntityDisappearNotify(gadget, VisionType.VISION_TYPE_DIE)); - } - - // Broadcasting - - public void broadcastPacket(BasePacket packet) { - // Send to all players - might have to check if player has been sent data packets - for (Player player : this.getPlayers()) { - player.getSession().send(packet); - } - } - - public void broadcastPacketToOthers(Player excludedPlayer, BasePacket packet) { - // Optimization - if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == excludedPlayer) { - return; - } - // Send to all players - might have to check if player has been sent data packets - for (Player player : this.getPlayers()) { - if (player == excludedPlayer) { - continue; - } - // Send - player.getSession().send(packet); - } - } - - public void addItemEntity(int itemId, int amount, GameEntity bornForm) { - ItemData itemData = GameData.getItemDataMap().get(itemId); - if (itemData == null) { - return; - } - if (itemData.isEquip()) { - float range = (1.5f + (.05f * amount)); - for (int i = 0; i < amount; i++) { - Position pos = bornForm.getPosition().nearby2d(range).addZ(.9f); // Why Z? - EntityItem entity = new EntityItem(this, null, itemData, pos, 1); - addEntity(entity); - } - } else { - EntityItem entity = - new EntityItem( - this, null, itemData, bornForm.getPosition().clone().addZ(.9f), amount); // Why Z? - addEntity(entity); - } - } - - public void loadNpcForPlayerEnter(Player player) { - this.npcBornEntrySet.addAll(loadNpcForPlayer(player)); - } - - private List loadNpcForPlayer(Player player) { - var pos = player.getPosition(); - var data = GameData.getSceneNpcBornData().get(getId()); - if (data == null) { - return List.of(); - } - - var npcList = - SceneIndexManager.queryNeighbors( - data.getIndex(), - pos.toDoubleArray(), - Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); - - var sceneNpcBornEntries = - npcList.stream().filter(i -> !this.npcBornEntrySet.contains(i)).toList(); - - if (sceneNpcBornEntries.size() > 0) { - this.broadcastPacket(new PacketGroupSuiteNotify(sceneNpcBornEntries)); - Grasscutter.getLogger().debug("Loaded Npc Group Suite {}", sceneNpcBornEntries); - } - return npcList; - } - - public void loadGroupForQuest(List sceneGroupSuite) { - if (!scriptManager.isInit()) { - return; - } - - sceneGroupSuite.forEach(i -> { - var group = scriptManager.getGroupById(i.getGroup()); - if (group == null) return; - - var groupInstance = scriptManager.getGroupInstanceById(i.getGroup()); - var suite = group.getSuiteByIndex(i.getSuite()); - if (suite == null || groupInstance == null) { - return; - } - - scriptManager.refreshGroup(groupInstance, i.getSuite(), false); - }); - } - - /** - * Adds an unlocked force to the scene. - * - * @param force The ID of the force to unlock. - */ - public void unlockForce(int force) { - this.unlockedForces.add(force); - this.broadcastPacket(new PacketSceneForceUnlockNotify(force, true)); - } - - /** - * Removes an unlocked force from the scene. - * - * @param force The ID of the force to lock. - */ - public void lockForce(int force) { - this.unlockedForces.remove(force); - this.broadcastPacket(new PacketSceneForceLockNotify(force)); - } - - public void selectWorktopOptionWith(SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq req) { - GameEntity entity = getEntityById(req.getGadgetEntityId()); - if (entity == null) { - return; - } - // Handle - if (entity instanceof EntityGadget gadget) { - if (gadget.getContent() instanceof GadgetWorktop worktop) { - boolean shouldDelete = worktop.onSelectWorktopOption(req); - if (shouldDelete) { - entity.getScene().removeEntity(entity, VisionType.VISION_TYPE_REMOVE); - } - } - } - } -} +package emu.grasscutter.game.world; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameDepot; +import emu.grasscutter.data.binout.SceneNpcBornEntry; +import emu.grasscutter.data.binout.routes.Route; +import emu.grasscutter.data.excels.*; +import emu.grasscutter.data.excels.codex.CodexAnimalData; +import emu.grasscutter.data.excels.monster.MonsterData; +import emu.grasscutter.data.excels.world.WorldLevelData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.dungeons.DungeonManager; +import emu.grasscutter.game.dungeons.DungeonSettleListener; +import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.entity.gadget.GadgetWorktop; +import emu.grasscutter.game.managers.blossom.BlossomManager; +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.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.SceneIndexManager; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneBlock; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.event.player.PlayerTeleportEvent; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.KahnsSort; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +public final class Scene { + @Getter private final World world; + @Getter private final SceneData sceneData; + @Getter private final List players; + @Getter private final Map entities; + @Getter private final Set spawnedEntities; + @Getter private final Set deadSpawnedEntities; + @Getter private final Set loadedBlocks; + @Getter private final Set loadedGroups; + @Getter private final BlossomManager blossomManager; + private final HashSet unlockedForces; + private final List afterLoadedCallbacks = new ArrayList<>(); + private final long startWorldTime; + @Getter @Setter DungeonManager dungeonManager; + @Getter Int2ObjectMap sceneRoutes; + private Set loadedGridBlocks; + @Getter @Setter private boolean dontDestroyWhenEmpty; + @Getter private final SceneScriptManager scriptManager; + @Getter @Setter private WorldChallenge challenge; + @Getter private List dungeonSettleListeners; + @Getter @Setter private int prevScene; // Id of the previous scene + @Getter @Setter private int prevScenePoint; + @Getter @Setter private int killedMonsterCount; + private Set npcBornEntrySet; + @Getter private boolean finishedLoading = false; + @Getter private int tickCount = 0; + @Getter private boolean isPaused = false; + + public Scene(World world, SceneData sceneData) { + this.world = world; + this.sceneData = sceneData; + this.players = new CopyOnWriteArrayList<>(); + this.entities = new ConcurrentHashMap<>(); + + this.prevScene = 3; + this.sceneRoutes = GameData.getSceneRoutes(getId()); + + this.startWorldTime = world.getWorldTime(); + + this.spawnedEntities = ConcurrentHashMap.newKeySet(); + this.deadSpawnedEntities = ConcurrentHashMap.newKeySet(); + this.loadedBlocks = ConcurrentHashMap.newKeySet(); + this.loadedGroups = ConcurrentHashMap.newKeySet(); + this.loadedGridBlocks = new HashSet<>(); + this.npcBornEntrySet = ConcurrentHashMap.newKeySet(); + this.scriptManager = new SceneScriptManager(this); + this.blossomManager = new BlossomManager(this); + this.unlockedForces = new HashSet<>(); + } + + public int getId() { + return sceneData.getId(); + } + + public SceneType getSceneType() { + return getSceneData().getSceneType(); + } + + public int getPlayerCount() { + return this.getPlayers().size(); + } + + public GameEntity getEntityById(int id) { + return this.entities.get(id); + } + + public GameEntity getEntityByConfigId(int configId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId) + .findFirst() + .orElse(null); + } + + public GameEntity getEntityByConfigId(int configId, int groupId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId && x.getGroupId() == groupId) + .findFirst() + .orElse(null); + } + + /** + * Sets the scene's pause state. Sends the current scene's time to all players. + * + * @param paused The new pause state. + */ + public void setPaused(boolean paused) { + if (this.isPaused != paused) { + this.isPaused = paused; + this.broadcastPacket(new PacketSceneTimeNotify(this)); + } + } + + /** + * Gets the time in seconds since the scene started. + * + * @return The time in seconds since the scene started. + */ + public int getSceneTime() { + return (int) (this.getWorld().getWorldTime() - this.startWorldTime); + } + + /** + * Gets {@link Scene#getSceneTime()} in seconds. + * + * @return The time in seconds since the scene started. + */ + public int getSceneTimeSeconds() { + return this.getSceneTime() / 1000; + } + + public void addDungeonSettleObserver(DungeonSettleListener dungeonSettleListener) { + if (dungeonSettleListeners == null) { + dungeonSettleListeners = new ArrayList<>(); + } + + dungeonSettleListeners.add(dungeonSettleListener); + } + + /** + * Triggers an event in the dungeon manager. + * + * @param conditionType The condition type to trigger. + * @param params The parameters to pass to the event. + */ + public void triggerDungeonEvent(DungeonPassConditionType conditionType, int... params) { + if (this.dungeonManager == null) return; + this.dungeonManager.triggerEvent(conditionType, params); + } + + public boolean isInScene(GameEntity entity) { + return this.entities.containsKey(entity.getId()); + } + + public synchronized void addPlayer(Player player) { + // Check if player already in + if (getPlayers().contains(player)) { + return; + } + + // Remove player from prev scene + if (player.getScene() != null) { + player.getScene().removePlayer(player); + } + + // Add + getPlayers().add(player); + player.setSceneId(this.getId()); + player.setScene(this); + + this.setupPlayerAvatars(player); + } + + public synchronized void removePlayer(Player player) { + // Remove from challenge if leaving + if (this.getChallenge() != null && this.getChallenge().inProgress()) { + player.sendPacket(new PacketDungeonChallengeFinishNotify(this.getChallenge())); + } + + // Remove player from scene + getPlayers().remove(player); + player.setScene(null); + + // Remove player avatars + this.removePlayerAvatars(player); + + // Remove player gadgets + for (EntityBaseGadget gadget : player.getTeamManager().getGadgets()) { + this.removeEntity(gadget); + } + + // Deregister scene if not in use + if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty) { + this.getWorld().deregisterScene(this); + } + } + + private void setupPlayerAvatars(Player player) { + // Clear entities from old team + player.getTeamManager().getActiveTeam().clear(); + + // Add new entities for player + TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo(); + for (int avatarId : teamInfo.getAvatars()) { + EntityAvatar entity = + new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatarId)); + player.getTeamManager().getActiveTeam().add(entity); + } + + // Limit character index in case its out of bounds + if (player.getTeamManager().getCurrentCharacterIndex() + >= player.getTeamManager().getActiveTeam().size() + || player.getTeamManager().getCurrentCharacterIndex() < 0) { + player + .getTeamManager() + .setCurrentCharacterIndex(player.getTeamManager().getCurrentCharacterIndex() - 1); + } + } + + private synchronized void removePlayerAvatars(Player player) { + var team = player.getTeamManager().getActiveTeam(); + // removeEntities(team, VisionType.VISION_TYPE_REMOVE); // List isn't cool apparently + // :( + team.forEach(e -> removeEntity(e, VisionType.VISION_TYPE_REMOVE)); + team.clear(); + } + + public void spawnPlayer(Player player) { + var teamManager = player.getTeamManager(); + if (this.isInScene(teamManager.getCurrentAvatarEntity())) { + return; + } + + if (teamManager.getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) + <= 0f) { + teamManager.getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f); + } + + this.addEntity(teamManager.getCurrentAvatarEntity()); + + // Notify the client of any extra skill charges + teamManager.getActiveTeam().stream() + .map(EntityAvatar::getAvatar) + .forEach(Avatar::sendSkillExtraChargeMap); + } + + private void addEntityDirectly(GameEntity entity) { + getEntities().put(entity.getId(), entity); + entity.onCreate(); // Call entity create event + } + + public synchronized void addEntity(GameEntity entity) { + this.addEntityDirectly(entity); + this.broadcastPacket(new PacketSceneEntityAppearNotify(entity)); + } + + public synchronized void addEntityToSingleClient(Player player, GameEntity entity) { + this.addEntityDirectly(entity); + player.sendPacket(new PacketSceneEntityAppearNotify(entity)); + } + + public void addEntities(Collection entities) { + addEntities(entities, VisionType.VISION_TYPE_BORN); + } + + public synchronized void addEntities( + Collection entities, VisionType visionType) { + if (entities == null || entities.isEmpty()) { + return; + } + for (GameEntity entity : entities) { + this.addEntityDirectly(entity); + } + + this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType)); + } + + private GameEntity removeEntityDirectly(GameEntity entity) { + var removed = getEntities().remove(entity.getId()); + if (removed != null) { + removed.onRemoved(); // Call entity remove event + } + return removed; + } + + public void removeEntity(GameEntity entity) { + this.removeEntity(entity, VisionType.VISION_TYPE_DIE); + } + + public synchronized void removeEntity(GameEntity entity, VisionType visionType) { + GameEntity removed = this.removeEntityDirectly(entity); + if (removed != null) { + this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType)); + } + } + + public synchronized void removeEntities(List entity, VisionType visionType) { + var toRemove = entity.stream().map(this::removeEntityDirectly).toList(); + if (toRemove.size() > 0) { + this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType)); + } + } + + public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) { + this.removeEntityDirectly(oldEntity); + this.addEntityDirectly(newEntity); + this.broadcastPacket( + new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VISION_TYPE_REPLACE)); + this.broadcastPacket( + new PacketSceneEntityAppearNotify( + newEntity, VisionType.VISION_TYPE_REPLACE, oldEntity.getId())); + } + + public void showOtherEntities(Player player) { + GameEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity(); + List entities = + this.getEntities().values().stream().filter(entity -> entity != currentEntity).toList(); + + player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_MEET)); + } + + public void handleAttack(AttackResult result) { + // GameEntity attacker = getEntityById(result.getAttackerId()); + GameEntity target = getEntityById(result.getDefenseId()); + ElementType attackType = ElementType.getTypeByValue(result.getElementType()); + + if (target == null) { + return; + } + + // Godmode check + if (target instanceof EntityAvatar) { + if (((EntityAvatar) target).getPlayer().isInGodMode()) { + return; + } + } + + // Sanity check + target.damage(result.getDamage(), result.getAttackerId(), attackType); + } + + public void killEntity(GameEntity target) { + killEntity(target, 0); + } + + public void killEntity(GameEntity target, int attackerId) { + GameEntity attacker = null; + + if (attackerId > 0) { + attacker = getEntityById(attackerId); + } + + if (attacker != null) { + // Check codex + if (attacker instanceof EntityClientGadget gadgetAttacker) { + var clientGadgetOwner = getEntityById(gadgetAttacker.getOwnerEntityId()); + if (clientGadgetOwner instanceof EntityAvatar) { + ((EntityClientGadget) attacker) + .getOwner() + .getCodex() + .checkAnimal(target, CodexAnimalData.CountType.CODEX_COUNT_TYPE_KILL); + } + } else if (attacker instanceof EntityAvatar avatarAttacker) { + avatarAttacker + .getPlayer() + .getCodex() + .checkAnimal(target, CodexAnimalData.CountType.CODEX_COUNT_TYPE_KILL); + } + } + + // Packet + this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD)); + + // Reward drop + if (target instanceof EntityMonster && this.getSceneType() != SceneType.SCENE_DUNGEON) { + getWorld().getServer().getDropSystem().callDrop((EntityMonster) target); + } + + // Remove entity from world + this.removeEntity(target); + + // Death event + target.onDeath(attackerId); + } + + public void onTick() { + // Disable ticking for the player's home world. + if (this.getSceneType() == SceneType.SCENE_HOME_WORLD + || this.getSceneType() == SceneType.SCENE_HOME_ROOM) { + return; + } + + if (this.getScriptManager().isInit()) { + this.checkBlocks(); + } else { + // TEMPORARY + this.checkSpawns(); + } + + // Triggers + this.scriptManager.checkRegions(); + + if (challenge != null) { + challenge.onCheckTimeOut(); + } + + this.blossomManager.onTick(); + + checkNpcGroup(); + + this.finishLoading(); + this.checkPlayerRespawn(); + if (this.tickCount++ % 10 == 0) broadcastPacket(new PacketSceneTimeNotify(this)); + } + + /** Validates a player's current position. Teleports the player if the player is out of bounds. */ + private void checkPlayerRespawn() { + if (this.getScriptManager().getConfig() == null) return; + var diePos = this.getScriptManager().getConfig().die_y; + + // Check players in the scene. + this.players.forEach( + player -> { + if (this.getScriptManager().getConfig() == null) return; + + // Check if we need a respawn + if (diePos >= player.getPosition().getY()) { + // Respawn the player. + this.respawnPlayer(player); + } + }); + + // Check entities in the scene. + this.getEntities() + .forEach( + (id, entity) -> { + if (diePos >= entity.getPosition().getY()) { + this.killEntity(entity); + } + }); + } + + /** + * @return The script's default location, or the player's location. + */ + public Position getDefaultLocation(Player player) { + val defaultPosition = getScriptManager().getConfig().born_pos; + return defaultPosition != null ? defaultPosition : player.getPosition(); + } + + /** + * @return The script's default rotation, or the player's rotation. + */ + private Position getDefaultRot(Player player) { + var defaultRotation = this.getScriptManager().getConfig().born_rot; + return defaultRotation != null ? defaultRotation : player.getRotation(); + } + + /** + * Gets the respawn position for the player. + * + * @param player The player to get the respawn position for. + * @return The respawn position for the player. + */ + private Position getRespawnLocation(Player player) { + // TODO: Get the last valid location the player stood on. + var lastCheckpointPos = dungeonManager != null ? dungeonManager.getRespawnLocation() : null; + return lastCheckpointPos != null ? lastCheckpointPos : getDefaultLocation(player); + } + + /** + * Gets the respawn rotation for the player. + * + * @param player The player to get the respawn rotation for. + * @return The respawn rotation for the player. + */ + private Position getRespawnRotation(Player player) { + var lastCheckpointRot = + this.dungeonManager != null ? this.dungeonManager.getRespawnRotation() : null; + return lastCheckpointRot != null ? lastCheckpointRot : this.getDefaultRot(player); + } + + /** + * Teleports the player to the respawn location. + * + * @param player The player to respawn. + * @return true if the player was successfully respawned, false otherwise. + */ + public boolean respawnPlayer(Player player) { + // Apply void damage as a penalty. + player.getTeamManager().applyVoidDamage(); + + // TODO: Respawn the player at the last valid location. + var targetPos = getRespawnLocation(player); + var targetRot = getRespawnRotation(player); + var teleportProps = + TeleportProperties.builder() + .sceneId(getId()) + .teleportTo(targetPos) + .teleportRot(targetRot) + .teleportType(PlayerTeleportEvent.TeleportType.INTERNAL) + .enterType(EnterTypeOuterClass.EnterType.ENTER_TYPE_GOTO) + .enterReason( + dungeonManager != null ? EnterReason.DungeonReviveOnWaypoint : EnterReason.Revival); + + return this.getWorld().transferPlayerToScene(player, teleportProps.build()); + } + + /** + * Invoked when the scene finishes loading. Runs all callbacks that were added with {@link + * #runWhenFinished(Runnable)}. + */ + public void finishLoading() { + if (this.finishedLoading) return; + + this.finishedLoading = true; + this.afterLoadedCallbacks.forEach(Runnable::run); + this.afterLoadedCallbacks.clear(); + } + + /** + * Adds a callback to be executed when the scene is finished loading. If the scene is already + * finished loading, the callback will be executed immediately. + * + * @param runnable The callback to be executed. + */ + public void runWhenFinished(Runnable runnable) { + if (this.isFinishedLoading()) { + runnable.run(); + return; + } + + this.afterLoadedCallbacks.add(runnable); + } + + public int getEntityLevel(int baseLevel, int worldLevelOverride) { + int level = worldLevelOverride > 0 ? worldLevelOverride + baseLevel - 22 : baseLevel; + level = Math.min(level, 100); + level = level <= 0 ? 1 : level; + + return level; + } + + public void checkNpcGroup() { + Set npcBornEntries = ConcurrentHashMap.newKeySet(); + for (Player player : this.getPlayers()) { + npcBornEntries.addAll(loadNpcForPlayer(player)); + } + + // clear the unreachable group for client + var toUnload = + this.npcBornEntrySet.stream() + .filter(i -> !npcBornEntries.contains(i)) + .map(SceneNpcBornEntry::getGroupId) + .toList(); + + if (toUnload.size() > 0) { + broadcastPacket(new PacketGroupUnloadNotify(toUnload)); + Grasscutter.getLogger().debug("Unload NPC Group {}", toUnload); + } + // exchange the new npcBornEntry Set + this.npcBornEntrySet = npcBornEntries; + } + + public synchronized void checkSpawns() { + Set loadedGridBlocks = new HashSet<>(); + for (Player player : this.getPlayers()) { + Collections.addAll( + loadedGridBlocks, + SpawnDataEntry.GridBlockId.getAdjacentGridBlockIds( + player.getSceneId(), player.getPosition())); + } + if (this.loadedGridBlocks.containsAll( + loadedGridBlocks)) { // Don't recalculate static spawns if nothing has changed + return; + } + this.loadedGridBlocks = loadedGridBlocks; + var spawnLists = GameDepot.getSpawnLists(); + Set visible = new HashSet<>(); + for (var block : loadedGridBlocks) { + var spawns = spawnLists.get(block); + if (spawns != null) { + visible.addAll(spawns); + } + } + + // World level + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getWorld().getWorldLevel()); + int worldLevelOverride = 0; + + if (worldLevelData != null) { + worldLevelOverride = worldLevelData.getMonsterLevel(); + } + + // Todo + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + var spawnedEntities = this.getSpawnedEntities(); + for (SpawnDataEntry entry : visible) { + // If spawn entry is in our view and hasnt been spawned/killed yet, we should spawn it + if (!spawnedEntities.contains(entry) && !this.getDeadSpawnedEntities().contains(entry)) { + // Entity object holder + GameEntity entity = null; + + // Check if spawn entry is monster or gadget + if (entry.getMonsterId() > 0) { + MonsterData data = GameData.getMonsterDataMap().get(entry.getMonsterId()); + if (data == null) continue; + + int level = this.getEntityLevel(entry.getLevel(), worldLevelOverride); + + EntityMonster monster = new EntityMonster(this, data, entry.getPos(), level); + monster.getRotation().set(entry.getRot()); + monster.setGroupId(entry.getGroup().getGroupId()); + monster.setPoseId(entry.getPoseId()); + monster.setConfigId(entry.getConfigId()); + monster.setSpawnEntry(entry); + + entity = monster; + } else if (entry.getGadgetId() > 0) { + EntityGadget gadget = + new EntityGadget(this, entry.getGadgetId(), entry.getPos(), entry.getRot()); + gadget.setGroupId(entry.getGroup().getGroupId()); + gadget.setConfigId(entry.getConfigId()); + gadget.setSpawnEntry(entry); + int state = entry.getGadgetState(); + if (state > 0) { + gadget.setState(state); + } + gadget.buildContent(); + + gadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY); + gadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY); + gadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY); + + entity = gadget; + blossomManager.initBlossom(gadget); + } + + if (entity == null) continue; + + // Add to scene and spawned list + toAdd.add(entity); + spawnedEntities.add(entry); + } + } + + for (GameEntity entity : this.getEntities().values()) { + var spawnEntry = entity.getSpawnEntry(); + if (spawnEntry != null && !visible.contains(spawnEntry)) { + toRemove.add(entity); + spawnedEntities.remove(spawnEntry); + } + } + + if (toAdd.size() > 0) { + toAdd.forEach(this::addEntityDirectly); + this.broadcastPacket(new PacketSceneEntityAppearNotify(toAdd, VisionType.VISION_TYPE_BORN)); + } + + if (toRemove.size() > 0) { + toRemove.forEach(this::removeEntityDirectly); + this.broadcastPacket( + new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); + blossomManager.recycleGadgetEntity(toRemove); + } + } + + public List getPlayerActiveBlocks(Player player) { + // consider the borders' entities of blocks, so we check if contains by index + return SceneIndexManager.queryNeighbors( + getScriptManager().getBlocksIndex(), + player.getPosition().toXZDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + } + + private boolean unloadBlockIfNotVisible(Collection visible, SceneBlock block) { + if (visible.contains(block)) return false; + this.onUnloadBlock(block); + return true; + } + + public synchronized boolean loadBlock(SceneBlock block) { + if (this.loadedBlocks.contains(block)) return false; + + this.onLoadBlock(block, this.players); + this.loadedBlocks.add(block); + return true; + } + + public synchronized void checkBlocks() { + Set visible = + this.players.stream() + .map(this::getPlayerActiveBlocks) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + this.loadedBlocks.removeIf(block -> unloadBlockIfNotVisible(visible, block)); + visible.stream() + .filter(block -> !this.loadBlock(block)) + .forEach( + block -> { + // dynamic load the groups for players in a loaded block + var toLoad = + this.players.stream() + .filter(p -> block.contains(p.getPosition())) + .map(p -> this.playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + this.onLoadGroup(toLoad); + }); + } + + public List playerMeetGroups(Player player, SceneBlock block) { + List sceneGroups = + SceneIndexManager.queryNeighbors( + block.sceneGroupIndex, + player.getPosition().toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + + List groups = + sceneGroups.stream() + .filter( + group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group)) + .peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group)) + .toList(); + + if (groups.size() == 0) { + return List.of(); + } + + return groups; + } + + public void onLoadBlock(SceneBlock block, List players) { + this.getScriptManager().loadBlockFromScript(block); + scriptManager.getLoadedGroupSetPerBlock().put(block.id, new HashSet<>()); + + // the groups form here is not added in current scene + var groups = + players.stream() + .filter(player -> block.contains(player.getPosition())) + .map(p -> playerMeetGroups(p, block)) + .flatMap(Collection::stream) + .toList(); + + onLoadGroup(groups); + Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + } + + public int loadDynamicGroup(int group_id) { + SceneGroup group = getScriptManager().getGroupById(group_id); + if (group == null || getScriptManager().getGroupInstanceById(group_id) != null) + return -1; // Group not found or already instanced + + onLoadGroup(new ArrayList<>(List.of(group))); + + if (GameData.getGroupReplacements().containsKey(group_id)) onRegisterGroups(); + + if (group.init_config == null) return -1; + return group.init_config.suite; + } + + public boolean unregisterDynamicGroup(int groupId) { + var group = getScriptManager().getGroupById(groupId); + if (group == null) return false; + + var block = getScriptManager().getBlocks().get(group.block_id); + this.unloadGroup(block, groupId); + return true; + } + + public void onRegisterGroups() { + var sceneGroups = this.loadedGroups; + var sceneGroupMap = + sceneGroups.stream().collect(Collectors.toMap(item -> item.id, item -> item)); + var sceneGroupsIds = sceneGroups.stream().map(group -> group.id).toList(); + var dynamicGroups = + sceneGroups.stream().filter(group -> group.dynamic_load).map(group -> group.id).toList(); + + // Create the graph + var nodes = new ArrayList(); + var groupList = new ArrayList(); + GameData.getGroupReplacements().values().stream() + .filter(replacement -> dynamicGroups.contains(replacement.id)) + .forEach( + replacement -> { + Grasscutter.getLogger().info("Graph ordering replacement {}", replacement); + replacement.replace_groups.forEach( + group -> { + nodes.add(new KahnsSort.Node(replacement.id, group)); + if (!groupList.contains(group)) groupList.add(group); + }); + + if (!groupList.contains(replacement.id)) groupList.add(replacement.id); + }); + + KahnsSort.Graph graph = new KahnsSort.Graph(nodes, groupList); + List dynamicGroupsOrdered = KahnsSort.doSort(graph); + if (dynamicGroupsOrdered == null) throw new RuntimeException("Invalid group replacement graph"); + + // Now we can start unloading and loading groups :D + dynamicGroupsOrdered.forEach( + group -> { + if (GameData.getGroupReplacements().containsKey((int) group)) { // isGroupJoinReplacement + var data = GameData.getGroupReplacements().get((int) group); + var sceneGroupReplacement = + this.loadedGroups.stream().filter(g -> g.id == group).findFirst().orElseThrow(); + if (sceneGroupReplacement.is_replaceable != null) { + var it = data.replace_groups.iterator(); + while (it.hasNext()) { + var replace_group = it.next(); + if (!sceneGroupsIds.contains(replace_group)) continue; + + // Check if we can replace this group + SceneGroup sceneGroup = sceneGroupMap.get(replace_group); + if (sceneGroup != null + && sceneGroup.is_replaceable != null + && ((sceneGroup.is_replaceable.value + && sceneGroup.is_replaceable.version + <= sceneGroupReplacement.is_replaceable.version) + || sceneGroup.is_replaceable.new_bin_only)) { + this.unloadGroup( + scriptManager.getBlocks().get(sceneGroup.block_id), replace_group); + it.remove(); + Grasscutter.getLogger().info("Graph ordering: unloaded {}", replace_group); + } + } + } + } + }); + } + + public void loadTriggerFromGroup(SceneGroup group, String triggerName) { + // Load triggers and regions + this.getScriptManager() + .registerTrigger( + group.triggers.values().stream() + .filter(p -> p.getName().contains(triggerName)) + .toList()); + group.regions.values().stream() + .filter(q -> q.config_id == Integer.parseInt(triggerName.substring(13))) + .map(region -> new EntityRegion(this, region)) + .forEach(getScriptManager()::registerRegion); + } + + public void onLoadGroup(List groups) { + if (groups == null || groups.isEmpty()) { + return; + } + + for (var group : groups) { + if (this.loadedGroups.contains(group)) continue; + + // We load the script files for the groups here + this.getScriptManager().loadGroupFromScript(group); + if (!this.scriptManager.getLoadedGroupSetPerBlock().containsKey(group.block_id)) + this.onLoadBlock(scriptManager.getBlocks().get(group.block_id), players); + this.scriptManager.getLoadedGroupSetPerBlock().get(group.block_id).add(group); + } + + // Spawn gadgets AFTER triggers are added + // TODO + var entities = new ArrayList(); + for (var group : groups) { + if (this.loadedGroups.contains(group)) continue; + + if (group.init_config == null) { + continue; + } + + var groupInstance = this.getScriptManager().getGroupInstanceById(group.id); + var cachedInstance = this.getScriptManager().getCachedGroupInstanceById(group.id); + if (cachedInstance != null) { + cachedInstance.setLuaGroup(group); + groupInstance = cachedInstance; + } + + // Load garbages + var garbageGadgets = group.getGarbageGadgets(); + + if (garbageGadgets != null) { + entities.addAll( + garbageGadgets.stream() + .map(g -> scriptManager.createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList()); + } + + // Load suites + // int suite = group.findInitSuiteIndex(0); + this.getScriptManager() + .refreshGroup(groupInstance, 0, false); // This is what the official server does + + this.loadedGroups.add(group); + } + + this.scriptManager.meetEntities(entities); + groups.forEach( + g -> scriptManager.callEvent(new ScriptArgs(g.id, EventType.EVENT_GROUP_LOAD, g.id))); + + Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); + } + + public void onUnloadBlock(SceneBlock block) { + List toRemove = + this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList(); + + if (toRemove.size() > 0) { + toRemove.forEach(this::removeEntityDirectly); + this.broadcastPacket( + new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); + } + + for (SceneGroup group : block.groups.values()) { + if (group.triggers != null) { + group.triggers.values().forEach(getScriptManager()::deregisterTrigger); + } + if (group.regions != null) { + group.regions.values().forEach(getScriptManager()::deregisterRegion); + } + } + scriptManager.getLoadedGroupSetPerBlock().remove(block.id); + Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); + } + + /** + * Unloads a Lua group. + * + * @param block The block that contains the group. + * @param groupId The group ID. + */ + public void unloadGroup(SceneBlock block, int groupId) { + var toRemove = + this.getEntities().values().stream() + .filter(e -> e != null && (e.getBlockId() == block.id && e.getGroupId() == groupId)) + .toList(); + + if (toRemove.size() > 0) { + toRemove.forEach(this::removeEntityDirectly); + this.broadcastPacket( + new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); + } + + var group = block.groups.get(groupId); + if (group.triggers != null) { + group.triggers.values().forEach(this.getScriptManager()::deregisterTrigger); + } + if (group.regions != null) { + group.regions.values().forEach(this.getScriptManager()::deregisterRegion); + } + + this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group); + this.loadedGroups.remove(group); + + if (this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).isEmpty()) { + this.scriptManager.getLoadedGroupSetPerBlock().remove(block.id); + Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); + } + + this.broadcastPacket(new PacketGroupUnloadNotify(List.of(groupId))); + this.scriptManager.unregisterGroup(group); + } + + // Gadgets + + public void onPlayerCreateGadget(EntityClientGadget gadget) { + // Directly add + this.addEntityDirectly(gadget); + + // Add to owner's gadget list + gadget.getOwner().getTeamManager().getGadgets().add(gadget); + + // Optimization + if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) { + return; + } + + this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityAppearNotify(gadget)); + } + + public void onPlayerDestroyGadget(int entityId) { + GameEntity entity = getEntities().get(entityId); + + if (!(entity instanceof EntityClientGadget gadget)) { + return; + } + + // Get and remove entity + this.removeEntityDirectly(gadget); + + // Remove from owner's gadget list + gadget.getOwner().getTeamManager().getGadgets().remove(gadget); + + // Optimization + if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) { + return; + } + + this.broadcastPacketToOthers( + gadget.getOwner(), + new PacketSceneEntityDisappearNotify(gadget, VisionType.VISION_TYPE_DIE)); + } + + // Broadcasting + + public void broadcastPacket(BasePacket packet) { + // Send to all players - might have to check if player has been sent data packets + for (Player player : this.getPlayers()) { + player.getSession().send(packet); + } + } + + public void broadcastPacketToOthers(Player excludedPlayer, BasePacket packet) { + // Optimization + if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == excludedPlayer) { + return; + } + // Send to all players - might have to check if player has been sent data packets + for (Player player : this.getPlayers()) { + if (player == excludedPlayer) { + continue; + } + // Send + player.getSession().send(packet); + } + } + + public void addItemEntity(int itemId, int amount, GameEntity bornForm) { + ItemData itemData = GameData.getItemDataMap().get(itemId); + if (itemData == null) { + return; + } + if (itemData.isEquip()) { + float range = (1.5f + (.05f * amount)); + for (int i = 0; i < amount; i++) { + Position pos = bornForm.getPosition().nearby2d(range).addZ(.9f); // Why Z? + EntityItem entity = new EntityItem(this, null, itemData, pos, 1); + addEntity(entity); + } + } else { + EntityItem entity = + new EntityItem( + this, null, itemData, bornForm.getPosition().clone().addZ(.9f), amount); // Why Z? + addEntity(entity); + } + } + + public void loadNpcForPlayerEnter(Player player) { + this.npcBornEntrySet.addAll(loadNpcForPlayer(player)); + } + + private List loadNpcForPlayer(Player player) { + var pos = player.getPosition(); + var data = GameData.getSceneNpcBornData().get(getId()); + if (data == null) { + return List.of(); + } + + var npcList = + SceneIndexManager.queryNeighbors( + data.getIndex(), + pos.toDoubleArray(), + Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); + + var sceneNpcBornEntries = + npcList.stream().filter(i -> !this.npcBornEntrySet.contains(i)).toList(); + + if (sceneNpcBornEntries.size() > 0) { + this.broadcastPacket(new PacketGroupSuiteNotify(sceneNpcBornEntries)); + Grasscutter.getLogger().debug("Loaded Npc Group Suite {}", sceneNpcBornEntries); + } + return npcList; + } + + public void loadGroupForQuest(List sceneGroupSuite) { + if (!scriptManager.isInit()) { + return; + } + + sceneGroupSuite.forEach( + i -> { + var group = scriptManager.getGroupById(i.getGroup()); + if (group == null) return; + + var groupInstance = scriptManager.getGroupInstanceById(i.getGroup()); + var suite = group.getSuiteByIndex(i.getSuite()); + if (suite == null || groupInstance == null) { + return; + } + + scriptManager.refreshGroup(groupInstance, i.getSuite(), false); + }); + } + + /** + * Adds an unlocked force to the scene. + * + * @param force The ID of the force to unlock. + */ + public void unlockForce(int force) { + this.unlockedForces.add(force); + this.broadcastPacket(new PacketSceneForceUnlockNotify(force, true)); + } + + /** + * Removes an unlocked force from the scene. + * + * @param force The ID of the force to lock. + */ + public void lockForce(int force) { + this.unlockedForces.remove(force); + this.broadcastPacket(new PacketSceneForceLockNotify(force)); + } + + public void selectWorktopOptionWith(SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq req) { + GameEntity entity = getEntityById(req.getGadgetEntityId()); + if (entity == null) { + return; + } + // Handle + if (entity instanceof EntityGadget gadget) { + if (gadget.getContent() instanceof GadgetWorktop worktop) { + boolean shouldDelete = worktop.onSelectWorktopOption(req); + if (shouldDelete) { + entity.getScene().removeEntity(entity, VisionType.VISION_TYPE_REMOVE); + } + } + } + } +} diff --git a/src/main/java/emu/grasscutter/game/world/SceneGroupInstance.java b/src/main/java/emu/grasscutter/game/world/SceneGroupInstance.java index 691e2fde1..3043eefee 100644 --- a/src/main/java/emu/grasscutter/game/world/SceneGroupInstance.java +++ b/src/main/java/emu/grasscutter/game/world/SceneGroupInstance.java @@ -1,86 +1,85 @@ -package emu.grasscutter.game.world; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.bson.types.ObjectId; - -import dev.morphia.annotations.Entity; -import dev.morphia.annotations.Id; -import dev.morphia.annotations.Indexed; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.scripts.data.SceneGadget; -import emu.grasscutter.scripts.data.SceneGroup; -import lombok.Getter; -import lombok.Setter; - -@Entity(value = "group_instances", useDiscriminator = false) -public final class SceneGroupInstance { - @Id private ObjectId id; - - @Indexed private int ownerUid; //This group is owned by the host player - @Getter private int groupId; - - @Getter private transient SceneGroup luaGroup; - @Getter @Setter private int targetSuiteId; - @Getter @Setter private int activeSuiteId; - @Getter private Set deadEntities; //Config_ids - private boolean isCached; - - @Getter private Map cachedGadgetStates; - @Getter private Map cachedVariables; - - @Getter @Setter private int lastTimeRefreshed; - - public SceneGroupInstance(SceneGroup group, Player owner) { - this.luaGroup = group; - this.groupId = group.id; - this.targetSuiteId = 0; - this.activeSuiteId = 0; - this.lastTimeRefreshed = 0; - this.ownerUid = owner.getUid(); - this.deadEntities = new HashSet<>(); - this.cachedGadgetStates = new ConcurrentHashMap<>(); - this.cachedVariables = new ConcurrentHashMap<>(); - - this.isCached = false; //This is true when the group is not loaded on scene but caches suite data - } - - @Deprecated // Morphia only! - SceneGroupInstance(){ - this.cachedVariables = new ConcurrentHashMap<>(); - this.deadEntities = new HashSet<>(); - this.cachedGadgetStates = new ConcurrentHashMap<>(); - } - - public void setLuaGroup(SceneGroup group) { - this.luaGroup = group; - this.groupId = group.id; - } - - public boolean isCached() { - return this.isCached; - } - - public void setCached(boolean value) { - this.isCached = value; - save(); //Save each time a group is registered or unregistered - } - - public void cacheGadgetState(SceneGadget g, int state) { - if(g.persistent) //Only cache when is persistent - cachedGadgetStates.put(g.config_id, state); - } - - public int getCachedGadgetState(SceneGadget g) { - Integer state = cachedGadgetStates.getOrDefault(g.config_id, null); - return (state == null) ? g.state : state; - } - - public void save() { - DatabaseHelper.saveGroupInstance(this); - } -} +package emu.grasscutter.game.world; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Indexed; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.scripts.data.SceneGadget; +import emu.grasscutter.scripts.data.SceneGroup; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import lombok.Setter; +import org.bson.types.ObjectId; + +@Entity(value = "group_instances", useDiscriminator = false) +public final class SceneGroupInstance { + @Id private ObjectId id; + + @Indexed private int ownerUid; // This group is owned by the host player + @Getter private int groupId; + + @Getter private transient SceneGroup luaGroup; + @Getter @Setter private int targetSuiteId; + @Getter @Setter private int activeSuiteId; + @Getter private Set deadEntities; // Config_ids + private boolean isCached; + + @Getter private Map cachedGadgetStates; + @Getter private Map cachedVariables; + + @Getter @Setter private int lastTimeRefreshed; + + public SceneGroupInstance(SceneGroup group, Player owner) { + this.luaGroup = group; + this.groupId = group.id; + this.targetSuiteId = 0; + this.activeSuiteId = 0; + this.lastTimeRefreshed = 0; + this.ownerUid = owner.getUid(); + this.deadEntities = new HashSet<>(); + this.cachedGadgetStates = new ConcurrentHashMap<>(); + this.cachedVariables = new ConcurrentHashMap<>(); + + this.isCached = + false; // This is true when the group is not loaded on scene but caches suite data + } + + @Deprecated // Morphia only! + SceneGroupInstance() { + this.cachedVariables = new ConcurrentHashMap<>(); + this.deadEntities = new HashSet<>(); + this.cachedGadgetStates = new ConcurrentHashMap<>(); + } + + public void setLuaGroup(SceneGroup group) { + this.luaGroup = group; + this.groupId = group.id; + } + + public boolean isCached() { + return this.isCached; + } + + public void setCached(boolean value) { + this.isCached = value; + save(); // Save each time a group is registered or unregistered + } + + public void cacheGadgetState(SceneGadget g, int state) { + if (g.persistent) // Only cache when is persistent + cachedGadgetStates.put(g.config_id, state); + } + + public int getCachedGadgetState(SceneGadget g) { + Integer state = cachedGadgetStates.getOrDefault(g.config_id, null); + return (state == null) ? g.state : state; + } + + public void save() { + DatabaseHelper.saveGroupInstance(this); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index d694e5b67..d2e8caec3 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -1,968 +1,1144 @@ -package emu.grasscutter.scripts; - -import com.github.davidmoten.rtreemulti.RTree; -import com.github.davidmoten.rtreemulti.geometry.Geometry; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.server.Grid; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.entity.*; -import emu.grasscutter.game.entity.gadget.platform.BaseRoute; -import emu.grasscutter.game.props.EntityType; -import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.QuestGroupSuite; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.game.world.SceneGroupInstance; -import emu.grasscutter.net.proto.VisionTypeOuterClass; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.*; -import emu.grasscutter.scripts.service.ScriptMonsterSpawnService; -import emu.grasscutter.scripts.service.ScriptMonsterTideService; -import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.GridPosition; -import emu.grasscutter.utils.JsonUtils; -import emu.grasscutter.utils.Position; -import io.netty.util.concurrent.FastThreadLocalThread; -import kotlin.Pair; -import lombok.val; - -import org.luaj.vm2.LuaError; -import org.luaj.vm2.LuaValue; -import org.luaj.vm2.lib.jse.CoerceJavaToLua; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import java.io.FileWriter; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import static emu.grasscutter.scripts.constants.EventType.*; - -public class SceneScriptManager { - private final Scene scene; - private final Map variables; - private SceneMeta meta; - private boolean isInit; - /** - * current triggers controlled by RefreshGroup - */ - private final Map> currentTriggers; - private final Map> triggersByGroupScene; - private final Map>> activeGroupTimers; - private final Map triggerInvocations; - private final Map regions; // - private final Map sceneGroups; - private final Map sceneGroupsInstances; - private final Map cachedSceneGroupsInstances; - private ScriptMonsterTideService scriptMonsterTideService; - private ScriptMonsterSpawnService scriptMonsterSpawnService; - /** - * blockid - loaded groupSet - */ - private final Map> loadedGroupSetPerBlock; - private List groupGrids; - public static final ExecutorService eventExecutor; - static { - eventExecutor = new ThreadPoolExecutor(4, 4, - 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000), - FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy()); - } - public SceneScriptManager(Scene scene) { - this.scene = scene; - this.currentTriggers = new ConcurrentHashMap<>(); - this.triggersByGroupScene = new ConcurrentHashMap<>(); - this.activeGroupTimers = new ConcurrentHashMap<>(); - this.triggerInvocations = new ConcurrentHashMap<>(); - - this.regions = new ConcurrentHashMap<>(); - this.variables = new ConcurrentHashMap<>(); - this.sceneGroups = new ConcurrentHashMap<>(); - this.sceneGroupsInstances = new ConcurrentHashMap<>(); - this.cachedSceneGroupsInstances = new ConcurrentHashMap<>(); - this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); - this.loadedGroupSetPerBlock = new ConcurrentHashMap<>(); - this.groupGrids = null; //This is changed on init - - // TEMPORARY - if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) { - return; - } - - // Create - this.init(); - } - - public Scene getScene() { - return scene; - } - - public List getGroupGrids() { - return groupGrids; - } - - public SceneConfig getConfig() { - return this.isInit ? this.meta.config : null; - } - - public Map getBlocks() { - return meta.blocks; - } - - @Nullable - public Map getVariables(int group_id) { - if(getCachedGroupInstanceById(group_id) == null) return null; - return getCachedGroupInstanceById(group_id).getCachedVariables(); - } - - public Set getTriggersByEvent(int eventId) { - return currentTriggers.computeIfAbsent(eventId, e -> ConcurrentHashMap.newKeySet()); - } - public int getTriggerCount() { - return currentTriggers.size(); - } - public void registerTrigger(List triggers) { - triggers.forEach(this::registerTrigger); - } - public void registerTrigger(SceneTrigger trigger) { - triggerInvocations.put(trigger.getName(), new AtomicInteger(0)); - getTriggersByEvent(trigger.getEvent()).add(trigger); - Grasscutter.getLogger().debug("Registered trigger {}", trigger.getName()); - } - - public void deregisterTrigger(List triggers) { - triggers.forEach(this::deregisterTrigger); - } - public void deregisterTrigger(SceneTrigger trigger) { - getTriggersByEvent(trigger.getEvent()).remove(trigger); - Grasscutter.getLogger().debug("deregistered trigger {}", trigger.getName()); - } - - public void resetTriggers(int eventId) { - currentTriggers.put(eventId, ConcurrentHashMap.newKeySet()); - } - - public void resetTriggersForGroupSuite(SceneGroup group, int suiteIndex) { - Grasscutter.getLogger().debug("reset triggers for group {} suite {}", group.id, suiteIndex); - var suite = group.getSuiteByIndex(suiteIndex); - if (suite == null) { - Grasscutter.getLogger().warn("Trying to load null suite Triggers for group {} with suiteindex {}", group.id, suiteIndex); - return; - } - - var groupSceneTriggers = triggersByGroupScene.get(group.id+"_"+suiteIndex); - if(groupSceneTriggers == null){ - groupSceneTriggers = new HashSet<>(); - } - - if(!groupSceneTriggers.isEmpty()) { - for (var trigger : groupSceneTriggers) { - currentTriggers.get(trigger.getEvent()).remove(trigger); - } - groupSceneTriggers.clear(); - } - - if (!suite.sceneTriggers.isEmpty()) { - groupSceneTriggers.addAll(suite.sceneTriggers); - for (var trigger : groupSceneTriggers) { - registerTrigger(trigger); - /*this.currentTriggers.computeIfAbsent(trigger.event, k -> ConcurrentHashMap.newKeySet()) - .add(trigger);*/ - } - } - triggersByGroupScene.put(group.id+"_"+suiteIndex, groupSceneTriggers); - } - - public void refreshGroup(SceneGroupInstance groupInstance) { - if(groupInstance == null || groupInstance.getLuaGroup().suites==null){ - return; - } - //for (int i = 1; i<= group.suites.size();i++){ - //refreshGroup(group, i); - refreshGroup(groupInstance, groupInstance.getActiveSuiteId(), false); //Refresh the last group triggers - //} - } - public int refreshGroup(SceneGroupInstance groupInstance, int suiteIndex, boolean excludePrevSuite) { - SceneGroup group = groupInstance.getLuaGroup(); - if(suiteIndex == 0) { - if(excludePrevSuite) { - suiteIndex = group.findInitSuiteIndex(groupInstance.getActiveSuiteId()); - } else - suiteIndex = group.findInitSuiteIndex(0); - } - if(suiteIndex == 0) return 0; - - var suiteData = group.getSuiteByIndex(suiteIndex); - if (suiteData == null) { - Grasscutter.getLogger().warn("Group {} suite {} not found", group.id, suiteIndex); - return 0; - } - - int prevSuiteIndex = groupInstance.getActiveSuiteId(); - boolean waitForOne = false; - SceneSuite prevSuiteData = null; - if(prevSuiteIndex != 0) { - prevSuiteData = group.getSuiteByIndex(prevSuiteIndex); - if (prevSuiteData != null) { - if(prevSuiteData.ban_refresh && !suiteData.ban_refresh) { - waitForOne = true; - } - } - } - - if(waitForOne && (groupInstance.getTargetSuiteId() == 0 || prevSuiteIndex != groupInstance.getTargetSuiteId())) { - groupInstance.setTargetSuiteId(suiteIndex); - Grasscutter.getLogger().debug("Group {} suite {} wating one more refresh", group.id, suiteIndex); - return 0; - } - - groupInstance.setTargetSuiteId(0); - - if(prevSuiteData != null) { - removeGroupSuite(group, prevSuiteData); - } //Remove old group suite - - addGroupSuite(groupInstance, suiteData); - - //Refesh variables here - group.variables.forEach(variable -> { - if(!variable.no_refresh) - groupInstance.getCachedVariables().put(variable.name, variable.value); - }); - - groupInstance.setActiveSuiteId(suiteIndex); - groupInstance.setLastTimeRefreshed(getScene().getWorld().getGameTime()); - return suiteIndex; - } - - public boolean refreshGroupSuite(int groupId, int suiteId, GameQuest quest) { - var targetGroupInstance = getGroupInstanceById(groupId); - if (targetGroupInstance == null) { - getGroupById(groupId); //Load the group, this ensures an instance is created and the if neccesary unloaded, but the suite data is stored - targetGroupInstance = getGroupInstanceById(groupId); - Grasscutter.getLogger().debug("trying to regresh group suite {} in an unloaded and uncached group {} in scene {}", suiteId, groupId, getScene().getId()); - } else { - Grasscutter.getLogger().debug("Refreshing group {} suite {}", groupId, suiteId); - suiteId = refreshGroup(targetGroupInstance, suiteId, false); //If suiteId is zero, the value of suiteId changes - quest.getOwner().sendPacket(new PacketGroupSuiteNotify(groupId, suiteId)); - } - - if(suiteId != 0 && quest != null) { - quest.getMainQuest().getQuestGroupSuites().add(QuestGroupSuite.of() - .scene(getScene().getId()) - .group(groupId) - .suite(suiteId) - .build()); - } - - return true; - } - - public boolean refreshGroupMonster(int groupId) { - var groupInstance = getGroupInstanceById(groupId); - if (groupInstance == null) { - Grasscutter.getLogger().warn("trying to refesh monster group in unloaded and uncached group {} in scene {}", groupId, getScene().getId()); - return false; - } - - var group = groupInstance.getLuaGroup(); - var monstersToSpawn = group.monsters.values().stream() - .filter(m -> { - var entity = scene.getEntityByConfigId(m.config_id); - return (entity == null || entity.getGroupId()!=group.id);/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities - }) - .map(mob -> createMonster(group.id, group.block_id, mob)) - .toList();//TODO check if it interferes with bigworld or anything else - this.addEntities(monstersToSpawn); - - return true; - } - public EntityRegion getRegionById(int id) { - return regions.get(id); - } - - public void registerRegion(EntityRegion region) { - regions.put(region.getId(), region); - Grasscutter.getLogger().debug("Registered region {} from group {}", region.getMetaRegion().config_id, region.getGroupId()); - } - public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite) { - suite.sceneRegions.stream().map(region -> new EntityRegion(this.getScene(), region)) - .forEach(this::registerRegion); - } - public synchronized void deregisterRegion(SceneRegion region) { - var instance = regions.values().stream() - .filter(r -> r.getConfigId() == region.config_id) - .findFirst(); - instance.ifPresent(entityRegion -> regions.remove(entityRegion.getId())); - } - - public Map> getLoadedGroupSetPerBlock() { - return loadedGroupSetPerBlock; - } - - // TODO optimize - public SceneGroup getGroupById(int groupId) { - for (var block : getBlocks().values()) { - this.getScene().loadBlock(block); - - var group = block.groups.get(groupId); - if (group == null) { - continue; - } - - if (!this.sceneGroupsInstances.containsKey(groupId)) { - this.getScene().onLoadGroup(List.of(group)); - this.getScene().onRegisterGroups(); - } - return group; - } - return null; - } - - public SceneGroupInstance getGroupInstanceById(int groupId) { - return sceneGroupsInstances.getOrDefault(groupId, null); - } - - public Map getCachedGroupInstances() { - return cachedSceneGroupsInstances; - } - - public SceneGroupInstance getCachedGroupInstanceById(int groupId) { - var instance = cachedSceneGroupsInstances.getOrDefault(groupId, null); - if(instance == null) { - instance = DatabaseHelper.loadGroupInstance(groupId, scene.getWorld().getHost()); - if(instance != null) cachedSceneGroupsInstances.put(groupId, instance); - } - - return instance; - } - - private static void addGridPositionToMap(Map> map, int group_id, int vision_level, Position position) { - //Convert position to grid position - GridPosition gridPos; - int width = Grasscutter.getConfig().server.game.visionOptions[vision_level].gridWidth; - gridPos = new GridPosition((int)(position.getX() / width), (int)(position.getZ() / width), width); - - Set groups = map.getOrDefault(gridPos, new HashSet<>()); - groups.add(group_id); - map.put(gridPos, groups); - } - - private static int getGadgetVisionLevel(int gadget_id) { - var gadget = GameData.getGadgetDataMap().get(gadget_id); - if(gadget == null || gadget.getVisionLevel() == null) return 0; - - var visionOptions = Grasscutter.getConfig().server.game.visionOptions; - for(int i = 0; i < visionOptions.length; i++) - if(visionOptions[i].name.compareTo(gadget.getVisionLevel()) == 0) { - return i; - } - - return 0; - } - - private void init() { - var meta = ScriptLoader.getSceneMeta(getScene().getId()); - if (meta == null) { - return; - } - this.meta = meta; - - var path = FileUtils.getScriptPath("Scene/" + getScene().getId() + "/scene_grid.json"); - - try { - this.groupGrids = JsonUtils.loadToList(path, Grid.class); - } catch (IOException ignored) { - Grasscutter.getLogger().error("Scene {} unable to load grid file.", getScene().getId()); - } catch (Exception e) { - Grasscutter.getLogger().error("Scene {} unable to load grid file.", e, getScene().getId()); - } - - boolean runForFirstTime = this.groupGrids == null; - - //Find if the scene entities are already generated, if not generate it - if(Grasscutter.getConfig().server.game.cacheSceneEntitiesEveryRun || runForFirstTime) { - List>> groupPositions = new ArrayList<>(); - for(int i = 0; i < 6; i++) groupPositions.add(new HashMap<>()); - - var visionOptions = Grasscutter.getConfig().server.game.visionOptions; - meta.blocks.values().forEach(block -> { - block.load(scene.getId(), meta.context); - block.groups.values().stream().filter(g -> !g.dynamic_load).forEach(group -> { - group.load(this.scene.getId()); - - //Add all entitites here - Set vision_levels = new HashSet<>(); - group.monsters.values().forEach(m -> { - addGridPositionToMap(groupPositions.get(m.vision_level), group.id, m.vision_level, m.pos); - vision_levels.add(m.vision_level); - }); - group.gadgets.values().forEach(g -> { - int vision_level = Math.max(getGadgetVisionLevel(g.gadget_id), g.vision_level); - addGridPositionToMap(groupPositions.get(vision_level), group.id, vision_level, g.pos); - vision_levels.add(vision_level); - }); - group.npcs.values().forEach(n -> addGridPositionToMap(groupPositions.get(n.vision_level), group.id, n.vision_level, n.pos)); - group.regions.values().forEach(r -> addGridPositionToMap(groupPositions.get(0), group.id, 0, r.pos)); - if(group.garbages != null && group.garbages.gadgets != null) group.garbages.gadgets.forEach(g -> addGridPositionToMap(groupPositions.get(g.vision_level), group.id, g.vision_level, g.pos)); - - int max_vision_level = -1; - if(!vision_levels.isEmpty()) { - for(int vision_level : vision_levels) { - if(max_vision_level == -1 || visionOptions[max_vision_level].visionRange < visionOptions[vision_level].visionRange) - max_vision_level = vision_level; - } - } - if(max_vision_level == -1) max_vision_level = 0; - - addGridPositionToMap(groupPositions.get(max_vision_level), group.id, max_vision_level, group.pos); - }); - }); - - this.groupGrids = new ArrayList<>(); - for(int i = 0; i < 6; i++) { - this.groupGrids.add(new Grid()); - this.groupGrids.get(i).grid = groupPositions.get(i); - } - - try (FileWriter file = new FileWriter(path.toFile())) { - file.write(JsonUtils.encode(groupGrids)); - } catch (IOException ignored) { - Grasscutter.getLogger().error("Scene {} unable to write to grid file.", getScene().getId()); - } catch (Exception e) { - Grasscutter.getLogger().error("Scene {} unable to save grid file.", e, getScene().getId()); - } - - Grasscutter.getLogger().info("Scene {} saved grid file.", getScene().getId()); - } - - // TEMP - this.isInit = true; - } - - public boolean isInit() { - return isInit; - } - - public void loadBlockFromScript(SceneBlock block) { - block.load(scene.getId(), meta.context); - } - - public void loadGroupFromScript(SceneGroup group) { - group.load(getScene().getId()); - - this.sceneGroups.put(group.id, group); - if(this.getCachedGroupInstanceById(group.id) != null) { - this.sceneGroupsInstances.put(group.id, this.cachedSceneGroupsInstances.get(group.id)); - this.cachedSceneGroupsInstances.get(group.id).setCached(false); - this.cachedSceneGroupsInstances.get(group.id).setLuaGroup(group); - } else { - var instance = new SceneGroupInstance(group, getScene().getWorld().getHost()); - this.sceneGroupsInstances.put(group.id, instance); - this.cachedSceneGroupsInstances.put(group.id, instance); - instance.save(); //Save the instance - } - - if (group.variables != null) { - group.variables.forEach(variable -> { - val variables = this.getVariables(group.id); - if(variables != null && !variables.containsKey(variable.name)) - variables.put(variable.name, variable.value); - }); - } - } - - public void unregisterGroup(SceneGroup group) { - this.sceneGroups.remove(group.id); - this.sceneGroupsInstances.values().removeIf(i -> i.getLuaGroup().equals(group)); - this.cachedSceneGroupsInstances.values().stream().filter(i -> i.getLuaGroup().equals(group)).forEach(s -> s.setCached(true)); - } - - public void checkRegions() { - if (this.regions.size() == 0) { - return; - } - - for (var region : this.regions.values()) { - // currently all condition_ENTER_REGION Events check for avatar, so we have no necessary to add other types of entity - var entities = getScene().getEntities().values() - .stream() - .filter(e -> e.getEntityType() == EntityType.Avatar.getValue() && region.getMetaRegion().contains(e.getPosition())) - .toList(); - entities.forEach(region::addEntity); - - int targetID = 0; - if (entities.size() > 0) { - targetID = entities.get(0).getId(); - } - - if (region.hasNewEntities()) { - Grasscutter.getLogger().trace("Call EVENT_ENTER_REGION_{}",region.getMetaRegion().config_id); - callEvent(new ScriptArgs(region.getGroupId(), EventType.EVENT_ENTER_REGION, region.getConfigId()) - .setSourceEntityId(region.getId()) - .setTargetEntityId(targetID) - ); - - region.resetNewEntities(); - } - - for (int entityId : region.getEntities()) { - if (getScene().getEntityById(entityId) == null || !region.getMetaRegion().contains(getScene().getEntityById(entityId).getPosition())) { - region.removeEntity(entityId); - - } - } - if (region.entityLeave()) { - callEvent(new ScriptArgs(region.getGroupId(), EventType.EVENT_LEAVE_REGION, region.getConfigId()) - .setSourceEntityId(region.getId()) - .setTargetEntityId(region.getFirstEntityId()) - ); - - region.resetNewEntities(); - } - } - } - - public List getGadgetsInGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { - var group = groupInstance.getLuaGroup(); - return suite.sceneGadgets.stream() - .filter(m -> { - var entity = scene.getEntityByConfigId(m.config_id); - return (entity == null || entity.getGroupId()!=group.id) && (!m.isOneoff || !m.persistent || !groupInstance.getDeadEntities().contains(m.config_id)); - }) - .map(g -> createGadget(group.id, group.block_id, g, groupInstance.getCachedGadgetState(g))) - .peek(g -> groupInstance.cacheGadgetState(g.getMetaGadget(), g.getState())) - .filter(Objects::nonNull) - .toList(); - } - public List getMonstersInGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { - var group = groupInstance.getLuaGroup(); - return suite.sceneMonsters.stream() - .filter(m -> { - var entity = scene.getEntityByConfigId(m.config_id); - return (entity == null || entity.getGroupId()!=group.id);/*&& !groupInstance.getDeadEntities().contains(entity); */ //TODO: Investigate the usage of deadEntities - }) //TODO: Add persistent monster cached data - .map(mob -> createMonster(group.id, group.block_id, mob)) - .filter(Objects::nonNull) - .toList(); - } - - public void addGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { - // we added trigger first - registerTrigger(suite.sceneTriggers); - - var group = groupInstance.getLuaGroup(); - var toCreate = new ArrayList(); - toCreate.addAll(getGadgetsInGroupSuite(groupInstance, suite)); - toCreate.addAll(getMonstersInGroupSuite(groupInstance, suite)); - addEntities(toCreate); - - registerRegionInGroupSuite(group, suite); - } - public void refreshGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { - // we added trigger first - registerTrigger(suite.sceneTriggers); - - var group = groupInstance.getLuaGroup(); - var toCreate = new ArrayList(); - toCreate.addAll(getGadgetsInGroupSuite(groupInstance, suite)); - toCreate.addAll(getMonstersInGroupSuite(groupInstance, suite)); - addEntities(toCreate); - - registerRegionInGroupSuite(group, suite); - } - public void removeGroupSuite(SceneGroup group, SceneSuite suite) { - deregisterTrigger(suite.sceneTriggers); - removeMonstersInGroup(group, suite); - removeGadgetsInGroup(group, suite); - - suite.sceneRegions.forEach(this::deregisterRegion); - } - public void killGroupSuite(SceneGroup group, SceneSuite suite) { - deregisterTrigger(suite.sceneTriggers); - - killMonstersInGroup(group, suite); - killGadgetsInGroup(group, suite); - - suite.sceneRegions.forEach(this::deregisterRegion); - } - - public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { - this.scriptMonsterTideService = - new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); - - } - public void unloadCurrentMonsterTide() { - if (this.getScriptMonsterTideService() == null) { - return; - } - this.getScriptMonsterTideService().unload(); - } - public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) { - // TODO delay - var entity = scene.getEntityByConfigId(configId); - if(entity!=null && entity.getGroupId() == group.id){ - Grasscutter.getLogger().debug("entity already exists failed in group {} with config {}", group.id, configId); - return; - } - entity = createMonster(group.id, group.block_id, group.monsters.get(configId)); - if(entity!=null){ - getScene().addEntity(entity); - } else { - Grasscutter.getLogger().warn("failed to create entity with group {} and config {}", group.id, configId); - } - } - // Events - public void callEvent(int groupId, int eventType) { - callEvent(new ScriptArgs(groupId, eventType)); - } - public void callEvent(@Nonnull ScriptArgs params) { - /** - * We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for every groups' trigger in every scene instances. - * But when callEvent is called in a ScriptLib func, it may cause NPE because the inner call cleans the ThreadLocal so that outer call could not get it. - * e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> (remove) - * So we use thread pool to clean the stack to avoid this new issue. - */ - eventExecutor.submit(() -> this.realCallEvent(params)); - } - - private void realCallEvent(@Nonnull ScriptArgs params) { - try { - ScriptLoader.getScriptLib().setSceneScriptManager(this); - int eventType = params.type; - Set relevantTriggers = new HashSet<>(); - if (eventType == EventType.EVENT_ENTER_REGION || eventType == EventType.EVENT_LEAVE_REGION) { - List relevantTriggersList = this.getTriggersByEvent(eventType).stream() - .filter(p -> p.getCondition().contains(String.valueOf(params.param1)) && - (p.getSource().isEmpty() || p.getSource().equals(params.getEventSource()))).toList(); - relevantTriggers = new HashSet<>(relevantTriggersList); - } else { - relevantTriggers = this.getTriggersByEvent(eventType).stream() - .filter(t -> params.getGroupId() == 0 || t.getCurrentGroup().id == params.getGroupId()) - .collect(Collectors.toSet()); - } - for (SceneTrigger trigger : relevantTriggers) { - handleEventForTrigger(params, trigger); - } - } catch (Throwable throwable){ - Grasscutter.getLogger().error("Condition Trigger "+ params.type +" triggered exception", throwable); - } finally { - // make sure it is removed - ScriptLoader.getScriptLib().removeSceneScriptManager(); - } - } - - private boolean handleEventForTrigger(ScriptArgs params, SceneTrigger trigger ){ - Grasscutter.getLogger().debug("checking trigger {} for event {}", trigger.getName(), params.type); - try { - // setup execution - ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); - ScriptLoader.getScriptLib().setCurrentCallParams(params); - - if (evaluateTriggerCondition(trigger, params)) { - callTrigger(trigger, params); - return true; - } else { - Grasscutter.getLogger().debug("Condition Trigger {} returned false", trigger.getCondition()); - } - //TODO some ret do not bool - return false; - } - catch (Throwable ex){ - Grasscutter.getLogger().error("Condition Trigger "+trigger.getName()+" triggered exception", ex); - return false; - }finally { - ScriptLoader.getScriptLib().removeCurrentGroup(); - } - } - - private boolean evaluateTriggerCondition(SceneTrigger trigger, ScriptArgs params){ - Grasscutter.getLogger().trace("Call Condition Trigger {}, [{},{},{}]", trigger.getCondition(), params.param1, params.source_eid, params.target_eid); - LuaValue ret = this.callScriptFunc(trigger.getCondition(), trigger.currentGroup, params); - return ret.isboolean() && ret.checkboolean(); - } - - private void callTrigger(SceneTrigger trigger, ScriptArgs params){ - // the SetGroupVariableValueByGroup in tower need the param to record the first stage time - var ret = this.callScriptFunc(trigger.getAction(), trigger.currentGroup, params); - var invocationsCounter = triggerInvocations.get(trigger.getName()); - var invocations = invocationsCounter.incrementAndGet(); - Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.getAction()); - - var activeChallenge = scene.getChallenge(); - if (activeChallenge != null){ - activeChallenge.onGroupTriggerDeath(trigger); - } - - if (trigger.getEvent() == EventType.EVENT_ENTER_REGION) { - var region = this.regions.values().stream() - .filter(p -> p.getConfigId() == params.param1) - .toList().get(0); - this.getScene().getPlayers() - .forEach(p -> p.onEnterRegion(region.getMetaRegion())); - this.deregisterRegion(region.getMetaRegion()); - } else if (trigger.getEvent() == EventType.EVENT_LEAVE_REGION) { - var region = this.regions.values().stream() - .filter(p -> p.getConfigId() == params.param1) - .toList().get(0); - this.getScene().getPlayers() - .forEach(p -> p.onLeaveRegion(region.getMetaRegion())); - this.deregisterRegion(region.getMetaRegion()); - } - - if (trigger.getEvent() == EVENT_TIMER_EVENT){ - cancelGroupTimerEvent(trigger.currentGroup.id, trigger.getSource()); - } - - // always deregister on error, otherwise only if the count is reached - if (ret.isboolean() && !ret.checkboolean() || ret.isint() && ret.checkint()!=0 - || trigger.getTrigger_count()>0 && invocations >= trigger.getTrigger_count()) { - deregisterTrigger(trigger); - } - } - - private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params) { - LuaValue funcLua = null; - if (funcName != null && !funcName.isEmpty()) { - funcLua = (LuaValue) group.getBindings().get(funcName); - } - - LuaValue ret = LuaValue.TRUE; - - if (funcLua != null) { - LuaValue args = LuaValue.NIL; - - if (params != null) { - args = CoerceJavaToLua.coerce(params); - } - - ret = safetyCall(funcName, funcLua, args, group); - } - return ret; - } - - public LuaValue safetyCall(String name, LuaValue func, LuaValue args, SceneGroup group) { - try { - return func.call(ScriptLoader.getScriptLibLua(), args); - }catch (LuaError error) { - ScriptLib.logger.error("[LUA] call trigger failed in group {} with {},{}",group.id,name,args,error); - return LuaValue.valueOf(-1); - } - } - - public ScriptMonsterTideService getScriptMonsterTideService() { - return scriptMonsterTideService; - } - - public ScriptMonsterSpawnService getScriptMonsterSpawnService() { - return scriptMonsterSpawnService; - } - - public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) { - return createGadget(groupId, blockId, g, g.state); - } - - public EntityGadget createGadget(int groupId, int blockId, SceneGadget g, int state) { - if (g.isOneoff) { - var hasEntity = getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityGadget) - .filter(e -> e.getGroupId() == g.group.id) - .filter(e -> e.getConfigId() == g.config_id) - .findFirst(); - if (hasEntity.isPresent()) { - return null; - } - } - EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); - - if (entity.getGadgetData() == null) { - return null; - } - - entity.setBlockId(blockId); - entity.setConfigId(g.config_id); - entity.setGroupId(groupId); - entity.getRotation().set(g.rot); - entity.setState(state); - - entity.setPointType(g.point_type); - entity.setRouteConfig(BaseRoute.fromSceneGadget(g)); - entity.setMetaGadget(g); - entity.buildContent(); - - return entity; - } - public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) { - return new EntityNPC(getScene(), npc, blockId, suiteId); - } - public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) { - if (monster == null) { - return null; - } - - var data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - return null; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonManager() != null) { - level = getScene().getDungeonManager().getLevelForMonster(monster.config_id); - } else if (getScene().getWorld().getWorldLevel() > 0) { - var worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(groupId); - entity.setBlockId(blockId); - entity.setConfigId(monster.config_id); - entity.setPoseId(monster.pose_id); - entity.setMetaMonster(monster); - - this.getScriptMonsterSpawnService() - .onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); - - return entity; - } - - public void addEntity(GameEntity gameEntity) { - getScene().addEntity(gameEntity); - } - - public void meetEntities(List gameEntity) { - getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_TYPE_MEET); - } - - public void addEntities(List gameEntity) { - getScene().addEntities(gameEntity); - } - - public void removeEntities(List gameEntity) { - getScene().removeEntities(gameEntity.stream().map(e -> (GameEntity) e).collect(Collectors.toList()), VisionTypeOuterClass.VisionType.VISION_TYPE_REFRESH); - } - - public RTree getBlocksIndex() { - return meta.sceneBlockIndex; - } - public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { - var configSet = suite.sceneMonsters.stream() - .map(m -> m.config_id) - .collect(Collectors.toSet()); - var toRemove = getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster) - .filter(e -> e.getGroupId() == group.id) - .filter(e -> configSet.contains(e.getConfigId())) - .toList(); - - getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_TYPE_MISS); - } - public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) { - var configSet = suite.sceneGadgets.stream() - .map(m -> m.config_id) - .collect(Collectors.toSet()); - var toRemove = getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityGadget) - .filter(e -> e.getGroupId() == group.id) - .filter(e -> configSet.contains(e.getConfigId())) - .toList(); - - getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_TYPE_MISS); - } - - public void killMonstersInGroup(SceneGroup group, SceneSuite suite) { - var configSet = suite.sceneMonsters.stream() - .map(m -> m.config_id) - .collect(Collectors.toSet()); - var toRemove = getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster) - .filter(e -> e.getGroupId() == group.id) - .filter(e -> configSet.contains(e.getConfigId())) - .toList(); - - toRemove.forEach(getScene()::killEntity); - } - public void killGadgetsInGroup(SceneGroup group, SceneSuite suite) { - var configSet = suite.sceneGadgets.stream() - .map(m -> m.config_id) - .collect(Collectors.toSet()); - var toRemove = getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityGadget) - .filter(e -> e.getGroupId() == group.id) - .filter(e -> configSet.contains(e.getConfigId())) - .toList(); - - toRemove.forEach(getScene()::killEntity); - } - - public int createGroupTimerEvent(int groupID, String source, double time) { - //TODO also remove timers when refreshing and test - var group = getGroupById(groupID); - if(group == null || group.triggers == null){ - Grasscutter.getLogger().warn("trying to create a timer for unknown group with id {} and source {}", groupID, source); - return 1; - } - Grasscutter.getLogger().info("creating group timer event for group {} with source {} and time {}", - groupID, source, time); - for(SceneTrigger trigger : group.triggers.values()){ - if(trigger.getEvent() == EVENT_TIMER_EVENT &&trigger.getSource().equals(source)){ - Grasscutter.getLogger().warn("[LUA] Found timer trigger with source {} for group {} : {}", - source, groupID, trigger.getName()); - var taskIdentifier = Grasscutter.getGameServer().getScheduler().scheduleDelayedRepeatingTask(() -> - callEvent(new ScriptArgs(groupID, EVENT_TIMER_EVENT) - .setEventSource(source)), (int)time, (int)time); - var groupTasks = activeGroupTimers.computeIfAbsent(groupID, k -> new HashSet<>()); - groupTasks.add(new Pair<>(source, taskIdentifier)); - - } - } - return 0; - } - public int cancelGroupTimerEvent(int groupID, String source) { - //TODO test - var groupTimers = activeGroupTimers.get(groupID); - if(groupTimers!=null && !groupTimers.isEmpty()) - for(var timer : groupTimers){ - if(timer.component1().equals(source)){ - Grasscutter.getGameServer().getScheduler().cancelTask(timer.component2()); - return 0; - } - } - - Grasscutter.getLogger().warn("trying to cancel a timer that's not active {} {}", groupID, source); - return 1; - } - - // todo use killed monsters instead of spawned entites for check? - public boolean isClearedGroupMonsters(int groupId) { - val groupInstance = getGroupInstanceById(groupId); - if (groupInstance == null || groupInstance.getLuaGroup() == null) return false; - - val monsters = groupInstance.getLuaGroup().monsters; - - if(monsters == null || monsters.isEmpty()) return true; - - return monsters.values().stream().noneMatch(m -> { - val entity = scene.getEntityByConfigId(m.config_id); - return entity != null && entity.getGroupId() == groupId; - }); - } - - public void onDestroy(){ - activeGroupTimers.forEach((gid,times) -> times.forEach((e)->Grasscutter.getGameServer().getScheduler().cancelTask(e.getSecond()))); - activeGroupTimers.clear(); - } -} +package emu.grasscutter.scripts; + +import static emu.grasscutter.scripts.constants.EventType.*; + +import com.github.davidmoten.rtreemulti.RTree; +import com.github.davidmoten.rtreemulti.geometry.Geometry; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.server.Grid; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.entity.*; +import emu.grasscutter.game.entity.gadget.platform.BaseRoute; +import emu.grasscutter.game.props.EntityType; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestGroupSuite; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.game.world.SceneGroupInstance; +import emu.grasscutter.net.proto.VisionTypeOuterClass; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.*; +import emu.grasscutter.scripts.service.ScriptMonsterSpawnService; +import emu.grasscutter.scripts.service.ScriptMonsterTideService; +import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.GridPosition; +import emu.grasscutter.utils.JsonUtils; +import emu.grasscutter.utils.Position; +import io.netty.util.concurrent.FastThreadLocalThread; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import kotlin.Pair; +import lombok.val; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; + +public class SceneScriptManager { + private final Scene scene; + private final Map variables; + private SceneMeta meta; + private boolean isInit; + /** current triggers controlled by RefreshGroup */ + private final Map> currentTriggers; + + private final Map> triggersByGroupScene; + private final Map>> activeGroupTimers; + private final Map triggerInvocations; + private final Map regions; // + private final Map sceneGroups; + private final Map sceneGroupsInstances; + private final Map cachedSceneGroupsInstances; + private ScriptMonsterTideService scriptMonsterTideService; + private ScriptMonsterSpawnService scriptMonsterSpawnService; + /** blockid - loaded groupSet */ + private final Map> loadedGroupSetPerBlock; + + private List groupGrids; + public static final ExecutorService eventExecutor; + + static { + eventExecutor = + new ThreadPoolExecutor( + 4, + 4, + 60, + TimeUnit.SECONDS, + new LinkedBlockingDeque<>(10000), + FastThreadLocalThread::new, + new ThreadPoolExecutor.AbortPolicy()); + } + + public SceneScriptManager(Scene scene) { + this.scene = scene; + this.currentTriggers = new ConcurrentHashMap<>(); + this.triggersByGroupScene = new ConcurrentHashMap<>(); + this.activeGroupTimers = new ConcurrentHashMap<>(); + this.triggerInvocations = new ConcurrentHashMap<>(); + + this.regions = new ConcurrentHashMap<>(); + this.variables = new ConcurrentHashMap<>(); + this.sceneGroups = new ConcurrentHashMap<>(); + this.sceneGroupsInstances = new ConcurrentHashMap<>(); + this.cachedSceneGroupsInstances = new ConcurrentHashMap<>(); + this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); + this.loadedGroupSetPerBlock = new ConcurrentHashMap<>(); + this.groupGrids = null; // This is changed on init + + // TEMPORARY + if (this.getScene().getId() < 10 + && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) { + return; + } + + // Create + this.init(); + } + + public Scene getScene() { + return scene; + } + + public List getGroupGrids() { + return groupGrids; + } + + public SceneConfig getConfig() { + return this.isInit ? this.meta.config : null; + } + + public Map getBlocks() { + return meta.blocks; + } + + @Nullable public Map getVariables(int group_id) { + if (getCachedGroupInstanceById(group_id) == null) return null; + return getCachedGroupInstanceById(group_id).getCachedVariables(); + } + + public Set getTriggersByEvent(int eventId) { + return currentTriggers.computeIfAbsent(eventId, e -> ConcurrentHashMap.newKeySet()); + } + + public int getTriggerCount() { + return currentTriggers.size(); + } + + public void registerTrigger(List triggers) { + triggers.forEach(this::registerTrigger); + } + + public void registerTrigger(SceneTrigger trigger) { + triggerInvocations.put(trigger.getName(), new AtomicInteger(0)); + getTriggersByEvent(trigger.getEvent()).add(trigger); + Grasscutter.getLogger().debug("Registered trigger {}", trigger.getName()); + } + + public void deregisterTrigger(List triggers) { + triggers.forEach(this::deregisterTrigger); + } + + public void deregisterTrigger(SceneTrigger trigger) { + getTriggersByEvent(trigger.getEvent()).remove(trigger); + Grasscutter.getLogger().debug("deregistered trigger {}", trigger.getName()); + } + + public void resetTriggers(int eventId) { + currentTriggers.put(eventId, ConcurrentHashMap.newKeySet()); + } + + public void resetTriggersForGroupSuite(SceneGroup group, int suiteIndex) { + Grasscutter.getLogger().debug("reset triggers for group {} suite {}", group.id, suiteIndex); + var suite = group.getSuiteByIndex(suiteIndex); + if (suite == null) { + Grasscutter.getLogger() + .warn( + "Trying to load null suite Triggers for group {} with suiteindex {}", + group.id, + suiteIndex); + return; + } + + var groupSceneTriggers = triggersByGroupScene.get(group.id + "_" + suiteIndex); + if (groupSceneTriggers == null) { + groupSceneTriggers = new HashSet<>(); + } + + if (!groupSceneTriggers.isEmpty()) { + for (var trigger : groupSceneTriggers) { + currentTriggers.get(trigger.getEvent()).remove(trigger); + } + groupSceneTriggers.clear(); + } + + if (!suite.sceneTriggers.isEmpty()) { + groupSceneTriggers.addAll(suite.sceneTriggers); + for (var trigger : groupSceneTriggers) { + registerTrigger(trigger); + /*this.currentTriggers.computeIfAbsent(trigger.event, k -> ConcurrentHashMap.newKeySet()) + .add(trigger);*/ + } + } + triggersByGroupScene.put(group.id + "_" + suiteIndex, groupSceneTriggers); + } + + public void refreshGroup(SceneGroupInstance groupInstance) { + if (groupInstance == null || groupInstance.getLuaGroup().suites == null) { + return; + } + // for (int i = 1; i<= group.suites.size();i++){ + // refreshGroup(group, i); + refreshGroup( + groupInstance, groupInstance.getActiveSuiteId(), false); // Refresh the last group triggers + // } + } + + public int refreshGroup( + SceneGroupInstance groupInstance, int suiteIndex, boolean excludePrevSuite) { + SceneGroup group = groupInstance.getLuaGroup(); + if (suiteIndex == 0) { + if (excludePrevSuite) { + suiteIndex = group.findInitSuiteIndex(groupInstance.getActiveSuiteId()); + } else suiteIndex = group.findInitSuiteIndex(0); + } + if (suiteIndex == 0) return 0; + + var suiteData = group.getSuiteByIndex(suiteIndex); + if (suiteData == null) { + Grasscutter.getLogger().warn("Group {} suite {} not found", group.id, suiteIndex); + return 0; + } + + int prevSuiteIndex = groupInstance.getActiveSuiteId(); + boolean waitForOne = false; + SceneSuite prevSuiteData = null; + if (prevSuiteIndex != 0) { + prevSuiteData = group.getSuiteByIndex(prevSuiteIndex); + if (prevSuiteData != null) { + if (prevSuiteData.ban_refresh && !suiteData.ban_refresh) { + waitForOne = true; + } + } + } + + if (waitForOne + && (groupInstance.getTargetSuiteId() == 0 + || prevSuiteIndex != groupInstance.getTargetSuiteId())) { + groupInstance.setTargetSuiteId(suiteIndex); + Grasscutter.getLogger() + .debug("Group {} suite {} wating one more refresh", group.id, suiteIndex); + return 0; + } + + groupInstance.setTargetSuiteId(0); + + if (prevSuiteData != null) { + removeGroupSuite(group, prevSuiteData); + } // Remove old group suite + + addGroupSuite(groupInstance, suiteData); + + // Refesh variables here + group.variables.forEach( + variable -> { + if (!variable.no_refresh) + groupInstance.getCachedVariables().put(variable.name, variable.value); + }); + + groupInstance.setActiveSuiteId(suiteIndex); + groupInstance.setLastTimeRefreshed(getScene().getWorld().getGameTime()); + return suiteIndex; + } + + public boolean refreshGroupSuite(int groupId, int suiteId, GameQuest quest) { + var targetGroupInstance = getGroupInstanceById(groupId); + if (targetGroupInstance == null) { + getGroupById( + groupId); // Load the group, this ensures an instance is created and the if neccesary + // unloaded, but the suite data is stored + targetGroupInstance = getGroupInstanceById(groupId); + Grasscutter.getLogger() + .debug( + "trying to regresh group suite {} in an unloaded and uncached group {} in scene {}", + suiteId, + groupId, + getScene().getId()); + } else { + Grasscutter.getLogger().debug("Refreshing group {} suite {}", groupId, suiteId); + suiteId = + refreshGroup( + targetGroupInstance, + suiteId, + false); // If suiteId is zero, the value of suiteId changes + quest.getOwner().sendPacket(new PacketGroupSuiteNotify(groupId, suiteId)); + } + + if (suiteId != 0 && quest != null) { + quest + .getMainQuest() + .getQuestGroupSuites() + .add( + QuestGroupSuite.of().scene(getScene().getId()).group(groupId).suite(suiteId).build()); + } + + return true; + } + + public boolean refreshGroupMonster(int groupId) { + var groupInstance = getGroupInstanceById(groupId); + if (groupInstance == null) { + Grasscutter.getLogger() + .warn( + "trying to refesh monster group in unloaded and uncached group {} in scene {}", + groupId, + getScene().getId()); + return false; + } + + var group = groupInstance.getLuaGroup(); + var monstersToSpawn = + group.monsters.values().stream() + .filter( + m -> { + var entity = scene.getEntityByConfigId(m.config_id); + return (entity == null + || entity.getGroupId() + != group + .id); /*&& !groupInstance.getDeadEntities().contains(entity); */ // TODO: Investigate the usage of deadEntities + }) + .map(mob -> createMonster(group.id, group.block_id, mob)) + .toList(); // TODO check if it interferes with bigworld or anything else + this.addEntities(monstersToSpawn); + + return true; + } + + public EntityRegion getRegionById(int id) { + return regions.get(id); + } + + public void registerRegion(EntityRegion region) { + regions.put(region.getId(), region); + Grasscutter.getLogger() + .debug( + "Registered region {} from group {}", + region.getMetaRegion().config_id, + region.getGroupId()); + } + + public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite) { + suite.sceneRegions.stream() + .map(region -> new EntityRegion(this.getScene(), region)) + .forEach(this::registerRegion); + } + + public synchronized void deregisterRegion(SceneRegion region) { + var instance = + regions.values().stream().filter(r -> r.getConfigId() == region.config_id).findFirst(); + instance.ifPresent(entityRegion -> regions.remove(entityRegion.getId())); + } + + public Map> getLoadedGroupSetPerBlock() { + return loadedGroupSetPerBlock; + } + + // TODO optimize + public SceneGroup getGroupById(int groupId) { + for (var block : getBlocks().values()) { + this.getScene().loadBlock(block); + + var group = block.groups.get(groupId); + if (group == null) { + continue; + } + + if (!this.sceneGroupsInstances.containsKey(groupId)) { + this.getScene().onLoadGroup(List.of(group)); + this.getScene().onRegisterGroups(); + } + return group; + } + return null; + } + + public SceneGroupInstance getGroupInstanceById(int groupId) { + return sceneGroupsInstances.getOrDefault(groupId, null); + } + + public Map getCachedGroupInstances() { + return cachedSceneGroupsInstances; + } + + public SceneGroupInstance getCachedGroupInstanceById(int groupId) { + var instance = cachedSceneGroupsInstances.getOrDefault(groupId, null); + if (instance == null) { + instance = DatabaseHelper.loadGroupInstance(groupId, scene.getWorld().getHost()); + if (instance != null) cachedSceneGroupsInstances.put(groupId, instance); + } + + return instance; + } + + private static void addGridPositionToMap( + Map> map, int group_id, int vision_level, Position position) { + // Convert position to grid position + GridPosition gridPos; + int width = Grasscutter.getConfig().server.game.visionOptions[vision_level].gridWidth; + gridPos = + new GridPosition((int) (position.getX() / width), (int) (position.getZ() / width), width); + + Set groups = map.getOrDefault(gridPos, new HashSet<>()); + groups.add(group_id); + map.put(gridPos, groups); + } + + private static int getGadgetVisionLevel(int gadget_id) { + var gadget = GameData.getGadgetDataMap().get(gadget_id); + if (gadget == null || gadget.getVisionLevel() == null) return 0; + + var visionOptions = Grasscutter.getConfig().server.game.visionOptions; + for (int i = 0; i < visionOptions.length; i++) + if (visionOptions[i].name.compareTo(gadget.getVisionLevel()) == 0) { + return i; + } + + return 0; + } + + private void init() { + var meta = ScriptLoader.getSceneMeta(getScene().getId()); + if (meta == null) { + return; + } + this.meta = meta; + + var path = FileUtils.getScriptPath("Scene/" + getScene().getId() + "/scene_grid.json"); + + try { + this.groupGrids = JsonUtils.loadToList(path, Grid.class); + } catch (IOException ignored) { + Grasscutter.getLogger().error("Scene {} unable to load grid file.", getScene().getId()); + } catch (Exception e) { + Grasscutter.getLogger().error("Scene {} unable to load grid file.", e, getScene().getId()); + } + + boolean runForFirstTime = this.groupGrids == null; + + // Find if the scene entities are already generated, if not generate it + if (Grasscutter.getConfig().server.game.cacheSceneEntitiesEveryRun || runForFirstTime) { + List>> groupPositions = new ArrayList<>(); + for (int i = 0; i < 6; i++) groupPositions.add(new HashMap<>()); + + var visionOptions = Grasscutter.getConfig().server.game.visionOptions; + meta.blocks + .values() + .forEach( + block -> { + block.load(scene.getId(), meta.context); + block.groups.values().stream() + .filter(g -> !g.dynamic_load) + .forEach( + group -> { + group.load(this.scene.getId()); + + // Add all entitites here + Set vision_levels = new HashSet<>(); + group + .monsters + .values() + .forEach( + m -> { + addGridPositionToMap( + groupPositions.get(m.vision_level), + group.id, + m.vision_level, + m.pos); + vision_levels.add(m.vision_level); + }); + group + .gadgets + .values() + .forEach( + g -> { + int vision_level = + Math.max(getGadgetVisionLevel(g.gadget_id), g.vision_level); + addGridPositionToMap( + groupPositions.get(vision_level), + group.id, + vision_level, + g.pos); + vision_levels.add(vision_level); + }); + group + .npcs + .values() + .forEach( + n -> + addGridPositionToMap( + groupPositions.get(n.vision_level), + group.id, + n.vision_level, + n.pos)); + group + .regions + .values() + .forEach( + r -> + addGridPositionToMap( + groupPositions.get(0), group.id, 0, r.pos)); + if (group.garbages != null && group.garbages.gadgets != null) + group.garbages.gadgets.forEach( + g -> + addGridPositionToMap( + groupPositions.get(g.vision_level), + group.id, + g.vision_level, + g.pos)); + + int max_vision_level = -1; + if (!vision_levels.isEmpty()) { + for (int vision_level : vision_levels) { + if (max_vision_level == -1 + || visionOptions[max_vision_level].visionRange + < visionOptions[vision_level].visionRange) + max_vision_level = vision_level; + } + } + if (max_vision_level == -1) max_vision_level = 0; + + addGridPositionToMap( + groupPositions.get(max_vision_level), + group.id, + max_vision_level, + group.pos); + }); + }); + + this.groupGrids = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + this.groupGrids.add(new Grid()); + this.groupGrids.get(i).grid = groupPositions.get(i); + } + + try (FileWriter file = new FileWriter(path.toFile())) { + file.write(JsonUtils.encode(groupGrids)); + } catch (IOException ignored) { + Grasscutter.getLogger().error("Scene {} unable to write to grid file.", getScene().getId()); + } catch (Exception e) { + Grasscutter.getLogger().error("Scene {} unable to save grid file.", e, getScene().getId()); + } + + Grasscutter.getLogger().info("Scene {} saved grid file.", getScene().getId()); + } + + // TEMP + this.isInit = true; + } + + public boolean isInit() { + return isInit; + } + + public void loadBlockFromScript(SceneBlock block) { + block.load(scene.getId(), meta.context); + } + + public void loadGroupFromScript(SceneGroup group) { + group.load(getScene().getId()); + + this.sceneGroups.put(group.id, group); + if (this.getCachedGroupInstanceById(group.id) != null) { + this.sceneGroupsInstances.put(group.id, this.cachedSceneGroupsInstances.get(group.id)); + this.cachedSceneGroupsInstances.get(group.id).setCached(false); + this.cachedSceneGroupsInstances.get(group.id).setLuaGroup(group); + } else { + var instance = new SceneGroupInstance(group, getScene().getWorld().getHost()); + this.sceneGroupsInstances.put(group.id, instance); + this.cachedSceneGroupsInstances.put(group.id, instance); + instance.save(); // Save the instance + } + + if (group.variables != null) { + group.variables.forEach( + variable -> { + val variables = this.getVariables(group.id); + if (variables != null && !variables.containsKey(variable.name)) + variables.put(variable.name, variable.value); + }); + } + } + + public void unregisterGroup(SceneGroup group) { + this.sceneGroups.remove(group.id); + this.sceneGroupsInstances.values().removeIf(i -> i.getLuaGroup().equals(group)); + this.cachedSceneGroupsInstances.values().stream() + .filter(i -> i.getLuaGroup().equals(group)) + .forEach(s -> s.setCached(true)); + } + + public void checkRegions() { + if (this.regions.size() == 0) { + return; + } + + for (var region : this.regions.values()) { + // currently all condition_ENTER_REGION Events check for avatar, so we have no necessary to + // add other types of entity + var entities = + getScene().getEntities().values().stream() + .filter( + e -> + e.getEntityType() == EntityType.Avatar.getValue() + && region.getMetaRegion().contains(e.getPosition())) + .toList(); + entities.forEach(region::addEntity); + + int targetID = 0; + if (entities.size() > 0) { + targetID = entities.get(0).getId(); + } + + if (region.hasNewEntities()) { + Grasscutter.getLogger() + .trace("Call EVENT_ENTER_REGION_{}", region.getMetaRegion().config_id); + callEvent( + new ScriptArgs(region.getGroupId(), EventType.EVENT_ENTER_REGION, region.getConfigId()) + .setSourceEntityId(region.getId()) + .setTargetEntityId(targetID)); + + region.resetNewEntities(); + } + + for (int entityId : region.getEntities()) { + if (getScene().getEntityById(entityId) == null + || !region.getMetaRegion().contains(getScene().getEntityById(entityId).getPosition())) { + region.removeEntity(entityId); + } + } + if (region.entityLeave()) { + callEvent( + new ScriptArgs(region.getGroupId(), EventType.EVENT_LEAVE_REGION, region.getConfigId()) + .setSourceEntityId(region.getId()) + .setTargetEntityId(region.getFirstEntityId())); + + region.resetNewEntities(); + } + } + } + + public List getGadgetsInGroupSuite( + SceneGroupInstance groupInstance, SceneSuite suite) { + var group = groupInstance.getLuaGroup(); + return suite.sceneGadgets.stream() + .filter( + m -> { + var entity = scene.getEntityByConfigId(m.config_id); + return (entity == null || entity.getGroupId() != group.id) + && (!m.isOneoff + || !m.persistent + || !groupInstance.getDeadEntities().contains(m.config_id)); + }) + .map(g -> createGadget(group.id, group.block_id, g, groupInstance.getCachedGadgetState(g))) + .peek(g -> groupInstance.cacheGadgetState(g.getMetaGadget(), g.getState())) + .filter(Objects::nonNull) + .toList(); + } + + public List getMonstersInGroupSuite( + SceneGroupInstance groupInstance, SceneSuite suite) { + var group = groupInstance.getLuaGroup(); + return suite.sceneMonsters.stream() + .filter( + m -> { + var entity = scene.getEntityByConfigId(m.config_id); + return (entity == null + || entity.getGroupId() + != group + .id); /*&& !groupInstance.getDeadEntities().contains(entity); */ // TODO: + // Investigate the usage of deadEntities + }) // TODO: Add persistent monster cached data + .map(mob -> createMonster(group.id, group.block_id, mob)) + .filter(Objects::nonNull) + .toList(); + } + + public void addGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { + // we added trigger first + registerTrigger(suite.sceneTriggers); + + var group = groupInstance.getLuaGroup(); + var toCreate = new ArrayList(); + toCreate.addAll(getGadgetsInGroupSuite(groupInstance, suite)); + toCreate.addAll(getMonstersInGroupSuite(groupInstance, suite)); + addEntities(toCreate); + + registerRegionInGroupSuite(group, suite); + } + + public void refreshGroupSuite(SceneGroupInstance groupInstance, SceneSuite suite) { + // we added trigger first + registerTrigger(suite.sceneTriggers); + + var group = groupInstance.getLuaGroup(); + var toCreate = new ArrayList(); + toCreate.addAll(getGadgetsInGroupSuite(groupInstance, suite)); + toCreate.addAll(getMonstersInGroupSuite(groupInstance, suite)); + addEntities(toCreate); + + registerRegionInGroupSuite(group, suite); + } + + public void removeGroupSuite(SceneGroup group, SceneSuite suite) { + deregisterTrigger(suite.sceneTriggers); + removeMonstersInGroup(group, suite); + removeGadgetsInGroup(group, suite); + + suite.sceneRegions.forEach(this::deregisterRegion); + } + + public void killGroupSuite(SceneGroup group, SceneSuite suite) { + deregisterTrigger(suite.sceneTriggers); + + killMonstersInGroup(group, suite); + killGadgetsInGroup(group, suite); + + suite.sceneRegions.forEach(this::deregisterRegion); + } + + public void startMonsterTideInGroup( + SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { + this.scriptMonsterTideService = + new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); + } + + public void unloadCurrentMonsterTide() { + if (this.getScriptMonsterTideService() == null) { + return; + } + this.getScriptMonsterTideService().unload(); + } + + public void spawnMonstersByConfigId(SceneGroup group, int configId, int delayTime) { + // TODO delay + var entity = scene.getEntityByConfigId(configId); + if (entity != null && entity.getGroupId() == group.id) { + Grasscutter.getLogger() + .debug("entity already exists failed in group {} with config {}", group.id, configId); + return; + } + entity = createMonster(group.id, group.block_id, group.monsters.get(configId)); + if (entity != null) { + getScene().addEntity(entity); + } else { + Grasscutter.getLogger() + .warn("failed to create entity with group {} and config {}", group.id, configId); + } + } + // Events + public void callEvent(int groupId, int eventType) { + callEvent(new ScriptArgs(groupId, eventType)); + } + + public void callEvent(@Nonnull ScriptArgs params) { + /** + * We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for + * every groups' trigger in every scene instances. But when callEvent is called in a ScriptLib + * func, it may cause NPE because the inner call cleans the ThreadLocal so that outer call could + * not get it. e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> + * (remove) So we use thread pool to clean the stack to avoid this new issue. + */ + eventExecutor.submit(() -> this.realCallEvent(params)); + } + + private void realCallEvent(@Nonnull ScriptArgs params) { + try { + ScriptLoader.getScriptLib().setSceneScriptManager(this); + int eventType = params.type; + Set relevantTriggers = new HashSet<>(); + if (eventType == EventType.EVENT_ENTER_REGION || eventType == EventType.EVENT_LEAVE_REGION) { + List relevantTriggersList = + this.getTriggersByEvent(eventType).stream() + .filter( + p -> + p.getCondition().contains(String.valueOf(params.param1)) + && (p.getSource().isEmpty() + || p.getSource().equals(params.getEventSource()))) + .toList(); + relevantTriggers = new HashSet<>(relevantTriggersList); + } else { + relevantTriggers = + this.getTriggersByEvent(eventType).stream() + .filter( + t -> params.getGroupId() == 0 || t.getCurrentGroup().id == params.getGroupId()) + .collect(Collectors.toSet()); + } + for (SceneTrigger trigger : relevantTriggers) { + handleEventForTrigger(params, trigger); + } + } catch (Throwable throwable) { + Grasscutter.getLogger() + .error("Condition Trigger " + params.type + " triggered exception", throwable); + } finally { + // make sure it is removed + ScriptLoader.getScriptLib().removeSceneScriptManager(); + } + } + + private boolean handleEventForTrigger(ScriptArgs params, SceneTrigger trigger) { + Grasscutter.getLogger() + .debug("checking trigger {} for event {}", trigger.getName(), params.type); + try { + // setup execution + ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); + ScriptLoader.getScriptLib().setCurrentCallParams(params); + + if (evaluateTriggerCondition(trigger, params)) { + callTrigger(trigger, params); + return true; + } else { + Grasscutter.getLogger() + .debug("Condition Trigger {} returned false", trigger.getCondition()); + } + // TODO some ret do not bool + return false; + } catch (Throwable ex) { + Grasscutter.getLogger() + .error("Condition Trigger " + trigger.getName() + " triggered exception", ex); + return false; + } finally { + ScriptLoader.getScriptLib().removeCurrentGroup(); + } + } + + private boolean evaluateTriggerCondition(SceneTrigger trigger, ScriptArgs params) { + Grasscutter.getLogger() + .trace( + "Call Condition Trigger {}, [{},{},{}]", + trigger.getCondition(), + params.param1, + params.source_eid, + params.target_eid); + LuaValue ret = this.callScriptFunc(trigger.getCondition(), trigger.currentGroup, params); + return ret.isboolean() && ret.checkboolean(); + } + + private void callTrigger(SceneTrigger trigger, ScriptArgs params) { + // the SetGroupVariableValueByGroup in tower need the param to record the first stage time + var ret = this.callScriptFunc(trigger.getAction(), trigger.currentGroup, params); + var invocationsCounter = triggerInvocations.get(trigger.getName()); + var invocations = invocationsCounter.incrementAndGet(); + Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.getAction()); + + var activeChallenge = scene.getChallenge(); + if (activeChallenge != null) { + activeChallenge.onGroupTriggerDeath(trigger); + } + + if (trigger.getEvent() == EventType.EVENT_ENTER_REGION) { + var region = + this.regions.values().stream() + .filter(p -> p.getConfigId() == params.param1) + .toList() + .get(0); + this.getScene().getPlayers().forEach(p -> p.onEnterRegion(region.getMetaRegion())); + this.deregisterRegion(region.getMetaRegion()); + } else if (trigger.getEvent() == EventType.EVENT_LEAVE_REGION) { + var region = + this.regions.values().stream() + .filter(p -> p.getConfigId() == params.param1) + .toList() + .get(0); + this.getScene().getPlayers().forEach(p -> p.onLeaveRegion(region.getMetaRegion())); + this.deregisterRegion(region.getMetaRegion()); + } + + if (trigger.getEvent() == EVENT_TIMER_EVENT) { + cancelGroupTimerEvent(trigger.currentGroup.id, trigger.getSource()); + } + + // always deregister on error, otherwise only if the count is reached + if (ret.isboolean() && !ret.checkboolean() + || ret.isint() && ret.checkint() != 0 + || trigger.getTrigger_count() > 0 && invocations >= trigger.getTrigger_count()) { + deregisterTrigger(trigger); + } + } + + private LuaValue callScriptFunc(String funcName, SceneGroup group, ScriptArgs params) { + LuaValue funcLua = null; + if (funcName != null && !funcName.isEmpty()) { + funcLua = (LuaValue) group.getBindings().get(funcName); + } + + LuaValue ret = LuaValue.TRUE; + + if (funcLua != null) { + LuaValue args = LuaValue.NIL; + + if (params != null) { + args = CoerceJavaToLua.coerce(params); + } + + ret = safetyCall(funcName, funcLua, args, group); + } + return ret; + } + + public LuaValue safetyCall(String name, LuaValue func, LuaValue args, SceneGroup group) { + try { + return func.call(ScriptLoader.getScriptLibLua(), args); + } catch (LuaError error) { + ScriptLib.logger.error( + "[LUA] call trigger failed in group {} with {},{}", group.id, name, args, error); + return LuaValue.valueOf(-1); + } + } + + public ScriptMonsterTideService getScriptMonsterTideService() { + return scriptMonsterTideService; + } + + public ScriptMonsterSpawnService getScriptMonsterSpawnService() { + return scriptMonsterSpawnService; + } + + public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) { + return createGadget(groupId, blockId, g, g.state); + } + + public EntityGadget createGadget(int groupId, int blockId, SceneGadget g, int state) { + if (g.isOneoff) { + var hasEntity = + getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == g.group.id) + .filter(e -> e.getConfigId() == g.config_id) + .findFirst(); + if (hasEntity.isPresent()) { + return null; + } + } + EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); + + if (entity.getGadgetData() == null) { + return null; + } + + entity.setBlockId(blockId); + entity.setConfigId(g.config_id); + entity.setGroupId(groupId); + entity.getRotation().set(g.rot); + entity.setState(state); + + entity.setPointType(g.point_type); + entity.setRouteConfig(BaseRoute.fromSceneGadget(g)); + entity.setMetaGadget(g); + entity.buildContent(); + + return entity; + } + + public EntityNPC createNPC(SceneNPC npc, int blockId, int suiteId) { + return new EntityNPC(getScene(), npc, blockId, suiteId); + } + + public EntityMonster createMonster(int groupId, int blockId, SceneMonster monster) { + if (monster == null) { + return null; + } + + var data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return null; + } + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonManager() != null) { + level = getScene().getDungeonManager().getLevelForMonster(monster.config_id); + } else if (getScene().getWorld().getWorldLevel() > 0) { + var worldLevelData = + GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(groupId); + entity.setBlockId(blockId); + entity.setConfigId(monster.config_id); + entity.setPoseId(monster.pose_id); + entity.setMetaMonster(monster); + + this.getScriptMonsterSpawnService() + .onMonsterCreatedListener + .forEach(action -> action.onNotify(entity)); + + return entity; + } + + public void addEntity(GameEntity gameEntity) { + getScene().addEntity(gameEntity); + } + + public void meetEntities(List gameEntity) { + getScene().addEntities(gameEntity, VisionTypeOuterClass.VisionType.VISION_TYPE_MEET); + } + + public void addEntities(List gameEntity) { + getScene().addEntities(gameEntity); + } + + public void removeEntities(List gameEntity) { + getScene() + .removeEntities( + gameEntity.stream().map(e -> (GameEntity) e).collect(Collectors.toList()), + VisionTypeOuterClass.VisionType.VISION_TYPE_REFRESH); + } + + public RTree getBlocksIndex() { + return meta.sceneBlockIndex; + } + + public void removeMonstersInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneMonsters.stream().map(m -> m.config_id).collect(Collectors.toSet()); + var toRemove = + getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_TYPE_MISS); + } + + public void removeGadgetsInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneGadgets.stream().map(m -> m.config_id).collect(Collectors.toSet()); + var toRemove = + getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + getScene().removeEntities(toRemove, VisionTypeOuterClass.VisionType.VISION_TYPE_MISS); + } + + public void killMonstersInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneMonsters.stream().map(m -> m.config_id).collect(Collectors.toSet()); + var toRemove = + getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + toRemove.forEach(getScene()::killEntity); + } + + public void killGadgetsInGroup(SceneGroup group, SceneSuite suite) { + var configSet = suite.sceneGadgets.stream().map(m -> m.config_id).collect(Collectors.toSet()); + var toRemove = + getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == group.id) + .filter(e -> configSet.contains(e.getConfigId())) + .toList(); + + toRemove.forEach(getScene()::killEntity); + } + + public int createGroupTimerEvent(int groupID, String source, double time) { + // TODO also remove timers when refreshing and test + var group = getGroupById(groupID); + if (group == null || group.triggers == null) { + Grasscutter.getLogger() + .warn( + "trying to create a timer for unknown group with id {} and source {}", + groupID, + source); + return 1; + } + Grasscutter.getLogger() + .info( + "creating group timer event for group {} with source {} and time {}", + groupID, + source, + time); + for (SceneTrigger trigger : group.triggers.values()) { + if (trigger.getEvent() == EVENT_TIMER_EVENT && trigger.getSource().equals(source)) { + Grasscutter.getLogger() + .warn( + "[LUA] Found timer trigger with source {} for group {} : {}", + source, + groupID, + trigger.getName()); + var taskIdentifier = + Grasscutter.getGameServer() + .getScheduler() + .scheduleDelayedRepeatingTask( + () -> + callEvent( + new ScriptArgs(groupID, EVENT_TIMER_EVENT).setEventSource(source)), + (int) time, + (int) time); + var groupTasks = activeGroupTimers.computeIfAbsent(groupID, k -> new HashSet<>()); + groupTasks.add(new Pair<>(source, taskIdentifier)); + } + } + return 0; + } + + public int cancelGroupTimerEvent(int groupID, String source) { + // TODO test + var groupTimers = activeGroupTimers.get(groupID); + if (groupTimers != null && !groupTimers.isEmpty()) + for (var timer : groupTimers) { + if (timer.component1().equals(source)) { + Grasscutter.getGameServer().getScheduler().cancelTask(timer.component2()); + return 0; + } + } + + Grasscutter.getLogger() + .warn("trying to cancel a timer that's not active {} {}", groupID, source); + return 1; + } + + // todo use killed monsters instead of spawned entites for check? + public boolean isClearedGroupMonsters(int groupId) { + val groupInstance = getGroupInstanceById(groupId); + if (groupInstance == null || groupInstance.getLuaGroup() == null) return false; + + val monsters = groupInstance.getLuaGroup().monsters; + + if (monsters == null || monsters.isEmpty()) return true; + + return monsters.values().stream() + .noneMatch( + m -> { + val entity = scene.getEntityByConfigId(m.config_id); + return entity != null && entity.getGroupId() == groupId; + }); + } + + public void onDestroy() { + activeGroupTimers.forEach( + (gid, times) -> + times.forEach( + (e) -> Grasscutter.getGameServer().getScheduler().cancelTask(e.getSecond()))); + activeGroupTimers.clear(); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/ScriptUtils.java b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java index 01961379f..763ac1172 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptUtils.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptUtils.java @@ -1,66 +1,64 @@ -package emu.grasscutter.scripts; - -import emu.grasscutter.Grasscutter; -import java.util.HashMap; - -import emu.grasscutter.utils.Position; -import lombok.val; -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; - -public interface ScriptUtils { - static HashMap toMap(LuaTable table) { - HashMap map = new HashMap<>(); - LuaValue[] rootKeys = table.keys(); - for (LuaValue k : rootKeys) { - if (table.get(k).istable()) { - map.put(k, toMap(table.get(k).checktable())); - } else { - map.put(k, table.get(k)); - } - } - return map; - } - - static void print(LuaTable table) { - Grasscutter.getLogger().info(toMap(table).toString()); - } - - /** - * Converts a position object into a Lua table. - * - * @param position The position object to convert. - * @return The Lua table. - */ - static LuaTable posToLua(Position position) { - var result = new LuaTable(); - if (position != null) { - result.set("x", position.getX()); - result.set("y", position.getY()); - result.set("z", position.getZ()); - } else { - result.set("x", 0); - result.set("y", 0); - result.set("z", 0); - } - - return result; - } - - /** - * Converts a Lua table into a position object. - * - * @param position The Lua table to convert. - * @return The position object. - */ - static Position luaToPos(LuaValue position) { - var result = new Position(); - if (position != null && !position.isnil()) { - result.setX(position.get("x").optint(0)); - result.setY(position.get("y").optint(0)); - result.setZ(position.get("z").optint(0)); - } - - return result; - } -} +package emu.grasscutter.scripts; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.utils.Position; +import java.util.HashMap; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +public interface ScriptUtils { + static HashMap toMap(LuaTable table) { + HashMap map = new HashMap<>(); + LuaValue[] rootKeys = table.keys(); + for (LuaValue k : rootKeys) { + if (table.get(k).istable()) { + map.put(k, toMap(table.get(k).checktable())); + } else { + map.put(k, table.get(k)); + } + } + return map; + } + + static void print(LuaTable table) { + Grasscutter.getLogger().info(toMap(table).toString()); + } + + /** + * Converts a position object into a Lua table. + * + * @param position The position object to convert. + * @return The Lua table. + */ + static LuaTable posToLua(Position position) { + var result = new LuaTable(); + if (position != null) { + result.set("x", position.getX()); + result.set("y", position.getY()); + result.set("z", position.getZ()); + } else { + result.set("x", 0); + result.set("y", 0); + result.set("z", 0); + } + + return result; + } + + /** + * Converts a Lua table into a position object. + * + * @param position The Lua table to convert. + * @return The position object. + */ + static Position luaToPos(LuaValue position) { + var result = new Position(); + if (position != null && !position.isnil()) { + result.setX(position.get("x").optint(0)); + result.setY(position.get("y").optint(0)); + result.setZ(position.get("z").optint(0)); + } + + return result; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java index f92534e2c..199f82b01 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneConfig.java @@ -1,16 +1,16 @@ -package emu.grasscutter.scripts.data; - -import emu.grasscutter.utils.Position; -import lombok.Setter; -import lombok.ToString; - -@ToString -@Setter -public class SceneConfig { - public Position vision_anchor; - public Position born_pos; - public Position born_rot; - public Position begin_pos; - public Position size; - public float die_y; -} +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneConfig { + public Position vision_anchor; + public Position born_pos; + public Position born_rot; + public Position begin_pos; + public Position size; + public float die_y; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 3bf330e50..9114fb9a9 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,183 +1,206 @@ -package emu.grasscutter.scripts.data; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.scripts.ScriptLoader; -import emu.grasscutter.utils.Position; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.luaj.vm2.LuaValue; - -import javax.script.Bindings; -import javax.script.CompiledScript; -import javax.script.ScriptException; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.stream.Collectors; - -@ToString -@Setter -public final class SceneGroup { - public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference - - public int id; - public int refresh_id; - public Position pos; - - public Map monsters; // - public Map npcs; // - public Map gadgets; // - public Map triggers; - public Map regions; - public List suites; - public List variables; - - public SceneBusiness business; - public SceneGarbage garbages; - public SceneInitConfig init_config; - @Getter public boolean dynamic_load = false; - - public SceneReplaceable is_replaceable; - - private transient boolean loaded; // Not an actual variable in the scripts either - private transient CompiledScript script; - private transient Bindings bindings; - public static SceneGroup of(int groupId) { - var group = new SceneGroup(); - group.id = groupId; - return group; - } - - public boolean isLoaded() { - return this.loaded; - } - - public void setLoaded(boolean loaded) { - this.loaded = loaded; - } - - public int getBusinessType() { - return this.business == null ? 0 : this.business.type; - } - - public List getGarbageGadgets() { - return this.garbages == null ? null : this.garbages.gadgets; - } - - public CompiledScript getScript() { - return this.script; - } - - public SceneSuite getSuiteByIndex(int index) { - if(index < 1 || index > suites.size()) { - return null; - } - return this.suites.get(index - 1); - } - - public Bindings getBindings() { - return this.bindings; - } - - public synchronized SceneGroup load(int sceneId) { - if (this.loaded) { - return this; - } - // Set flag here so if there is no script, we don't call this function over and over again. - this.setLoaded(true); - - this.bindings = ScriptLoader.getEngine().createBindings(); - - CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua"); - - if (cs == null) { - return this; - } - - this.script = cs; - - // Eval script - try { - cs.eval(this.bindings); - - // Set - this.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, this.bindings.get("monsters")).stream() - .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); - this.monsters.values().forEach(m -> m.group = this); - - this.npcs = ScriptLoader.getSerializer().toList(SceneNPC.class, this.bindings.get("npcs")).stream() - .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); - this.npcs.values().forEach(m -> m.group = this); - - this.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, this.bindings.get("gadgets")).stream() - .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); - this.gadgets.values().forEach(m -> m.group = this); - - this.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, this.bindings.get("triggers")).stream() - .collect(Collectors.toMap(SceneTrigger::getName, y -> y, (a, b) -> a)); - this.triggers.values().forEach(t -> t.currentGroup = this); - - this.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites")); - this.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, this.bindings.get("regions")).stream() - .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); - this.regions.values().forEach(m -> m.group = this); - - this.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, this.bindings.get("init_config")); - - // Garbages // TODO: fix properly later - Object garbagesValue = this.bindings.get("garbages"); - if (garbagesValue instanceof LuaValue garbagesTable) { - this.garbages = new SceneGarbage(); - if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { - this.garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); - this.garbages.gadgets.forEach(m -> m.group = this); - } - } - - // Add variables to suite - this.variables = ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables")); - - // Add monsters and gadgets to suite - this.suites.forEach(i -> i.init(this)); - - } catch (ScriptException e) { - Grasscutter.getLogger().error("An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e); - } - - Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId); - return this; - } - - public int findInitSuiteIndex(int exclude_index) { //TODO: Investigate end index - if (init_config == null) return 1; - if (init_config.io_type == 1) return init_config.suite; //IO TYPE FLOW - if (init_config.rand_suite) { - if (suites.size() == 1) { - return init_config.suite; - } else { - List randSuiteList = new ArrayList<>(); - for (int i = 0; i < suites.size(); i++) { - if (i == exclude_index) continue; - - var suite = suites.get(i); - for(int j = 0; j < suite.rand_weight; j++) randSuiteList.add(i); - } - - return randSuiteList.get(new Random().nextInt(randSuiteList.size())); - } - } - return init_config.suite; - } - - public Optional searchBossChestInGroup() { - return this.gadgets.values().stream() - .filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0) - .map(g -> g.boss_chest) - .findFirst(); - } - -} +package emu.grasscutter.scripts.data; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.utils.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.stream.Collectors; +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.luaj.vm2.LuaValue; + +@ToString +@Setter +public final class SceneGroup { + public transient int + block_id; // Not an actual variable in the scripts but we will keep it here for reference + + public int id; + public int refresh_id; + public Position pos; + + public Map monsters; // + public Map npcs; // + public Map gadgets; // + public Map triggers; + public Map regions; + public List suites; + public List variables; + + public SceneBusiness business; + public SceneGarbage garbages; + public SceneInitConfig init_config; + @Getter public boolean dynamic_load = false; + + public SceneReplaceable is_replaceable; + + private transient boolean loaded; // Not an actual variable in the scripts either + private transient CompiledScript script; + private transient Bindings bindings; + + public static SceneGroup of(int groupId) { + var group = new SceneGroup(); + group.id = groupId; + return group; + } + + public boolean isLoaded() { + return this.loaded; + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + + public int getBusinessType() { + return this.business == null ? 0 : this.business.type; + } + + public List getGarbageGadgets() { + return this.garbages == null ? null : this.garbages.gadgets; + } + + public CompiledScript getScript() { + return this.script; + } + + public SceneSuite getSuiteByIndex(int index) { + if (index < 1 || index > suites.size()) { + return null; + } + return this.suites.get(index - 1); + } + + public Bindings getBindings() { + return this.bindings; + } + + public synchronized SceneGroup load(int sceneId) { + if (this.loaded) { + return this; + } + // Set flag here so if there is no script, we don't call this function over and over again. + this.setLoaded(true); + + this.bindings = ScriptLoader.getEngine().createBindings(); + + CompiledScript cs = + ScriptLoader.getScript( + "Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua"); + + if (cs == null) { + return this; + } + + this.script = cs; + + // Eval script + try { + cs.eval(this.bindings); + + // Set + this.monsters = + ScriptLoader.getSerializer() + .toList(SceneMonster.class, this.bindings.get("monsters")) + .stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); + this.monsters.values().forEach(m -> m.group = this); + + this.npcs = + ScriptLoader.getSerializer().toList(SceneNPC.class, this.bindings.get("npcs")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); + this.npcs.values().forEach(m -> m.group = this); + + this.gadgets = + ScriptLoader.getSerializer() + .toList(SceneGadget.class, this.bindings.get("gadgets")) + .stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); + this.gadgets.values().forEach(m -> m.group = this); + + this.triggers = + ScriptLoader.getSerializer() + .toList(SceneTrigger.class, this.bindings.get("triggers")) + .stream() + .collect(Collectors.toMap(SceneTrigger::getName, y -> y, (a, b) -> a)); + this.triggers.values().forEach(t -> t.currentGroup = this); + + this.suites = + ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites")); + this.regions = + ScriptLoader.getSerializer() + .toList(SceneRegion.class, this.bindings.get("regions")) + .stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); + this.regions.values().forEach(m -> m.group = this); + + this.init_config = + ScriptLoader.getSerializer() + .toObject(SceneInitConfig.class, this.bindings.get("init_config")); + + // Garbages // TODO: fix properly later + Object garbagesValue = this.bindings.get("garbages"); + if (garbagesValue instanceof LuaValue garbagesTable) { + this.garbages = new SceneGarbage(); + if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { + this.garbages.gadgets = + ScriptLoader.getSerializer() + .toList( + SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); + this.garbages.gadgets.forEach(m -> m.group = this); + } + } + + // Add variables to suite + this.variables = + ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables")); + + // Add monsters and gadgets to suite + this.suites.forEach(i -> i.init(this)); + + } catch (ScriptException e) { + Grasscutter.getLogger() + .error( + "An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e); + } + + Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId); + return this; + } + + public int findInitSuiteIndex(int exclude_index) { // TODO: Investigate end index + if (init_config == null) return 1; + if (init_config.io_type == 1) return init_config.suite; // IO TYPE FLOW + if (init_config.rand_suite) { + if (suites.size() == 1) { + return init_config.suite; + } else { + List randSuiteList = new ArrayList<>(); + for (int i = 0; i < suites.size(); i++) { + if (i == exclude_index) continue; + + var suite = suites.get(i); + for (int j = 0; j < suite.rand_weight; j++) randSuiteList.add(i); + } + + return randSuiteList.get(new Random().nextInt(randSuiteList.size())); + } + } + return init_config.suite; + } + + public Optional searchBossChestInGroup() { + return this.gadgets.values().stream() + .filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0) + .map(g -> g.boss_chest) + .findFirst(); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java index 95f6216dd..147b4064b 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -1,13 +1,13 @@ -package emu.grasscutter.scripts.data; - -import lombok.Setter; -import lombok.ToString; - -@ToString -@Setter -public final class SceneInitConfig { - public int suite; - public int end_suite; - public int io_type; - public boolean rand_suite; -} +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public final class SceneInitConfig { + public int suite; + public int end_suite; + public int io_type; + public boolean rand_suite; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index dc84c6690..5a264c935 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -1,15 +1,15 @@ -package emu.grasscutter.scripts.data; - -import lombok.Setter; -import lombok.ToString; - -@ToString -@Setter -public class SceneMonster extends SceneObject{ - public int monster_id; - public int pose_id; - public int drop_id; - public boolean disableWander; - public int title_id; - public int special_name_id; -} +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneMonster extends SceneObject { + public int monster_id; + public int pose_id; + public int drop_id; + public boolean disableWander; + public int title_id; + public int special_name_id; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java index dc2561ff6..b884b29c4 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneObject.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneObject.java @@ -1,19 +1,19 @@ -package emu.grasscutter.scripts.data; - -import emu.grasscutter.utils.Position; -import lombok.Setter; -import lombok.ToString; - -@ToString -@Setter -public abstract class SceneObject { - public int level; - public int config_id; - public int area_id; - public int vision_level = 0; - - public Position pos; - public Position rot; - /** not set by lua */ - public transient SceneGroup group; -} +package emu.grasscutter.scripts.data; + +import emu.grasscutter.utils.Position; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public abstract class SceneObject { + public int level; + public int config_id; + public int area_id; + public int vision_level = 0; + + public Position pos; + public Position rot; + /** not set by lua */ + public transient SceneGroup group; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 14d71b00f..d43316213 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -1,63 +1,61 @@ -package emu.grasscutter.scripts.data; - -import java.util.ArrayList; -import java.util.List; - -import lombok.Setter; -import lombok.ToString; - -@ToString -@Setter -public class SceneSuite { - // make it refer the default empty list to avoid NPE caused by some group - public List monsters = List.of(); - public List gadgets = List.of(); - public List triggers = List.of(); - public List regions = List.of(); - public int rand_weight; - - public boolean ban_refresh = false; - - public transient List sceneMonsters = List.of(); - public transient List sceneGadgets = List.of(); - public transient List sceneTriggers = List.of(); - public transient List sceneRegions = List.of(); - - public void init(SceneGroup sceneGroup) { - if(sceneGroup.monsters != null){ - this.sceneMonsters = new ArrayList<>( - this.monsters.stream() - .filter(sceneGroup.monsters::containsKey) - .map(sceneGroup.monsters::get) - .toList() - ); - } - - if(sceneGroup.gadgets != null){ - this.sceneGadgets = new ArrayList<>( - this.gadgets.stream() - .filter(sceneGroup.gadgets::containsKey) - .map(sceneGroup.gadgets::get) - .toList() - ); - } - - if(sceneGroup.triggers != null) { - this.sceneTriggers = new ArrayList<>( - this.triggers.stream() - .filter(sceneGroup.triggers::containsKey) - .map(sceneGroup.triggers::get) - .toList() - ); - } - if(sceneGroup.regions != null) { - this.sceneRegions = new ArrayList<>( - this.regions.stream() - .filter(sceneGroup.regions::containsKey) - .map(sceneGroup.regions::get) - .toList() - ); - } - - } -} +package emu.grasscutter.scripts.data; + +import java.util.ArrayList; +import java.util.List; +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class SceneSuite { + // make it refer the default empty list to avoid NPE caused by some group + public List monsters = List.of(); + public List gadgets = List.of(); + public List triggers = List.of(); + public List regions = List.of(); + public int rand_weight; + + public boolean ban_refresh = false; + + public transient List sceneMonsters = List.of(); + public transient List sceneGadgets = List.of(); + public transient List sceneTriggers = List.of(); + public transient List sceneRegions = List.of(); + + public void init(SceneGroup sceneGroup) { + if (sceneGroup.monsters != null) { + this.sceneMonsters = + new ArrayList<>( + this.monsters.stream() + .filter(sceneGroup.monsters::containsKey) + .map(sceneGroup.monsters::get) + .toList()); + } + + if (sceneGroup.gadgets != null) { + this.sceneGadgets = + new ArrayList<>( + this.gadgets.stream() + .filter(sceneGroup.gadgets::containsKey) + .map(sceneGroup.gadgets::get) + .toList()); + } + + if (sceneGroup.triggers != null) { + this.sceneTriggers = + new ArrayList<>( + this.triggers.stream() + .filter(sceneGroup.triggers::containsKey) + .map(sceneGroup.triggers::get) + .toList()); + } + if (sceneGroup.regions != null) { + this.sceneRegions = + new ArrayList<>( + this.regions.stream() + .filter(sceneGroup.regions::containsKey) + .map(sceneGroup.regions::get) + .toList()); + } + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 2fc63cbd8..4104619b4 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -1,45 +1,57 @@ -package emu.grasscutter.scripts.data; - -import lombok.*; - -@Setter -@Getter -@NoArgsConstructor -// todo find way to deserialize from lua with final fields, maybe with the help of Builder? -public final class SceneTrigger { - private String name; - private int config_id; - private int event; - private int trigger_count = 1; - private String source; - private String condition; - private String action; - private String tag; - - public transient SceneGroup currentGroup; - - @Override - public boolean equals(Object obj) { - if (obj instanceof SceneTrigger sceneTrigger){ - return this.name.equals(sceneTrigger.name); - } else return super.equals(obj); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public String toString() { - return "SceneTrigger{" + - "name='" + name + '\'' + - ", config_id=" + config_id + - ", event=" + event + - ", source='" + source + '\'' + - ", condition='" + condition + '\'' + - ", action='" + action + '\'' + - ", trigger_count='" + trigger_count + '\'' + - '}'; - } -} +package emu.grasscutter.scripts.data; + +import lombok.*; + +@Setter +@Getter +@NoArgsConstructor +// todo find way to deserialize from lua with final fields, maybe with the help of Builder? +public final class SceneTrigger { + private String name; + private int config_id; + private int event; + private int trigger_count = 1; + private String source; + private String condition; + private String action; + private String tag; + + public transient SceneGroup currentGroup; + + @Override + public boolean equals(Object obj) { + if (obj instanceof SceneTrigger sceneTrigger) { + return this.name.equals(sceneTrigger.name); + } else return super.equals(obj); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "SceneTrigger{" + + "name='" + + name + + '\'' + + ", config_id=" + + config_id + + ", event=" + + event + + ", source='" + + source + + '\'' + + ", condition='" + + condition + + '\'' + + ", action='" + + action + + '\'' + + ", trigger_count='" + + trigger_count + + '\'' + + '}'; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java index a072c32ec..da2ceac70 100644 --- a/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java +++ b/src/main/java/emu/grasscutter/scripts/data/ScriptArgs.java @@ -1,90 +1,90 @@ -package emu.grasscutter.scripts.data; - -public class ScriptArgs { - public int param1; - public int param2; - public int param3; - public int source_eid; // Source entity - public int target_eid; - public int group_id; - public String source; // source string, used for timers - public int type; // lua event type, used by scripts and the ScriptManager - - public ScriptArgs(int groupId, int eventType) { - this(groupId, eventType, 0,0); - } - - public ScriptArgs(int groupId, int eventType, int param1) { - this(groupId, eventType, param1,0); - } - - public ScriptArgs(int groupId, int eventType, int param1, int param2) { - this.type = eventType; - this.param1 = param1; - this.param2 = param2; - this.group_id = groupId; - } - - public int getParam1() { - return param1; - } - - public ScriptArgs setParam1(int param1) { - this.param1 = param1; - return this; - } - - public int getParam2() { - return param2; - } - - public ScriptArgs setParam2(int param2) { - this.param2 = param2; - return this; - } - - public int getParam3() { - return param3; - } - - public ScriptArgs setParam3(int param3) { - this.param3 = param3; - return this; - } - - public int getSourceEntityId() { - return source_eid; - } - - public ScriptArgs setSourceEntityId(int source_eid) { - this.source_eid = source_eid; - return this; - } - - public int getTargetEntityId() { - return target_eid; - } - - public ScriptArgs setTargetEntityId(int target_eid) { - this.target_eid = target_eid; - return this; - } - - public String getEventSource() { - return source; - } - - public ScriptArgs setEventSource(String source) { - this.source = source; - return this; - } - - public int getGroupId() { - return group_id; - } - - public ScriptArgs setGroupId(int group_id) { - this.group_id = group_id; - return this; - } -} +package emu.grasscutter.scripts.data; + +public class ScriptArgs { + public int param1; + public int param2; + public int param3; + public int source_eid; // Source entity + public int target_eid; + public int group_id; + public String source; // source string, used for timers + public int type; // lua event type, used by scripts and the ScriptManager + + public ScriptArgs(int groupId, int eventType) { + this(groupId, eventType, 0, 0); + } + + public ScriptArgs(int groupId, int eventType, int param1) { + this(groupId, eventType, param1, 0); + } + + public ScriptArgs(int groupId, int eventType, int param1, int param2) { + this.type = eventType; + this.param1 = param1; + this.param2 = param2; + this.group_id = groupId; + } + + public int getParam1() { + return param1; + } + + public ScriptArgs setParam1(int param1) { + this.param1 = param1; + return this; + } + + public int getParam2() { + return param2; + } + + public ScriptArgs setParam2(int param2) { + this.param2 = param2; + return this; + } + + public int getParam3() { + return param3; + } + + public ScriptArgs setParam3(int param3) { + this.param3 = param3; + return this; + } + + public int getSourceEntityId() { + return source_eid; + } + + public ScriptArgs setSourceEntityId(int source_eid) { + this.source_eid = source_eid; + return this; + } + + public int getTargetEntityId() { + return target_eid; + } + + public ScriptArgs setTargetEntityId(int target_eid) { + this.target_eid = target_eid; + return this; + } + + public String getEventSource() { + return source; + } + + public ScriptArgs setEventSource(String source) { + this.source = source; + return this; + } + + public int getGroupId() { + return group_id; + } + + public ScriptArgs setGroupId(int group_id) { + this.group_id = group_id; + return this; + } +} diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index d1d180dfd..37c810550 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -1,92 +1,102 @@ -package emu.grasscutter.scripts.service; - -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.scripts.SceneScriptManager; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.scripts.listener.ScriptMonsterListener; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -public final class ScriptMonsterTideService { - private final SceneScriptManager sceneScriptManager; - private final SceneGroup currentGroup; - private final AtomicInteger monsterAlive; - private final AtomicInteger monsterTideCount; - private final AtomicInteger monsterKillCount; - private final int monsterSceneLimit; - private final ConcurrentLinkedQueue monsterConfigOrders; - private final List monsterConfigIds; - private final OnMonsterCreated onMonsterCreated= new OnMonsterCreated(); - private final OnMonsterDead onMonsterDead= new OnMonsterDead(); - - public ScriptMonsterTideService(SceneScriptManager sceneScriptManager, - SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){ - this.sceneScriptManager = sceneScriptManager; - this.currentGroup = group; - this.monsterSceneLimit = monsterSceneLimit; - this.monsterTideCount = new AtomicInteger(tideCount); - this.monsterKillCount = new AtomicInteger(0); - this.monsterAlive = new AtomicInteger(0); - this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); - this.monsterConfigIds = List.of(ordersConfigId); - - this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(onMonsterCreated); - this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); - // spawn the first turn - for (int i = 0; i < this.monsterSceneLimit; i++) { - sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster())); - } - } - - public class OnMonsterCreated implements ScriptMonsterListener{ - @Override - public void onNotify(EntityMonster sceneMonster) { - if(monsterConfigIds.contains(sceneMonster.getConfigId()) && monsterSceneLimit > 0){ - monsterAlive.incrementAndGet(); - monsterTideCount.decrementAndGet(); - } - } - } - - public SceneMonster getNextMonster(){ - var nextId = this.monsterConfigOrders.poll(); - if(currentGroup.monsters.containsKey(nextId)){ - return currentGroup.monsters.get(nextId); - } - // TODO some monster config_id do not exist in groups, so temporarily set it to the first - return currentGroup.monsters.values().stream().findFirst().orElse(null); - } - - public class OnMonsterDead implements ScriptMonsterListener { - @Override - public void onNotify(EntityMonster sceneMonster) { - if (monsterSceneLimit <= 0) { - return; - } - if (monsterAlive.decrementAndGet() >= monsterSceneLimit) { - // maybe not happen - return; - } - monsterKillCount.incrementAndGet(); - if (monsterTideCount.get() > 0) { - // add more - sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster())); - } - // spawn the last turn of monsters - // fix the 5-2 - sceneScriptManager.callEvent(new ScriptArgs(currentGroup.id, EventType.EVENT_MONSTER_TIDE_DIE, monsterKillCount.get())); - } - - } - - public void unload(){ - this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterCreatedListener(onMonsterCreated); - this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead); - } -} +package emu.grasscutter.scripts.service; + +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.scripts.listener.ScriptMonsterListener; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public final class ScriptMonsterTideService { + private final SceneScriptManager sceneScriptManager; + private final SceneGroup currentGroup; + private final AtomicInteger monsterAlive; + private final AtomicInteger monsterTideCount; + private final AtomicInteger monsterKillCount; + private final int monsterSceneLimit; + private final ConcurrentLinkedQueue monsterConfigOrders; + private final List monsterConfigIds; + private final OnMonsterCreated onMonsterCreated = new OnMonsterCreated(); + private final OnMonsterDead onMonsterDead = new OnMonsterDead(); + + public ScriptMonsterTideService( + SceneScriptManager sceneScriptManager, + SceneGroup group, + int tideCount, + int monsterSceneLimit, + Integer[] ordersConfigId) { + this.sceneScriptManager = sceneScriptManager; + this.currentGroup = group; + this.monsterSceneLimit = monsterSceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterKillCount = new AtomicInteger(0); + this.monsterAlive = new AtomicInteger(0); + this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + this.monsterConfigIds = List.of(ordersConfigId); + + this.sceneScriptManager + .getScriptMonsterSpawnService() + .addMonsterCreatedListener(onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); + // spawn the first turn + for (int i = 0; i < this.monsterSceneLimit; i++) { + sceneScriptManager.addEntity( + this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster())); + } + } + + public class OnMonsterCreated implements ScriptMonsterListener { + @Override + public void onNotify(EntityMonster sceneMonster) { + if (monsterConfigIds.contains(sceneMonster.getConfigId()) && monsterSceneLimit > 0) { + monsterAlive.incrementAndGet(); + monsterTideCount.decrementAndGet(); + } + } + } + + public SceneMonster getNextMonster() { + var nextId = this.monsterConfigOrders.poll(); + if (currentGroup.monsters.containsKey(nextId)) { + return currentGroup.monsters.get(nextId); + } + // TODO some monster config_id do not exist in groups, so temporarily set it to the first + return currentGroup.monsters.values().stream().findFirst().orElse(null); + } + + public class OnMonsterDead implements ScriptMonsterListener { + @Override + public void onNotify(EntityMonster sceneMonster) { + if (monsterSceneLimit <= 0) { + return; + } + if (monsterAlive.decrementAndGet() >= monsterSceneLimit) { + // maybe not happen + return; + } + monsterKillCount.incrementAndGet(); + if (monsterTideCount.get() > 0) { + // add more + sceneScriptManager.addEntity( + sceneScriptManager.createMonster( + currentGroup.id, currentGroup.block_id, getNextMonster())); + } + // spawn the last turn of monsters + // fix the 5-2 + sceneScriptManager.callEvent( + new ScriptArgs( + currentGroup.id, EventType.EVENT_MONSTER_TIDE_DIE, monsterKillCount.get())); + } + } + + public void unload() { + this.sceneScriptManager + .getScriptMonsterSpawnService() + .removeMonsterCreatedListener(onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead); + } +} diff --git a/src/main/java/emu/grasscutter/server/event/entity/EntityDamageEvent.java b/src/main/java/emu/grasscutter/server/event/entity/EntityDamageEvent.java index 3aabbd8c2..d897af8c1 100644 --- a/src/main/java/emu/grasscutter/server/event/entity/EntityDamageEvent.java +++ b/src/main/java/emu/grasscutter/server/event/entity/EntityDamageEvent.java @@ -1,24 +1,27 @@ -package emu.grasscutter.server.event.entity; - -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.ElementType; -import emu.grasscutter.server.event.Cancellable; -import emu.grasscutter.server.event.types.EntityEvent; -import lombok.Getter; -import lombok.Setter; - -import javax.annotation.Nullable; - -public final class EntityDamageEvent extends EntityEvent implements Cancellable { - @Getter @Setter private float damage; - @Getter @Setter private ElementType attackElementType; - @Getter @Nullable private final GameEntity damager; - - public EntityDamageEvent(GameEntity entity, float damage, ElementType attackElementType, @Nullable GameEntity damager) { - super(entity); - - this.damage = damage; - this.attackElementType = attackElementType; - this.damager = damager; - } -} +package emu.grasscutter.server.event.entity; + +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.server.event.Cancellable; +import emu.grasscutter.server.event.types.EntityEvent; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.Setter; + +public final class EntityDamageEvent extends EntityEvent implements Cancellable { + @Getter @Setter private float damage; + @Getter @Setter private ElementType attackElementType; + @Getter @Nullable private final GameEntity damager; + + public EntityDamageEvent( + GameEntity entity, + float damage, + ElementType attackElementType, + @Nullable GameEntity damager) { + super(entity); + + this.damage = damage; + this.attackElementType = attackElementType; + this.damager = damager; + } +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 05acfdff7..5fa3ee5b9 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -1,279 +1,279 @@ -package emu.grasscutter.server.game; - -import static emu.grasscutter.config.Configuration.GAME_INFO; -import static emu.grasscutter.utils.Language.translate; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.game.battlepass.BattlePassSystem; -import emu.grasscutter.game.chat.ChatSystem; -import emu.grasscutter.game.chat.ChatSystemHandler; -import emu.grasscutter.game.combine.CombineManger; -import emu.grasscutter.game.drop.DropSystem; -import emu.grasscutter.game.dungeons.DungeonSystem; -import emu.grasscutter.game.expedition.ExpeditionSystem; -import emu.grasscutter.game.gacha.GachaSystem; -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.AnnouncementSystem; -import emu.grasscutter.game.systems.InventorySystem; -import emu.grasscutter.game.systems.MultiplayerSystem; -import emu.grasscutter.game.tower.TowerSystem; -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.event.game.ServerTickEvent; -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 java.net.InetSocketAddress; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import kcp.highway.ChannelConfig; -import kcp.highway.KcpServer; -import lombok.Getter; - -@Getter -public final class GameServer extends KcpServer { - // Game server base - private final InetSocketAddress address; - private final GameServerPacketHandler packetHandler; - private final Map players; - private final Set worlds; - - // Server systems - private final InventorySystem inventorySystem; - private final GachaSystem gachaSystem; - private final ShopSystem shopSystem; - private final MultiplayerSystem multiplayerSystem; - private final DungeonSystem dungeonSystem; - private final ExpeditionSystem expeditionSystem; - private final DropSystem dropSystem; - private final WorldDataSystem worldDataSystem; - private final BattlePassSystem battlePassSystem; - private final CombineManger combineSystem; - private final TowerSystem towerSystem; - private final AnnouncementSystem announcementSystem; - private final QuestSystem questSystem; - - // Extra - private final ServerTaskScheduler scheduler; - private final TaskMap taskMap; - - private ChatSystemHandler chatManager; - - public GameServer() { - this(getAdapterInetSocketAddress()); - } - - public GameServer(InetSocketAddress address) { - ChannelConfig channelConfig = new ChannelConfig(); - channelConfig.nodelay(true, GAME_INFO.kcpInterval, 2, true); - channelConfig.setMtu(1400); - channelConfig.setSndwnd(256); - channelConfig.setRcvwnd(256); - channelConfig.setTimeoutMillis(30 * 1000); // 30s - channelConfig.setUseConvChannel(true); - channelConfig.setAckNoDelay(false); - - this.init(GameSessionManager.getListener(), channelConfig, address); - - EnergyManager.initialize(); - StaminaManager.initialize(); - CookingManager.initialize(); - CookingCompoundManager.initialize(); - CombineManger.initialize(); - - // Game Server base - this.address = address; - this.packetHandler = new GameServerPacketHandler(PacketHandler.class); - this.players = new ConcurrentHashMap<>(); - this.worlds = Collections.synchronizedSet(new HashSet<>()); - - // Extra - this.scheduler = new ServerTaskScheduler(); - this.taskMap = new TaskMap(true); - - // Create game systems - this.inventorySystem = new InventorySystem(this); - this.gachaSystem = new GachaSystem(this); - this.shopSystem = new ShopSystem(this); - this.multiplayerSystem = new MultiplayerSystem(this); - this.dungeonSystem = new DungeonSystem(this); - this.dropSystem = new DropSystem(this); - this.expeditionSystem = new ExpeditionSystem(this); - this.combineSystem = new CombineManger(this); - this.towerSystem = new TowerSystem(this); - this.worldDataSystem = new WorldDataSystem(this); - this.battlePassSystem = new BattlePassSystem(this); - this.announcementSystem = new AnnouncementSystem(this); - this.questSystem = new QuestSystem(this); - - // Chata manager - this.chatManager = new ChatSystem(this); - - // Hook into shutdown event. - Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); - } - - private static InetSocketAddress getAdapterInetSocketAddress() { - InetSocketAddress inetSocketAddress; - if (GAME_INFO.bindAddress.equals("")) { - inetSocketAddress = new InetSocketAddress(GAME_INFO.bindPort); - } else { - inetSocketAddress = new InetSocketAddress(GAME_INFO.bindAddress, GAME_INFO.bindPort); - } - return inetSocketAddress; - } - - @Deprecated - public ChatSystemHandler getChatManager() { - return chatManager; - } - - @Deprecated - public void setChatManager(ChatSystemHandler chatManager) { - this.chatManager = chatManager; - } - - public ChatSystemHandler getChatSystem() { - return chatManager; - } - - public void setChatSystem(ChatSystemHandler chatManager) { - this.chatManager = chatManager; - } - - public void registerPlayer(Player player) { - getPlayers().put(player.getUid(), player); - } - - public Player getPlayerByUid(int id) { - return this.getPlayerByUid(id, false); - } - - public Player getPlayerByUid(int id, boolean allowOfflinePlayers) { - // Console check - if (id == GameConstants.SERVER_CONSOLE_UID) { - return null; - } - - // Get from online players - Player player = this.getPlayers().get(id); - - if (!allowOfflinePlayers) { - return player; - } - - // Check database if character isnt here - if (player == null) { - player = DatabaseHelper.getPlayerByUid(id); - } - - return player; - } - - public Player getPlayerByAccountId(String accountId) { - Optional playerOpt = - getPlayers().values().stream() - .filter(player -> player.getAccount().getId().equals(accountId)) - .findFirst(); - return playerOpt.orElse(null); - } - - public SocialDetail.Builder getSocialDetailByUid(int id) { - // Get from online players - Player player = this.getPlayerByUid(id, true); - - if (player == null) { - return null; - } - - return player.getSocialDetail(); - } - - public Account getAccountByName(String username) { - Optional playerOpt = - getPlayers().values().stream() - .filter(player -> player.getAccount().getUsername().equals(username)) - .findFirst(); - if (playerOpt.isPresent()) { - return playerOpt.get().getAccount(); - } - return DatabaseHelper.getAccountByName(username); - } - - public synchronized void onTick() { - var tickStart = Instant.now(); - - // Tick worlds. - this.worlds.removeIf(World::onTick); - - // Tick players. - this.players.values().forEach(Player::onTick); - - // Tick scheduler. - this.getScheduler().runTasks(); - - // Call server tick event. - ServerTickEvent event = new ServerTickEvent(tickStart, Instant.now()); - event.call(); - } - - public void registerWorld(World world) { - this.getWorlds().add(world); - } - - public void deregisterWorld(World world) { - // TODO Auto-generated method stub - - } - - public void start() { - // Schedule game loop. - Timer gameLoop = new Timer(); - gameLoop.scheduleAtFixedRate( - new TimerTask() { - @Override - public void run() { - try { - onTick(); - } catch (Exception e) { - Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e); - } - } - }, - new Date(), - 1000L); - Grasscutter.getLogger().info(translate("messages.status.free_software")); - Grasscutter.getLogger() - .info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort())); - ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); - event.call(); - } - - public void onServerShutdown() { - ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); - event.call(); - - // Kick and save all players - List list = new ArrayList<>(this.getPlayers().size()); - list.addAll(this.getPlayers().values()); - - for (Player player : list) { - player.getSession().close(); - } - } -} +package emu.grasscutter.server.game; + +import static emu.grasscutter.config.Configuration.GAME_INFO; +import static emu.grasscutter.utils.Language.translate; + +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.battlepass.BattlePassSystem; +import emu.grasscutter.game.chat.ChatSystem; +import emu.grasscutter.game.chat.ChatSystemHandler; +import emu.grasscutter.game.combine.CombineManger; +import emu.grasscutter.game.drop.DropSystem; +import emu.grasscutter.game.dungeons.DungeonSystem; +import emu.grasscutter.game.expedition.ExpeditionSystem; +import emu.grasscutter.game.gacha.GachaSystem; +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.AnnouncementSystem; +import emu.grasscutter.game.systems.InventorySystem; +import emu.grasscutter.game.systems.MultiplayerSystem; +import emu.grasscutter.game.tower.TowerSystem; +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.event.game.ServerTickEvent; +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 java.net.InetSocketAddress; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import kcp.highway.ChannelConfig; +import kcp.highway.KcpServer; +import lombok.Getter; + +@Getter +public final class GameServer extends KcpServer { + // Game server base + private final InetSocketAddress address; + private final GameServerPacketHandler packetHandler; + private final Map players; + private final Set worlds; + + // Server systems + private final InventorySystem inventorySystem; + private final GachaSystem gachaSystem; + private final ShopSystem shopSystem; + private final MultiplayerSystem multiplayerSystem; + private final DungeonSystem dungeonSystem; + private final ExpeditionSystem expeditionSystem; + private final DropSystem dropSystem; + private final WorldDataSystem worldDataSystem; + private final BattlePassSystem battlePassSystem; + private final CombineManger combineSystem; + private final TowerSystem towerSystem; + private final AnnouncementSystem announcementSystem; + private final QuestSystem questSystem; + + // Extra + private final ServerTaskScheduler scheduler; + private final TaskMap taskMap; + + private ChatSystemHandler chatManager; + + public GameServer() { + this(getAdapterInetSocketAddress()); + } + + public GameServer(InetSocketAddress address) { + ChannelConfig channelConfig = new ChannelConfig(); + channelConfig.nodelay(true, GAME_INFO.kcpInterval, 2, true); + channelConfig.setMtu(1400); + channelConfig.setSndwnd(256); + channelConfig.setRcvwnd(256); + channelConfig.setTimeoutMillis(30 * 1000); // 30s + channelConfig.setUseConvChannel(true); + channelConfig.setAckNoDelay(false); + + this.init(GameSessionManager.getListener(), channelConfig, address); + + EnergyManager.initialize(); + StaminaManager.initialize(); + CookingManager.initialize(); + CookingCompoundManager.initialize(); + CombineManger.initialize(); + + // Game Server base + this.address = address; + this.packetHandler = new GameServerPacketHandler(PacketHandler.class); + this.players = new ConcurrentHashMap<>(); + this.worlds = Collections.synchronizedSet(new HashSet<>()); + + // Extra + this.scheduler = new ServerTaskScheduler(); + this.taskMap = new TaskMap(true); + + // Create game systems + this.inventorySystem = new InventorySystem(this); + this.gachaSystem = new GachaSystem(this); + this.shopSystem = new ShopSystem(this); + this.multiplayerSystem = new MultiplayerSystem(this); + this.dungeonSystem = new DungeonSystem(this); + this.dropSystem = new DropSystem(this); + this.expeditionSystem = new ExpeditionSystem(this); + this.combineSystem = new CombineManger(this); + this.towerSystem = new TowerSystem(this); + this.worldDataSystem = new WorldDataSystem(this); + this.battlePassSystem = new BattlePassSystem(this); + this.announcementSystem = new AnnouncementSystem(this); + this.questSystem = new QuestSystem(this); + + // Chata manager + this.chatManager = new ChatSystem(this); + + // Hook into shutdown event. + Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); + } + + private static InetSocketAddress getAdapterInetSocketAddress() { + InetSocketAddress inetSocketAddress; + if (GAME_INFO.bindAddress.equals("")) { + inetSocketAddress = new InetSocketAddress(GAME_INFO.bindPort); + } else { + inetSocketAddress = new InetSocketAddress(GAME_INFO.bindAddress, GAME_INFO.bindPort); + } + return inetSocketAddress; + } + + @Deprecated + public ChatSystemHandler getChatManager() { + return chatManager; + } + + @Deprecated + public void setChatManager(ChatSystemHandler chatManager) { + this.chatManager = chatManager; + } + + public ChatSystemHandler getChatSystem() { + return chatManager; + } + + public void setChatSystem(ChatSystemHandler chatManager) { + this.chatManager = chatManager; + } + + public void registerPlayer(Player player) { + getPlayers().put(player.getUid(), player); + } + + public Player getPlayerByUid(int id) { + return this.getPlayerByUid(id, false); + } + + public Player getPlayerByUid(int id, boolean allowOfflinePlayers) { + // Console check + if (id == GameConstants.SERVER_CONSOLE_UID) { + return null; + } + + // Get from online players + Player player = this.getPlayers().get(id); + + if (!allowOfflinePlayers) { + return player; + } + + // Check database if character isnt here + if (player == null) { + player = DatabaseHelper.getPlayerByUid(id); + } + + return player; + } + + public Player getPlayerByAccountId(String accountId) { + Optional playerOpt = + getPlayers().values().stream() + .filter(player -> player.getAccount().getId().equals(accountId)) + .findFirst(); + return playerOpt.orElse(null); + } + + public SocialDetail.Builder getSocialDetailByUid(int id) { + // Get from online players + Player player = this.getPlayerByUid(id, true); + + if (player == null) { + return null; + } + + return player.getSocialDetail(); + } + + public Account getAccountByName(String username) { + Optional playerOpt = + getPlayers().values().stream() + .filter(player -> player.getAccount().getUsername().equals(username)) + .findFirst(); + if (playerOpt.isPresent()) { + return playerOpt.get().getAccount(); + } + return DatabaseHelper.getAccountByName(username); + } + + public synchronized void onTick() { + var tickStart = Instant.now(); + + // Tick worlds. + this.worlds.removeIf(World::onTick); + + // Tick players. + this.players.values().forEach(Player::onTick); + + // Tick scheduler. + this.getScheduler().runTasks(); + + // Call server tick event. + ServerTickEvent event = new ServerTickEvent(tickStart, Instant.now()); + event.call(); + } + + public void registerWorld(World world) { + this.getWorlds().add(world); + } + + public void deregisterWorld(World world) { + // TODO Auto-generated method stub + + } + + public void start() { + // Schedule game loop. + Timer gameLoop = new Timer(); + gameLoop.scheduleAtFixedRate( + new TimerTask() { + @Override + public void run() { + try { + onTick(); + } catch (Exception e) { + Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e); + } + } + }, + new Date(), + 1000L); + Grasscutter.getLogger().info(translate("messages.status.free_software")); + Grasscutter.getLogger() + .info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort())); + ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); + event.call(); + } + + public void onServerShutdown() { + ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); + event.call(); + + // Kick and save all players + List list = new ArrayList<>(this.getPlayers().size()); + list.addAll(this.getPlayers().values()); + + for (Player player : list) { + player.getSession().close(); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddCustomTeamReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddCustomTeamReq.java index 75c35150f..ebb713f99 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddCustomTeamReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddCustomTeamReq.java @@ -1,14 +1,14 @@ -//package emu.grasscutter.server.packet.recv; -// -//import emu.grasscutter.net.packet.Opcodes; -//import emu.grasscutter.net.packet.PacketHandler; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.server.game.GameSession; -// -//@Opcodes(PacketOpcodes.AddCustomTeamReq) -//public class HandlerAddCustomTeamReq extends PacketHandler { -// @Override -// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { -// session.getPlayer().getTeamManager().addNewCustomTeam(); -// } -//} +// package emu.grasscutter.server.packet.recv; +// +// import emu.grasscutter.net.packet.Opcodes; +// import emu.grasscutter.net.packet.PacketHandler; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.server.game.GameSession; +// +// @Opcodes(PacketOpcodes.AddCustomTeamReq) +// public class HandlerAddCustomTeamReq extends PacketHandler { +// @Override +// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { +// session.getPlayer().getTeamManager().addNewCustomTeam(); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java index fd87e595f..a15426881 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java @@ -1,26 +1,25 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; - -@Opcodes(PacketOpcodes.AddQuestContentProgressReq) -public class HandlerAddQuestContentProgressReq extends PacketHandler { - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - var req = AddQuestContentProgressReq.parseFrom(payload); - - // Find all conditions in quest that are the same as the given one - var type = QuestContent.getContentTriggerByValue(req.getContentType()); - if(type != null) { - session.getPlayer().getQuestManager().queueEvent(type, req.getParam()); - } - - session.send(new PacketAddQuestContentProgressRsp(req.getContentType())); - } - -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; + +@Opcodes(PacketOpcodes.AddQuestContentProgressReq) +public class HandlerAddQuestContentProgressReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = AddQuestContentProgressReq.parseFrom(payload); + + // Find all conditions in quest that are the same as the given one + var type = QuestContent.getContentTriggerByValue(req.getContentType()); + if (type != null) { + session.getPlayer().getQuestManager().queueEvent(type, req.getParam()); + } + + session.send(new PacketAddQuestContentProgressRsp(req.getContentType())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeElementTypeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeElementTypeReq.java index e3853c95f..1da03a9ff 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeElementTypeReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarChangeElementTypeReq.java @@ -1,39 +1,38 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq; -import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp; -import lombok.val; - -/** - * Changes the currently active avatars Element if possible - */ -@Opcodes(PacketOpcodes.AvatarChangeElementTypeReq) -public class HandlerAvatarChangeElementTypeReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - var req = AvatarChangeElementTypeReq.parseFrom(payload); - var area = GameData.getWorldAreaDataMap().get(req.getAreaId()); - - if (area == null || area.getElementType() == null || area.getElementType().getDepotIndex() <= 0) { - session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); - return; - } - - val avatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(); - if (!avatar.changeElement(area.getElementType())) { - session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); - return; - } - - // Success - session.send(new PacketAvatarChangeElementTypeRsp()); - } - -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq; +import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp; +import lombok.val; + +/** Changes the currently active avatars Element if possible */ +@Opcodes(PacketOpcodes.AvatarChangeElementTypeReq) +public class HandlerAvatarChangeElementTypeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = AvatarChangeElementTypeReq.parseFrom(payload); + var area = GameData.getWorldAreaDataMap().get(req.getAreaId()); + + if (area == null + || area.getElementType() == null + || area.getElementType().getDepotIndex() <= 0) { + session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); + return; + } + + val avatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(); + if (!avatar.changeElement(area.getElementType())) { + session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); + return; + } + + // Success + session.send(new PacketAvatarChangeElementTypeRsp()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java index 421dd3cb7..12741321b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java @@ -1,26 +1,26 @@ -//package emu.grasscutter.server.packet.recv; -// -//import emu.grasscutter.net.packet.Opcodes; -//import emu.grasscutter.net.packet.PacketHandler; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.Unk2700BEDLIGJANCJClientReq; -//import emu.grasscutter.server.game.GameSession; -//import emu.grasscutter.server.packet.send.PacketChangeHomeBgmNotify; -//import emu.grasscutter.server.packet.send.PacketChangeHomeBgmRsp; -// -//@Opcodes(PacketOpcodes.Unk2700_BEDLIGJANCJ_ClientReq) -//public class HandlerChangeHomeBgmReq extends PacketHandler { -// @Override -// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { -// var req = Unk2700BEDLIGJANCJClientReq.Unk2700_BEDLIGJANCJ_ClientReq.parseFrom(payload); -// -// int homeBgmId = req.getUnk2700BJHAMKKECEI(); -// var home = session.getPlayer().getHome(); -// -// home.getHomeSceneItem(session.getPlayer().getSceneId()).setHomeBgmId(homeBgmId); -// home.save(); -// -// session.send(new PacketChangeHomeBgmNotify(homeBgmId)); -// session.send(new PacketChangeHomeBgmRsp()); -// } -//} +// package emu.grasscutter.server.packet.recv; +// +// import emu.grasscutter.net.packet.Opcodes; +// import emu.grasscutter.net.packet.PacketHandler; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.Unk2700BEDLIGJANCJClientReq; +// import emu.grasscutter.server.game.GameSession; +// import emu.grasscutter.server.packet.send.PacketChangeHomeBgmNotify; +// import emu.grasscutter.server.packet.send.PacketChangeHomeBgmRsp; +// +// @Opcodes(PacketOpcodes.Unk2700_BEDLIGJANCJ_ClientReq) +// public class HandlerChangeHomeBgmReq extends PacketHandler { +// @Override +// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { +// var req = Unk2700BEDLIGJANCJClientReq.Unk2700_BEDLIGJANCJ_ClientReq.parseFrom(payload); +// +// int homeBgmId = req.getUnk2700BJHAMKKECEI(); +// var home = session.getPlayer().getHome(); +// +// home.getHomeSceneItem(session.getPlayer().getSceneId()).setHomeBgmId(homeBgmId); +// home.save(); +// +// session.send(new PacketChangeHomeBgmNotify(homeBgmId)); +// session.send(new PacketChangeHomeBgmRsp()); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 4b4ab7139..c2246e97e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,191 +1,191 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; -import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; -import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; -import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; -import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo; -import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; -import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; -import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; -import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; -import emu.grasscutter.server.event.entity.EntityMoveEvent; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.utils.Position; - -@Opcodes(PacketOpcodes.CombatInvocationsNotify) -public class HandlerCombatInvocationsNotify extends PacketHandler { - - private float cachedLandingSpeed = 0; - private long cachedLandingTimeMillisecond = 0; - private boolean monitorLandingEvent = false; - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); - for (CombatInvokeEntry entry : notif.getInvokeListList()) { - // Handle combat invoke - switch (entry.getArgumentType()) { - case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> { - EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); - AttackResult attackResult = hitInfo.getAttackResult(); - Player player = session.getPlayer(); - - // Check if the player is invulnerable. - if (attackResult.getAttackerId() - != player.getTeamManager().getCurrentAvatarEntity().getId() - && player.getAbilityManager().isAbilityInvulnerable()) break; - - // Handle damage - player.getAttackResults().add(attackResult); - player.getEnergyManager().handleAttackHit(hitInfo); - } - case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> { - // Handle movement - EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); - GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); - if (entity != null) { - // Move player - MotionInfo motionInfo = moveInfo.getMotionInfo(); - MotionState motionState = motionInfo.getState(); - - // Call entity move event. - EntityMoveEvent event = - new EntityMoveEvent( - entity, - new Position(motionInfo.getPos()), - new Position(motionInfo.getRot()), - motionState); - event.call(); - - entity.move(event.getPosition(), event.getRotation()); - entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); - entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); - entity.setMotionState(motionState); - - session - .getPlayer() - .getStaminaManager() - .handleCombatInvocationsNotify(session, moveInfo, entity); - - // TODO: handle MOTION_FIGHT landing which has a different damage factor - // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. - // May need the height when starting plunge attack. - - // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. - // Cache land speed for later use. - if (motionState == MotionState.MOTION_STATE_LAND_SPEED) { - cachedLandingSpeed = motionInfo.getSpeed().getY(); - cachedLandingTimeMillisecond = System.currentTimeMillis(); - monitorLandingEvent = true; - } - if (monitorLandingEvent) { - if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) { - monitorLandingEvent = false; - handleFallOnGround(session, entity, motionState); - } - } - - // MOTION_STATE_NOTIFY = Dont send to other players - if (motionState == MotionState.MOTION_STATE_NOTIFY) { - continue; - } - } - } - case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> { - EvtAnimatorParameterInfo paramInfo = - EvtAnimatorParameterInfo.parseFrom(entry.getCombatData()); - if (paramInfo.getIsServerCache()) { - paramInfo = paramInfo.toBuilder().setIsServerCache(false).build(); - entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build(); - } - } - default -> {} - } - - session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); - } - } - - private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { - if (session.getPlayer().isInGodMode()) { - return; - } - // People have reported that after plunge attack (client sends a FIGHT instead of - // FALL_ON_GROUND) they will die - // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping - // again. - // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. - // 200ms seems to be a reasonable delay. - int maxDelay = 200; - long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; - Grasscutter.getLogger() - .trace( - "MOTION_FALL_ON_GROUND received after " - + actualDelay - + "/" - + maxDelay - + "ms." - + (actualDelay > maxDelay ? " Discard" : "")); - if (actualDelay > maxDelay) { - return; - } - float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float damageFactor = 0; - if (cachedLandingSpeed < -23.5) { - damageFactor = 0.33f; - } - if (cachedLandingSpeed < -25) { - damageFactor = 0.5f; - } - if (cachedLandingSpeed < -26.5) { - damageFactor = 0.66f; - } - if (cachedLandingSpeed < -28) { - damageFactor = 1f; - } - float damage = maxHP * damageFactor; - float newHP = currentHP - damage; - if (newHP < 0) { - newHP = 0; - } - if (damageFactor > 0) { - Grasscutter.getLogger() - .debug( - currentHP - + "/" - + maxHP - + "\tLandingSpeed: " - + cachedLandingSpeed - + "\tDamageFactor: " - + damageFactor - + "\tDamage: " - + damage - + "\tNewHP: " - + newHP); - } else { - Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage"); - } - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - entity - .getWorld() - .broadcastPacket( - new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); - if (newHP == 0) { - session - .getPlayer() - .getStaminaManager() - .killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL); - } - cachedLandingSpeed = 0; - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; +import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; +import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo; +import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; +import emu.grasscutter.server.event.entity.EntityMoveEvent; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.utils.Position; + +@Opcodes(PacketOpcodes.CombatInvocationsNotify) +public class HandlerCombatInvocationsNotify extends PacketHandler { + + private float cachedLandingSpeed = 0; + private long cachedLandingTimeMillisecond = 0; + private boolean monitorLandingEvent = false; + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); + for (CombatInvokeEntry entry : notif.getInvokeListList()) { + // Handle combat invoke + switch (entry.getArgumentType()) { + case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> { + EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); + AttackResult attackResult = hitInfo.getAttackResult(); + Player player = session.getPlayer(); + + // Check if the player is invulnerable. + if (attackResult.getAttackerId() + != player.getTeamManager().getCurrentAvatarEntity().getId() + && player.getAbilityManager().isAbilityInvulnerable()) break; + + // Handle damage + player.getAttackResults().add(attackResult); + player.getEnergyManager().handleAttackHit(hitInfo); + } + case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> { + // Handle movement + EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); + GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); + if (entity != null) { + // Move player + MotionInfo motionInfo = moveInfo.getMotionInfo(); + MotionState motionState = motionInfo.getState(); + + // Call entity move event. + EntityMoveEvent event = + new EntityMoveEvent( + entity, + new Position(motionInfo.getPos()), + new Position(motionInfo.getRot()), + motionState); + event.call(); + + entity.move(event.getPosition(), event.getRotation()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + entity.setMotionState(motionState); + + session + .getPlayer() + .getStaminaManager() + .handleCombatInvocationsNotify(session, moveInfo, entity); + + // TODO: handle MOTION_FIGHT landing which has a different damage factor + // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. + + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. + // Cache land speed for later use. + if (motionState == MotionState.MOTION_STATE_LAND_SPEED) { + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } + if (monitorLandingEvent) { + if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) { + monitorLandingEvent = false; + handleFallOnGround(session, entity, motionState); + } + } + + // MOTION_STATE_NOTIFY = Dont send to other players + if (motionState == MotionState.MOTION_STATE_NOTIFY) { + continue; + } + } + } + case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> { + EvtAnimatorParameterInfo paramInfo = + EvtAnimatorParameterInfo.parseFrom(entry.getCombatData()); + if (paramInfo.getIsServerCache()) { + paramInfo = paramInfo.toBuilder().setIsServerCache(false).build(); + entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build(); + } + } + default -> {} + } + + session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); + } + } + + private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { + if (session.getPlayer().isInGodMode()) { + return; + } + // People have reported that after plunge attack (client sends a FIGHT instead of + // FALL_ON_GROUND) they will die + // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping + // again. + // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. + // 200ms seems to be a reasonable delay. + int maxDelay = 200; + long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; + Grasscutter.getLogger() + .trace( + "MOTION_FALL_ON_GROUND received after " + + actualDelay + + "/" + + maxDelay + + "ms." + + (actualDelay > maxDelay ? " Discard" : "")); + if (actualDelay > maxDelay) { + return; + } + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float damageFactor = 0; + if (cachedLandingSpeed < -23.5) { + damageFactor = 0.33f; + } + if (cachedLandingSpeed < -25) { + damageFactor = 0.5f; + } + if (cachedLandingSpeed < -26.5) { + damageFactor = 0.66f; + } + if (cachedLandingSpeed < -28) { + damageFactor = 1f; + } + float damage = maxHP * damageFactor; + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } + if (damageFactor > 0) { + Grasscutter.getLogger() + .debug( + currentHP + + "/" + + maxHP + + "\tLandingSpeed: " + + cachedLandingSpeed + + "\tDamageFactor: " + + damageFactor + + "\tDamage: " + + damage + + "\tNewHP: " + + newHP); + } else { + Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage"); + } + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + entity + .getWorld() + .broadcastPacket( + new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + if (newHP == 0) { + session + .getPlayer() + .getStaminaManager() + .killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL); + } + cachedLandingSpeed = 0; + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index b1c13d419..fb07df832 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -1,29 +1,29 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; -import emu.grasscutter.server.game.GameSession; - -@Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) -public class HandlerEvtDoSkillSuccNotify extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); - - var player = session.getPlayer(); - int skillId = notify.getSkillId(); - int casterId = notify.getCasterId(); - - // Call skill perform in the player's ability manager. - player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId); - - // Handle skill notify in other managers. - player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); - player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); - player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) +public class HandlerEvtDoSkillSuccNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); + + var player = session.getPlayer(); + int skillId = notify.getSkillId(); + int casterId = notify.getCasterId(); + + // Call skill perform in the player's ability manager. + player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId); + + // Handle skill notify in other managers. + player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); + player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); + player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExecuteGadgetLuaReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExecuteGadgetLuaReq.java index a18f87630..1ef70e3a1 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExecuteGadgetLuaReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExecuteGadgetLuaReq.java @@ -1,29 +1,29 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.ExecuteGadgetLuaReqOuterClass.ExecuteGadgetLuaReq; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketExecuteGadgetLuaRsp; - -@Opcodes(PacketOpcodes.ExecuteGadgetLuaReq) -public class HandlerExecuteGadgetLuaReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - ExecuteGadgetLuaReq req = ExecuteGadgetLuaReq.parseFrom(payload); - - Player player = session.getPlayer(); - GameEntity entity = player.getScene().getEntities().get(req.getSourceEntityId()); - - int result = 1; - if (entity instanceof EntityGadget gadget) - result = gadget.onClientExecuteRequest(req.getParam1(), req.getParam2(), req.getParam3()); - - player.sendPacket(new PacketExecuteGadgetLuaRsp(result)); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ExecuteGadgetLuaReqOuterClass.ExecuteGadgetLuaReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketExecuteGadgetLuaRsp; + +@Opcodes(PacketOpcodes.ExecuteGadgetLuaReq) +public class HandlerExecuteGadgetLuaReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + ExecuteGadgetLuaReq req = ExecuteGadgetLuaReq.parseFrom(payload); + + Player player = session.getPlayer(); + GameEntity entity = player.getScene().getEntities().get(req.getSourceEntityId()); + + int result = 1; + if (entity instanceof EntityGadget gadget) + result = gadget.onClientExecuteRequest(req.getParam1(), req.getParam2(), req.getParam3()); + + player.sendPacket(new PacketExecuteGadgetLuaRsp(result)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java index 8759be3c5..b5a9b34dc 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java @@ -1,23 +1,23 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; -import emu.grasscutter.server.game.GameSession; - -@Opcodes(PacketOpcodes.GadgetInteractReq) -public class HandlerGadgetInteractReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); - - session - .getPlayer() - .getQuestManager() - .queueEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId()); - session.getPlayer().interactWith(req.getGadgetEntityId(), req); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.GadgetInteractReq) +public class HandlerGadgetInteractReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); + + session + .getPlayer() + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId()); + session.getPlayer().interactWith(req.getGadgetEntityId(), req); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllMailNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllMailNotify.java index 9ccbcfdc4..4e5a13042 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllMailNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetAllMailNotify.java @@ -1,17 +1,17 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketGetAllMailResultNotify; - -@Opcodes(PacketOpcodes.GetAllMailNotify) -public final class HandlerGetAllMailNotify extends PacketHandler { - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - var req = GetAllMailNotify.parseFrom(payload); - session.send(new PacketGetAllMailResultNotify(session.getPlayer(), req.getIsCollected())); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetAllMailNotifyOuterClass.GetAllMailNotify; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetAllMailResultNotify; + +@Opcodes(PacketOpcodes.GetAllMailNotify) +public final class HandlerGetAllMailNotify extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = GetAllMailNotify.parseFrom(payload); + session.send(new PacketGetAllMailResultNotify(session.getPlayer(), req.getIsCollected())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeUnknown2Req.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeUnknown2Req.java index 155fa19ac..5a0a51adb 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeUnknown2Req.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeUnknown2Req.java @@ -1,19 +1,19 @@ -//package emu.grasscutter.server.packet.recv; -// -//import emu.grasscutter.net.packet.Opcodes; -//import emu.grasscutter.net.packet.PacketHandler; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.server.game.GameSession; -//import emu.grasscutter.server.packet.send.PacketHomeUnknown2Rsp; -// -//@Opcodes(PacketOpcodes.Unk2700_ACILPONNGGK_ClientReq) -//public class HandlerHomeUnknown2Req extends PacketHandler { -// -// @Override -// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { -// /* -// * This packet is about the edit mode -// */ -// session.send(new PacketHomeUnknown2Rsp()); -// } -//} +// package emu.grasscutter.server.packet.recv; +// +// import emu.grasscutter.net.packet.Opcodes; +// import emu.grasscutter.net.packet.PacketHandler; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.server.game.GameSession; +// import emu.grasscutter.server.packet.send.PacketHomeUnknown2Rsp; +// +// @Opcodes(PacketOpcodes.Unk2700_ACILPONNGGK_ClientReq) +// public class HandlerHomeUnknown2Req extends PacketHandler { +// +// @Override +// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { +// /* +// * This packet is about the edit mode +// */ +// session.send(new PacketHomeUnknown2Rsp()); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java index 651a3dbc3..41ddc0624 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java @@ -1,66 +1,75 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler; -import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData; -import emu.grasscutter.game.props.ActivityType; -import emu.grasscutter.game.props.WatcherTriggerType; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass.MusicGameSettleReq; -import emu.grasscutter.net.proto.RetcodeOuterClass; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; -import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp; -import lombok.val; - -@Opcodes(PacketOpcodes.MusicGameSettleReq) -public class HandlerMusicGameSettleReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - val req = MusicGameSettleReq.parseFrom(payload); - - val activityManager = session.getPlayer().getActivityManager(); - - val playerDataOpt = activityManager.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); - if (playerDataOpt.isEmpty()) { - session.send(new PacketMusicGameSettleRsp(RetcodeOuterClass.Retcode.RET_MUSIC_GAME_LEVEL_CONFIG_NOT_FOUND, req)); - return; - } - - val playerData = playerDataOpt.get(); - val handler = (MusicGameActivityHandler) playerData.getActivityHandler(); - boolean isNewRecord = false; - - // check if custom beatmap - if (req.getUgcGuid() == 0) { - session.getPlayer().getActivityManager().triggerWatcher( - WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE, - String.valueOf(req.getMusicBasicId()), - String.valueOf(req.getScore()) - ); - - isNewRecord = handler.setMusicGameRecord(playerData, - MusicGamePlayerData.MusicGameRecord.of() - .musicId(req.getMusicBasicId()) - .maxCombo(req.getMaxCombo()) - .maxScore(req.getScore()) - .build()); - - // update activity info - session.send(new PacketActivityInfoNotify(handler.toProto(playerData, activityManager.getConditionExecutor()))); - } else { - handler.setMusicGameCustomBeatmapRecord(playerData, - MusicGamePlayerData.CustomBeatmapRecord.of() - .musicShareId(req.getUgcGuid()) - .score(req.getMaxCombo()) - .settle(req.getIsSaveScore()) - .build()); - } - - - session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord)); - } - -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler; +import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData; +import emu.grasscutter.game.props.ActivityType; +import emu.grasscutter.game.props.WatcherTriggerType; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass.MusicGameSettleReq; +import emu.grasscutter.net.proto.RetcodeOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; +import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp; +import lombok.val; + +@Opcodes(PacketOpcodes.MusicGameSettleReq) +public class HandlerMusicGameSettleReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + val req = MusicGameSettleReq.parseFrom(payload); + + val activityManager = session.getPlayer().getActivityManager(); + + val playerDataOpt = + activityManager.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); + if (playerDataOpt.isEmpty()) { + session.send( + new PacketMusicGameSettleRsp( + RetcodeOuterClass.Retcode.RET_MUSIC_GAME_LEVEL_CONFIG_NOT_FOUND, req)); + return; + } + + val playerData = playerDataOpt.get(); + val handler = (MusicGameActivityHandler) playerData.getActivityHandler(); + boolean isNewRecord = false; + + // check if custom beatmap + if (req.getUgcGuid() == 0) { + session + .getPlayer() + .getActivityManager() + .triggerWatcher( + WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE, + String.valueOf(req.getMusicBasicId()), + String.valueOf(req.getScore())); + + isNewRecord = + handler.setMusicGameRecord( + playerData, + MusicGamePlayerData.MusicGameRecord.of() + .musicId(req.getMusicBasicId()) + .maxCombo(req.getMaxCombo()) + .maxScore(req.getScore()) + .build()); + + // update activity info + session.send( + new PacketActivityInfoNotify( + handler.toProto(playerData, activityManager.getConditionExecutor()))); + } else { + handler.setMusicGameCustomBeatmapRecord( + playerData, + MusicGamePlayerData.CustomBeatmapRecord.of() + .musicShareId(req.getUgcGuid()) + .score(req.getMaxCombo()) + .settle(req.getIsSaveScore()) + .build()); + } + + session.send( + new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java index 64928e8b2..d57e6f121 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java @@ -1,67 +1,67 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.MainQuestData; -import emu.grasscutter.data.binout.MainQuestData.TalkData; -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketNpcTalkRsp; - -@Opcodes(PacketOpcodes.NpcTalkReq) -public class HandlerNpcTalkReq extends PacketHandler { - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - NpcTalkReq req = NpcTalkReq.parseFrom(payload); - - // Check if mainQuest exists - // remove last 2 digits to get a mainQuestId - int talkId = req.getTalkId(); - int mainQuestId = talkId / 100; - MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId); - - if (mainQuestData != null) { - // This talk is associated with a quest. Handle it. - // If the quest has no talk data defined on it, create one. - TalkData talkForQuest = new TalkData(talkId, ""); - if (mainQuestData.getTalks() != null) { - var talks = mainQuestData.getTalks().stream().filter(p -> p.getId() == talkId).toList(); - - if (talks.size() > 0) { - talkForQuest = talks.get(0); - } - } - - // Add to the list of done talks for this quest. - var mainQuest = session.getPlayer().getQuestManager().getMainQuestById(mainQuestId); - if (mainQuest != null) { - session - .getPlayer() - .getQuestManager() - .getMainQuestById(mainQuestId) - .getTalks() - .put(talkId, talkForQuest); - } - - // Fire quest triggers. - session - .getPlayer() - .getQuestManager() - .queueEvent( - QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0); - session - .getPlayer() - .getQuestManager() - .queueEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0); - session - .getPlayer() - .getQuestManager() - .queueEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(), 0); - } - - session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.data.binout.MainQuestData.TalkData; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketNpcTalkRsp; + +@Opcodes(PacketOpcodes.NpcTalkReq) +public class HandlerNpcTalkReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + NpcTalkReq req = NpcTalkReq.parseFrom(payload); + + // Check if mainQuest exists + // remove last 2 digits to get a mainQuestId + int talkId = req.getTalkId(); + int mainQuestId = talkId / 100; + MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId); + + if (mainQuestData != null) { + // This talk is associated with a quest. Handle it. + // If the quest has no talk data defined on it, create one. + TalkData talkForQuest = new TalkData(talkId, ""); + if (mainQuestData.getTalks() != null) { + var talks = mainQuestData.getTalks().stream().filter(p -> p.getId() == talkId).toList(); + + if (talks.size() > 0) { + talkForQuest = talks.get(0); + } + } + + // Add to the list of done talks for this quest. + var mainQuest = session.getPlayer().getQuestManager().getMainQuestById(mainQuestId); + if (mainQuest != null) { + session + .getPlayer() + .getQuestManager() + .getMainQuestById(mainQuestId) + .getTalks() + .put(talkId, talkForQuest); + } + + // Fire quest triggers. + session + .getPlayer() + .getQuestManager() + .queueEvent( + QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0); + session + .getPlayer() + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0); + session + .getPlayer() + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(), 0); + } + + session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java index a75a9bd3a..c7998a6d0 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java @@ -1,25 +1,25 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.props.SceneType; -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp; - -@Opcodes(PacketOpcodes.PostEnterSceneReq) -public class HandlerPostEnterSceneReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - if (session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM) { - session - .getPlayer() - .getQuestManager() - .queueEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0); - } - - session.send(new PacketPostEnterSceneRsp(session.getPlayer())); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp; + +@Opcodes(PacketOpcodes.PostEnterSceneReq) +public class HandlerPostEnterSceneReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + if (session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM) { + session + .getPlayer() + .getQuestManager() + .queueEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0); + } + + session.send(new PacketPostEnterSceneRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerRemoveCustomTeamReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerRemoveCustomTeamReq.java index 0ba43739c..b1442dab7 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerRemoveCustomTeamReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerRemoveCustomTeamReq.java @@ -1,16 +1,16 @@ -//package emu.grasscutter.server.packet.recv; -// -//import emu.grasscutter.net.packet.Opcodes; -//import emu.grasscutter.net.packet.PacketHandler; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.RemoveCustomTeamReqOuterClass.RemoveCustomTeamReq; -//import emu.grasscutter.server.game.GameSession; -// -//@Opcodes(PacketOpcodes.RemoveCustomTeamReq) -//public class HandlerRemoveCustomTeamReq extends PacketHandler { -// @Override -// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { -// RemoveCustomTeamReq req = RemoveCustomTeamReq.parseFrom(payload); -// session.getPlayer().getTeamManager().removeCustomTeam(req.getId()); -// } -//} +// package emu.grasscutter.server.packet.recv; +// +// import emu.grasscutter.net.packet.Opcodes; +// import emu.grasscutter.net.packet.PacketHandler; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.RemoveCustomTeamReqOuterClass.RemoveCustomTeamReq; +// import emu.grasscutter.server.game.GameSession; +// +// @Opcodes(PacketOpcodes.RemoveCustomTeamReq) +// public class HandlerRemoveCustomTeamReq extends PacketHandler { +// @Override +// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { +// RemoveCustomTeamReq req = RemoveCustomTeamReq.parseFrom(payload); +// session.getPlayer().getTeamManager().removeCustomTeam(req.getId()); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSaveUgcReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSaveUgcReq.java index ab6ee8745..f76c9723b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSaveUgcReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSaveUgcReq.java @@ -1,97 +1,102 @@ -//package emu.grasscutter.server.packet.recv; -// -//import emu.grasscutter.database.DatabaseHelper; -//import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler; -//import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap; -//import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData; -//import emu.grasscutter.game.props.ActivityType; -//import emu.grasscutter.net.packet.Opcodes; -//import emu.grasscutter.net.packet.PacketHandler; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.RetcodeOuterClass; -//import emu.grasscutter.net.proto.SaveUgcReqOuterClass; -//import emu.grasscutter.net.proto.UgcTypeOuterClass; -//import emu.grasscutter.server.game.GameSession; -//import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; -//import emu.grasscutter.server.packet.send.PacketMusicGameCreateBeatmapRsp; -//import emu.grasscutter.utils.Utils; -//import java.util.Objects; -//import lombok.val; -// -//@Opcodes(PacketOpcodes.SaveUgcReq) -//public class HandlerSaveUgcReq extends PacketHandler { -// -// @Override -// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { -// val req = SaveUgcReqOuterClass.SaveUgcReq.parseFrom(payload); -// -// // We only support music game user generated content -// if (req.getUgcType() != UgcTypeOuterClass.UgcType.UGC_TYPE_MUSIC_GAME) { -// session.send( -// new PacketMusicGameCreateBeatmapRsp( -// RetcodeOuterClass.Retcode.RET_UGC_DISABLED, req.getUgcType())); -// return; -// } -// val briefInfo = req.getMusicBriefInfo(); -// -// val musicGameBeatmap = -// MusicGameBeatmap.of() -// .musicId(briefInfo.getMusicId()) -// .musicNoteCount(briefInfo.getNoteCount()) -// .savePosition(briefInfo.getSaveIdx()) -// .savePageType(briefInfo.getSavePageType()) -// .version(briefInfo.getVersion()) -// .afterNoteList(briefInfo.getAfterNoteListList()) -// .beforeNoteList(briefInfo.getBeforeNoteListList()) -// .timeLineEditTime(briefInfo.getTimeLineEditTime()) -// .publishTime(briefInfo.getPublishTime()) -// .realTimeEditTime(briefInfo.getRealTimeEditTime()) -// .maxScore(briefInfo.getMaxScore()) -// .authorUid(session.getPlayer().getUid()) -// .beatmap(MusicGameBeatmap.parse(req.getMusicRecord().getMusicTrackListList())) -// .createTime(Utils.getCurrentSeconds()) -// .build(); -// -// musicGameBeatmap.save(); -// -// val playerData = -// session -// .getPlayer() -// .getActivityManager() -// .getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); -// if (playerData.isEmpty()) { -// session.send( -// new PacketMusicGameCreateBeatmapRsp( -// RetcodeOuterClass.Retcode.RET_UGC_DATA_NOT_FOUND, req.getUgcType())); -// return; -// } -// -// val handler = (MusicGameActivityHandler) playerData.get().getActivityHandler(); -// val musicGamePlayerData = handler.getMusicGamePlayerData(playerData.get()); -// -// val oldBeatmap = -// musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream() -// .map(MusicGamePlayerData.CustomBeatmapRecord::getMusicShareId) -// .map(DatabaseHelper::getMusicGameBeatmap) -// .filter(Objects::nonNull) -// .filter(item -> item.getAuthorUid() == session.getPlayer().getUid()) -// .filter(item -> item.getMusicId() == req.getMusicBriefInfo().getMusicId()) -// .filter(item -> item.getSavePosition() == req.getMusicBriefInfo().getSaveIdx()) -// .findFirst(); -// -// // delete old beatmap for player -// // the old beatmap is still in database so that others can still play. -// oldBeatmap.ifPresent(i -> handler.removePersonalBeatmap(playerData.get(), i)); -// -// // link this beatmap to player's personal data -// handler.addPersonalBeatmap(playerData.get(), musicGameBeatmap); -// -// session.send( -// new PacketActivityInfoNotify( -// handler.toProto( -// playerData.get(), -// session.getPlayer().getActivityManager().getConditionExecutor()))); -// session.send( -// new PacketMusicGameCreateBeatmapRsp(musicGameBeatmap.getMusicShareId(), req.getUgcType())); -// } -//} +// package emu.grasscutter.server.packet.recv; +// +// import emu.grasscutter.database.DatabaseHelper; +// import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler; +// import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap; +// import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData; +// import emu.grasscutter.game.props.ActivityType; +// import emu.grasscutter.net.packet.Opcodes; +// import emu.grasscutter.net.packet.PacketHandler; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.RetcodeOuterClass; +// import emu.grasscutter.net.proto.SaveUgcReqOuterClass; +// import emu.grasscutter.net.proto.UgcTypeOuterClass; +// import emu.grasscutter.server.game.GameSession; +// import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; +// import emu.grasscutter.server.packet.send.PacketMusicGameCreateBeatmapRsp; +// import emu.grasscutter.utils.Utils; +// import java.util.Objects; +// import lombok.val; +// +// @Opcodes(PacketOpcodes.SaveUgcReq) +// public class HandlerSaveUgcReq extends PacketHandler { +// +// @Override +// public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { +// val req = SaveUgcReqOuterClass.SaveUgcReq.parseFrom(payload); +// +// // We only support music game user generated content +// if (req.getUgcType() != UgcTypeOuterClass.UgcType.UGC_TYPE_MUSIC_GAME) { +// session.send( +// new PacketMusicGameCreateBeatmapRsp( +// RetcodeOuterClass.Retcode.RET_UGC_DISABLED, req.getUgcType())); +// return; +// } +// val briefInfo = req.getMusicBriefInfo(); +// +// val musicGameBeatmap = +// MusicGameBeatmap.of() +// .musicId(briefInfo.getMusicId()) +// .musicNoteCount(briefInfo.getNoteCount()) +// .savePosition(briefInfo.getSaveIdx()) +// .savePageType(briefInfo.getSavePageType()) +// .version(briefInfo.getVersion()) +// .afterNoteList(briefInfo.getAfterNoteListList()) +// .beforeNoteList(briefInfo.getBeforeNoteListList()) +// .timeLineEditTime(briefInfo.getTimeLineEditTime()) +// .publishTime(briefInfo.getPublishTime()) +// .realTimeEditTime(briefInfo.getRealTimeEditTime()) +// .maxScore(briefInfo.getMaxScore()) +// .authorUid(session.getPlayer().getUid()) +// +// .beatmap(MusicGameBeatmap.parse(req.getMusicRecord().getMusicTrackListList())) +// .createTime(Utils.getCurrentSeconds()) +// .build(); +// +// musicGameBeatmap.save(); +// +// val playerData = +// session +// .getPlayer() +// .getActivityManager() +// +// .getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); +// if (playerData.isEmpty()) { +// session.send( +// new PacketMusicGameCreateBeatmapRsp( +// RetcodeOuterClass.Retcode.RET_UGC_DATA_NOT_FOUND, req.getUgcType())); +// return; +// } +// +// val handler = (MusicGameActivityHandler) playerData.get().getActivityHandler(); +// val musicGamePlayerData = handler.getMusicGamePlayerData(playerData.get()); +// +// val oldBeatmap = +// musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream() +// .map(MusicGamePlayerData.CustomBeatmapRecord::getMusicShareId) +// .map(DatabaseHelper::getMusicGameBeatmap) +// .filter(Objects::nonNull) +// .filter(item -> item.getAuthorUid() == session.getPlayer().getUid()) +// .filter(item -> item.getMusicId() == req.getMusicBriefInfo().getMusicId()) +// .filter(item -> item.getSavePosition() == +// req.getMusicBriefInfo().getSaveIdx()) +// .findFirst(); +// +// // delete old beatmap for player +// // the old beatmap is still in database so that others can still play. +// oldBeatmap.ifPresent(i -> handler.removePersonalBeatmap(playerData.get(), i)); +// +// // link this beatmap to player's personal data +// handler.addPersonalBeatmap(playerData.get(), musicGameBeatmap); +// +// session.send( +// new PacketActivityInfoNotify( +// handler.toProto( +// playerData.get(), +// +// session.getPlayer().getActivityManager().getConditionExecutor()))); +// session.send( +// new PacketMusicGameCreateBeatmapRsp(musicGameBeatmap.getMusicShareId(), +// req.getUgcType())); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java index a8191a6ca..f8e4e98b1 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSelectWorktopOptionReq.java @@ -1,39 +1,49 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.quest.enums.QuestContent; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; -import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.data.ScriptArgs; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp; - -@Opcodes(PacketOpcodes.SelectWorktopOptionReq) -public class HandlerSelectWorktopOptionReq extends PacketHandler { - - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - SelectWorktopOptionReq req = SelectWorktopOptionReq.parseFrom(payload); - - try { - GameEntity entity = session.getPlayer().getScene().getEntityById(req.getGadgetEntityId()); - - if (!(entity instanceof EntityGadget)) { - return; - } - session.getPlayer().getScene().selectWorktopOptionWith(req); - session.getPlayer().getScene().getScriptManager().callEvent( - new ScriptArgs(entity.getGroupId(), EventType.EVENT_SELECT_OPTION, entity.getConfigId(), req.getOptionId()) - ); - session.getPlayer().getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_WORKTOP_SELECT, entity.getConfigId(), req.getOptionId()); - } finally { - // Always send packet - session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); - } - } - -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.quest.enums.QuestContent; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp; + +@Opcodes(PacketOpcodes.SelectWorktopOptionReq) +public class HandlerSelectWorktopOptionReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SelectWorktopOptionReq req = SelectWorktopOptionReq.parseFrom(payload); + + try { + GameEntity entity = session.getPlayer().getScene().getEntityById(req.getGadgetEntityId()); + + if (!(entity instanceof EntityGadget)) { + return; + } + session.getPlayer().getScene().selectWorktopOptionWith(req); + session + .getPlayer() + .getScene() + .getScriptManager() + .callEvent( + new ScriptArgs( + entity.getGroupId(), + EventType.EVENT_SELECT_OPTION, + entity.getConfigId(), + req.getOptionId())); + session + .getPlayer() + .getQuestManager() + .queueEvent( + QuestContent.QUEST_CONTENT_WORKTOP_SELECT, entity.getConfigId(), req.getOptionId()); + } finally { + // Always send packet + session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSkipPlayerGameTimeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSkipPlayerGameTimeReq.java index 398a76583..d30f8fb94 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSkipPlayerGameTimeReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSkipPlayerGameTimeReq.java @@ -1,21 +1,21 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.SkipPlayerGameTimeReqOuterClass; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify; -import emu.grasscutter.server.packet.send.PacketSkipPlayerGameTimeRsp; - -@Opcodes(PacketOpcodes.SkipPlayerGameTimeReq) -public class HandlerSkipPlayerGameTimeReq extends PacketHandler { - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - var req = SkipPlayerGameTimeReqOuterClass.SkipPlayerGameTimeReq.parseFrom(payload); - var player = session.getPlayer(); - player.updatePlayerGameTime(req.getGameTime()); - player.getScene().broadcastPacket(new PacketPlayerGameTimeNotify(player)); - player.sendPacket(new PacketSkipPlayerGameTimeRsp(req)); - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SkipPlayerGameTimeReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify; +import emu.grasscutter.server.packet.send.PacketSkipPlayerGameTimeRsp; + +@Opcodes(PacketOpcodes.SkipPlayerGameTimeReq) +public class HandlerSkipPlayerGameTimeReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = SkipPlayerGameTimeReqOuterClass.SkipPlayerGameTimeReq.parseFrom(payload); + var player = session.getPlayer(); + player.updatePlayerGameTime(req.getGameTime()); + player.getScene().broadcastPacket(new PacketPlayerGameTimeNotify(player)); + player.sendPacket(new PacketSkipPlayerGameTimeRsp(req)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUpdateAbilityCreatedMovingPlatformNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUpdateAbilityCreatedMovingPlatformNotify.java index cf5e1ebd5..277796c77 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUpdateAbilityCreatedMovingPlatformNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUpdateAbilityCreatedMovingPlatformNotify.java @@ -1,27 +1,28 @@ -package emu.grasscutter.server.packet.recv; - -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.gadget.platform.AbilityRoute; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify; -import emu.grasscutter.server.game.GameSession; - -@Opcodes(PacketOpcodes.UpdateAbilityCreatedMovingPlatformNotify) -public class HandlerUpdateAbilityCreatedMovingPlatformNotify extends PacketHandler { - @Override - public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - var notify = UpdateAbilityCreatedMovingPlatformNotify.parseFrom(payload); - var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId()); - - if (!(entity instanceof EntityGadget entityGadget) || !(entityGadget.getRouteConfig() instanceof AbilityRoute)) { - return; - } - - switch (notify.getOpType()) { - case OP_TYPE_ACTIVATE -> entityGadget.startPlatform(); - case OP_TYPE_DEACTIVATE -> entityGadget.stopPlatform(); - } - } -} +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.game.entity.gadget.platform.AbilityRoute; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.UpdateAbilityCreatedMovingPlatformNotify) +public class HandlerUpdateAbilityCreatedMovingPlatformNotify extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var notify = UpdateAbilityCreatedMovingPlatformNotify.parseFrom(payload); + var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId()); + + if (!(entity instanceof EntityGadget entityGadget) + || !(entityGadget.getRouteConfig() instanceof AbilityRoute)) { + return; + } + + switch (notify.getOpType()) { + case OP_TYPE_ACTIVATE -> entityGadget.startPlatform(); + case OP_TYPE_DEACTIVATE -> entityGadget.stopPlatform(); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAddCustomTeamRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAddCustomTeamRsp.java index a083fb7a5..1742deea6 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketAddCustomTeamRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAddCustomTeamRsp.java @@ -1,20 +1,21 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.AddCustomTeamRspOuterClass.AddCustomTeamRsp; -//import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; -// -//public class PacketAddCustomTeamRsp extends BasePacket { -// public PacketAddCustomTeamRsp(Retcode retcode) { -// super(PacketOpcodes.AddCustomTeamRsp); -// -// AddCustomTeamRsp proto = AddCustomTeamRsp.newBuilder().setRetcode(retcode.getNumber()).build(); -// -// this.setData(proto); -// } -// -// public PacketAddCustomTeamRsp() { -// this(Retcode.RET_SUCC); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.AddCustomTeamRspOuterClass.AddCustomTeamRsp; +// import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; +// +// public class PacketAddCustomTeamRsp extends BasePacket { +// public PacketAddCustomTeamRsp(Retcode retcode) { +// super(PacketOpcodes.AddCustomTeamRsp); +// +// AddCustomTeamRsp proto = +// AddCustomTeamRsp.newBuilder().setRetcode(retcode.getNumber()).build(); +// +// this.setData(proto); +// } +// +// public PacketAddCustomTeamRsp() { +// this(Retcode.RET_SUCC); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketBeginCameraSceneLookNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketBeginCameraSceneLookNotify.java index 6da4bcd56..b1aa5ea90 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketBeginCameraSceneLookNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketBeginCameraSceneLookNotify.java @@ -1,61 +1,61 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.BeginCameraSceneLookNotifyOuterClass.BeginCameraSceneLookNotify; -//import emu.grasscutter.utils.Position; -//import java.util.ArrayList; -//import java.util.Collection; -//import lombok.Data; -//import lombok.NoArgsConstructor; -// -//public class PacketBeginCameraSceneLookNotify extends BasePacket { -// -// public PacketBeginCameraSceneLookNotify(CameraSceneLookNotify parameters) { -// super(PacketOpcodes.BeginCameraSceneLookNotify); -// var builder = -// BeginCameraSceneLookNotify.newBuilder() -// .setLookPos(parameters.lookPos.toProto()) -// .setFollowPos(parameters.followPos.toProto()) -// .setDuration(parameters.duration) -// .setIsAllowInput(parameters.isAllowInput) -// .setIsSetFollowPos(parameters.setFollowPos) -// .setIsSetScreenXy(parameters.isScreenXY) -// .setIsRecoverKeepCurrent(parameters.recoverKeepCurrent) -// .setIsChangePlayMode(parameters.isChangePlayMode) -// .setScreenY(parameters.screenY) -// .setScreenX(parameters.screenX) -// .setIsForce(parameters.isForce) -// .setIsForce(parameters.isForceWalk) -// .setEntityId(parameters.entityId) -// .addAllOtherParams(parameters.otherParams); -// this.setData(builder); -// } -// -// // TODO check default values -// // todo find missing field usages: -// // enum Unk2700_HIAKNNCKHJB (Unk2700_LNCHDDOOECD) -// // Unk3000_MNLLCJMPMNH (uint32) -// // Unk2700_DHAHEKOGHBJ (float) -// // Unk3000_IEFIKMHCKDH (uint32) -// // Unk3000_OGCLMFFADBD (float) -// -// @Data -// @NoArgsConstructor -// public static class CameraSceneLookNotify { -// Position lookPos = new Position(); -// Position followPos = new Position(); -// float duration = 0.0f; -// boolean isAllowInput = true; -// boolean setFollowPos = false; -// boolean isScreenXY = false; -// boolean recoverKeepCurrent = true; -// boolean isForceWalk = false; -// boolean isForce = false; -// boolean isChangePlayMode = false; -// float screenY = 0.0f; -// float screenX = 0.0f; -// int entityId = 0; -// Collection otherParams = new ArrayList<>(0); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.BeginCameraSceneLookNotifyOuterClass.BeginCameraSceneLookNotify; +// import emu.grasscutter.utils.Position; +// import java.util.ArrayList; +// import java.util.Collection; +// import lombok.Data; +// import lombok.NoArgsConstructor; +// +// public class PacketBeginCameraSceneLookNotify extends BasePacket { +// +// public PacketBeginCameraSceneLookNotify(CameraSceneLookNotify parameters) { +// super(PacketOpcodes.BeginCameraSceneLookNotify); +// var builder = +// BeginCameraSceneLookNotify.newBuilder() +// .setLookPos(parameters.lookPos.toProto()) +// .setFollowPos(parameters.followPos.toProto()) +// .setDuration(parameters.duration) +// .setIsAllowInput(parameters.isAllowInput) +// .setIsSetFollowPos(parameters.setFollowPos) +// .setIsSetScreenXy(parameters.isScreenXY) +// .setIsRecoverKeepCurrent(parameters.recoverKeepCurrent) +// .setIsChangePlayMode(parameters.isChangePlayMode) +// .setScreenY(parameters.screenY) +// .setScreenX(parameters.screenX) +// .setIsForce(parameters.isForce) +// .setIsForce(parameters.isForceWalk) +// .setEntityId(parameters.entityId) +// .addAllOtherParams(parameters.otherParams); +// this.setData(builder); +// } +// +// // TODO check default values +// // todo find missing field usages: +// // enum Unk2700_HIAKNNCKHJB (Unk2700_LNCHDDOOECD) +// // Unk3000_MNLLCJMPMNH (uint32) +// // Unk2700_DHAHEKOGHBJ (float) +// // Unk3000_IEFIKMHCKDH (uint32) +// // Unk3000_OGCLMFFADBD (float) +// +// @Data +// @NoArgsConstructor +// public static class CameraSceneLookNotify { +// Position lookPos = new Position(); +// Position followPos = new Position(); +// float duration = 0.0f; +// boolean isAllowInput = true; +// boolean setFollowPos = false; +// boolean isScreenXY = false; +// boolean recoverKeepCurrent = true; +// boolean isForceWalk = false; +// boolean isForce = false; +// boolean isChangePlayMode = false; +// float screenY = 0.0f; +// float screenX = 0.0f; +// int entityId = 0; +// Collection otherParams = new ArrayList<>(0); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java index 62766db57..528d63da7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeGameTimeRsp.java @@ -1,18 +1,18 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.ChangeGameTimeRspOuterClass.ChangeGameTimeRsp; - -public class PacketChangeGameTimeRsp extends BasePacket { - - public PacketChangeGameTimeRsp(Player player) { - super(PacketOpcodes.ChangeGameTimeRsp); - - ChangeGameTimeRsp proto = - ChangeGameTimeRsp.newBuilder().setCurGameTime(player.getWorld().getGameTime()).build(); - - this.setData(proto); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ChangeGameTimeRspOuterClass.ChangeGameTimeRsp; + +public class PacketChangeGameTimeRsp extends BasePacket { + + public PacketChangeGameTimeRsp(Player player) { + super(PacketOpcodes.ChangeGameTimeRsp); + + ChangeGameTimeRsp proto = + ChangeGameTimeRsp.newBuilder().setCurGameTime(player.getWorld().getGameTime()).build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmNotify.java index 28656c624..9adf462d2 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmNotify.java @@ -1,18 +1,18 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.Unk2700FJEHHCPCBLGServerNotify; -// -//public class PacketChangeHomeBgmNotify extends BasePacket { -// public PacketChangeHomeBgmNotify(int homeBgmId) { -// super(PacketOpcodes.Unk2700_FJEHHCPCBLG_ServerNotify); -// -// var notify = -// Unk2700FJEHHCPCBLGServerNotify.Unk2700_FJEHHCPCBLG_ServerNotify.newBuilder() -// .setUnk2700BJHAMKKECEI(homeBgmId) -// .build(); -// -// this.setData(notify); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.Unk2700FJEHHCPCBLGServerNotify; +// +// public class PacketChangeHomeBgmNotify extends BasePacket { +// public PacketChangeHomeBgmNotify(int homeBgmId) { +// super(PacketOpcodes.Unk2700_FJEHHCPCBLG_ServerNotify); +// +// var notify = +// Unk2700FJEHHCPCBLGServerNotify.Unk2700_FJEHHCPCBLG_ServerNotify.newBuilder() +// .setUnk2700BJHAMKKECEI(homeBgmId) +// .build(); +// +// this.setData(notify); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmRsp.java index a7983b12d..3b730fe56 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChangeHomeBgmRsp.java @@ -1,18 +1,18 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.Unk2700OGHMHELMBNNServerRsp; -// -//public class PacketChangeHomeBgmRsp extends BasePacket { -// public PacketChangeHomeBgmRsp() { -// super(PacketOpcodes.Unk2700_OGHMHELMBNN_ServerRsp); -// -// var rsp = -// Unk2700OGHMHELMBNNServerRsp.Unk2700_OGHMHELMBNN_ServerRsp.newBuilder() -// .setRetcode(0) -// .build(); -// -// this.setData(rsp); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.Unk2700OGHMHELMBNNServerRsp; +// +// public class PacketChangeHomeBgmRsp extends BasePacket { +// public PacketChangeHomeBgmRsp() { +// super(PacketOpcodes.Unk2700_OGHMHELMBNN_ServerRsp); +// +// var rsp = +// Unk2700OGHMHELMBNNServerRsp.Unk2700_OGHMHELMBNN_ServerRsp.newBuilder() +// .setRetcode(0) +// .build(); +// +// this.setData(rsp); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCustomTeamListNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCustomTeamListNotify.java index 6df5d3537..f101696b7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketCustomTeamListNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCustomTeamListNotify.java @@ -1,29 +1,29 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.game.player.Player; -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.CustomTeamListNotifyOuterClass.CustomTeamListNotify; -// -//public class PacketCustomTeamListNotify extends BasePacket { -// public PacketCustomTeamListNotify(Player player) { -// super(PacketOpcodes.CustomTeamListNotify); -// -// CustomTeamListNotify.Builder proto = CustomTeamListNotify.newBuilder(); -// -// // Add the id list for custom teams. -// for (int id : player.getTeamManager().getTeams().keySet()) { -// if (id > 4) { -// proto.addCustomTeamIds(id); -// } -// } -// -// // Add the avatar lists for all the teams the player has. -// player -// .getTeamManager() -// .getTeams() -// .forEach((id, teamInfo) -> proto.putAvatarTeamMap(id, teamInfo.toProto(player))); -// -// this.setData(proto); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.game.player.Player; +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.CustomTeamListNotifyOuterClass.CustomTeamListNotify; +// +// public class PacketCustomTeamListNotify extends BasePacket { +// public PacketCustomTeamListNotify(Player player) { +// super(PacketOpcodes.CustomTeamListNotify); +// +// CustomTeamListNotify.Builder proto = CustomTeamListNotify.newBuilder(); +// +// // Add the id list for custom teams. +// for (int id : player.getTeamManager().getTeams().keySet()) { +// if (id > 4) { +// proto.addCustomTeamIds(id); +// } +// } +// +// // Add the avatar lists for all the teams the player has. +// player +// .getTeamManager() +// .getTeams() +// .forEach((id, teamInfo) -> proto.putAvatarTeamMap(id, teamInfo.toProto(player))); +// +// this.setData(proto); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 5439c8922..8ebcea0cb 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -1,13 +1,13 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; - -public class PacketDungeonSettleNotify extends BasePacket { - public PacketDungeonSettleNotify(BaseDungeonResult result) { - super(PacketOpcodes.DungeonSettleNotify); - - this.setData(result.getProto()); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; + +public class PacketDungeonSettleNotify extends BasePacket { + public PacketDungeonSettleNotify(BaseDungeonResult result) { + super(PacketOpcodes.DungeonSettleNotify); + + this.setData(result.getProto()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java index c86521fe7..87b4118c0 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropUpdateNotify.java @@ -1,33 +1,31 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.EntityFightPropUpdateNotifyOuterClass.EntityFightPropUpdateNotify; - -import java.util.Collection; - -public class PacketEntityFightPropUpdateNotify extends BasePacket { - public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) { - super(PacketOpcodes.EntityFightPropUpdateNotify); - - EntityFightPropUpdateNotify proto = - EntityFightPropUpdateNotify.newBuilder() - .setEntityId(entity.getId()) - .putFightPropMap(prop.getId(), entity.getFightProperty(prop)) - .build(); - - this.setData(proto); - } - - public PacketEntityFightPropUpdateNotify(GameEntity entity, Collection props) { - super(PacketOpcodes.EntityFightPropUpdateNotify); - - var protoBuilder = EntityFightPropUpdateNotify.newBuilder() - .setEntityId(entity.getId()); - props.forEach(p -> protoBuilder.putFightPropMap(p.getId(), entity.getFightProperty(p))); - - this.setData(protoBuilder); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EntityFightPropUpdateNotifyOuterClass.EntityFightPropUpdateNotify; +import java.util.Collection; + +public class PacketEntityFightPropUpdateNotify extends BasePacket { + public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) { + super(PacketOpcodes.EntityFightPropUpdateNotify); + + EntityFightPropUpdateNotify proto = + EntityFightPropUpdateNotify.newBuilder() + .setEntityId(entity.getId()) + .putFightPropMap(prop.getId(), entity.getFightProperty(prop)) + .build(); + + this.setData(proto); + } + + public PacketEntityFightPropUpdateNotify(GameEntity entity, Collection props) { + super(PacketOpcodes.EntityFightPropUpdateNotify); + + var protoBuilder = EntityFightPropUpdateNotify.newBuilder().setEntityId(entity.getId()); + props.forEach(p -> protoBuilder.putFightPropMap(p.getId(), entity.getFightProperty(p))); + + this.setData(protoBuilder); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java index 6da35e3fc..43e7a930c 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java @@ -1,26 +1,26 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.GameMainQuest; -import emu.grasscutter.game.quest.enums.ParentQuestState; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify; - -public class PacketFinishedParentQuestNotify extends BasePacket { - - public PacketFinishedParentQuestNotify(Player player) { - super(PacketOpcodes.FinishedParentQuestNotify, true); - - FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder(); - - for (GameMainQuest mainQuest : player.getQuestManager().getMainQuests().values()) { - //Canceled Quests do not appear in this packet - if (mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_CANCELED) { - proto.addParentQuestList(mainQuest.toProto(false)); - } - } - - this.setData(proto); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.enums.ParentQuestState; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify; + +public class PacketFinishedParentQuestNotify extends BasePacket { + + public PacketFinishedParentQuestNotify(Player player) { + super(PacketOpcodes.FinishedParentQuestNotify, true); + + FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder(); + + for (GameMainQuest mainQuest : player.getQuestManager().getMainQuests().values()) { + // Canceled Quests do not appear in this packet + if (mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_CANCELED) { + proto.addParentQuestList(mainQuest.toProto(false)); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java index d3e4542f4..08cc2df9b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java @@ -1,33 +1,33 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.quest.GameMainQuest; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; - -import java.util.List; - -public class PacketFinishedParentQuestUpdateNotify extends BasePacket { - - public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { - super(PacketOpcodes.FinishedParentQuestUpdateNotify); - - FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder() - .addParentQuestList(quest.toProto(true)) - .build(); - - this.setData(proto); - } - - public PacketFinishedParentQuestUpdateNotify(List quests) { - super(PacketOpcodes.FinishedParentQuestUpdateNotify); - - var proto = FinishedParentQuestUpdateNotify.newBuilder(); - - for (GameMainQuest mainQuest : quests) { - proto.addParentQuestList(mainQuest.toProto(true)); - } - proto.build(); - this.setData(proto); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; +import java.util.List; + +public class PacketFinishedParentQuestUpdateNotify extends BasePacket { + + public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { + super(PacketOpcodes.FinishedParentQuestUpdateNotify); + + FinishedParentQuestUpdateNotify proto = + FinishedParentQuestUpdateNotify.newBuilder() + .addParentQuestList(quest.toProto(true)) + .build(); + + this.setData(proto); + } + + public PacketFinishedParentQuestUpdateNotify(List quests) { + super(PacketOpcodes.FinishedParentQuestUpdateNotify); + + var proto = FinishedParentQuestUpdateNotify.newBuilder(); + + for (GameMainQuest mainQuest : quests) { + proto.addParentQuestList(mainQuest.toProto(true)); + } + proto.build(); + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailResultNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailResultNotify.java index 87689de28..a38b3eb7f 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailResultNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetAllMailResultNotify.java @@ -1,40 +1,42 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify; -import emu.grasscutter.utils.Utils; - -import java.time.Instant; -import java.util.List; - -public final class PacketGetAllMailResultNotify extends BasePacket { - /** - * @param player The player to fetch the mail for. - * @param gifts Is the mail for gifts? - */ - public PacketGetAllMailResultNotify(Player player, boolean gifts) { - super(PacketOpcodes.GetAllMailResultNotify); - - var packet = GetAllMailResultNotify.newBuilder() - .setTransaction(player.getUid() + "-" + Utils.getCurrentSeconds() + "-" + 0) - .setIsCollected(gifts) - .setPacketBeSentNum(1) - .setPacketNum(1); - - var inbox = player.getAllMail(); - if (!gifts && inbox.size() > 0) { - packet.addAllMailList(inbox.stream() - .filter(mail -> mail.stateValue == 1) - .filter(mail -> mail.expireTime > Instant.now().getEpochSecond()) - .map(mail -> mail.toProto(player)).toList()); - } else { - // Empty mailbox. - // TODO: Implement the gift mailbox. - packet.addAllMailList(List.of()); - } - - this.setData(packet.build()); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetAllMailResultNotifyOuterClass.GetAllMailResultNotify; +import emu.grasscutter.utils.Utils; +import java.time.Instant; +import java.util.List; + +public final class PacketGetAllMailResultNotify extends BasePacket { + /** + * @param player The player to fetch the mail for. + * @param gifts Is the mail for gifts? + */ + public PacketGetAllMailResultNotify(Player player, boolean gifts) { + super(PacketOpcodes.GetAllMailResultNotify); + + var packet = + GetAllMailResultNotify.newBuilder() + .setTransaction(player.getUid() + "-" + Utils.getCurrentSeconds() + "-" + 0) + .setIsCollected(gifts) + .setPacketBeSentNum(1) + .setPacketNum(1); + + var inbox = player.getAllMail(); + if (!gifts && inbox.size() > 0) { + packet.addAllMailList( + inbox.stream() + .filter(mail -> mail.stateValue == 1) + .filter(mail -> mail.expireTime > Instant.now().getEpochSecond()) + .map(mail -> mail.toProto(player)) + .toList()); + } else { + // Empty mailbox. + // TODO: Implement the gift mailbox. + packet.addAllMailList(List.of()); + } + + this.setData(packet.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown1Notify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown1Notify.java index 636f30b80..55606c63d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown1Notify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown1Notify.java @@ -1,18 +1,18 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.HomeUnknown1NotifyOuterClass; -// -//public class PacketHomeUnknown1Notify extends BasePacket { -// -// public PacketHomeUnknown1Notify(boolean isEnterEditMode) { -// super(PacketOpcodes.Unk2700_JDMPECKFGIG_ServerNotify); -// -// var proto = HomeUnknown1NotifyOuterClass.HomeUnknown1Notify.newBuilder(); -// -// proto.setIsEnterEditMode(isEnterEditMode); -// -// this.setData(proto); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.HomeUnknown1NotifyOuterClass; +// +// public class PacketHomeUnknown1Notify extends BasePacket { +// +// public PacketHomeUnknown1Notify(boolean isEnterEditMode) { +// super(PacketOpcodes.Unk2700_JDMPECKFGIG_ServerNotify); +// +// var proto = HomeUnknown1NotifyOuterClass.HomeUnknown1Notify.newBuilder(); +// +// proto.setIsEnterEditMode(isEnterEditMode); +// +// this.setData(proto); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown2Rsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown2Rsp.java index 0d74182ce..9264a0d26 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown2Rsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeUnknown2Rsp.java @@ -1,11 +1,11 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -// -//public class PacketHomeUnknown2Rsp extends BasePacket { -// -// public PacketHomeUnknown2Rsp() { -// super(PacketOpcodes.Unk2700_KIIOGMKFNNP_ServerRsp); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// +// public class PacketHomeUnknown2Rsp extends BasePacket { +// +// public PacketHomeUnknown2Rsp() { +// super(PacketOpcodes.Unk2700_KIIOGMKFNNP_ServerRsp); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java index 5651851bc..f546ed8ac 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java @@ -1,32 +1,33 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass; -import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass; -import emu.grasscutter.net.proto.RetcodeOuterClass; - -public class PacketMusicGameSettleRsp extends BasePacket { - - public PacketMusicGameSettleRsp(int musicBasicId, long musicShareId, boolean isNewRecord) { - super(PacketOpcodes.MusicGameSettleRsp); - - var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder(); - - proto.setMusicBasicId(musicBasicId).setUgcGuid(musicShareId).setIsNewRecord(isNewRecord); - - this.setData(proto); - } - - public PacketMusicGameSettleRsp(RetcodeOuterClass.Retcode errorCode, MusicGameSettleReqOuterClass.MusicGameSettleReq req) { - super(PacketOpcodes.MusicGameSettleRsp); - - var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder() - .setRetcode(errorCode.getNumber()) - .setMusicBasicId(req.getMusicBasicId()) - .setUgcGuid(req.getUgcGuid()); - - - this.setData(proto); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass; +import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass; +import emu.grasscutter.net.proto.RetcodeOuterClass; + +public class PacketMusicGameSettleRsp extends BasePacket { + + public PacketMusicGameSettleRsp(int musicBasicId, long musicShareId, boolean isNewRecord) { + super(PacketOpcodes.MusicGameSettleRsp); + + var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder(); + + proto.setMusicBasicId(musicBasicId).setUgcGuid(musicShareId).setIsNewRecord(isNewRecord); + + this.setData(proto); + } + + public PacketMusicGameSettleRsp( + RetcodeOuterClass.Retcode errorCode, MusicGameSettleReqOuterClass.MusicGameSettleReq req) { + super(PacketOpcodes.MusicGameSettleRsp); + + var proto = + MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder() + .setRetcode(errorCode.getNumber()) + .setMusicBasicId(req.getMusicBasicId()) + .setUgcGuid(req.getUgcGuid()); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStartRouteNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStartRouteNotify.java index 7414feb5b..c2c89bbd9 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStartRouteNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStartRouteNotify.java @@ -1,20 +1,21 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.PlatformStartRouteNotifyOuterClass.PlatformStartRouteNotify; -import lombok.val; - -public class PacketPlatformStartRouteNotify extends BasePacket { - public PacketPlatformStartRouteNotify(EntityGadget gadgetEntity) { - super(PacketOpcodes.PlatformStartRouteNotify); - - val notify = PlatformStartRouteNotify.newBuilder() - .setEntityId(gadgetEntity.getId()) - .setSceneTime(gadgetEntity.getScene().getSceneTime()) - .setPlatform(gadgetEntity.getPlatformInfo()); - - this.setData(notify); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlatformStartRouteNotifyOuterClass.PlatformStartRouteNotify; +import lombok.val; + +public class PacketPlatformStartRouteNotify extends BasePacket { + public PacketPlatformStartRouteNotify(EntityGadget gadgetEntity) { + super(PacketOpcodes.PlatformStartRouteNotify); + + val notify = + PlatformStartRouteNotify.newBuilder() + .setEntityId(gadgetEntity.getId()) + .setSceneTime(gadgetEntity.getScene().getSceneTime()) + .setPlatform(gadgetEntity.getPlatformInfo()); + + this.setData(notify); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStopRouteNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStopRouteNotify.java index 08cd2c64b..8d79dbb91 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStopRouteNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlatformStopRouteNotify.java @@ -1,20 +1,21 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.PlatformStopRouteNotifyOuterClass; - -public class PacketPlatformStopRouteNotify extends BasePacket { - public PacketPlatformStopRouteNotify(EntityGadget gadgetEntity) { - super(PacketOpcodes.PlatformStopRouteNotify); - - var notify = PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder() - .setPlatform(gadgetEntity.getPlatformInfo()) - .setSceneTime(gadgetEntity.getScene().getSceneTime()) - .setEntityId(gadgetEntity.getId()) - .build(); - - this.setData(notify); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.EntityGadget; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlatformStopRouteNotifyOuterClass; + +public class PacketPlatformStopRouteNotify extends BasePacket { + public PacketPlatformStopRouteNotify(EntityGadget gadgetEntity) { + super(PacketOpcodes.PlatformStopRouteNotify); + + var notify = + PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder() + .setPlatform(gadgetEntity.getPlatformInfo()) + .setSceneTime(gadgetEntity.getScene().getSceneTime()) + .setEntityId(gadgetEntity.getId()) + .build(); + + this.setData(notify); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java index 6c0f0ca78..79524c844 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerGameTimeNotify.java @@ -1,21 +1,21 @@ -package emu.grasscutter.server.packet.send; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.packet.BasePacket; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.PlayerGameTimeNotifyOuterClass.PlayerGameTimeNotify; - -public class PacketPlayerGameTimeNotify extends BasePacket { - - public PacketPlayerGameTimeNotify(Player player) { - super(PacketOpcodes.PlayerGameTimeNotify); - - PlayerGameTimeNotify proto = - PlayerGameTimeNotify.newBuilder() - .setGameTime(player.getWorld().getGameTime()) - .setUid(player.getUid()) - .build(); - - this.setData(proto); - } -} +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerGameTimeNotifyOuterClass.PlayerGameTimeNotify; + +public class PacketPlayerGameTimeNotify extends BasePacket { + + public PacketPlayerGameTimeNotify(Player player) { + super(PacketOpcodes.PlayerGameTimeNotify); + + PlayerGameTimeNotify proto = + PlayerGameTimeNotify.newBuilder() + .setGameTime(player.getWorld().getGameTime()) + .setUid(player.getUid()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketRemoveCustomTeamRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketRemoveCustomTeamRsp.java index 9661e2b16..a8e26ac6c 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketRemoveCustomTeamRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketRemoveCustomTeamRsp.java @@ -1,21 +1,22 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.RemoveCustomTeamRspOuterClass.RemoveCustomTeamRsp; -//import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; -// -//public class PacketRemoveCustomTeamRsp extends BasePacket { -// public PacketRemoveCustomTeamRsp(Retcode retcode, int id) { -// super(PacketOpcodes.RemoveCustomTeamRsp); -// -// RemoveCustomTeamRsp proto = -// RemoveCustomTeamRsp.newBuilder().setRetcode(retcode.getNumber()).setId(id).build(); -// -// this.setData(proto); -// } -// -// public PacketRemoveCustomTeamRsp(int id) { -// this(Retcode.RET_SUCC, id); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.RemoveCustomTeamRspOuterClass.RemoveCustomTeamRsp; +// import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; +// +// public class PacketRemoveCustomTeamRsp extends BasePacket { +// public PacketRemoveCustomTeamRsp(Retcode retcode, int id) { +// super(PacketOpcodes.RemoveCustomTeamRsp); +// +// RemoveCustomTeamRsp proto = +// +// RemoveCustomTeamRsp.newBuilder().setRetcode(retcode.getNumber()).setId(id).build(); +// +// this.setData(proto); +// } +// +// public PacketRemoveCustomTeamRsp(int id) { +// this(Retcode.RET_SUCC, id); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockHomeBgmNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockHomeBgmNotify.java index 4e7bd2e04..ce5549123 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockHomeBgmNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockHomeBgmNotify.java @@ -1,18 +1,18 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.Unk2700MEBFPBDNPGOServerNotify; -// -//public class PacketUnlockHomeBgmNotify extends BasePacket { -// public PacketUnlockHomeBgmNotify(int homeBgmId) { -// super(PacketOpcodes.Unk2700_MEBFPBDNPGO_ServerNotify); -// -// var notify = -// Unk2700MEBFPBDNPGOServerNotify.Unk2700_MEBFPBDNPGO_ServerNotify.newBuilder() -// .addUnk2700ELJPLMIHNIP(homeBgmId) -// .build(); -// -// this.setData(notify); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.Unk2700MEBFPBDNPGOServerNotify; +// +// public class PacketUnlockHomeBgmNotify extends BasePacket { +// public PacketUnlockHomeBgmNotify(int homeBgmId) { +// super(PacketOpcodes.Unk2700_MEBFPBDNPGO_ServerNotify); +// +// var notify = +// Unk2700MEBFPBDNPGOServerNotify.Unk2700_MEBFPBDNPGO_ServerNotify.newBuilder() +// .addUnk2700ELJPLMIHNIP(homeBgmId) +// .build(); +// +// this.setData(notify); +// } +// } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockedHomeBgmNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockedHomeBgmNotify.java index 5c3cfd8ef..366a29229 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockedHomeBgmNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockedHomeBgmNotify.java @@ -1,25 +1,25 @@ -//package emu.grasscutter.server.packet.send; -// -//import emu.grasscutter.game.player.Player; -//import emu.grasscutter.net.packet.BasePacket; -//import emu.grasscutter.net.packet.PacketOpcodes; -//import emu.grasscutter.net.proto.Unk2700LOHBMOKOPLHServerNotify; -// -//public class PacketUnlockedHomeBgmNotify extends BasePacket { -// public PacketUnlockedHomeBgmNotify(Player player) { -// super(PacketOpcodes.Unk2700_LOHBMOKOPLH_ServerNotify); -// -// if (player.getRealmList() == null) { -// return; -// } -// -// var unlocked = player.getHome().getUnlockedHomeBgmList(); -// -// var notify = -// Unk2700LOHBMOKOPLHServerNotify.Unk2700_LOHBMOKOPLH_ServerNotify.newBuilder() -// .addAllUnk2700KMEKMNONMGE(unlocked) -// .build(); -// -// this.setData(notify); -// } -//} +// package emu.grasscutter.server.packet.send; +// +// import emu.grasscutter.game.player.Player; +// import emu.grasscutter.net.packet.BasePacket; +// import emu.grasscutter.net.packet.PacketOpcodes; +// import emu.grasscutter.net.proto.Unk2700LOHBMOKOPLHServerNotify; +// +// public class PacketUnlockedHomeBgmNotify extends BasePacket { +// public PacketUnlockedHomeBgmNotify(Player player) { +// super(PacketOpcodes.Unk2700_LOHBMOKOPLH_ServerNotify); +// +// if (player.getRealmList() == null) { +// return; +// } +// +// var unlocked = player.getHome().getUnlockedHomeBgmList(); +// +// var notify = +// Unk2700LOHBMOKOPLHServerNotify.Unk2700_LOHBMOKOPLH_ServerNotify.newBuilder() +// .addAllUnk2700KMEKMNONMGE(unlocked) +// .build(); +// +// this.setData(notify); +// } +// } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 94600ec5a..c6018a6e6 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -1,513 +1,513 @@ -package emu.grasscutter.utils; - -import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE; -import static emu.grasscutter.utils.FileUtils.getResourcePath; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.ResourceLoader; -import emu.grasscutter.data.excels.achievement.AchievementData; -import emu.grasscutter.game.player.Player; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -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.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import lombok.EqualsAndHashCode; - -public final class Language { - private static final Map cachedLanguages = new ConcurrentHashMap<>(); - private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE04; - private static final Pattern textMapKeyValueRegex = Pattern.compile("\"(\\d+)\": \"(.+)\""); - private static final Path TEXTMAP_CACHE_PATH = - Path.of(Utils.toFilePath("cache/TextMapCache.bin")); - private static boolean scannedTextmaps = - false; // Ensure that we don't infinitely rescan on cache misses that don't exist - private static Int2ObjectMap textMapStrings; - private final String languageCode; - private final Map translations = new ConcurrentHashMap<>(); - - /** Reads a file and creates a language instance. */ - private Language(LanguageStreamDescription description) { - languageCode = description.getLanguageCode(); - - try { - var object = - JsonUtils.decode( - Utils.readFromInputStream(description.getLanguageFile()), JsonObject.class); - object - .entrySet() - .forEach(entry -> putFlattenedKey(translations, entry.getKey(), entry.getValue())); - } catch (Exception exception) { - Grasscutter.getLogger() - .warn("Failed to load language file: " + description.getLanguageCode(), exception); - } - } - - /** - * Creates a language instance from a code. - * - * @param langCode The language code. - * @return A language instance. - */ - public static Language getLanguage(String langCode) { - if (cachedLanguages.containsKey(langCode)) { - return cachedLanguages.get(langCode); - } - - var fallbackLanguageCode = Utils.getLanguageCode(FALLBACK_LANGUAGE); - var description = getLanguageFileDescription(langCode, fallbackLanguageCode); - var actualLanguageCode = description.getLanguageCode(); - - Language languageInst; - if (description.getLanguageFile() != null) { - languageInst = new Language(description); - cachedLanguages.put(actualLanguageCode, languageInst); - } else { - languageInst = cachedLanguages.get(actualLanguageCode); - cachedLanguages.put(langCode, languageInst); - } - - return languageInst; - } - - /** - * Returns the translated value from the key while substituting arguments. - * - * @param key The key of the translated value to return. - * @param args The arguments to substitute. - * @return A translated value with arguments substituted. - */ - public static String translate(String key, Object... args) { - String translated = Grasscutter.getLanguage().get(key); - - for (int i = 0; i < args.length; i++) { - args[i] = - switch (args[i].getClass().getSimpleName()) { - case "String" -> args[i]; - case "TextStrings" -> ((TextStrings) args[i]) - .get(0) - .replace("\\\\n", "\\n"); // TODO: Change this to server language - default -> args[i].toString(); - }; - } - - try { - return translated.formatted(args); - } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to format string: " + key, exception); - return translated; - } - } - - /** - * Returns the translated value from the key while substituting arguments. - * - * @param player Target player - * @param key The key of the translated value to return. - * @param args The arguments to substitute. - * @return A translated value with arguments substituted. - */ - public static String translate(Player player, String key, Object... args) { - if (player == null) { - return translate(key, args); - } - - var langCode = Utils.getLanguageCode(player.getAccount().getLocale()); - String translated = getLanguage(langCode).get(key); - - for (int i = 0; i < args.length; i++) { - args[i] = - switch (args[i].getClass().getSimpleName()) { - case "String" -> args[i]; - case "TextStrings" -> ((TextStrings) args[i]) - .getGC(langCode) - .replace("\\\\n", "\n"); // Note that we don't unescape \n for server console - default -> args[i].toString(); - }; - } - - try { - return translated.formatted(args); - } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to format string: " + key, exception); - return translated; - } - } - - /** - * Recursive helper function to flatten a Json tree Converts input like {"foo": {"bar": "baz"}} to - * {"foo.bar": "baz"} - * - * @param map The map to insert the keys into - * @param key The flattened key of the current element - * @param element The current element - */ - private static void putFlattenedKey(Map map, String key, JsonElement element) { - if (element.isJsonObject()) { - element - .getAsJsonObject() - .entrySet() - .forEach( - entry -> { - String keyPrefix = key.isEmpty() ? "" : key + "."; - putFlattenedKey(map, keyPrefix + entry.getKey(), entry.getValue()); - }); - } else { - map.put(key, element.getAsString()); - } - } - - /** - * create a LanguageStreamDescription - * - * @param languageCode The name of the language code. - * @param fallbackLanguageCode The name of the fallback language code. - */ - private static LanguageStreamDescription getLanguageFileDescription( - String languageCode, String fallbackLanguageCode) { - var fileName = languageCode + ".json"; - var fallback = fallbackLanguageCode + ".json"; - - String actualLanguageCode = languageCode; - InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); - - if (file == null) { // Provided fallback language. - Grasscutter.getLogger() - .warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); - actualLanguageCode = fallbackLanguageCode; - if (cachedLanguages.containsKey(actualLanguageCode)) { - return new LanguageStreamDescription(actualLanguageCode, null); - } - - file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); - } - - if (file == null) { // Fallback the fallback language. - Grasscutter.getLogger() - .warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); - actualLanguageCode = "en-US"; - if (cachedLanguages.containsKey(actualLanguageCode)) { - return new LanguageStreamDescription(actualLanguageCode, null); - } - - file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); - } - - if (file == null) - throw new RuntimeException( - "Unable to load the primary, fallback, and 'en-US' language files."); - - return new LanguageStreamDescription(actualLanguageCode, file); - } - - private static Int2ObjectMap loadTextMapFile(String language, IntSet nameHashes) { - Int2ObjectMap output = new Int2ObjectOpenHashMap<>(); - try (BufferedReader file = - Files.newBufferedReader( - getResourcePath("TextMap/TextMap" + language + ".json"), StandardCharsets.UTF_8)) { - Matcher matcher = textMapKeyValueRegex.matcher(""); - return new Int2ObjectOpenHashMap<>( - file.lines() - .sequential() - .map(matcher::reset) // Side effects, but it's faster than making a new one - .filter(Matcher::find) - .filter( - m -> - nameHashes.contains( - (int) Long.parseLong(m.group(1)))) // TODO: Cache this parse somehow - .collect( - Collectors.toMap( - m -> (int) Long.parseLong(m.group(1)), - m -> m.group(2).replace("\\\"", "\"")))); - } catch (Exception e) { - Grasscutter.getLogger().error("Error loading textmap: " + language); - Grasscutter.getLogger().error(e.toString()); - } - return output; - } - - private static Int2ObjectMap loadTextMapFiles(IntSet nameHashes) { - Map> - mapLanguageMaps = // Separate step to process the textmaps in parallel - TextStrings.LIST_LANGUAGES.parallelStream() - .collect( - Collectors.toConcurrentMap( - s -> TextStrings.MAP_LANGUAGES.getInt(s), - s -> loadTextMapFile(s, nameHashes))); - List> languageMaps = - IntStream.range(0, TextStrings.NUM_LANGUAGES) - .mapToObj(i -> mapLanguageMaps.get(i)) - .collect(Collectors.toList()); - - Map canonicalTextStrings = new HashMap<>(); - return new Int2ObjectOpenHashMap( - nameHashes - .intStream() - .boxed() - .collect( - Collectors.toMap( - key -> key, - key -> { - TextStrings t = - new TextStrings( - IntStream.range(0, TextStrings.NUM_LANGUAGES) - .mapToObj(i -> languageMaps.get(i).get((int) key)) - .collect(Collectors.toList()), - key); - return canonicalTextStrings.computeIfAbsent(t, x -> t); - }))); - } - - @SuppressWarnings("unchecked") - private static Int2ObjectMap loadTextMapsCache() throws Exception { - try (ObjectInputStream file = - new ObjectInputStream( - new BufferedInputStream(Files.newInputStream(TEXTMAP_CACHE_PATH), 0x100000))) { - final int fileVersion = file.readInt(); - if (fileVersion != TEXTMAP_CACHE_VERSION) throw new Exception("Invalid cache version"); - return (Int2ObjectMap) file.readObject(); - } - } - - private static void saveTextMapsCache(Int2ObjectMap input) throws IOException { - try { - Files.createDirectory(Path.of("cache")); - } catch (FileAlreadyExistsException ignored) { - } - try (ObjectOutputStream file = - new ObjectOutputStream( - new BufferedOutputStream( - Files.newOutputStream(TEXTMAP_CACHE_PATH, StandardOpenOption.CREATE), 0x100000))) { - file.writeInt(TEXTMAP_CACHE_VERSION); - file.writeObject(input); - } - } - - @Deprecated(forRemoval = true) - public static Int2ObjectMap getTextMapStrings() { - if (textMapStrings == null) loadTextMaps(); - return textMapStrings; - } - - public static TextStrings getTextMapKey(int key) { - if ((textMapStrings == null) || (!scannedTextmaps && !textMapStrings.containsKey(key))) - loadTextMaps(); - return textMapStrings.get(key); - } - - public static TextStrings getTextMapKey(long hash) { - return getTextMapKey((int) hash); - } - - public static void loadTextMaps() { - // Check system timestamps on cache and resources - try { - long cacheModified = Files.getLastModifiedTime(TEXTMAP_CACHE_PATH).toMillis(); - - long textmapsModified = - Files.list(getResourcePath("TextMap")) - .filter(path -> path.toString().endsWith(".json")) - .map( - path -> { - try { - return Files.getLastModifiedTime(path).toMillis(); - } catch (Exception ignored) { - Grasscutter.getLogger() - .debug("Exception while checking modified time: ", path); - return Long.MAX_VALUE; // Don't use cache, something has gone wrong - } - }) - .max(Long::compare) - .get(); - - Grasscutter.getLogger() - .debug( - "Cache modified %d, textmap modified %d".formatted(cacheModified, textmapsModified)); - if (textmapsModified < cacheModified) { - // Try loading from cache - Grasscutter.getLogger().debug("Loading cached 'TextMaps'..."); - textMapStrings = loadTextMapsCache(); - return; - } - } catch (Exception e) { - Grasscutter.getLogger().debug("Exception while checking cache: ", e); - } - - // Regenerate cache - Grasscutter.getLogger().debug("Generating TextMaps cache"); - ResourceLoader.loadAll(); - IntSet usedHashes = new IntOpenHashSet(); - GameData.getAchievementDataMap().values().stream() - .filter(AchievementData::isUsed) - .forEach( - a -> { - usedHashes.add((int) a.getTitleTextMapHash()); - usedHashes.add((int) a.getDescTextMapHash()); - }); - GameData.getAvatarDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); - GameData.getAvatarSkillDataMap() - .forEach( - (k, v) -> { - usedHashes.add((int) v.getNameTextMapHash()); - usedHashes.add((int) v.getDescTextMapHash()); - }); - GameData.getItemDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); - GameData.getHomeWorldBgmDataMap() - .forEach((k, v) -> usedHashes.add((int) v.getBgmNameTextMapHash())); - GameData.getMonsterDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); - GameData.getMainQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getTitleTextMapHash())); - GameData.getQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getDescTextMapHash())); - // Incidental strings - usedHashes.add((int) 4233146695L); // Character - usedHashes.add((int) 4231343903L); // Weapon - usedHashes.add((int) 332935371L); // Standard Wish - usedHashes.add((int) 2272170627L); // Character Event Wish - usedHashes.add((int) 3352513147L); // Character Event Wish-2 - usedHashes.add((int) 2864268523L); // Weapon Event Wish - - textMapStrings = loadTextMapFiles(usedHashes); - scannedTextmaps = true; - try { - saveTextMapsCache(textMapStrings); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to save TextMap cache: ", e); - } - } - - /** get language code */ - public String getLanguageCode() { - return languageCode; - } - - /** - * Returns the value (as a string) from a nested key. - * - * @param key The key to look for. - * @return The value (as a string) from a nested key. - */ - public String get(String key) { - if (translations.containsKey(key)) return translations.get(key); - String valueNotFoundPattern = "This value does not exist. Please report this to the Discord: "; - String result = valueNotFoundPattern + key; - if (!languageCode.equals("en-US")) { - String englishValue = getLanguage("en-US").get(key); - if (!englishValue.contains(valueNotFoundPattern)) { - result += "\nhere is english version:\n" + englishValue; - } - } - return result; - } - - private static class LanguageStreamDescription { - private final String languageCode; - private final InputStream languageFile; - - public LanguageStreamDescription(String languageCode, InputStream languageFile) { - this.languageCode = languageCode; - this.languageFile = languageFile; - } - - public String getLanguageCode() { - return languageCode; - } - - public InputStream getLanguageFile() { - return languageFile; - } - } - - @EqualsAndHashCode - public static class TextStrings implements Serializable { - public static final String[] ARR_LANGUAGES = { - "EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI" - }; - public static final String[] ARR_GC_LANGUAGES = { - "en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "en-US", "es-ES", "fr-FR", "en-US", "en-US", - "ru-RU", "en-US", "en-US" - }; // TODO: Update the placeholder en-US entries if we ever add GC translations for the missing - // client languages - public static final int NUM_LANGUAGES = ARR_LANGUAGES.length; - public static final List LIST_LANGUAGES = Arrays.asList(ARR_LANGUAGES); - public static final Object2IntMap - MAP_LANGUAGES = // Map "EN": 0, "CHS": 1, ..., "VI": 12 - new Object2IntOpenHashMap<>( - IntStream.range(0, ARR_LANGUAGES.length) - .boxed() - .collect(Collectors.toMap(i -> ARR_LANGUAGES[i], i -> i))); - public static final Object2IntMap MAP_GC_LANGUAGES = // Map "en-US": 0, "zh-CN": 1, ... - new Object2IntOpenHashMap<>( - IntStream.range(0, ARR_GC_LANGUAGES.length) - .boxed() - .collect( - Collectors.toMap( - i -> ARR_GC_LANGUAGES[i], - i -> i, - (i1, i2) -> i1))); // Have to handle duplicates referring back to the first - public String[] strings = new String[ARR_LANGUAGES.length]; - - public TextStrings() {} - - public TextStrings(String init) { - for (int i = 0; i < NUM_LANGUAGES; i++) this.strings[i] = init; - } - - public TextStrings(List strings, int key) { - // Some hashes don't have strings for some languages :( - String nullReplacement = "[N/A] %d".formatted((long) key & 0xFFFFFFFFL); - for (int i = 0; i < NUM_LANGUAGES; i++) { // Find first non-null if there is any - String s = strings.get(i); - if (s != null) { - nullReplacement = "[%s] - %s".formatted(ARR_LANGUAGES[i], s); - break; - } - } - for (int i = 0; i < NUM_LANGUAGES; i++) { - String s = strings.get(i); - if (s != null) this.strings[i] = s; - else this.strings[i] = nullReplacement; - } - } - - public static List getLanguages() { - return Arrays.stream(ARR_GC_LANGUAGES).map(Language::getLanguage).toList(); - } - - public String get(int languageIndex) { - return strings[languageIndex]; - } - - public String get(String languageCode) { - return strings[MAP_LANGUAGES.getOrDefault(languageCode, 0)]; - } - - public String getGC(String languageCode) { - return strings[MAP_GC_LANGUAGES.getOrDefault(languageCode, 0)]; - } - - public boolean set(String languageCode, String string) { - int index = MAP_LANGUAGES.getOrDefault(languageCode, -1); - if (index < 0) return false; - strings[index] = string; - return true; - } - } -} +package emu.grasscutter.utils; + +import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE; +import static emu.grasscutter.utils.FileUtils.getResourcePath; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; +import emu.grasscutter.data.excels.achievement.AchievementData; +import emu.grasscutter.game.player.Player; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +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.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import lombok.EqualsAndHashCode; + +public final class Language { + private static final Map cachedLanguages = new ConcurrentHashMap<>(); + private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE04; + private static final Pattern textMapKeyValueRegex = Pattern.compile("\"(\\d+)\": \"(.+)\""); + private static final Path TEXTMAP_CACHE_PATH = + Path.of(Utils.toFilePath("cache/TextMapCache.bin")); + private static boolean scannedTextmaps = + false; // Ensure that we don't infinitely rescan on cache misses that don't exist + private static Int2ObjectMap textMapStrings; + private final String languageCode; + private final Map translations = new ConcurrentHashMap<>(); + + /** Reads a file and creates a language instance. */ + private Language(LanguageStreamDescription description) { + languageCode = description.getLanguageCode(); + + try { + var object = + JsonUtils.decode( + Utils.readFromInputStream(description.getLanguageFile()), JsonObject.class); + object + .entrySet() + .forEach(entry -> putFlattenedKey(translations, entry.getKey(), entry.getValue())); + } catch (Exception exception) { + Grasscutter.getLogger() + .warn("Failed to load language file: " + description.getLanguageCode(), exception); + } + } + + /** + * Creates a language instance from a code. + * + * @param langCode The language code. + * @return A language instance. + */ + public static Language getLanguage(String langCode) { + if (cachedLanguages.containsKey(langCode)) { + return cachedLanguages.get(langCode); + } + + var fallbackLanguageCode = Utils.getLanguageCode(FALLBACK_LANGUAGE); + var description = getLanguageFileDescription(langCode, fallbackLanguageCode); + var actualLanguageCode = description.getLanguageCode(); + + Language languageInst; + if (description.getLanguageFile() != null) { + languageInst = new Language(description); + cachedLanguages.put(actualLanguageCode, languageInst); + } else { + languageInst = cachedLanguages.get(actualLanguageCode); + cachedLanguages.put(langCode, languageInst); + } + + return languageInst; + } + + /** + * Returns the translated value from the key while substituting arguments. + * + * @param key The key of the translated value to return. + * @param args The arguments to substitute. + * @return A translated value with arguments substituted. + */ + public static String translate(String key, Object... args) { + String translated = Grasscutter.getLanguage().get(key); + + for (int i = 0; i < args.length; i++) { + args[i] = + switch (args[i].getClass().getSimpleName()) { + case "String" -> args[i]; + case "TextStrings" -> ((TextStrings) args[i]) + .get(0) + .replace("\\\\n", "\\n"); // TODO: Change this to server language + default -> args[i].toString(); + }; + } + + try { + return translated.formatted(args); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to format string: " + key, exception); + return translated; + } + } + + /** + * Returns the translated value from the key while substituting arguments. + * + * @param player Target player + * @param key The key of the translated value to return. + * @param args The arguments to substitute. + * @return A translated value with arguments substituted. + */ + public static String translate(Player player, String key, Object... args) { + if (player == null) { + return translate(key, args); + } + + var langCode = Utils.getLanguageCode(player.getAccount().getLocale()); + String translated = getLanguage(langCode).get(key); + + for (int i = 0; i < args.length; i++) { + args[i] = + switch (args[i].getClass().getSimpleName()) { + case "String" -> args[i]; + case "TextStrings" -> ((TextStrings) args[i]) + .getGC(langCode) + .replace("\\\\n", "\n"); // Note that we don't unescape \n for server console + default -> args[i].toString(); + }; + } + + try { + return translated.formatted(args); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to format string: " + key, exception); + return translated; + } + } + + /** + * Recursive helper function to flatten a Json tree Converts input like {"foo": {"bar": "baz"}} to + * {"foo.bar": "baz"} + * + * @param map The map to insert the keys into + * @param key The flattened key of the current element + * @param element The current element + */ + private static void putFlattenedKey(Map map, String key, JsonElement element) { + if (element.isJsonObject()) { + element + .getAsJsonObject() + .entrySet() + .forEach( + entry -> { + String keyPrefix = key.isEmpty() ? "" : key + "."; + putFlattenedKey(map, keyPrefix + entry.getKey(), entry.getValue()); + }); + } else { + map.put(key, element.getAsString()); + } + } + + /** + * create a LanguageStreamDescription + * + * @param languageCode The name of the language code. + * @param fallbackLanguageCode The name of the fallback language code. + */ + private static LanguageStreamDescription getLanguageFileDescription( + String languageCode, String fallbackLanguageCode) { + var fileName = languageCode + ".json"; + var fallback = fallbackLanguageCode + ".json"; + + String actualLanguageCode = languageCode; + InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); + + if (file == null) { // Provided fallback language. + Grasscutter.getLogger() + .warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); + actualLanguageCode = fallbackLanguageCode; + if (cachedLanguages.containsKey(actualLanguageCode)) { + return new LanguageStreamDescription(actualLanguageCode, null); + } + + file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); + } + + if (file == null) { // Fallback the fallback language. + Grasscutter.getLogger() + .warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); + actualLanguageCode = "en-US"; + if (cachedLanguages.containsKey(actualLanguageCode)) { + return new LanguageStreamDescription(actualLanguageCode, null); + } + + file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); + } + + if (file == null) + throw new RuntimeException( + "Unable to load the primary, fallback, and 'en-US' language files."); + + return new LanguageStreamDescription(actualLanguageCode, file); + } + + private static Int2ObjectMap loadTextMapFile(String language, IntSet nameHashes) { + Int2ObjectMap output = new Int2ObjectOpenHashMap<>(); + try (BufferedReader file = + Files.newBufferedReader( + getResourcePath("TextMap/TextMap" + language + ".json"), StandardCharsets.UTF_8)) { + Matcher matcher = textMapKeyValueRegex.matcher(""); + return new Int2ObjectOpenHashMap<>( + file.lines() + .sequential() + .map(matcher::reset) // Side effects, but it's faster than making a new one + .filter(Matcher::find) + .filter( + m -> + nameHashes.contains( + (int) Long.parseLong(m.group(1)))) // TODO: Cache this parse somehow + .collect( + Collectors.toMap( + m -> (int) Long.parseLong(m.group(1)), + m -> m.group(2).replace("\\\"", "\"")))); + } catch (Exception e) { + Grasscutter.getLogger().error("Error loading textmap: " + language); + Grasscutter.getLogger().error(e.toString()); + } + return output; + } + + private static Int2ObjectMap loadTextMapFiles(IntSet nameHashes) { + Map> + mapLanguageMaps = // Separate step to process the textmaps in parallel + TextStrings.LIST_LANGUAGES.parallelStream() + .collect( + Collectors.toConcurrentMap( + s -> TextStrings.MAP_LANGUAGES.getInt(s), + s -> loadTextMapFile(s, nameHashes))); + List> languageMaps = + IntStream.range(0, TextStrings.NUM_LANGUAGES) + .mapToObj(i -> mapLanguageMaps.get(i)) + .collect(Collectors.toList()); + + Map canonicalTextStrings = new HashMap<>(); + return new Int2ObjectOpenHashMap( + nameHashes + .intStream() + .boxed() + .collect( + Collectors.toMap( + key -> key, + key -> { + TextStrings t = + new TextStrings( + IntStream.range(0, TextStrings.NUM_LANGUAGES) + .mapToObj(i -> languageMaps.get(i).get((int) key)) + .collect(Collectors.toList()), + key); + return canonicalTextStrings.computeIfAbsent(t, x -> t); + }))); + } + + @SuppressWarnings("unchecked") + private static Int2ObjectMap loadTextMapsCache() throws Exception { + try (ObjectInputStream file = + new ObjectInputStream( + new BufferedInputStream(Files.newInputStream(TEXTMAP_CACHE_PATH), 0x100000))) { + final int fileVersion = file.readInt(); + if (fileVersion != TEXTMAP_CACHE_VERSION) throw new Exception("Invalid cache version"); + return (Int2ObjectMap) file.readObject(); + } + } + + private static void saveTextMapsCache(Int2ObjectMap input) throws IOException { + try { + Files.createDirectory(Path.of("cache")); + } catch (FileAlreadyExistsException ignored) { + } + try (ObjectOutputStream file = + new ObjectOutputStream( + new BufferedOutputStream( + Files.newOutputStream(TEXTMAP_CACHE_PATH, StandardOpenOption.CREATE), 0x100000))) { + file.writeInt(TEXTMAP_CACHE_VERSION); + file.writeObject(input); + } + } + + @Deprecated(forRemoval = true) + public static Int2ObjectMap getTextMapStrings() { + if (textMapStrings == null) loadTextMaps(); + return textMapStrings; + } + + public static TextStrings getTextMapKey(int key) { + if ((textMapStrings == null) || (!scannedTextmaps && !textMapStrings.containsKey(key))) + loadTextMaps(); + return textMapStrings.get(key); + } + + public static TextStrings getTextMapKey(long hash) { + return getTextMapKey((int) hash); + } + + public static void loadTextMaps() { + // Check system timestamps on cache and resources + try { + long cacheModified = Files.getLastModifiedTime(TEXTMAP_CACHE_PATH).toMillis(); + + long textmapsModified = + Files.list(getResourcePath("TextMap")) + .filter(path -> path.toString().endsWith(".json")) + .map( + path -> { + try { + return Files.getLastModifiedTime(path).toMillis(); + } catch (Exception ignored) { + Grasscutter.getLogger() + .debug("Exception while checking modified time: ", path); + return Long.MAX_VALUE; // Don't use cache, something has gone wrong + } + }) + .max(Long::compare) + .get(); + + Grasscutter.getLogger() + .debug( + "Cache modified %d, textmap modified %d".formatted(cacheModified, textmapsModified)); + if (textmapsModified < cacheModified) { + // Try loading from cache + Grasscutter.getLogger().debug("Loading cached 'TextMaps'..."); + textMapStrings = loadTextMapsCache(); + return; + } + } catch (Exception e) { + Grasscutter.getLogger().debug("Exception while checking cache: ", e); + } + + // Regenerate cache + Grasscutter.getLogger().debug("Generating TextMaps cache"); + ResourceLoader.loadAll(); + IntSet usedHashes = new IntOpenHashSet(); + GameData.getAchievementDataMap().values().stream() + .filter(AchievementData::isUsed) + .forEach( + a -> { + usedHashes.add((int) a.getTitleTextMapHash()); + usedHashes.add((int) a.getDescTextMapHash()); + }); + GameData.getAvatarDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); + GameData.getAvatarSkillDataMap() + .forEach( + (k, v) -> { + usedHashes.add((int) v.getNameTextMapHash()); + usedHashes.add((int) v.getDescTextMapHash()); + }); + GameData.getItemDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); + GameData.getHomeWorldBgmDataMap() + .forEach((k, v) -> usedHashes.add((int) v.getBgmNameTextMapHash())); + GameData.getMonsterDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash())); + GameData.getMainQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getTitleTextMapHash())); + GameData.getQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getDescTextMapHash())); + // Incidental strings + usedHashes.add((int) 4233146695L); // Character + usedHashes.add((int) 4231343903L); // Weapon + usedHashes.add((int) 332935371L); // Standard Wish + usedHashes.add((int) 2272170627L); // Character Event Wish + usedHashes.add((int) 3352513147L); // Character Event Wish-2 + usedHashes.add((int) 2864268523L); // Weapon Event Wish + + textMapStrings = loadTextMapFiles(usedHashes); + scannedTextmaps = true; + try { + saveTextMapsCache(textMapStrings); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to save TextMap cache: ", e); + } + } + + /** get language code */ + public String getLanguageCode() { + return languageCode; + } + + /** + * Returns the value (as a string) from a nested key. + * + * @param key The key to look for. + * @return The value (as a string) from a nested key. + */ + public String get(String key) { + if (translations.containsKey(key)) return translations.get(key); + String valueNotFoundPattern = "This value does not exist. Please report this to the Discord: "; + String result = valueNotFoundPattern + key; + if (!languageCode.equals("en-US")) { + String englishValue = getLanguage("en-US").get(key); + if (!englishValue.contains(valueNotFoundPattern)) { + result += "\nhere is english version:\n" + englishValue; + } + } + return result; + } + + private static class LanguageStreamDescription { + private final String languageCode; + private final InputStream languageFile; + + public LanguageStreamDescription(String languageCode, InputStream languageFile) { + this.languageCode = languageCode; + this.languageFile = languageFile; + } + + public String getLanguageCode() { + return languageCode; + } + + public InputStream getLanguageFile() { + return languageFile; + } + } + + @EqualsAndHashCode + public static class TextStrings implements Serializable { + public static final String[] ARR_LANGUAGES = { + "EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI" + }; + public static final String[] ARR_GC_LANGUAGES = { + "en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "en-US", "es-ES", "fr-FR", "en-US", "en-US", + "ru-RU", "en-US", "en-US" + }; // TODO: Update the placeholder en-US entries if we ever add GC translations for the missing + // client languages + public static final int NUM_LANGUAGES = ARR_LANGUAGES.length; + public static final List LIST_LANGUAGES = Arrays.asList(ARR_LANGUAGES); + public static final Object2IntMap + MAP_LANGUAGES = // Map "EN": 0, "CHS": 1, ..., "VI": 12 + new Object2IntOpenHashMap<>( + IntStream.range(0, ARR_LANGUAGES.length) + .boxed() + .collect(Collectors.toMap(i -> ARR_LANGUAGES[i], i -> i))); + public static final Object2IntMap MAP_GC_LANGUAGES = // Map "en-US": 0, "zh-CN": 1, ... + new Object2IntOpenHashMap<>( + IntStream.range(0, ARR_GC_LANGUAGES.length) + .boxed() + .collect( + Collectors.toMap( + i -> ARR_GC_LANGUAGES[i], + i -> i, + (i1, i2) -> i1))); // Have to handle duplicates referring back to the first + public String[] strings = new String[ARR_LANGUAGES.length]; + + public TextStrings() {} + + public TextStrings(String init) { + for (int i = 0; i < NUM_LANGUAGES; i++) this.strings[i] = init; + } + + public TextStrings(List strings, int key) { + // Some hashes don't have strings for some languages :( + String nullReplacement = "[N/A] %d".formatted((long) key & 0xFFFFFFFFL); + for (int i = 0; i < NUM_LANGUAGES; i++) { // Find first non-null if there is any + String s = strings.get(i); + if (s != null) { + nullReplacement = "[%s] - %s".formatted(ARR_LANGUAGES[i], s); + break; + } + } + for (int i = 0; i < NUM_LANGUAGES; i++) { + String s = strings.get(i); + if (s != null) this.strings[i] = s; + else this.strings[i] = nullReplacement; + } + } + + public static List getLanguages() { + return Arrays.stream(ARR_GC_LANGUAGES).map(Language::getLanguage).toList(); + } + + public String get(int languageIndex) { + return strings[languageIndex]; + } + + public String get(String languageCode) { + return strings[MAP_LANGUAGES.getOrDefault(languageCode, 0)]; + } + + public String getGC(String languageCode) { + return strings[MAP_GC_LANGUAGES.getOrDefault(languageCode, 0)]; + } + + public boolean set(String languageCode, String string) { + int index = MAP_LANGUAGES.getOrDefault(languageCode, -1); + if (index < 0) return false; + strings[index] = string; + return true; + } + } +}