Merge branch 'development' into unstable-quests

# Conflicts:
#	src/generated/main/java/emu/grasscutter/net/proto/ResinChangeNotifyOuterClass.java
#	src/main/java/emu/grasscutter/game/managers/ResinManager.java
#	src/main/java/emu/grasscutter/game/player/Player.java
#	src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddItem.java
#	src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java
#	src/main/java/emu/grasscutter/server/packet/send/PacketResinChangeNotify.java
#	src/main/java/emu/grasscutter/utils/Crypto.java
This commit is contained in:
KingRainbow44
2023-04-10 22:13:50 -04:00
15 changed files with 3822 additions and 1600 deletions

View File

@@ -1,7 +1,7 @@
## Description ## Description
Please carefully read the [Contributing note](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) and [Code of conduct](https://github.com/Grasscutters/Grasscutter/blob/development/CODE_OF_CONDUCT.md) before making any pull requests. Please carefully read the [Contributing note](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) and [Code of conduct](https://github.com/Grasscutters/Grasscutter/blob/development/CODE_OF_CONDUCT.md) before making any pull requests.
And, **Do not make a pull request to merge into stable unless it is a hotfix. Use the development branch instead.**
## Issues fixed by this PR ## Issues fixed by this PR
<!--- Put the links of issues that may be fixed by this PR here (if any). --> <!--- Put the links of issues that may be fixed by this PR here (if any). -->
@@ -19,4 +19,4 @@ And, **Do not make a pull request to merge into stable unless it is a hotfix. Us
- [ ] My code follows the style guidelines of this project - [ ] My code follows the style guidelines of this project
- [ ] My pull request is unique and no other pull requests have been opened for these changes - [ ] My pull request is unique and no other pull requests have been opened for these changes
- [ ] I have read the [Contributing note](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) and [Code of conduct](https://github.com/Grasscutters/Grasscutter/blob/development/CODE_OF_CONDUCT.md) - [ ] I have read the [Contributing note](https://github.com/Grasscutters/Grasscutter/blob/stable/CONTRIBUTING.md) and [Code of conduct](https://github.com/Grasscutters/Grasscutter/blob/development/CODE_OF_CONDUCT.md)
- [ ] I am responsible for any copyright issues with my code if it occurs in the future. - [ ] I am responsible for any copyright issues with my code if it occurs in the future.

View File

@@ -0,0 +1,475 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: BuyResinReq.proto
package emu.grasscutter.net.proto;
public final class BuyResinReqOuterClass {
private BuyResinReqOuterClass() {}
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 BuyResinReqOrBuilder extends
// @@protoc_insertion_point(interface_extends:BuyResinReq)
com.google.protobuf.MessageOrBuilder {
}
/**
* <pre>
* Name: NIBFJEFHMIM
* CmdId: 630
* </pre>
*
* Protobuf type {@code BuyResinReq}
*/
public static final class BuyResinReq extends
com.google.protobuf.GeneratedMessageV3 implements
// @@protoc_insertion_point(message_implements:BuyResinReq)
BuyResinReqOrBuilder {
private static final long serialVersionUID = 0L;
// Use BuyResinReq.newBuilder() to construct.
private BuyResinReq(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super(builder);
}
private BuyResinReq() {
}
@java.lang.Override
@SuppressWarnings({"unused"})
protected java.lang.Object newInstance(
UnusedPrivateParameter unused) {
return new BuyResinReq();
}
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return this.unknownFields;
}
private BuyResinReq(
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;
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.BuyResinReqOuterClass.internal_static_BuyResinReq_descriptor;
}
@java.lang.Override
protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internalGetFieldAccessorTable() {
return emu.grasscutter.net.proto.BuyResinReqOuterClass.internal_static_BuyResinReq_fieldAccessorTable
.ensureFieldAccessorsInitialized(
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.class, emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.Builder.class);
}
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 {
unknownFields.writeTo(output);
}
@java.lang.Override
public int getSerializedSize() {
int size = memoizedSize;
if (size != -1) return size;
size = 0;
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.BuyResinReqOuterClass.BuyResinReq)) {
return super.equals(obj);
}
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq other = (emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq) obj;
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 = (29 * hash) + unknownFields.hashCode();
memoizedHashCode = hash;
return hash;
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq parseFrom(
java.nio.ByteBuffer data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq 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.BuyResinReqOuterClass.BuyResinReq parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq 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.BuyResinReqOuterClass.BuyResinReq parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq parseFrom(java.io.InputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq 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.BuyResinReqOuterClass.BuyResinReq parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseDelimitedWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq 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.BuyResinReqOuterClass.BuyResinReq parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq 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.BuyResinReqOuterClass.BuyResinReq 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;
}
/**
* <pre>
* Name: NIBFJEFHMIM
* CmdId: 630
* </pre>
*
* Protobuf type {@code BuyResinReq}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
// @@protoc_insertion_point(builder_implements:BuyResinReq)
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReqOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return emu.grasscutter.net.proto.BuyResinReqOuterClass.internal_static_BuyResinReq_descriptor;
}
@java.lang.Override
protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internalGetFieldAccessorTable() {
return emu.grasscutter.net.proto.BuyResinReqOuterClass.internal_static_BuyResinReq_fieldAccessorTable
.ensureFieldAccessorsInitialized(
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.class, emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.Builder.class);
}
// Construct using emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.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();
return this;
}
@java.lang.Override
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return emu.grasscutter.net.proto.BuyResinReqOuterClass.internal_static_BuyResinReq_descriptor;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq getDefaultInstanceForType() {
return emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.getDefaultInstance();
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq build() {
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq buildPartial() {
emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq result = new emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq(this);
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.BuyResinReqOuterClass.BuyResinReq) {
return mergeFrom((emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq other) {
if (other == emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq.getDefaultInstance()) return this;
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.BuyResinReqOuterClass.BuyResinReq parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
parsedMessage = (emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq) e.getUnfinishedMessage();
throw e.unwrapIOException();
} finally {
if (parsedMessage != null) {
mergeFrom(parsedMessage);
}
}
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:BuyResinReq)
}
// @@protoc_insertion_point(class_scope:BuyResinReq)
private static final emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq DEFAULT_INSTANCE;
static {
DEFAULT_INSTANCE = new emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq();
}
public static emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq getDefaultInstance() {
return DEFAULT_INSTANCE;
}
private static final com.google.protobuf.Parser<BuyResinReq>
PARSER = new com.google.protobuf.AbstractParser<BuyResinReq>() {
@java.lang.Override
public BuyResinReq parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new BuyResinReq(input, extensionRegistry);
}
};
public static com.google.protobuf.Parser<BuyResinReq> parser() {
return PARSER;
}
@java.lang.Override
public com.google.protobuf.Parser<BuyResinReq> getParserForType() {
return PARSER;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinReqOuterClass.BuyResinReq getDefaultInstanceForType() {
return DEFAULT_INSTANCE;
}
}
private static final com.google.protobuf.Descriptors.Descriptor
internal_static_BuyResinReq_descriptor;
private static final
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internal_static_BuyResinReq_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\021BuyResinReq.proto\"\r\n\013BuyResinReqB\033\n\031em" +
"u.grasscutter.net.protob\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
});
internal_static_BuyResinReq_descriptor =
getDescriptor().getMessageTypes().get(0);
internal_static_BuyResinReq_fieldAccessorTable = new
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
internal_static_BuyResinReq_descriptor,
new java.lang.String[] { });
}
// @@protoc_insertion_point(outer_class_scope)
}

View File

@@ -0,0 +1,616 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: BuyResinRsp.proto
package emu.grasscutter.net.proto;
public final class BuyResinRspOuterClass {
private BuyResinRspOuterClass() {}
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 BuyResinRspOrBuilder extends
// @@protoc_insertion_point(interface_extends:BuyResinRsp)
com.google.protobuf.MessageOrBuilder {
/**
* <code>int32 retcode = 9;</code>
* @return The retcode.
*/
int getRetcode();
/**
* <code>uint32 cur_value = 3;</code>
* @return The curValue.
*/
int getCurValue();
}
/**
* <pre>
* Name: PFLBPGOHFKJ
* CmdId: 687
* </pre>
*
* Protobuf type {@code BuyResinRsp}
*/
public static final class BuyResinRsp extends
com.google.protobuf.GeneratedMessageV3 implements
// @@protoc_insertion_point(message_implements:BuyResinRsp)
BuyResinRspOrBuilder {
private static final long serialVersionUID = 0L;
// Use BuyResinRsp.newBuilder() to construct.
private BuyResinRsp(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super(builder);
}
private BuyResinRsp() {
}
@java.lang.Override
@SuppressWarnings({"unused"})
protected java.lang.Object newInstance(
UnusedPrivateParameter unused) {
return new BuyResinRsp();
}
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return this.unknownFields;
}
private BuyResinRsp(
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 24: {
curValue_ = input.readUInt32();
break;
}
case 72: {
retcode_ = input.readInt32();
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.BuyResinRspOuterClass.internal_static_BuyResinRsp_descriptor;
}
@java.lang.Override
protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internalGetFieldAccessorTable() {
return emu.grasscutter.net.proto.BuyResinRspOuterClass.internal_static_BuyResinRsp_fieldAccessorTable
.ensureFieldAccessorsInitialized(
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.class, emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.Builder.class);
}
public static final int RETCODE_FIELD_NUMBER = 9;
private int retcode_;
/**
* <code>int32 retcode = 9;</code>
* @return The retcode.
*/
@java.lang.Override
public int getRetcode() {
return retcode_;
}
public static final int CUR_VALUE_FIELD_NUMBER = 3;
private int curValue_;
/**
* <code>uint32 cur_value = 3;</code>
* @return The curValue.
*/
@java.lang.Override
public int getCurValue() {
return curValue_;
}
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 (curValue_ != 0) {
output.writeUInt32(3, curValue_);
}
if (retcode_ != 0) {
output.writeInt32(9, retcode_);
}
unknownFields.writeTo(output);
}
@java.lang.Override
public int getSerializedSize() {
int size = memoizedSize;
if (size != -1) return size;
size = 0;
if (curValue_ != 0) {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(3, curValue_);
}
if (retcode_ != 0) {
size += com.google.protobuf.CodedOutputStream
.computeInt32Size(9, retcode_);
}
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.BuyResinRspOuterClass.BuyResinRsp)) {
return super.equals(obj);
}
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp other = (emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp) obj;
if (getRetcode()
!= other.getRetcode()) return false;
if (getCurValue()
!= other.getCurValue()) 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) + CUR_VALUE_FIELD_NUMBER;
hash = (53 * hash) + getCurValue();
hash = (29 * hash) + unknownFields.hashCode();
memoizedHashCode = hash;
return hash;
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp parseFrom(
java.nio.ByteBuffer data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp 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.BuyResinRspOuterClass.BuyResinRsp parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp 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.BuyResinRspOuterClass.BuyResinRsp parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp parseFrom(java.io.InputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp 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.BuyResinRspOuterClass.BuyResinRsp parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseDelimitedWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp 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.BuyResinRspOuterClass.BuyResinRsp parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseWithIOException(PARSER, input);
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp 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.BuyResinRspOuterClass.BuyResinRsp 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;
}
/**
* <pre>
* Name: PFLBPGOHFKJ
* CmdId: 687
* </pre>
*
* Protobuf type {@code BuyResinRsp}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
// @@protoc_insertion_point(builder_implements:BuyResinRsp)
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRspOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return emu.grasscutter.net.proto.BuyResinRspOuterClass.internal_static_BuyResinRsp_descriptor;
}
@java.lang.Override
protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internalGetFieldAccessorTable() {
return emu.grasscutter.net.proto.BuyResinRspOuterClass.internal_static_BuyResinRsp_fieldAccessorTable
.ensureFieldAccessorsInitialized(
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.class, emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.Builder.class);
}
// Construct using emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.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();
retcode_ = 0;
curValue_ = 0;
return this;
}
@java.lang.Override
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return emu.grasscutter.net.proto.BuyResinRspOuterClass.internal_static_BuyResinRsp_descriptor;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp getDefaultInstanceForType() {
return emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.getDefaultInstance();
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp build() {
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp buildPartial() {
emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp result = new emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp(this);
result.retcode_ = retcode_;
result.curValue_ = curValue_;
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.BuyResinRspOuterClass.BuyResinRsp) {
return mergeFrom((emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp other) {
if (other == emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp.getDefaultInstance()) return this;
if (other.getRetcode() != 0) {
setRetcode(other.getRetcode());
}
if (other.getCurValue() != 0) {
setCurValue(other.getCurValue());
}
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.BuyResinRspOuterClass.BuyResinRsp parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
parsedMessage = (emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp) e.getUnfinishedMessage();
throw e.unwrapIOException();
} finally {
if (parsedMessage != null) {
mergeFrom(parsedMessage);
}
}
return this;
}
private int retcode_ ;
/**
* <code>int32 retcode = 9;</code>
* @return The retcode.
*/
@java.lang.Override
public int getRetcode() {
return retcode_;
}
/**
* <code>int32 retcode = 9;</code>
* @param value The retcode to set.
* @return This builder for chaining.
*/
public Builder setRetcode(int value) {
retcode_ = value;
onChanged();
return this;
}
/**
* <code>int32 retcode = 9;</code>
* @return This builder for chaining.
*/
public Builder clearRetcode() {
retcode_ = 0;
onChanged();
return this;
}
private int curValue_ ;
/**
* <code>uint32 cur_value = 3;</code>
* @return The curValue.
*/
@java.lang.Override
public int getCurValue() {
return curValue_;
}
/**
* <code>uint32 cur_value = 3;</code>
* @param value The curValue to set.
* @return This builder for chaining.
*/
public Builder setCurValue(int value) {
curValue_ = value;
onChanged();
return this;
}
/**
* <code>uint32 cur_value = 3;</code>
* @return This builder for chaining.
*/
public Builder clearCurValue() {
curValue_ = 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:BuyResinRsp)
}
// @@protoc_insertion_point(class_scope:BuyResinRsp)
private static final emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp DEFAULT_INSTANCE;
static {
DEFAULT_INSTANCE = new emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp();
}
public static emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp getDefaultInstance() {
return DEFAULT_INSTANCE;
}
private static final com.google.protobuf.Parser<BuyResinRsp>
PARSER = new com.google.protobuf.AbstractParser<BuyResinRsp>() {
@java.lang.Override
public BuyResinRsp parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new BuyResinRsp(input, extensionRegistry);
}
};
public static com.google.protobuf.Parser<BuyResinRsp> parser() {
return PARSER;
}
@java.lang.Override
public com.google.protobuf.Parser<BuyResinRsp> getParserForType() {
return PARSER;
}
@java.lang.Override
public emu.grasscutter.net.proto.BuyResinRspOuterClass.BuyResinRsp getDefaultInstanceForType() {
return DEFAULT_INSTANCE;
}
}
private static final com.google.protobuf.Descriptors.Descriptor
internal_static_BuyResinRsp_descriptor;
private static final
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internal_static_BuyResinRsp_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\021BuyResinRsp.proto\"1\n\013BuyResinRsp\022\017\n\007re" +
"tcode\030\t \001(\005\022\021\n\tcur_value\030\003 \001(\rB\033\n\031emu.gr" +
"asscutter.net.protob\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
});
internal_static_BuyResinRsp_descriptor =
getDescriptor().getMessageTypes().get(0);
internal_static_BuyResinRsp_fieldAccessorTable = new
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
internal_static_BuyResinRsp_descriptor,
new java.lang.String[] { "Retcode", "CurValue", });
}
// @@protoc_insertion_point(outer_class_scope)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,154 +1,173 @@
package emu.grasscutter.game.managers; package emu.grasscutter.game.managers;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.BasePlayerManager; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.packet.send.PacketResinChangeNotify; import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.utils.Utils; import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketResinChangeNotify;
public class ResinManager extends BasePlayerManager { import emu.grasscutter.utils.Utils;
public ResinManager(Player player) { import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
super(player);
} public class ResinManager extends BasePlayerManager {
public static final int MAX_RESIN_BUYING_COUNT = 6;
/******************** public static final int AMOUNT_TO_ADD = 60;
* Change resin. public static final int[] HCOIN_NUM_TO_BUY_RESIN = new int[]{50, 100, 100, 150, 200, 200};
********************/
public synchronized boolean useResin(int amount) { public ResinManager(Player player) {
// Check if resin enabled. super(player);
if (!GAME_OPTIONS.resinOptions.resinUsage) { }
return true;
} /********************
* Change resin.
int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); ********************/
public synchronized boolean useResin(int amount) {
// Check if the player has sufficient resin. // Check if resin enabled.
if (currentResin < amount) { if (!GAME_OPTIONS.resinOptions.resinUsage) {
return false; return true;
} }
// Deduct the resin from the player. int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
int newResin = currentResin - amount;
this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin); // Check if the player has sufficient resin.
if (currentResin < amount) {
// Check if this has taken the player under the recharge cap, return false;
// starting the recharging process. }
if (this.player.getNextResinRefresh() == 0 && newResin < GAME_OPTIONS.resinOptions.cap) {
int currentTime = Utils.getCurrentSeconds(); // Deduct the resin from the player.
this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime); int newResin = currentResin - amount;
} this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin);
// Send packets. // Check if this has taken the player under the recharge cap,
this.player.sendPacket(new PacketResinChangeNotify(this.player)); // starting the recharging process.
if (this.player.getNextResinRefresh() == 0 && newResin < GAME_OPTIONS.resinOptions.cap) {
// Battle Pass trigger int currentTime = Utils.getCurrentSeconds();
this.player this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime);
.getBattlePassManager() }
.triggerMission(
WatcherTriggerType.TRIGGER_COST_MATERIAL, 106, amount); // Resin item id = 106 // Send packets.
this.player.sendPacket(new PacketResinChangeNotify(this.player));
return true;
} // Battle Pass trigger
this.player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, 106, amount); // Resin item id = 106
public synchronized boolean useCondensedResin(int amount) {
// Don't deduct if resin disabled. return true;
if (!GAME_OPTIONS.resinOptions.resinUsage) return true; }
return this.player.getInventory().payItem(220007, amount);
} public synchronized boolean useCondensedResin(int amount) {
// Don't deduct if resin disabled.
public synchronized void addResin(int amount) { if (!GAME_OPTIONS.resinOptions.resinUsage) return true;
// Check if resin enabled. return this.player.getInventory().payItem(220007, amount);
if (!GAME_OPTIONS.resinOptions.resinUsage) { }
return;
} public synchronized void addResin(int amount) {
// Check if resin enabled.
// Add resin. if (!GAME_OPTIONS.resinOptions.resinUsage) {
int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); return;
int newResin = currentResin + amount; }
this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin);
// Add resin.
// Stop recharging if player is now at or over the cap. int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
if (newResin >= GAME_OPTIONS.resinOptions.cap) { int newResin = currentResin + amount;
this.player.setNextResinRefresh(0); this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin);
}
// Stop recharging if player is now at or over the cap.
// Send packets. if (newResin >= GAME_OPTIONS.resinOptions.cap) {
this.player.sendPacket(new PacketResinChangeNotify(this.player)); this.player.setNextResinRefresh(0);
} }
/******************** // Send packets.
* Recharge resin. this.player.sendPacket(new PacketResinChangeNotify(this.player));
********************/ }
public synchronized void rechargeResin() {
// Check if resin enabled. /********************
if (!GAME_OPTIONS.resinOptions.resinUsage) { * Recharge resin.
return; ********************/
} public synchronized void rechargeResin() {
// Check if resin enabled.
int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); if (!GAME_OPTIONS.resinOptions.resinUsage) {
int currentTime = Utils.getCurrentSeconds(); return;
}
// Make sure we are currently in "recharging mode".
// This is denoted by Player.nextResinRefresh being greater than 0. int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
if (this.player.getNextResinRefresh() <= 0) { int currentTime = Utils.getCurrentSeconds();
return;
} // Make sure we are currently in "recharging mode".
// This is denoted by Player.nextResinRefresh being greater than 0.
// Determine if we actually need to recharge yet. if (this.player.getNextResinRefresh() <= 0) {
if (currentTime < this.player.getNextResinRefresh()) { return;
return; }
}
// Determine if we actually need to recharge yet.
// Calculate how much resin we need to refill and update player. if (currentTime < this.player.getNextResinRefresh()) {
// Note that this can be more than one in case the player return;
// logged off with uncapped resin and is now logging in again. }
int recharge =
1 // Calculate how much resin we need to refill and update player.
+ ((currentTime - this.player.getNextResinRefresh()) // Note that this can be more than one in case the player
/ GAME_OPTIONS.resinOptions.rechargeTime); // logged off with uncapped resin and is now logging in again.
int newResin = Math.min(GAME_OPTIONS.resinOptions.cap, currentResin + recharge); int recharge = 1 + (int)((currentTime - this.player.getNextResinRefresh()) / GAME_OPTIONS.resinOptions.rechargeTime);
int resinChange = newResin - currentResin; int newResin = Math.min(GAME_OPTIONS.resinOptions.cap, currentResin + recharge);
int resinChange = newResin - currentResin;
this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin);
this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, newResin);
// Calculate next recharge time.
// Set to zero to disable recharge (because on/over cap.) // Calculate next recharge time.
if (newResin >= GAME_OPTIONS.resinOptions.cap) { // Set to zero to disable recharge (because on/over cap.)
this.player.setNextResinRefresh(0); if (newResin >= GAME_OPTIONS.resinOptions.cap) {
} else { this.player.setNextResinRefresh(0);
int nextRecharge = }
this.player.getNextResinRefresh() + resinChange * GAME_OPTIONS.resinOptions.rechargeTime; else {
this.player.setNextResinRefresh(nextRecharge); int nextRecharge = this.player.getNextResinRefresh() + resinChange * GAME_OPTIONS.resinOptions.rechargeTime;
} this.player.setNextResinRefresh(nextRecharge);
}
// Send packets.
this.player.sendPacket(new PacketResinChangeNotify(this.player)); // Send packets.
} this.player.sendPacket(new PacketResinChangeNotify(this.player));
}
/********************
* Player login. /********************
********************/ * Player login.
public synchronized void onPlayerLogin() { ********************/
// If resin usage is disabled, set resin to cap. public synchronized void onPlayerLogin() {
if (!GAME_OPTIONS.resinOptions.resinUsage) { // If resin usage is disabled, set resin to cap.
this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, GAME_OPTIONS.resinOptions.cap); if (!GAME_OPTIONS.resinOptions.resinUsage) {
this.player.setNextResinRefresh(0); this.player.setProperty(PlayerProperty.PROP_PLAYER_RESIN, GAME_OPTIONS.resinOptions.cap);
} this.player.setNextResinRefresh(0);
}
// In case server administrators change the resin cap while players are capped,
// we need to restart recharging here. // In case server administrators change the resin cap while players are capped,
int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); // we need to restart recharging here.
int currentTime = Utils.getCurrentSeconds(); int currentResin = this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
int currentTime = Utils.getCurrentSeconds();
if (currentResin < GAME_OPTIONS.resinOptions.cap && this.player.getNextResinRefresh() == 0) {
this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime); if (currentResin < GAME_OPTIONS.resinOptions.cap && this.player.getNextResinRefresh() == 0) {
} this.player.setNextResinRefresh(currentTime + GAME_OPTIONS.resinOptions.rechargeTime);
}
// Send initial notifications on logon.
this.player.sendPacket(new PacketResinChangeNotify(this.player)); // Send initial notifications on logon.
} this.player.sendPacket(new PacketResinChangeNotify(this.player));
} }
public int buy() {
if (this.player.getResinBuyCount() >= MAX_RESIN_BUYING_COUNT) {
return RetcodeOuterClass.Retcode.RET_RESIN_BOUGHT_COUNT_EXCEEDED_VALUE;
}
var res = this.player.getInventory().payItem(201, HCOIN_NUM_TO_BUY_RESIN[this.player.getResinBuyCount()]);
if (!res) {
return RetcodeOuterClass.Retcode.RET_HCOIN_NOT_ENOUGH_VALUE;
}
this.player.setResinBuyCount(this.player.getResinBuyCount() + 1);
this.player.setProperty(PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN, 0);
this.addResin(AMOUNT_TO_ADD);
this.player.sendPacket(new PacketItemAddHintNotify(new GameItem(106, AMOUNT_TO_ADD), ActionReason.BuyResin));
return 0;
}
}

View File

@@ -5,7 +5,7 @@ import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.PlayerLevelData; import emu.grasscutter.data.excels.PlayerLevelData;
import emu.grasscutter.data.excels.world.WeatherData; import emu.grasscutter.data.excels.WeatherData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest; import emu.grasscutter.game.CoopRequest;
@@ -15,7 +15,6 @@ import emu.grasscutter.game.activity.ActivityManager;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.battlepass.BattlePassManager; import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo; import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList; import emu.grasscutter.game.friends.FriendsList;
@@ -45,26 +44,27 @@ import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerData;
import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.tower.TowerManager;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType; import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo; import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason; import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass;
import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.server.event.player.PlayerJoinEvent; import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent;
@@ -88,7 +88,6 @@ import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Stream;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS; import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@@ -117,13 +116,12 @@ public class Player {
@Getter @Setter private int sceneId; @Getter @Setter private int sceneId;
@Getter @Setter private int regionId; @Getter @Setter private int regionId;
@Getter private int mainCharacterId; @Getter private int mainCharacterId;
@Getter @Setter private boolean inGodMode; @Setter private boolean godmode; // Getter is inGodmode
@Getter @Setter private boolean unlimitedStamina; private boolean stamina; // Getter is getUnlimitedStamina, Setter is setUnlimitedStamina
@Getter private Set<Integer> nameCardList; @Getter private Set<Integer> nameCardList;
@Getter private Set<Integer> flyCloakList; @Getter private Set<Integer> flyCloakList;
@Getter private Set<Integer> costumeList; @Getter private Set<Integer> costumeList;
@Getter private Set<Integer> personalLineList;
@Getter @Setter private Set<Integer> rewardedLevels; @Getter @Setter private Set<Integer> rewardedLevels;
@Getter @Setter private Set<Integer> homeRewardedLevels; @Getter @Setter private Set<Integer> homeRewardedLevels;
@Getter @Setter private Set<Integer> realmList; @Getter @Setter private Set<Integer> realmList;
@@ -205,12 +203,9 @@ public class Player {
@Getter @Setter private long springLastUsed; @Getter @Setter private long springLastUsed;
private HashMap<String, MapMark> mapMarks; // Getter makes an empty hashmap - maybe do this elsewhere? private HashMap<String, MapMark> mapMarks; // Getter makes an empty hashmap - maybe do this elsewhere?
@Getter @Setter private int nextResinRefresh; @Getter @Setter private int nextResinRefresh;
@Getter @Setter private int resinBuyCount;
@Getter @Setter private int lastDailyReset; @Getter @Setter private int lastDailyReset;
@Getter private transient MpSettingType mpSetting = MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; @Getter private transient MpSettingType mpSetting = MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TODO
@Getter private long playerGameTime = 0;
@Getter private PlayerProgress playerProgress;
@Getter private Set<Integer> activeQuestTimers;
@Deprecated @Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
@@ -247,7 +242,7 @@ public class Player {
this.unlockedCombines = new HashSet<>(); this.unlockedCombines = new HashSet<>();
this.unlockedFurniture = new HashSet<>(); this.unlockedFurniture = new HashSet<>();
this.unlockedFurnitureSuite = new HashSet<>(); this.unlockedFurnitureSuite = new HashSet<>();
this.activeCookCompounds = new HashMap<>(); this.activeCookCompounds=new HashMap<>();
this.activeForges = new ArrayList<>(); this.activeForges = new ArrayList<>();
this.unlockedRecipies = new HashMap<>(); this.unlockedRecipies = new HashMap<>();
this.questGlobalVariables = new HashMap<>(); this.questGlobalVariables = new HashMap<>();
@@ -281,7 +276,7 @@ public class Player {
this.progressManager = new PlayerProgressManager(this); this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this); this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this); this.cookingManager = new CookingManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this); this.cookingCompoundManager=new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this); this.satiationManager = new SatiationManager(this);
} }
@@ -317,22 +312,10 @@ public class Player {
this.progressManager = new PlayerProgressManager(this); this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this); this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this); this.cookingManager = new CookingManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this); this.cookingCompoundManager=new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this); this.satiationManager = new SatiationManager(this);
} }
/**
* Updates the player's game time if it has changed.
*
* @param gameTime The new game time.
*/
public void updatePlayerGameTime(long gameTime) {
if (this.playerGameTime == gameTime) return;
this.playerGameTime = gameTime;
this.save();
}
public int getUid() { public int getUid() {
return id; return id;
} }
@@ -473,8 +456,6 @@ public class Player {
// Handle open state unlocks from level-up. // Handle open state unlocks from level-up.
this.getProgressManager().tryUnlockOpenStates(); this.getProgressManager().tryUnlockOpenStates();
this.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_PLAYER_LEVEL_UP, level);
this.getQuestManager().queueEvent(QuestCond.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER, level);
return true; return true;
} }
@@ -542,7 +523,6 @@ public class Player {
public boolean setHomeCoin(int coin) { public boolean setHomeCoin(int coin) {
return this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin); return this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin);
} }
private int getExpRequired(int level) { private int getExpRequired(int level) {
PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level); PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
@@ -584,14 +564,14 @@ public class Player {
int newWorldLevel = int newWorldLevel =
(currentLevel >= 55) ? 8 : (currentLevel >= 55) ? 8 :
(currentLevel >= 50) ? 7 : (currentLevel >= 50) ? 7 :
(currentLevel >= 45) ? 6 : (currentLevel >= 45) ? 6 :
(currentLevel >= 40) ? 5 : (currentLevel >= 40) ? 5 :
(currentLevel >= 35) ? 4 : (currentLevel >= 35) ? 4 :
(currentLevel >= 30) ? 3 : (currentLevel >= 30) ? 3 :
(currentLevel >= 25) ? 2 : (currentLevel >= 25) ? 2 :
(currentLevel >= 20) ? 1 : (currentLevel >= 20) ? 1 :
0; 0;
if (newWorldLevel != currentWorldLevel) { if (newWorldLevel != currentWorldLevel) {
this.setWorldLevel(newWorldLevel); this.setWorldLevel(newWorldLevel);
@@ -616,12 +596,11 @@ public class Player {
public void onEnterRegion(SceneRegion region) { public void onEnterRegion(SceneRegion region) {
getQuestManager().forEachActiveQuest(quest -> { getQuestManager().forEachActiveQuest(quest -> {
if (quest.getTriggerData() != null && quest.getTriggers().containsKey("ENTER_REGION_"+ region.config_id)) { if (quest.getTriggers().containsKey("ENTER_REGION_"+ region.config_id)) {
// If trigger hasn't been fired yet // If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("ENTER_REGION_" + region.config_id, true))) { if (!Boolean.TRUE.equals(quest.getTriggers().put("ENTER_REGION_"+ region.config_id, true))) {
//getSession().send(new PacketServerCondMeetQuestListUpdateNotify()); //getSession().send(new PacketServerCondMeetQuestListUpdateNotify());
getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_TRIGGER_FIRE, getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("ENTER_REGION_"+ region.config_id).getId(),0);
quest.getTriggerData().get("ENTER_REGION_" + region.config_id).getId(), 0);
} }
} }
}); });
@@ -630,12 +609,11 @@ public class Player {
public void onLeaveRegion(SceneRegion region) { public void onLeaveRegion(SceneRegion region) {
getQuestManager().forEachActiveQuest(quest -> { getQuestManager().forEachActiveQuest(quest -> {
if (quest.getTriggers().containsKey("LEAVE_REGION_" + region.config_id)) { if (quest.getTriggers().containsKey("LEAVE_REGION_"+ region.config_id)) {
// If trigger hasn't been fired yet // If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("LEAVE_REGION_" + region.config_id, true))) { if (!Boolean.TRUE.equals(quest.getTriggers().put("LEAVE_REGION_"+ region.config_id, true))) {
getSession().send(new PacketServerCondMeetQuestListUpdateNotify()); getSession().send(new PacketServerCondMeetQuestListUpdateNotify());
getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_TRIGGER_FIRE, getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("LEAVE_REGION_"+ region.config_id).getId(),0);
quest.getTriggerData().get("LEAVE_REGION_" + region.config_id).getId(), 0);
} }
} }
}); });
@@ -727,7 +705,9 @@ public class Player {
} else { } else {
moonCardDuration += 30; moonCardDuration += 30;
} }
moonCardGetTimes.add(moonCardStartTime); if (!moonCardGetTimes.contains(moonCardStartTime)) {
moonCardGetTimes.add(moonCardStartTime);
}
return true; return true;
} }
@@ -796,6 +776,18 @@ public class Player {
this.save(); this.save();
} }
public boolean getUnlimitedStamina() {
return stamina;
}
public void setUnlimitedStamina(boolean stamina) {
this.stamina = stamina;
}
public boolean inGodmode() {
return godmode;
}
public boolean hasSentLoginPackets() { public boolean hasSentLoginPackets() {
return hasSentLoginPackets; return hasSentLoginPackets;
} }
@@ -827,85 +819,6 @@ public class Player {
addAvatar(avatar, true); addAvatar(avatar, true);
} }
public void addAvatar(int avatarId) {
// I dont see why we cant do this lolz
addAvatar(new Avatar(avatarId), true);
}
public List<Integer> 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();
var trialCustomParams = GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList();
return trialCustomParams.isEmpty() ? List.of() : Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList();
}
public boolean addTrialAvatar(int trialAvatarId, GrantReason reason, int questMainId){
List<Integer> trialAvatarBasicParam = getTrialAvatarParam(trialAvatarId);
if (trialAvatarBasicParam.isEmpty()) return false;
Avatar avatar = new Avatar(trialAvatarBasicParam.get(0));
if (avatar.getAvatarData() == null || !hasSentLoginPackets()) return false;
avatar.setOwner(this);
// Add trial weapons and relics
avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), trialAvatarId, reason, questMainId);
avatar.equipTrialItems();
// Recalc stats
avatar.recalcStats();
// Packet, mimic official server behaviour, add to player's bag but not saving to db
sendPacket(new PacketAvatarAddNotify(avatar, false));
// add to avatar to temporary trial team
getTeamManager().addAvatarToTrialTeam(avatar);
return true;
}
public boolean addTrialAvatarForQuest(int trialAvatarId, int questMainId) {
// TODO: Find method for 'setupTrialAvatarTeamForQuest'.
getTeamManager().setupTrialAvatars(true);
if (!addTrialAvatar(
trialAvatarId,
GrantReason.GRANT_REASON_BY_QUEST,
questMainId)) return false;
getTeamManager().trialAvatarTeamPostUpdate();
// Packet, mimic official server behaviour, neccessary to stop player from modifying team
sendPacket(new PacketAvatarTeamUpdateNotify(this));
return true;
}
public void addTrialAvatarsForActivity(List<Integer> trialAvatarIds) {
getTeamManager().setupTrialAvatars(false);
trialAvatarIds.forEach(trialAvatarId -> addTrialAvatar(
trialAvatarId,
GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY,
0));
getTeamManager().trialAvatarTeamPostUpdate(0);
}
public boolean removeTrialAvatarForQuest(int trialAvatarId) {
if (!getTeamManager().isUsingTrialTeam()) return false;
sendPacket(new PacketAvatarDelNotify(List.of(getTeamManager().getTrialAvatarGuid(trialAvatarId))));
getTeamManager().removeTrialAvatarTeam(trialAvatarId);
sendPacket(new PacketAvatarTeamUpdateNotify());
return true;
}
public void removeTrialAvatarForActivity() {
if (!getTeamManager().isUsingTrialTeam()) return;
sendPacket(new PacketAvatarDelNotify(getTeamManager().getActiveTeam().stream()
.map(x -> x.getAvatar().getGuid()).toList()));
getTeamManager().removeTrialAvatarTeam();
}
public void addFlycloak(int flycloakId) { public void addFlycloak(int flycloakId) {
this.getFlyCloakList().add(flycloakId); this.getFlyCloakList().add(flycloakId);
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId)); this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
@@ -916,11 +829,6 @@ public class Player {
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId)); this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
} }
public void addPersonalLine(int personalLineId) {
this.getPersonalLineList().add(personalLineId);
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);
}
public void addNameCard(int nameCardId) { public void addNameCard(int nameCardId) {
this.getNameCardList().add(nameCardId); this.getNameCardList().add(nameCardId);
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId)); this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
@@ -936,11 +844,6 @@ public class Player {
this.sendPacket(new PacketSetNameCardRsp(nameCardId)); this.sendPacket(new PacketSetNameCardRsp(nameCardId));
} }
/**
* Sends a message to this player.
*
* @param message The message to send.
*/
public void dropMessage(Object message) { public void dropMessage(Object message) {
if (this.messageHandler != null) { if (this.messageHandler != null) {
this.messageHandler.append(message.toString()); this.messageHandler.append(message.toString());
@@ -950,46 +853,6 @@ public class Player {
this.getServer().getChatSystem().sendPrivateMessageFromServer(getUid(), message.toString()); this.getServer().getChatSystem().sendPrivateMessageFromServer(getUid(), message.toString());
} }
public void setAvatarsAbilityForScene(Scene scene) {
try {
var levelEntityConfig = scene.getSceneData().getLevelEntityConfig();
var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig);
if (config == null){
return;
}
List<Integer> avatarIds = scene.getSceneData().getSpecifiedAvatarList();
List<EntityAvatar> specifiedAvatarList = getTeamManager().getActiveTeam();
if (avatarIds != null && avatarIds.size() > 0){
// certain scene could limit specifc avatars' entry
specifiedAvatarList.clear();
for (int id : avatarIds){
var avatar = getAvatars().getAvatarById(id);
if (avatar == null){
continue;
}
specifiedAvatarList.add(new EntityAvatar(scene, avatar));
}
}
for (EntityAvatar entityAvatar : specifiedAvatarList){
var avatarData = entityAvatar.getAvatar().getAvatarData();
if (avatarData == null){
continue;
}
avatarData.buildEmbryo();
if (config.getAvatarAbilities() == null){
continue; // continue and not break because has to rebuild ability for the next avatar if any
}
for (var 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);
}
}
/** /**
* Sends a message to another player. * Sends a message to another player.
* *
@@ -1002,9 +865,7 @@ public class Player {
// ---------------------MAIL------------------------ // ---------------------MAIL------------------------
public List<Mail> getAllMail() { public List<Mail> getAllMail() { return this.getMailHandler().getMail(); }
return this.getMailHandler().getMail();
}
public void sendMail(Mail message) { public void sendMail(Mail message) {
this.getMailHandler().sendMail(message); this.getMailHandler().sendMail(message);
@@ -1014,9 +875,7 @@ public class Player {
return this.getMailHandler().deleteMail(mailId); return this.getMailHandler().deleteMail(mailId);
} }
public Mail getMail(int index) { public Mail getMail(int index) { return this.getMailHandler().getMailById(index); }
return this.getMailHandler().getMailById(index);
}
public int getMailId(Mail message) { public int getMailId(Mail message) {
return this.getMailHandler().getMailIndex(message); return this.getMailHandler().getMailIndex(message);
@@ -1050,13 +909,13 @@ public class Player {
public OnlinePlayerInfo getOnlinePlayerInfo() { public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder() OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setNickname(this.getNickname()) .setNickname(this.getNickname())
.setPlayerLevel(this.getLevel()) .setPlayerLevel(this.getLevel())
.setMpSettingType(this.getMpSetting()) .setMpSettingType(this.getMpSetting())
.setNameCardId(this.getNameCardId()) .setNameCardId(this.getNameCardId())
.setSignature(this.getSignature()) .setSignature(this.getSignature())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage())); .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) { if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount()); onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount());
@@ -1082,12 +941,12 @@ public class Player {
if (this.getShowAvatarList() != null) { if (this.getShowAvatarList() != null) {
for (int avatarId : this.getShowAvatarList()) { for (int avatarId : this.getShowAvatarList()) {
socialShowAvatarInfoList.add( socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(), socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder() SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId) .setAvatarId(avatarId)
.setLevel(getAvatars().getAvatarById(avatarId).getLevel()) .setLevel(getAvatars().getAvatarById(avatarId).getLevel())
.setCostumeId(getAvatars().getAvatarById(avatarId).getCostume()) .setCostumeId(getAvatars().getAvatarById(avatarId).getCostume())
.build() .build()
); );
} }
} }
@@ -1098,31 +957,32 @@ public class Player {
if (showAvatarList != null) { if (showAvatarList != null) {
for (int avatarId : showAvatarList) { for (int avatarId : showAvatarList) {
socialShowAvatarInfoList.add( socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(), socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder() SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId) .setAvatarId(avatarId)
.setLevel(avatars.getAvatarById(avatarId).getLevel()) .setLevel(avatars.getAvatarById(avatarId).getLevel())
.setCostumeId(avatars.getAvatarById(avatarId).getCostume()) .setCostumeId(avatars.getAvatarById(avatarId).getCostume())
.build() .build()
); );
} }
} }
} }
return SocialDetail.newBuilder() SocialDetail.Builder social = SocialDetail.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage())) .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()))
.setNickname(this.getNickname()) .setNickname(this.getNickname())
.setSignature(this.getSignature()) .setSignature(this.getSignature())
.setLevel(this.getLevel()) .setLevel(this.getLevel())
.setBirthday(this.getBirthday().getFilledProtoWhenNotEmpty()) .setBirthday(this.getBirthday().getFilledProtoWhenNotEmpty())
.setWorldLevel(this.getWorldLevel()) .setWorldLevel(this.getWorldLevel())
.setNameCardId(this.getNameCardId()) .setNameCardId(this.getNameCardId())
.setIsShowAvatar(this.isShowAvatars()) .setIsShowAvatar(this.isShowAvatars())
.addAllShowAvatarInfoList(socialShowAvatarInfoList) .addAllShowAvatarInfoList(socialShowAvatarInfoList)
.addAllShowNameCardIdList(this.getShowNameCardInfoList()) .addAllShowNameCardIdList(this.getShowNameCardInfoList())
.setFinishAchievementNum(this.getFinishedAchievementNum()) .setFinishAchievementNum(this.getFinishedAchievementNum())
.setFriendEnterHomeOptionValue(this.getHome() == null ? 0 : this.getHome().getEnterHomeOption()); .setFriendEnterHomeOptionValue(this.getHome() == null ? 0 : this.getHome().getEnterHomeOption());
return social;
} }
public int getFinishedAchievementNum() { public int getFinishedAchievementNum() {
@@ -1165,17 +1025,17 @@ public class Player {
public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() { public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder() return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
.setSceneId(this.getSceneId()) .setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo()) .setPlayerLoc(this.getPlayerLocationInfo())
.build(); .build();
} }
public PlayerLocationInfo getPlayerLocationInfo() { public PlayerLocationInfo getPlayerLocationInfo() {
return PlayerLocationInfo.newBuilder() return PlayerLocationInfo.newBuilder()
.setUid(this.getUid()) .setUid(this.getUid())
.setPos(this.getPosition().toProto()) .setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto()) .setRot(this.getRotation().toProto())
.build(); .build();
} }
public void loadBattlePassManager() { public void loadBattlePassManager() {
@@ -1185,7 +1045,7 @@ public class Player {
} }
public PlayerCollectionRecords getCollectionRecordStore() { public PlayerCollectionRecords getCollectionRecordStore() {
if (this.collectionRecordStore == null) { if (this.collectionRecordStore==null) {
this.collectionRecordStore = new PlayerCollectionRecords(); this.collectionRecordStore = new PlayerCollectionRecords();
} }
return collectionRecordStore; return collectionRecordStore;
@@ -1261,8 +1121,6 @@ public class Player {
// Home resources // Home resources
this.getHome().updateHourlyResources(this); this.getHome().updateHourlyResources(this);
this.getQuestManager().onTick();
} }
private synchronized void doDailyReset() { private synchronized void doDailyReset() {
@@ -1292,6 +1150,9 @@ public class Player {
this.getBattlePassManager().resetWeeklyMissions(); this.getBattlePassManager().resetWeeklyMissions();
} }
// Reset resin-buying count.
this.setResinBuyCount(0);
// Done. Update last reset time. // Done. Update last reset time.
this.setLastDailyReset(currentTime); this.setLastDailyReset(currentTime);
} }
@@ -1328,17 +1189,12 @@ public class Player {
this.achievements = Achievements.getByPlayer(this); this.achievements = Achievements.getByPlayer(this);
this.getAvatars().loadFromDatabase(); this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase(); this.getInventory().loadFromDatabase();
this.loadBattlePassManager(); // Call before avatar postLoad to avoid null pointer
this.getAvatars().postLoad(); // Needs to be called after inventory is handled
this.getFriendsList().loadFromDatabase(); this.getFriendsList().loadFromDatabase();
this.getMailHandler().loadFromDatabase(); this.getMailHandler().loadFromDatabase();
this.getQuestManager().loadFromDatabase(); this.getQuestManager().loadFromDatabase();
this.loadBattlePassManager();
this.getAvatars().postLoad(); // Needs to be called after inventory is handled
}
public void onPlayerBorn() {
getQuestManager().onPlayerBorn();
} }
public void onLogin() { public void onLogin() {
@@ -1418,8 +1274,7 @@ public class Player {
session.setState(SessionState.ACTIVE); session.setState(SessionState.ACTIVE);
// Call join event. // Call join event.
PlayerJoinEvent event = new PlayerJoinEvent(this); PlayerJoinEvent event = new PlayerJoinEvent(this); event.call();
event.call();
if (event.isCanceled()) { // If event is not cancelled, continue. if (event.isCanceled()) { // If event is not cancelled, continue.
session.close(); session.close();
return; return;
@@ -1458,12 +1313,11 @@ public class Player {
this.getFriendsList().save(); this.getFriendsList().save();
// Call quit event. // Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this); PlayerQuitEvent event = new PlayerQuitEvent(this); event.call();
event.call(); }catch (Throwable e) {
} catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid()); Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
} finally { }finally {
removeFromServer(); removeFromServer();
} }
} }
@@ -1478,15 +1332,23 @@ public class Player {
public int getLegendaryKey() { public int getLegendaryKey() {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY); return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
} }
public synchronized void addLegendaryKey(int count) { public synchronized void addLegendaryKey(int count) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() + count); this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() + count);
} }
public synchronized void useLegendaryKey(int count) { public synchronized void useLegendaryKey(int count) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() - count); this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() - count);
} }
public enum SceneLoadState {
NONE(0), LOADING(1), INIT(2), LOADED(3);
@Getter private final int value;
SceneLoadState(int value) {
this.value = value;
}
}
public int getPropertyMin(PlayerProperty prop) { public int getPropertyMin(PlayerProperty prop) {
if (prop.isDynamicRange()) { if (prop.isDynamicRange()) {
return 0; return 0;
@@ -1529,15 +1391,4 @@ public class Player {
} }
} }
public enum SceneLoadState {
NONE(0), LOADING(1), INIT(2), LOADED(3);
@Getter
private final int value;
SceneLoadState(int value) {
this.value = value;
}
}
} }

View File

@@ -1,25 +1,25 @@
package emu.grasscutter.game.props.ItemUseAction; package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddItem extends ItemUseInt {
private int count = 0; public class ItemUseAddItem extends ItemUseInt {
private int count = 0;
public ItemUseAddItem(String[] useParam) {
super(useParam); @Override
try { public ItemUseOp getItemUseOp() {
this.count = Integer.parseInt(useParam[1]); return ItemUseOp.ITEM_USE_ADD_ITEM;
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) { }
}
} public ItemUseAddItem(String[] useParam) {
super(useParam);
@Override try {
public ItemUseOp getItemUseOp() { this.count = Integer.parseInt(useParam[1]);
return ItemUseOp.ITEM_USE_ADD_ITEM; } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
} }
@Override @Override
public boolean useItem(UseItemParams params) { public boolean useItem(UseItemParams params) {
return params.player.getInventory().addItem(this.i, this.count * params.count); return params.player.getInventory().addItem(this.i, this.count * params.count, ActionReason.PlayerUseItem);
} }
} }

View File

@@ -1,330 +1,301 @@
package emu.grasscutter.server.http.dispatch; package emu.grasscutter.server.http.dispatch;
import static emu.grasscutter.config.Configuration.*; import com.google.protobuf.ByteString;
import emu.grasscutter.GameConstants;
import com.google.protobuf.ByteString; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp;
import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp;
import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; import emu.grasscutter.net.proto.StopServerInfoOuterClass.StopServerInfo;
import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent;
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson; import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Crypto;
import io.javalin.Javalin; import emu.grasscutter.utils.Utils;
import io.javalin.http.Context; import io.javalin.Javalin;
import java.io.ByteArrayOutputStream; import io.javalin.http.Context;
import java.security.Signature;
import java.util.ArrayList; import java.time.Instant;
import java.util.Arrays; import javax.crypto.Cipher;
import java.util.List; import java.io.ByteArrayOutputStream;
import java.util.Map; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern; import java.security.Signature;
import javax.crypto.Cipher; import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.Logger;
/** Handles requests related to region queries. */
public final class RegionHandler implements Router { import static emu.grasscutter.config.Configuration.*;
private static final Map<String, RegionData> regions = new ConcurrentHashMap<>(); import static emu.grasscutter.utils.Language.translate;
private static String regionListResponse;
private static String regionListResponsecn; /**
* Handles requests related to region queries.
public RegionHandler() { */
try { // Read & initialize region data. public final class RegionHandler implements Router {
this.initialize(); private static final Map<String, RegionData> regions = new ConcurrentHashMap<>();
} catch (Exception exception) { private static String regionListResponse;
Grasscutter.getLogger().error("Failed to initialize region data.", exception); private static String regionListResponsecn;
}
} public RegionHandler() {
try { // Read & initialize region data.
/** this.initialize();
* Handle query region list request. } catch (Exception exception) {
* Grasscutter.getLogger().error("Failed to initialize region data.", exception);
* @param ctx The context object for handling the request. }
* @route /query_region_list }
*/
private static void queryRegionList(Context ctx) { /**
// Get logger and query parameters. * Configures region data according to configuration.
Logger logger = Grasscutter.getLogger(); */
if (ctx.queryParamMap().containsKey("version") && ctx.queryParamMap().containsKey("platform")) { private void initialize() {
String versionName = ctx.queryParam("version"); String dispatchDomain = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
String versionCode = versionName.replaceAll("[/.0-9]*", ""); + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
String platformName = ctx.queryParam("platform"); + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort);
// Determine the region list to use based on the version and platform. // Create regions.
if ("CNRELiOS".equals(versionCode) List<RegionSimpleInfo> servers = new ArrayList<>();
|| "CNRELWin".equals(versionCode) List<String> usedNames = new ArrayList<>(); // List to check for potential naming conflicts.
|| "CNRELAndroid".equals(versionCode)) {
// Use the CN region list. var configuredRegions = new ArrayList<>(List.of(DISPATCH_INFO.regions));
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponsecn); if (SERVER.runMode != ServerRunMode.HYBRID && configuredRegions.size() == 0) {
event.call(); Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state.");
logger.debug("Connect to Chinese version"); System.exit(1);
} else if (configuredRegions.size() == 0)
// Respond with the event result. configuredRegions.add(new Region("os_usa", DISPATCH_INFO.defaultName,
ctx.result(event.getRegionList()); lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress),
} else if ("OSRELiOS".equals(versionCode) lr(GAME_INFO.accessPort, GAME_INFO.bindPort)));
|| "OSRELWin".equals(versionCode)
|| "OSRELAndroid".equals(versionCode)) { configuredRegions.forEach(region -> {
// Use the OS region list. if (usedNames.contains(region.Name)) {
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); Grasscutter.getLogger().error("Region name already in use.");
event.call(); return;
logger.debug("Connect to global version"); }
// Respond with the event result. // Create a region identifier.
ctx.result(event.getRegionList()); var identifier = RegionSimpleInfo.newBuilder()
} else { .setName(region.Name).setTitle(region.Title).setType("DEV_PUBLIC")
/* .setDispatchUrl(dispatchDomain + "/query_cur_region/" + region.Name)
* String regionListResponse = "CP///////////wE="; .build();
* QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); usedNames.add(region.Name); servers.add(identifier);
* event.call();
* ctx.result(event.getRegionList()); // Create a region info object.
* return; var regionInfo = RegionInfo.newBuilder()
*/ .setGateserverIp(region.Ip).setGateserverPort(region.Port)
// Use the default region list. .setSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); .build();
event.call(); // Create an updated region query.
logger.debug("Connect to global version"); var updatedQuery = QueryCurrRegionHttpRsp.newBuilder().setRegionInfo(regionInfo).build();
regions.put(region.Name, new RegionData(updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray())));
// Respond with the event result. });
ctx.result(event.getRegionList());
} // Create a config object.
} else { byte[] customConfig = "{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}".getBytes();
// Use the default region list. Crypto.xor(customConfig, Crypto.DISPATCH_KEY); // XOR the config with the key.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
event.call(); // Create an updated region list.
logger.debug("Connect to global version"); QueryRegionListHttpRsp updatedRegionList = QueryRegionListHttpRsp.newBuilder()
.addAllRegionList(servers)
// Respond with the event result. .setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
ctx.result(event.getRegionList()); .setClientCustomConfigEncrypted(ByteString.copyFrom(customConfig))
} .setEnableLoginPc(true).build();
// Log the request to the console.
Grasscutter.getLogger() // Set the region list response.
.info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip())); regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray());
}
// CN
/** // Create a config object.
* @route /query_cur_region/{region} byte[] customConfigcn = "{\"sdkenv\":\"0\",\"checkdevice\":\"true\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}".getBytes();
*/ Crypto.xor(customConfigcn, Crypto.DISPATCH_KEY); // XOR the config with the key.
private static void queryCurrentRegion(Context ctx) {
// Get region to query. // Create an updated region list.
String regionName = ctx.pathParam("region"); QueryRegionListHttpRsp updatedRegionListcn = QueryRegionListHttpRsp.newBuilder()
String versionName = ctx.queryParam("version"); .addAllRegionList(servers)
var region = regions.get(regionName); .setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED))
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfigcn))
// Get region data. .setEnableLoginPc(true).build();
String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (ctx.queryParamMap().values().size() > 0) { // Set the region list response.
if (region != null) regionData = region.getBase64(); regionListResponsecn = Utils.base64Encode(updatedRegionListcn.toByteString().toByteArray());
} }
String[] versionCode = @Override
versionName.replaceAll(Pattern.compile("[a-zA-Z]").pattern(), "").split("\\."); public void applyRoutes(Javalin javalin) {
int versionMajor = Integer.parseInt(versionCode[0]); javalin.get("/query_region_list", RegionHandler::queryRegionList);
int versionMinor = Integer.parseInt(versionCode[1]); javalin.get("/query_cur_region/{region}", RegionHandler::queryCurrentRegion);
int versionFix = Integer.parseInt(versionCode[2]); }
if (versionMajor >= 3 /**
|| (versionMajor == 2 && versionMinor == 7 && versionFix >= 50) * Handle query region list request.
|| (versionMajor == 2 && versionMinor == 8)) { *
try { * @param ctx The context object for handling the request.
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); * @route /query_region_list
event.call(); */
private static void queryRegionList(Context ctx) {
if (ctx.queryParam("dispatchSeed") == null) { // Get logger and query parameters.
// More love for UA Patch players Logger logger = Grasscutter.getLogger();
var rsp = new QueryCurRegionRspJson(); if (ctx.queryParamMap().containsKey("version") && ctx.queryParamMap().containsKey("platform")) {
String versionName = ctx.queryParam("version");
rsp.content = event.getRegionInfo(); String versionCode = versionName.replaceAll("[/.0-9]*", "");
rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz"; String platformName = ctx.queryParam("platform");
ctx.json(rsp); // Determine the region list to use based on the version and platform.
return; if ("CNRELiOS".equals(versionCode) || "CNRELWin".equals(versionCode)
} || "CNRELAndroid".equals(versionCode)) {
// Use the CN region list.
String key_id = ctx.queryParam("key_id"); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponsecn);
event.call();
if (key_id == null) throw new Exception("Key ID was not set"); logger.debug("Connect to Chinese version");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // Respond with the event result.
cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(Integer.valueOf(key_id))); ctx.result(event.getRegionList());
var regionInfo = Utils.base64Decode(event.getRegionInfo()); } else if ("OSRELiOS".equals(versionCode) || "OSRELWin".equals(versionCode)
|| "OSRELAndroid".equals(versionCode)) {
// Encrypt regionInfo in chunks // Use the OS region list.
ByteArrayOutputStream encryptedRegionInfoStream = new ByteArrayOutputStream(); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
event.call();
// Thank you so much GH Copilot logger.debug("Connect to global version");
int chunkSize = 256 - 11;
int regionInfoLength = regionInfo.length; // Respond with the event result.
int numChunks = (int) Math.ceil(regionInfoLength / (double) chunkSize); ctx.result(event.getRegionList());
} else {
for (int i = 0; i < numChunks; i++) { /*
byte[] chunk = * String regionListResponse = "CP///////////wE=";
Arrays.copyOfRange( * QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength)); * event.call();
byte[] encryptedChunk = cipher.doFinal(chunk); * ctx.result(event.getRegionList());
encryptedRegionInfoStream.write(encryptedChunk); * return;
} */
// Use the default region list.
Signature privateSignature = Signature.getInstance("SHA256withRSA"); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
privateSignature.initSign(Crypto.CUR_SIGNING_KEY); event.call();
privateSignature.update(regionInfo); logger.debug("Connect to global version");
var rsp = new QueryCurRegionRspJson(); // Respond with the event result.
ctx.result(event.getRegionList());
rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray()); }
rsp.sign = Utils.base64Encode(privateSignature.sign()); } else {
// Use the default region list.
ctx.json(rsp); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse);
} catch (Exception e) { event.call();
Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e); logger.debug("Connect to global version");
}
} else { // Respond with the event result.
// Invoke event. ctx.result(event.getRegionList());
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); }
event.call(); // Log the request to the console.
// Respond with event result. Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip()));
ctx.result(event.getRegionInfo()); }
}
// Log to console. /**
Grasscutter.getLogger() * @route /query_cur_region/{region}
.info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName)); */
} private static void queryCurrentRegion(Context ctx) {
// Get region to query.
/** String regionName = ctx.pathParam("region");
* Gets the current region query. String versionName = ctx.queryParam("version");
* var region = regions.get(regionName);
* @return A {@link QueryCurrRegionHttpRsp} object.
*/ // Get region data.
public static QueryCurrRegionHttpRsp getCurrentRegion() { String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
return SERVER.runMode == ServerRunMode.HYBRID ? regions.get("os_usa").getRegionQuery() : null; if (ctx.queryParamMap().values().size() > 0) {
} if (region != null)
regionData = region.getBase64();
/** Configures region data according to configuration. */ }
private void initialize() {
String dispatchDomain = String clientVersion = versionName.replaceAll(Pattern.compile("[a-zA-Z]").pattern(), "");
"http" String[] versionCode = clientVersion.split("\\.");
+ (HTTP_ENCRYPTION.useInRouting ? "s" : "") int versionMajor = Integer.parseInt(versionCode[0]);
+ "://" int versionMinor = Integer.parseInt(versionCode[1]);
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) int versionFix = Integer.parseInt(versionCode[2]);
+ ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort); if (versionMajor >= 3 || (versionMajor == 2 && versionMinor == 7 && versionFix >= 50) || (versionMajor == 2 && versionMinor == 8)) {
try {
// Create regions. QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
List<RegionSimpleInfo> servers = new ArrayList<>();
List<String> usedNames = new ArrayList<>(); // List to check for potential naming conflicts. String key_id = ctx.queryParam("key_id");
var configuredRegions = new ArrayList<>(List.of(DISPATCH_INFO.regions)); if (!clientVersion.equals(GameConstants.VERSION)) { // Reject clients when there is a version mismatch
if (SERVER.runMode != ServerRunMode.HYBRID && configuredRegions.size() == 0) {
Grasscutter.getLogger() boolean updateClient = GameConstants.VERSION.compareTo(clientVersion) > 0;
.error(
"[Dispatch] There are no game servers available. Exiting due to unplayable state."); QueryCurrRegionHttpRsp rsp = QueryCurrRegionHttpRsp.newBuilder()
System.exit(1); .setRetcode(Retcode.RET_STOP_SERVER_VALUE)
} else if (configuredRegions.size() == 0) .setMsg("Connection Failed!")
configuredRegions.add( .setRegionInfo(RegionInfo.newBuilder())
new Region( .setStopServer(StopServerInfo.newBuilder()
"os_usa", .setUrl("https://discord.gg/grasscutters")
DISPATCH_INFO.defaultName, .setStopBeginTime((int) Instant.now().getEpochSecond())
lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress), .setStopEndTime((int) Instant.now().getEpochSecond()*2)
lr(GAME_INFO.accessPort, GAME_INFO.bindPort))); .setContentMsg(updateClient ? "\nVersion mismatch outdated client! \n\nServer version: %s\nClient version: %s".formatted(GameConstants.VERSION, clientVersion) : "\nVersion mismatch outdated server! \n\nServer version: %s\nClient version: %s".formatted(GameConstants.VERSION, clientVersion))
.build())
configuredRegions.forEach( .buildPartial();
region -> {
if (usedNames.contains(region.Name)) { Grasscutter.getLogger().info(String.format("Connection denied for %s due to %s", ctx.ip(), updateClient ? "outdated client!" : "outdated server!"));
Grasscutter.getLogger().error("Region name already in use.");
return; ctx.json(Crypto.encryptAndSignRegionData(rsp.toByteArray(), key_id));
} return;
}
// Create a region identifier.
var identifier = if (ctx.queryParam("dispatchSeed") == null) {
RegionSimpleInfo.newBuilder() // More love for UA Patch players
.setName(region.Name) var rsp = new QueryCurRegionRspJson();
.setTitle(region.Title)
.setType("DEV_PUBLIC") rsp.content = event.getRegionInfo();
.setDispatchUrl(dispatchDomain + "/query_cur_region/" + region.Name) rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz";
.build();
usedNames.add(region.Name); ctx.json(rsp);
servers.add(identifier); return;
}
// Create a region info object.
var regionInfo =
RegionInfo.newBuilder() var regionInfo = Utils.base64Decode(event.getRegionInfo());
.setGateserverIp(region.Ip)
.setGateserverPort(region.Port) ctx.json(Crypto.encryptAndSignRegionData(regionInfo, key_id));
.setSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) }
.build(); catch (Exception e) {
// Create an updated region query. Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e);
var updatedQuery = QueryCurrRegionHttpRsp.newBuilder().setRegionInfo(regionInfo).build(); }
regions.put( }
region.Name, else {
new RegionData( // Invoke event.
updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray()))); QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
}); // Respond with event result.
ctx.result(event.getRegionInfo());
// Create a config object. }
byte[] customConfig = // Log to console.
"{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}" Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName));
.getBytes(); }
Crypto.xor(customConfig, Crypto.DISPATCH_KEY); // XOR the config with the key.
/**
// Create an updated region list. * Region data container.
QueryRegionListHttpRsp updatedRegionList = */
QueryRegionListHttpRsp.newBuilder() public static class RegionData {
.addAllRegionList(servers) private final QueryCurrRegionHttpRsp regionQuery;
.setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) private final String base64;
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfig))
.setEnableLoginPc(true) public RegionData(QueryCurrRegionHttpRsp prq, String b64) {
.build(); this.regionQuery = prq;
this.base64 = b64;
// Set the region list response. }
regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray());
public QueryCurrRegionHttpRsp getRegionQuery() {
// CN return this.regionQuery;
// Create a config object. }
byte[] customConfigcn =
"{\"sdkenv\":\"0\",\"checkdevice\":\"true\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}" public String getBase64() {
.getBytes(); return this.base64;
Crypto.xor(customConfigcn, Crypto.DISPATCH_KEY); // XOR the config with the key. }
}
// Create an updated region list.
QueryRegionListHttpRsp updatedRegionListcn = /**
QueryRegionListHttpRsp.newBuilder() * Gets the current region query.
.addAllRegionList(servers) * @return A {@link QueryCurrRegionHttpRsp} object.
.setClientSecretKey(ByteString.copyFrom(Crypto.DISPATCH_SEED)) */
.setClientCustomConfigEncrypted(ByteString.copyFrom(customConfigcn)) public static QueryCurrRegionHttpRsp getCurrentRegion() {
.setEnableLoginPc(true) return SERVER.runMode == ServerRunMode.HYBRID ? regions.get("os_usa").getRegionQuery() : null;
.build(); }
}
// Set the region list response.
regionListResponsecn = Utils.base64Encode(updatedRegionListcn.toByteString().toByteArray());
}
@Override
public void applyRoutes(Javalin javalin) {
javalin.get("/query_region_list", RegionHandler::queryRegionList);
javalin.get("/query_cur_region/{region}", RegionHandler::queryCurrentRegion);
}
/** Region data container. */
public static class RegionData {
private final QueryCurrRegionHttpRsp regionQuery;
private final String base64;
public RegionData(QueryCurrRegionHttpRsp prq, String b64) {
this.regionQuery = prq;
this.base64 = b64;
}
public QueryCurrRegionHttpRsp getRegionQuery() {
return this.regionQuery;
}
public String getBase64() {
return this.base64;
}
}
}

View File

@@ -0,0 +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.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketBuyResinRsp;
@Opcodes(PacketOpcodes.BuyResinReq)
public class HandlerBuyResinReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var player = session.getPlayer();
session.send(new PacketBuyResinRsp(player, player.getResinManager().buy()));
}
}

View File

@@ -0,0 +1,31 @@
package emu.grasscutter.server.packet.recv;
import javax.lang.model.type.TypeMirror;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SceneAudioNotifyOuterClass.SceneAudioNotify;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSceneAudioNotify;
import java.util.List;
@Opcodes(PacketOpcodes.SceneAudioNotify)
public class HandlerSceneAudioNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
SceneAudioNotify notify = SceneAudioNotify.parseFrom(payload);
int sourceUid = notify.getSourceUid();
List<Float> param2 = notify.getParam2List();
List<String> param3 = notify.getParam3List();
int type = notify.getType();
List<Integer> param1 = notify.getParam1List();
session.getPlayer().getScene().broadcastPacket(new PacketSceneAudioNotify(sourceUid, param2, param3, type, param1));
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.BuyResinRspOuterClass;
public class PacketBuyResinRsp extends BasePacket {
public PacketBuyResinRsp(Player player, int ret) {
super(PacketOpcodes.BuyResinRsp);
this.setData(BuyResinRspOuterClass.BuyResinRsp.newBuilder()
.setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN))
.setRetcode(ret)
.build());
}
}

View File

@@ -1,24 +1,22 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ResinChangeNotifyOuterClass.ResinChangeNotify; import emu.grasscutter.net.proto.ResinChangeNotifyOuterClass.ResinChangeNotify;
public class PacketResinChangeNotify extends BasePacket { public class PacketResinChangeNotify extends BasePacket {
public PacketResinChangeNotify(Player player) { public PacketResinChangeNotify(Player player) {
super(PacketOpcodes.ResinChangeNotify); super(PacketOpcodes.ResinChangeNotify);
ResinChangeNotify proto = ResinChangeNotify proto = ResinChangeNotify.newBuilder()
ResinChangeNotify.newBuilder() .setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN))
.setCurValue(player.getProperty(PlayerProperty.PROP_PLAYER_RESIN)) .setNextAddTimestamp(player.getNextResinRefresh())
.setNextAddTimestamp(player.getNextResinRefresh()) .setCurBuyCount(player.getResinBuyCount())
.build(); .build();
// ToDo: Add ability to buy resin with primogems, has to be included here. this.setData(proto);
}
this.setData(proto); }
}
}

View File

@@ -0,0 +1,23 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SceneAudioNotifyOuterClass;
import java.util.List;
public class PacketSceneAudioNotify extends BasePacket {
public PacketSceneAudioNotify(int sourceUid, List<Float> param2, List<String> param3, int type, List<Integer> param1) {
super(PacketOpcodes.SceneAudioNotify);
SceneAudioNotifyOuterClass.SceneAudioNotify proto = SceneAudioNotifyOuterClass.SceneAudioNotify.newBuilder()
.setSourceUid(sourceUid)
.addAllParam2(param2)
.addAllParam3(param3)
.setType(type)
.addAllParam1(param1)
.build();
this.setData(proto);
}
}

View File

@@ -1,77 +1,116 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import emu.grasscutter.Grasscutter; import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import java.nio.file.Path; import java.io.ByteArrayOutputStream;
import java.security.KeyFactory; import java.io.File;
import java.security.PrivateKey; import java.nio.file.Path;
import java.security.PublicKey; import java.security.KeyFactory;
import java.security.SecureRandom; import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec; import java.security.SecureRandom;
import java.util.HashMap; import java.security.Signature;
import java.util.Map; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.regex.Pattern; import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
public final class Crypto { import java.util.Map;
private static final SecureRandom secureRandom = new SecureRandom(); import java.util.HashMap;
import java.util.regex.Pattern;
public static byte[] DISPATCH_KEY;
public static byte[] DISPATCH_SEED; import emu.grasscutter.Grasscutter;
import javax.crypto.Cipher;
public static byte[] ENCRYPT_KEY;
public static long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); public final class Crypto {
public static byte[] ENCRYPT_SEED_BUFFER = new byte[0];
private static final SecureRandom secureRandom = new SecureRandom();
public static PrivateKey CUR_SIGNING_KEY;
public static byte[] DISPATCH_KEY;
public static Map<Integer, PublicKey> EncryptionKeys = new HashMap<>(); public static byte[] DISPATCH_SEED;
public static void loadKeys() { public static byte[] ENCRYPT_KEY;
DISPATCH_KEY = FileUtils.readResource("/keys/dispatchKey.bin"); public static long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968");
DISPATCH_SEED = FileUtils.readResource("/keys/dispatchSeed.bin"); public static byte[] ENCRYPT_SEED_BUFFER = new byte[0];
ENCRYPT_KEY = FileUtils.readResource("/keys/secretKey.bin"); public static PrivateKey CUR_SIGNING_KEY;
ENCRYPT_SEED_BUFFER = FileUtils.readResource("/keys/secretKeyBuffer.bin");
public static Map<Integer, PublicKey> EncryptionKeys = new HashMap<>();
try {
CUR_SIGNING_KEY = public static void loadKeys() {
KeyFactory.getInstance("RSA") DISPATCH_KEY = FileUtils.readResource("/keys/dispatchKey.bin");
.generatePrivate( DISPATCH_SEED = FileUtils.readResource("/keys/dispatchSeed.bin");
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
ENCRYPT_KEY = FileUtils.readResource("/keys/secretKey.bin");
Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der"); ENCRYPT_SEED_BUFFER = FileUtils.readResource("/keys/secretKeyBuffer.bin");
for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
if (path.toString().endsWith("_Pub.der")) { try {
CUR_SIGNING_KEY = KeyFactory.getInstance("RSA")
var m = pattern.matcher(path.getFileName().toString()); .generatePrivate(new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
if (m.matches()) { Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der");
var key = for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
KeyFactory.getInstance("RSA") if (path.toString().endsWith("_Pub.der")) {
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
var m = pattern.matcher(path.getFileName().toString());
EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
} if (m.matches()) {
} var key = KeyFactory.getInstance("RSA")
} .generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while loading keys.", e); EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
} }
} }
}
public static void xor(byte[] packet, byte[] key) { } catch (Exception e) {
try { Grasscutter.getLogger().error("An error occurred while loading keys.", e);
for (int i = 0; i < packet.length; i++) { }
packet[i] ^= key[i % key.length]; }
}
} catch (Exception e) { public static void xor(byte[] packet, byte[] key) {
Grasscutter.getLogger().error("Crypto error.", e); try {
} for (int i = 0; i < packet.length; i++) {
} packet[i] ^= key[i % key.length];
}
public static byte[] createSessionKey(int length) { } catch (Exception e) {
byte[] bytes = new byte[length]; Grasscutter.getLogger().error("Crypto error.", e);
secureRandom.nextBytes(bytes); }
return bytes; }
}
} public static byte[] createSessionKey(int length) {
byte[] bytes = new byte[length];
secureRandom.nextBytes(bytes);
return bytes;
}
public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo, String key_id) throws Exception {
if (key_id == null) {
throw new Exception("Key ID was not set");
}
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, EncryptionKeys.get(Integer.valueOf(key_id)));
//Encrypt regionInfo in chunks
ByteArrayOutputStream encryptedRegionInfoStream = new ByteArrayOutputStream();
//Thank you so much GH Copilot
int chunkSize = 256 - 11;
int regionInfoLength = regionInfo.length;
int numChunks = (int) Math.ceil(regionInfoLength / (double) chunkSize);
for (int i = 0; i < numChunks; i++) {
byte[] chunk = Arrays.copyOfRange(regionInfo, i * chunkSize,
Math.min((i + 1) * chunkSize, regionInfoLength));
byte[] encryptedChunk = cipher.doFinal(chunk);
encryptedRegionInfoStream.write(encryptedChunk);
}
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(CUR_SIGNING_KEY);
privateSignature.update(regionInfo);
var rsp = new QueryCurRegionRspJson();
rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray());
rsp.sign = Utils.base64Encode(privateSignature.sign());
return rsp;
}
}