diff --git a/.gitignore b/.gitignore index 6b5cd202ba8..543cd13b686 100644 --- a/.gitignore +++ b/.gitignore @@ -232,3 +232,6 @@ creativeitems.json recipes.json data/ data/* + +# a file that can be used for your helm chart values +/helm-values.local.yaml diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 00000000000..045f840c8e0 --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,33 @@ +FROM arm64v8/openjdk:8-jdk-slim AS build +WORKDIR /usr/local/src/nukkit +COPY src /usr/local/src/nukkit/src +COPY mvn* pom.xml /usr/local/src/nukkit/ +COPY .git /usr/local/src/nukkit/.git +COPY .mvn /usr/local/src/nukkit/.mvn +COPY .gitmodules /usr/local/src/nukkit/.gitmodules +RUN apt-get -y update && \ + apt-get install -y build-essential git maven && \ + git submodule update --init && \ + mvn clean package + +FROM arm64v8/openjdk:8-jre-slim AS run +LABEL maintainer="Chris Fordham " +COPY --from=build /usr/local/src/nukkit/target/nukkit-1.0-SNAPSHOT.jar /opt/nukkit/nukkit.jar +COPY nukkit.yml.default /etc/opt/nukkit/nukkit.yml +RUN useradd --user-group \ + --no-create-home \ + --home-dir /var/opt/nukkit \ + --shell /usr/sbin/nologin \ + minecraft && \ + mkdir -p /var/opt/nukkit && \ + chown -R minecraft /opt/nukkit /var/opt/nukkit /etc/opt/nukkit/nukkit.yml && \ + ln -sfv /etc/opt/nukkit/nukkit.yml /var/opt/nukkit/nukkit.yml && \ + apt-get -y update && \ + apt-get -y install lsof && \ + rm -rf /var/lib/apt/lists/* +USER minecraft +VOLUME /etc/opt/nukkit /var/opt/nukkit /opt/nukkit +EXPOSE 19132 +WORKDIR /var/opt/nukkit +ENTRYPOINT ["java"] +CMD [ "-jar", "/opt/nukkit/nukkit.jar" ] diff --git a/Jenkinsfile b/Jenkinsfile index 03b8192a3ba..9ea4f8740e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,11 +24,30 @@ pipeline { when { branch "master" } + steps { - sh 'mvn javadoc:javadoc javadoc:jar source:jar deploy -DskipTests' - step([$class: 'JavadocArchiver', - javadocDir: 'target/site/apidocs', - keepAll: false]) + rtMavenDeployer ( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver ( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtMavenRun ( + pom: 'pom.xml', + goals: 'javadoc:javadoc javadoc:jar source:jar install -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo ( + serverId: "opencollab-artifactory" + ) + step([$class: 'JavadocArchiver', javadocDir: 'target/site/apidocs', keepAll: false]) } } } diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..2bf78eea82f --- /dev/null +++ b/Makefile @@ -0,0 +1,85 @@ +DOCKER_REGISTRY = index.docker.io +IMAGE_NAME = nukkit +IMAGE_VERSION = latest +IMAGE_ORG = flaccid +IMAGE_TAG = $(DOCKER_REGISTRY)/$(IMAGE_ORG)/$(IMAGE_NAME):$(IMAGE_VERSION) +export DOCKER_BUILDKIT = 1 + +WORKING_DIR := $(shell pwd) + +.DEFAULT_GOAL := docker-build + +.PHONY: build run + +# TODO: finish +#build:: ## builds nukkit + +# TODO: finish +#run:: ## runs the binary + +docker-release:: docker-build docker-push ## builds and pushes the docker image to the registry +docker-release-arm64:: docker-build-arm64 docker-push-arm64 ## builds and pushes the arm64 docker image to the registry + +docker-push:: ## pushes the docker image to the registry + @docker push $(IMAGE_TAG) + +docker-push-arm64:: ## pushes the arm64 docker image to the registry + @docker push $(DOCKER_REGISTRY)/$(IMAGE_ORG)/$(IMAGE_NAME):arm64 + +docker-build:: ## builds the docker image locally + @echo http_proxy=$(HTTP_PROXY) http_proxy=$(HTTPS_PROXY) + @echo building $(IMAGE_TAG) + @docker build --pull \ + --build-arg=http_proxy=$(HTTP_PROXY) \ + --build-arg=https_proxy=$(HTTPS_PROXY) \ + -t $(IMAGE_TAG) $(WORKING_DIR) + +docker-build-arm64:: ## builds the arm64 docker image locally + @echo http_proxy=$(HTTP_PROXY) http_proxy=$(HTTPS_PROXY) + @echo building $(IMAGE_TAG) + @docker build --pull \ + --build-arg=http_proxy=$(HTTP_PROXY) \ + --build-arg=https_proxy=$(HTTPS_PROXY) \ + -f Dockerfile.arm64 \ + -t $(DOCKER_REGISTRY)/$(IMAGE_ORG)/$(IMAGE_NAME):arm64 $(WORKING_DIR) + +docker-run:: ## runs the docker image locally + @docker run \ + -it \ + -p 19132:19132 \ + $(DOCKER_REGISTRY)/$(IMAGE_ORG)/$(IMAGE_NAME):$(IMAGE_VERSION) + +helm-install:: ## installs using helm from chart in repo + @helm install --name nukkit ./charts/nukkit + +helm-upgrade:: ## upgrades deployed helm release + @helm upgrade nukkit ./charts/nukkit + +helm-purge:: ## deletes and purges deployed helm release + @helm delete --purge nukkit + +helm-render:: ## prints out the rendered chart + @helm install --dry-run --debug charts/nukkit + +helm-validate:: ## runs a lint on the helm chart + @helm lint charts/nukkit + +install-ghr:: ## installs ghr + @cd /tmp + @wget https://github.com/tcnksm/ghr/releases/download/v0.12.2/ghr_v0.12.2_linux_amd64.tar.gz + @tar zxvf ghr_v0.12.2_linux_amd64.tar.gz + @sudo mv ghr_v0.12.2_linux_amd64/ghr /usr/local/bin/ + +# a help target including self-documenting targets (see the awk statement) +define HELP_TEXT +Usage: make [TARGET]... [MAKEVAR1=SOMETHING]... + +Available targets: +endef +export HELP_TEXT +help: ## this help target + @cat .banner + @echo + @echo "$$HELP_TEXT" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / \ + {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) diff --git a/README.md b/README.md index 6080b4c5e47..15784d085df 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It has a few key advantages over other server software: * Written in Java, Nukkit is faster and more stable. * Having a friendly structure, it's easy to contribute to Nukkit's development and rewrite plugins from other platforms into Nukkit plugins. -Nukkit is **under improvement** yet, we welcome contributions. +Nukkit is **under improvement** yet, we welcome contributions. Links -------------------- @@ -67,6 +67,51 @@ Use [docker-compose](https://docs.docker.com/compose/overview/) to start server docker-compose up -d ``` +Kubernetes & Helm +------------- + +Validate the chart: + +`helm lint charts/nukkit` + +Dry run and print out rendered YAML: + +`helm install --dry-run --debug nukkit charts/nukkit` + +Install the chart: + +`helm install nukkit charts/nukkit` + +Or, with some different values: + +``` +helm install nukkit \ + --set image.tag="arm64" \ + --set service.type="LoadBalancer" \ + charts/nukkit +``` + +Or, the same but with a custom values from a file: + +``` +helm install nukkit \ + -f helm-values.local.yaml \ + charts/nukkit +``` + +Upgrade the chart: + +`helm upgrade nukkit charts/nukkit` + +Testing after deployment: + +`helm test nukkit` + +Completely remove the chart: + +`helm uninstall nukkit` + + Contributing ------------ Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) guide before submitting any issue. Issues with insufficient information or in the wrong format will be closed and will not be reviewed. diff --git a/charts/nukkit/.helmignore b/charts/nukkit/.helmignore new file mode 100644 index 00000000000..0e8a0eb36f4 --- /dev/null +++ b/charts/nukkit/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/nukkit/Chart.yaml b/charts/nukkit/Chart.yaml new file mode 100644 index 00000000000..18fb6920fba --- /dev/null +++ b/charts/nukkit/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: nukkit +description: A Helm chart for Nukkit +type: application +version: 0.1.0 +appVersion: 0.1.0 diff --git a/charts/nukkit/templates/NOTES.txt b/charts/nukkit/templates/NOTES.txt new file mode 100644 index 00000000000..996aa14e3e1 --- /dev/null +++ b/charts/nukkit/templates/NOTES.txt @@ -0,0 +1,15 @@ +1. Get the application URL by running these commands: +{{- if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nukkit.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nukkit.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nukkit.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nukkit.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 +{{- end }} diff --git a/charts/nukkit/templates/_helpers.tpl b/charts/nukkit/templates/_helpers.tpl new file mode 100644 index 00000000000..09bfbe78fd2 --- /dev/null +++ b/charts/nukkit/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "nukkit.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nukkit.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nukkit.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nukkit.labels" -}} +helm.sh/chart: {{ include "nukkit.chart" . }} +{{ include "nukkit.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nukkit.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nukkit.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "nukkit.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "nukkit.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/nukkit/templates/configmap.yaml b/charts/nukkit/templates/configmap.yaml new file mode 100644 index 00000000000..00cbcee8771 --- /dev/null +++ b/charts/nukkit/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "nukkit.name" . }}-conf + labels: + app: {{ template "nukkit.name" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: + nukkit.yml: |- +{{- if .Values.config }} +{{ .Values.config | indent 4 }} +{{- end -}} diff --git a/charts/nukkit/templates/deployment.yaml b/charts/nukkit/templates/deployment.yaml new file mode 100644 index 00000000000..5937c9d08fe --- /dev/null +++ b/charts/nukkit/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nukkit.fullname" . }} + labels: + {{- include "nukkit.labels" . | nindent 4 }} +spec: +{{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} +{{- end }} + selector: + matchLabels: + {{- include "nukkit.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nukkit.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nukkit.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - name: {{ template "nukkit.name" . }}-conf + subPath: nukkit.yml + mountPath: /var/opt/nukkit/nukkit.yml + ports: + - name: udp + containerPort: 19132 + protocol: UDP + # TODO: fix/re-test + # readinessProbe: + # exec: + # command: + # - lsof + # - -i + # - :19132 + # initialDelaySeconds: 3 + # periodSeconds: 5 + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: {{ template "nukkit.name" . }}-conf + configMap: + name: {{ template "nukkit.name" . }}-conf + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/nukkit/templates/hpa.yaml b/charts/nukkit/templates/hpa.yaml new file mode 100644 index 00000000000..205cc5415fa --- /dev/null +++ b/charts/nukkit/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "nukkit.fullname" . }} + labels: + {{- include "nukkit.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "nukkit.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/nukkit/templates/service.yaml b/charts/nukkit/templates/service.yaml new file mode 100644 index 00000000000..58dfb2f8303 --- /dev/null +++ b/charts/nukkit/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nukkit.fullname" . }} + labels: + {{- include "nukkit.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: udp + protocol: UDP + name: udp +{{- if eq .Values.service.port "NodePort" }} + nodePort: {{ .Values.service.nodePort }} +{{- end }} + selector: + {{- include "nukkit.selectorLabels" . | nindent 4 }} +{{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} +{{- end }} diff --git a/charts/nukkit/templates/serviceaccount.yaml b/charts/nukkit/templates/serviceaccount.yaml new file mode 100644 index 00000000000..dc56a543579 --- /dev/null +++ b/charts/nukkit/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nukkit.serviceAccountName" . }} + labels: + {{- include "nukkit.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/nukkit/templates/tests/test-connection.yaml b/charts/nukkit/templates/tests/test-connection.yaml new file mode 100644 index 00000000000..3c0330acc4b --- /dev/null +++ b/charts/nukkit/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "nukkit.fullname" . }}-test-connection" + labels: + {{- include "nukkit.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "nukkit.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/nukkit/values.yaml b/charts/nukkit/values.yaml new file mode 100644 index 00000000000..8f6721ece63 --- /dev/null +++ b/charts/nukkit/values.yaml @@ -0,0 +1,74 @@ +# Default values for nukkit. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: flaccid/nukkit + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 19132 + loadBalancerIP: "" + nodePort: 39132 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +config: | + settings: + # Multi-language setting + # Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu + language: "eng" diff --git a/nukkit.yml.default b/nukkit.yml.default new file mode 100644 index 00000000000..3d4d2aae20f --- /dev/null +++ b/nukkit.yml.default @@ -0,0 +1,4 @@ +settings: + # Multi-language setting + # Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu + language: "eng" diff --git a/pom.xml b/pom.xml index 103ca385de2..dafe13a28ac 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,8 @@ - nukkitx-repo-release - https://repo.nukkitx.com/maven-releases/ + opencollab-repo-release + https://repo.opencollab.dev/maven-releases/ true @@ -29,8 +29,8 @@ - nukkitx-repo-snapshot - https://repo.nukkitx.com/maven-snapshots/ + opencollab-repo-snapshot + https://repo.opencollab.dev/maven-snapshots/ false @@ -40,24 +40,11 @@ - - - releases - nukkitx-releases - https://repo.nukkitx.com/release - - - snapshots - nukkitx-snapshots - https://repo.nukkitx.com/snapshot - - - com.nukkitx.network raknet - 1.6.20 + 1.6.25 compile @@ -75,7 +62,7 @@ com.google.code.gson gson - 2.4 + 2.8.6 compile diff --git a/src/main/java/cn/nukkit/AdventureSettings.java b/src/main/java/cn/nukkit/AdventureSettings.java index d49304b5389..bceac5c162d 100644 --- a/src/main/java/cn/nukkit/AdventureSettings.java +++ b/src/main/java/cn/nukkit/AdventureSettings.java @@ -53,7 +53,7 @@ public void update() { } pk.commandPermission = (player.isOp() ? AdventureSettingsPacket.PERMISSION_OPERATOR : AdventureSettingsPacket.PERMISSION_NORMAL); - pk.playerPermission = (player.isOp() ? Player.PERMISSION_OPERATOR : Player.PERMISSION_MEMBER); + pk.playerPermission = (player.isOp() && !player.isSpectator() ? Player.PERMISSION_OPERATOR : Player.PERMISSION_MEMBER); pk.entityUniqueId = player.getId(); Server.broadcastPacket(player.getViewers().values(), pk); diff --git a/src/main/java/cn/nukkit/Nukkit.java b/src/main/java/cn/nukkit/Nukkit.java index 783fb0b45be..259f6aa3453 100644 --- a/src/main/java/cn/nukkit/Nukkit.java +++ b/src/main/java/cn/nukkit/Nukkit.java @@ -42,7 +42,7 @@ public class Nukkit { public final static Properties GIT_INFO = getGitInfo(); public final static String VERSION = getVersion(); - public final static String API_VERSION = "1.0.11"; + public final static String API_VERSION = "1.0.12"; public final static String CODENAME = ""; @Deprecated public final static String MINECRAFT_VERSION = ProtocolInfo.MINECRAFT_VERSION; diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 164d30468a4..869b73e3f22 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -13,7 +13,6 @@ import cn.nukkit.entity.item.*; import cn.nukkit.entity.projectile.EntityArrow; import cn.nukkit.entity.projectile.EntityThrownTrident; -import cn.nukkit.event.block.ItemFrameDropItemEvent; import cn.nukkit.event.entity.EntityDamageByBlockEvent; import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.entity.EntityDamageEvent; @@ -23,6 +22,7 @@ import cn.nukkit.event.inventory.InventoryCloseEvent; import cn.nukkit.event.inventory.InventoryPickupArrowEvent; import cn.nukkit.event.inventory.InventoryPickupItemEvent; +import cn.nukkit.event.inventory.InventoryPickupTridentEvent; import cn.nukkit.event.player.*; import cn.nukkit.event.player.PlayerAsyncPreLoginEvent.LoginResult; import cn.nukkit.event.player.PlayerInteractEvent.Action; @@ -35,6 +35,7 @@ import cn.nukkit.inventory.transaction.CraftingTransaction; import cn.nukkit.inventory.transaction.EnchantTransaction; import cn.nukkit.inventory.transaction.InventoryTransaction; +import cn.nukkit.inventory.transaction.RepairItemTransaction; import cn.nukkit.inventory.transaction.action.InventoryAction; import cn.nukkit.inventory.transaction.data.ReleaseItemData; import cn.nukkit.inventory.transaction.data.UseItemData; @@ -140,6 +141,7 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected final BiMap windowIndex = windows.inverse(); protected final Set permanentWindows = new IntOpenHashSet(); private boolean inventoryOpen; + protected int closingWindowId = Integer.MIN_VALUE; protected int messageCounter = 2; @@ -155,6 +157,7 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected CraftingGrid craftingGrid; protected CraftingTransaction craftingTransaction; protected EnchantTransaction enchantTransaction; + protected RepairItemTransaction repairItemTransaction; public long creationTime = 0; @@ -205,8 +208,6 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected boolean checkMovement = true; - private final Map> batchedPackets = new TreeMap<>(); - private PermissibleBase perm = null; private int exp = 0; @@ -251,6 +252,8 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected double lastRightClickTime = 0.0; protected Vector3 lastRightClickPos = null; + private int timeSinceRest; + public int getStartActionTick() { return startAction; } @@ -777,7 +780,7 @@ public void sendChunk(int x, int z, int subChunkCount, byte[] payload) { pk.subChunkCount = subChunkCount; pk.data = payload; - this.batchDataPacket(pk); + this.dataPacket(pk); if (this.spawned) { for (Entity entity : this.level.getChunkEntities(x, z).values()) { @@ -839,16 +842,10 @@ protected void sendNextChunk() { protected void doFirstSpawn() { this.spawned = true; - this.setEnableClientCommand(true); - - this.getAdventureSettings().update(); - this.sendAttributes(); - - this.sendPotionEffects(this); - this.sendData(this); this.inventory.sendContents(this); this.inventory.sendArmorContents(this); this.offhandInventory.sendContents(this); + this.setEnableClientCommand(true); SetTimePacket setTimePacket = new SetTimePacket(); setTimePacket.time = this.level.getTime(); @@ -879,7 +876,6 @@ protected void doFirstSpawn() { this.noDamageTicks = 60; this.getServer().sendRecipeList(this); - inventory.sendCreativeContents(); for (long index : this.usedChunks.keySet()) { @@ -911,9 +907,6 @@ protected void doFirstSpawn() { //todo Updater //Weather - if (this.level.isRaining() || this.level.isThundering()) { - this.getLevel().sendWeather(this); - } this.getLevel().sendWeather(this); //FoodLevel @@ -1017,25 +1010,9 @@ protected boolean orderChunks() { return true; } + @Deprecated public boolean batchDataPacket(DataPacket packet) { - if (!this.connected) { - return false; - } - - try (Timing timing = Timings.getSendDataPacketTiming(packet)) { - DataPacketSendEvent event = new DataPacketSendEvent(this, packet); - this.server.getPluginManager().callEvent(event); - if (event.isCancelled()) { - return false; - } - - if (!this.batchedPackets.containsKey(packet.getChannel())) { - this.batchedPackets.put(packet.getChannel(), new ArrayList<>()); - } - - this.batchedPackets.get(packet.getChannel()).add(packet.clone()); - } - return true; + return this.dataPacket(packet); } /** @@ -1049,7 +1026,8 @@ public boolean dataPacket(DataPacket packet) { if (!this.connected) { return false; } - try (Timing timing = Timings.getSendDataPacketTiming(packet)) { + + try (Timing ignored = Timings.getSendDataPacketTiming(packet)) { DataPacketSendEvent ev = new DataPacketSendEvent(this, packet); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -1060,14 +1038,14 @@ public boolean dataPacket(DataPacket packet) { log.trace("Outbound {}: {}", this.getName(), packet); } - this.interfaz.putPacket(this, packet, false, false); + this.interfaz.putPacket(this, packet, false, true); } return true; } @Deprecated public int dataPacket(DataPacket packet, boolean needACK) { - return this.dataPacket(packet) ? 0 : -1; + return dataPacket(packet) ? 1 : 0; } /** @@ -1077,26 +1055,14 @@ public int dataPacket(DataPacket packet, boolean needACK) { * @param packet packet to send * @return packet successfully sent */ + @Deprecated public boolean directDataPacket(DataPacket packet) { - if (!this.connected) { - return false; - } - - try (Timing timing = Timings.getSendDataPacketTiming(packet)) { - DataPacketSendEvent ev = new DataPacketSendEvent(this, packet); - this.server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return false; - } - - this.interfaz.putPacket(this, packet, false, true); - } - return true; + return this.dataPacket(packet); } @Deprecated public int directDataPacket(DataPacket packet, boolean needACK) { - return this.directDataPacket(packet) ? 0 : -1; + return this.dataPacket(packet) ? 1 : 0; } public int getPing() { @@ -1132,6 +1098,8 @@ public boolean sleepOn(Vector3 pos) { this.level.sleepTicks = 60; + this.timeSinceRest = 0; + return true; } @@ -1285,7 +1253,6 @@ public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings n this.resetFallDistance(); this.inventory.sendContents(this); - this.inventory.sendContents(this.getViewers().values()); this.inventory.sendHeldItem(this.hasSpawned.values()); this.offhandInventory.sendContents(this); this.offhandInventory.sendContents(this.getViewers().values()); @@ -1464,7 +1431,7 @@ protected void processMovement(int tickDiff) { if (this.checkMovement && !server.getAllowFlight() && (this.isSurvival() || this.isAdventure())) { // Some say: I cant move my head when riding because the server // blocked my movement - if (!this.isSleeping() && this.riding == null && !this.hasEffect(Effect.LEVITATION)) { + if (!this.isSleeping() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING)) { double diffHorizontalSqr = (diffX * diffX + diffZ * diffZ) / ((double) (tickDiff * tickDiff)); if (diffHorizontalSqr > 0.5) { PlayerInvalidMoveEvent ev; @@ -1701,7 +1668,7 @@ public boolean onUpdate(int currentTick) { this.inAirTicks = 0; this.highestPosition = this.y; } else { - if (this.checkMovement && !this.isGliding() && !server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && this.inAirTicks > 20 && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION)) { + if (this.checkMovement && !this.isGliding() && !server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && this.inAirTicks > 20 && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING)) { double expectedVelocity = (-this.getGravity()) / ((double) this.getDrag()) - ((-this.getGravity()) / ((double) this.getDrag())) * Math.exp(-((double) this.getDrag()) * ((double) (this.inAirTicks - this.startAirTicks))); double diff = (this.speed.y - expectedVelocity) * (this.speed.y - expectedVelocity); @@ -1734,6 +1701,10 @@ public boolean onUpdate(int currentTick) { if (this.getFoodData() != null) this.getFoodData().update(tickDiff); } } + + if (!this.isSleeping()) { + this.timeSinceRest++; + } } this.checkTeleportPosition(); @@ -1817,17 +1788,6 @@ public void checkNetwork() { return; } - if (!this.batchedPackets.isEmpty()) { - Player[] pArr = new Player[]{this}; - for (Entry> entry : this.batchedPackets.entrySet()) { - List packets = entry.getValue(); - DataPacket[] arr = packets.toArray(new DataPacket[0]); - packets.clear(); - this.server.batchPackets(pArr, arr, false); - } - this.batchedPackets.clear(); - } - if (this.nextChunkOrderRun-- <= 0 || this.chunk == null) { this.orderChunks(); } @@ -1835,7 +1795,6 @@ public void checkNetwork() { if (!this.loadQueue.isEmpty() || !this.spawned) { this.sendNextChunk(); } - } public boolean canInteract(Vector3 pos, double maxDistance) { @@ -1986,6 +1945,11 @@ protected void processLogin() { this.forceMovement = this.teleportPosition = this.getPosition(); + if (!this.namedTag.contains("TimeSinceRest")) { + this.namedTag.putInt("TimeSinceRest", 0); + } + this.timeSinceRest = this.namedTag.getInt("TimeSinceRest"); + ResourcePacksInfoPacket infoPacket = new ResourcePacksInfoPacket(); infoPacket.resourcePackEntries = this.server.getResourcePackManager().getResourceStack(); infoPacket.mustAccept = this.server.getForceResources(); @@ -2034,11 +1998,17 @@ protected void completeLoginSequence() { startGamePacket.levelId = ""; startGamePacket.worldName = this.getServer().getNetwork().getName(); startGamePacket.generator = 1; //0 old, 1 infinite, 2 flat - //startGamePacket.isInventoryServerAuthoritative = true; this.dataPacket(startGamePacket); this.dataPacket(new BiomeDefinitionListPacket()); this.dataPacket(new AvailableEntityIdentifiersPacket()); + this.inventory.sendCreativeContents(); + this.getAdventureSettings().update(); + + this.sendAttributes(); + + this.sendPotionEffects(this); + this.sendData(this); this.loggedIn = true; @@ -2072,7 +2042,7 @@ public void handleDataPacket(DataPacket packet) { return; } - try (Timing timing = Timings.getReceiveDataPacketTiming(packet)) { + try (Timing ignored = Timings.getReceiveDataPacketTiming(packet)) { DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -2114,7 +2084,7 @@ public void handleDataPacket(DataPacket packet) { disconnectPacket.encode(); BatchPacket batch = new BatchPacket(); batch.payload = disconnectPacket.getBuffer(); - this.directDataPacket(batch); + this.dataPacket(batch); // Still want to run close() to allow the player to be removed properly } this.close("", message, false); @@ -2185,33 +2155,33 @@ public void handleDataPacket(DataPacket packet) { Player playerInstance = this; this.preLoginEventTask = new AsyncTask() { - - private PlayerAsyncPreLoginEvent e; + private PlayerAsyncPreLoginEvent event; @Override public void onRun() { - e = new PlayerAsyncPreLoginEvent(username, uuid, Player.this.getAddress(), Player.this.getPort()); - server.getPluginManager().callEvent(e); + this.event = new PlayerAsyncPreLoginEvent(username, uuid, loginChainData, playerInstance.getSkin(), playerInstance.getAddress(), playerInstance.getPort()); + server.getPluginManager().callEvent(this.event); } @Override public void onCompletion(Server server) { - if (!playerInstance.closed) { - if (e.getLoginResult() == LoginResult.KICK) { - playerInstance.close(e.getKickMessage(), e.getKickMessage()); - } else if (playerInstance.shouldLogin) { - playerInstance.completeLoginSequence(); - - for (Consumer action : e.getScheduledActions()) { - action.accept(server); - } + if (playerInstance.closed) { + return; + } + + if (this.event.getLoginResult() == LoginResult.KICK) { + playerInstance.close(this.event.getKickMessage(), this.event.getKickMessage()); + } else if (playerInstance.shouldLogin) { + playerInstance.setSkin(this.event.getSkin()); + playerInstance.completeLoginSequence(); + for (Consumer action : this.event.getScheduledActions()) { + action.accept(server); } } } }; this.server.getScheduler().scheduleAsyncTask(this.preLoginEventTask); - this.processLogin(); break; case ProtocolInfo.RESOURCE_PACK_CLIENT_RESPONSE_PACKET: @@ -2293,7 +2263,7 @@ public void onCompletion(Server server) { break; case ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET: - log.warn("Received packet violation warning: " + packet.toString()); + log.warn("Violation warning from {}: {}", this.getName(), packet.toString()); break; case ProtocolInfo.EMOTE_PACKET: for (Player viewer : this.getViewers().values()) { @@ -2430,8 +2400,15 @@ public void onCompletion(Server server) { ((BlockNoteblock) target).emitSound(); break actionswitch; case Block.DRAGON_EGG: - ((BlockDragonEgg) target).teleport(); - break actionswitch; + if (!this.isCreative()) { + ((BlockDragonEgg) target).teleport(); + break actionswitch; + } + case Block.ITEM_FRAME_BLOCK: + BlockEntity itemFrame = this.level.getBlockEntity(pos); + if (itemFrame instanceof BlockEntityItemFrame && ((BlockEntityItemFrame) itemFrame).dropItem(this)) { + break actionswitch; + } } Block block = target.getSide(face); if (block.getId() == Block.FIRE) { @@ -2775,6 +2752,15 @@ public void onCompletion(Server server) { this.dataPacket(entityEventPacket); Server.broadcastPacket(this.getViewers().values(), entityEventPacket); + } else if (entityEventPacket.event == EntityEventPacket.ENCHANT) { + if (entityEventPacket.eid != this.id) { + break; + } + + Inventory inventory = this.getWindowById(ANVIL_WINDOW_ID); + if (inventory instanceof AnvilInventory) { + ((AnvilInventory) inventory).setCost(-entityEventPacket.data); + } } break; case ProtocolInfo.COMMAND_REQUEST_PACKET: @@ -2819,13 +2805,16 @@ public void onCompletion(Server server) { if (this.windowIndex.containsKey(containerClosePacket.windowId)) { this.server.getPluginManager().callEvent(new InventoryCloseEvent(this.windowIndex.get(containerClosePacket.windowId), this)); if (containerClosePacket.windowId == ContainerIds.INVENTORY) this.inventoryOpen = false; - this.removeWindow(this.windowIndex.get(containerClosePacket.windowId)); + this.closingWindowId = containerClosePacket.windowId; + this.removeWindow(this.windowIndex.get(containerClosePacket.windowId), true); + this.closingWindowId = Integer.MIN_VALUE; } if (containerClosePacket.windowId == -1) { this.craftingType = CRAFTING_SMALL; this.resetCraftingGridType(); this.addWindow(this.craftingGrid, ContainerIds.NONE); ContainerClosePacket pk = new ContainerClosePacket(); + pk.wasServerInitiated = false; pk.windowId = -1; this.dataPacket(pk); } @@ -2884,24 +2873,9 @@ public void onCompletion(Server server) { case ProtocolInfo.ITEM_FRAME_DROP_ITEM_PACKET: ItemFrameDropItemPacket itemFrameDropItemPacket = (ItemFrameDropItemPacket) packet; Vector3 vector3 = this.temporalVector.setComponents(itemFrameDropItemPacket.x, itemFrameDropItemPacket.y, itemFrameDropItemPacket.z); - BlockEntity blockEntityItemFrame = this.level.getBlockEntity(vector3); - BlockEntityItemFrame itemFrame = (BlockEntityItemFrame) blockEntityItemFrame; - if (itemFrame != null) { - block = itemFrame.getBlock(); - Item itemDrop = itemFrame.getItem(); - ItemFrameDropItemEvent itemFrameDropItemEvent = new ItemFrameDropItemEvent(this, block, itemFrame, itemDrop); - this.server.getPluginManager().callEvent(itemFrameDropItemEvent); - if (!itemFrameDropItemEvent.isCancelled()) { - if (itemDrop.getId() != Item.AIR) { - vector3 = this.temporalVector.setComponents(itemFrame.x + 0.5, itemFrame.y, itemFrame.z + 0.5); - this.level.dropItem(vector3, itemDrop); - itemFrame.setItem(new ItemBlock(Block.get(BlockID.AIR))); - itemFrame.setItemRotation(0); - this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_ITEM_REMOVED); - } - } else { - itemFrame.spawnTo(this); - } + BlockEntity itemFrame = this.level.getBlockEntity(vector3); + if (itemFrame instanceof BlockEntityItemFrame) { + ((BlockEntityItemFrame) itemFrame).dropItem(this); } break; case ProtocolInfo.MAP_INFO_REQUEST_PACKET: @@ -2995,6 +2969,19 @@ public void onCompletion(Server server) { this.enchantTransaction = null; } return; + } else if (transactionPacket.isRepairItemPart) { + if (this.repairItemTransaction == null) { + this.repairItemTransaction = new RepairItemTransaction(this, actions); + } else { + for (InventoryAction action : actions) { + this.repairItemTransaction.addAction(action); + } + } + if (this.repairItemTransaction.canExecute()) { + this.repairItemTransaction.execute(); + this.repairItemTransaction = null; + } + return; } else if (this.craftingTransaction != null) { if (craftingTransaction.checkForCraftingPart(actions)) { for (InventoryAction action : actions) { @@ -3019,6 +3006,18 @@ public void onCompletion(Server server) { this.sendAllInventories(); this.enchantTransaction = null; } + } else if (this.repairItemTransaction != null) { + if (RepairItemTransaction.checkForRepairItemPart(actions)) { + for (InventoryAction action : actions) { + this.repairItemTransaction.addAction(action); + } + return; + } else { + this.server.getLogger().debug("Got unexpected normal inventory action with incomplete repair item transaction from " + this.getName() + ", refusing to execute repair item " + transactionPacket.toString()); + this.removeAllWindows(false); + this.sendAllInventories(); + this.repairItemTransaction = null; + } } switch (transactionPacket.transactionType) { @@ -3147,7 +3146,7 @@ public void onCompletion(Server server) { } if (item.onClickAir(this, directionVector)) { - if (this.isSurvival()) { + if (!this.isCreative()) { this.inventory.setItemInHand(item); } @@ -3227,7 +3226,7 @@ public void onCompletion(Server server) { } else if (target instanceof Player) { if ((((Player) target).getGamemode() & 0x01) > 0) { break; - } else if (!this.server.getPropertyBoolean("pvp")) { + } else if (!this.server.getPropertyBoolean("pvp") || this.server.getDifficulty() == 0) { break; } } @@ -3379,6 +3378,14 @@ public void onCompletion(Server server) { } } break; + case ProtocolInfo.FILTER_TEXT_PACKET: + FilterTextPacket filterTextPacket = (FilterTextPacket) packet; + + FilterTextPacket textResponsePacket = new FilterTextPacket(); + textResponsePacket.text = filterTextPacket.text; + textResponsePacket.fromServer = true; + this.dataPacket(textResponsePacket); + break; default: break; } @@ -3648,7 +3655,7 @@ public void close(TextContainer message, String reason, boolean notify) { if (notify && reason.length() > 0) { DisconnectPacket pk = new DisconnectPacket(); pk.message = reason; - this.directDataPacket(pk); + this.dataPacket(pk); } this.connected = false; @@ -3772,6 +3779,8 @@ public void save(boolean async) { this.namedTag.putInt("foodLevel", this.getFoodData().getLevel()); this.namedTag.putFloat("foodSaturationLevel", this.getFoodData().getFoodSaturationLevel()); + this.namedTag.putInt("TimeSinceRest", this.timeSinceRest); + if (!this.username.isEmpty() && this.namedTag != null) { this.server.saveOfflinePlayerData(this.uuid, this.namedTag, async); } @@ -3912,34 +3921,6 @@ public void kill() { ev.setKeepExperience(this.level.gameRules.getBoolean(GameRule.KEEP_INVENTORY)); ev.setKeepInventory(ev.getKeepExperience()); - if (cause != null && cause.getCause() != DamageCause.VOID) { - PlayerOffhandInventory offhandInventory = this.getOffhandInventory(); - PlayerInventory playerInventory = this.getInventory(); - if (offhandInventory.getItem(0).getId() == Item.TOTEM || playerInventory.getItemInHand().getId() == Item.TOTEM) { - this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_TOTEM); - this.extinguish(); - this.removeAllEffects(); - this.setHealth(1); - - this.addEffect(Effect.getEffect(Effect.REGENERATION).setDuration(800).setAmplifier(1)); - this.addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(800).setAmplifier(1)); - this.addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(100).setAmplifier(1)); - - EntityEventPacket pk = new EntityEventPacket(); - pk.eid = this.getId(); - pk.event = EntityEventPacket.CONSUME_TOTEM; - this.dataPacket(pk); - - if (offhandInventory.getItem(0).getId() == Item.TOTEM) { - offhandInventory.clear(0); - } else { - playerInventory.clear(playerInventory.getHeldItemIndex()); - } - - ev.setCancelled(true); - } - } - this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { @@ -3953,7 +3934,9 @@ public void kill() { if (!ev.getKeepInventory() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { for (Item item : ev.getDrops()) { - this.level.dropItem(this, item, null, true, 40); + if (!item.hasEnchantment(Enchantment.ID_VANISHING_CURSE)) { + this.level.dropItem(this, item, null, true, 40); + } } if (this.inventory != null) { @@ -3973,6 +3956,8 @@ public void kill() { this.setExperience(0, 0); } + this.timeSinceRest = 0; + if (showMessages && !ev.getDeathMessage().toString().isEmpty()) { this.server.broadcast(ev.getDeathMessage(), Server.BROADCAST_CHANNEL_USERS); } @@ -4341,11 +4326,7 @@ protected void sendPlayStatus(int status, boolean immediate) { PlayStatusPacket pk = new PlayStatusPacket(); pk.status = status; - if (immediate) { - this.directDataPacket(pk); - } else { - this.dataPacket(pk); - } + this.dataPacket(pk); } @Override @@ -4626,8 +4607,12 @@ public Optional getTopWindow() { } public void removeWindow(Inventory inventory) { + this.removeWindow(inventory, false); + } + + protected void removeWindow(Inventory inventory, boolean isResponse) { inventory.close(this); - if (!this.permanentWindows.contains(this.getWindowId(inventory))) + if (isResponse && !this.permanentWindows.contains(this.getWindowId(inventory))) this.windows.remove(inventory); } @@ -4716,6 +4701,10 @@ public void removeAllWindows(boolean permanent) { } } + public int getClosingWindowId() { + return this.closingWindowId; + } + @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { this.server.getPlayerMetadata().setMetadata(this, metadataKey, newMetadataValue); @@ -4816,7 +4805,7 @@ private void setDimension(int dimension) { pk.x = (float) this.x; pk.y = (float) this.y; pk.z = (float) this.z; - this.directDataPacket(pk); + this.dataPacket(pk); } @Override @@ -4882,8 +4871,6 @@ public void transfer(InetSocketAddress address) { pk.address = hostName; pk.port = port; this.dataPacket(pk); - String message = "Transferred to " + hostName + ":" + port; - this.close("", message, false); } public LoginChainData getLoginChainData() { @@ -4891,7 +4878,7 @@ public LoginChainData getLoginChainData() { } public boolean pickupEntity(Entity entity, boolean near) { - if (!this.spawned || !this.isAlive() || !this.isOnline() || this.getGamemode() == SPECTATOR || entity.isClosed()) { + if (!this.spawned || !this.isAlive() || !this.isOnline() || this.isSpectator() || entity.isClosed()) { return false; } @@ -4931,6 +4918,12 @@ public boolean pickupEntity(Entity entity, boolean near) { return false; } + InventoryPickupTridentEvent ev = new InventoryPickupTridentEvent(this.inventory, (EntityThrownTrident) entity); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + TakeItemEntityPacket pk = new TakeItemEntityPacket(); pk.entityId = this.getId(); pk.target = entity.getId(); @@ -5106,4 +5099,12 @@ public String toString() { "', location=" + super.toString() + ')'; } + + public int getTimeSinceRest() { + return timeSinceRest; + } + + public void setTimeSinceRest(int timeSinceRest) { + this.timeSinceRest = timeSinceRest; + } } diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 45a8c1a8406..900e62fd4ee 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -4,6 +4,7 @@ import cn.nukkit.blockentity.*; import cn.nukkit.command.*; import cn.nukkit.console.NukkitConsole; +import cn.nukkit.dispenser.DispenseBehaviorRegister; import cn.nukkit.entity.Attribute; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityHuman; @@ -19,6 +20,7 @@ import cn.nukkit.event.server.BatchPacketsEvent; import cn.nukkit.event.server.PlayerDataSerializeEvent; import cn.nukkit.event.server.QueryRegenerateEvent; +import cn.nukkit.event.server.ServerStopEvent; import cn.nukkit.inventory.CraftingManager; import cn.nukkit.inventory.Recipe; import cn.nukkit.item.Item; @@ -44,6 +46,7 @@ import cn.nukkit.metadata.EntityMetadataStore; import cn.nukkit.metadata.LevelMetadataStore; import cn.nukkit.metadata.PlayerMetadataStore; +import cn.nukkit.metrics.NukkitMetrics; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.DoubleTag; @@ -161,6 +164,8 @@ public class Server { private boolean autoSave = true; + private boolean redstoneEnabled = true; + private RCON rcon; private EntityMetadataStore entityMetadata; @@ -243,7 +248,7 @@ public Level remove(Object key) { private DB nameLookup; - private PlayerDataSerializer playerDataSerializer = new DefaultPlayerDataSerializer(this); + private PlayerDataSerializer playerDataSerializer; private final Set ignoredPackets = new HashSet<>(); @@ -272,6 +277,8 @@ public Level remove(Object key) { this.consoleThread = new ConsoleThread(); this.consoleThread.start(); + this.playerDataSerializer = new DefaultPlayerDataSerializer(this); + //todo: VersionString 现在不必要 if (!new File(this.dataPath + "nukkit.yml").exists()) { @@ -405,6 +412,7 @@ public Level remove(Object key) { this.autoTickRateLimit = this.getConfig("level-settings.auto-tick-rate-limit", 20); this.alwaysTickPlayers = this.getConfig("level-settings.always-tick-players", false); this.baseTickRate = this.getConfig("level-settings.base-tick-rate", 1); + this.redstoneEnabled = this.getConfig("level-settings.tick-redstone", true); this.scheduler = new ServerScheduler(); @@ -458,6 +466,9 @@ public Level remove(Object key) { this.consoleSender = new ConsoleCommandSender(); this.commandMap = new SimpleCommandMap(this); + // Initialize metrics + new NukkitMetrics(this); + this.registerEntities(); this.registerBlockEntities(); @@ -468,6 +479,7 @@ public Level remove(Object key) { Effect.init(); Potion.init(); Attribute.init(); + DispenseBehaviorRegister.init(); GlobalBlockPalette.getOrCreateRuntimeId(0, 0); //Force it to load // Convert legacy data before plugins get the chance to mess with it. @@ -649,26 +661,27 @@ public int broadcast(TextContainer message, String permissions) { } public static void broadcastPacket(Collection players, DataPacket packet) { - broadcastPacket(players.toArray(new Player[0]), packet); + packet.tryEncode(); + + for (Player player : players) { + player.dataPacket(packet); + } } public static void broadcastPacket(Player[] players, DataPacket packet) { - packet.encode(); - packet.isEncoded = true; + packet.tryEncode(); - if (packet.pid() == ProtocolInfo.BATCH_PACKET) { - for (Player player : players) { - player.dataPacket(packet); - } - } else { - getInstance().batchPackets(players, new DataPacket[]{packet}, true); + for (Player player : players) { + player.dataPacket(packet); } } + @Deprecated public void batchPackets(Player[] players, DataPacket[] packets) { this.batchPackets(players, packets, false); } + @Deprecated public void batchPackets(Player[] players, DataPacket[] packets, boolean forceSync) { if (players == null || packets == null || players.length == 0 || packets.length == 0) { return; @@ -682,18 +695,14 @@ public void batchPackets(Player[] players, DataPacket[] packets, boolean forceSy Timings.playerNetworkSendTimer.startTiming(); byte[][] payload = new byte[packets.length * 2][]; - int size = 0; for (int i = 0; i < packets.length; i++) { DataPacket p = packets[i]; - if (!p.isEncoded) { - p.encode(); - } + int idx = i * 2; + p.tryEncode(); byte[] buf = p.getBuffer(); - payload[i * 2] = Binary.writeUnsignedVarInt(buf.length); - payload[i * 2 + 1] = buf; + payload[idx] = Binary.writeUnsignedVarInt(buf.length); + payload[idx + 1] = buf; packets[i] = null; - size += payload[i * 2].length; - size += payload[i * 2 + 1].length; } List targets = new ArrayList<>(); @@ -753,8 +762,11 @@ public boolean dispatchCommand(CommandSender sender, String commandLine) throws if (!this.isPrimaryThread()) { getLogger().warning("Command Dispatched Async: " + commandLine); getLogger().warning("Please notify author of plugin causing this execution to fix this bug!", new Throwable()); - // TODO: We should sync the command to the main thread too! + + this.scheduler.scheduleTask(null, () -> dispatchCommand(sender, commandLine)); + return true; } + if (sender == null) { throw new ServerException("CommandSender is not valid"); } @@ -828,6 +840,9 @@ public void forceShutdown() { this.hasStopped = true; + ServerStopEvent serverStopEvent = new ServerStopEvent(); + getPluginManager().callEvent(serverStopEvent); + if (this.rcon != null) { this.rcon.close(); } @@ -1032,6 +1047,13 @@ public void removePlayerListData(UUID uuid, Player[] players) { Server.broadcastPacket(players, pk); } + public void removePlayerListData(UUID uuid, Player player) { + PlayerListPacket pk = new PlayerListPacket(); + pk.type = PlayerListPacket.TYPE_REMOVE; + pk.entries = new PlayerListPacket.Entry[]{new PlayerListPacket.Entry(uuid)}; + player.dataPacket(pk); + } + public void removePlayerListData(UUID uuid, Collection players) { this.removePlayerListData(uuid, players.toArray(new Player[0])); } @@ -1428,7 +1450,7 @@ public static int getDifficultyFromString(String str) { public int getDifficulty() { if (this.difficulty == Integer.MAX_VALUE) { - this.difficulty = this.getPropertyInt("difficulty", 1); + this.difficulty = getDifficultyFromString(this.getPropertyString("difficulty", "1")); } return this.difficulty; } @@ -1464,7 +1486,11 @@ public String getMotd() { } public String getSubMotd() { - return this.getPropertyString("sub-motd", "https://nukkitx.com"); + String subMotd = this.getPropertyString("sub-motd", "https://nukkitx.com"); + if (subMotd.isEmpty()) { + subMotd = "https://nukkitx.com"; // The client doesn't allow empty sub-motd in 1.16.210 + } + return subMotd; } public boolean getForceResources() { @@ -2048,6 +2074,14 @@ public boolean isLanguageForced() { return forceLanguage; } + public boolean isRedstoneEnabled() { + return redstoneEnabled; + } + + public void setRedstoneEnabled(boolean redstoneEnabled) { + this.redstoneEnabled = redstoneEnabled; + } + public Network getNetwork() { return network; } @@ -2088,8 +2122,8 @@ public String getPropertyString(String variable) { return this.getPropertyString(variable, null); } - public String getPropertyString(String variable, String defaultValue) { - return this.properties.exists(variable) ? (String) this.properties.get(variable) : defaultValue; + public String getPropertyString(String key, String defaultValue) { + return this.properties.exists(key) ? this.properties.get(key).toString() : defaultValue; } public int getPropertyInt(String variable) { @@ -2366,6 +2400,9 @@ private void registerBlockEntities() { BlockEntity.registerBlockEntity(BlockEntity.SHULKER_BOX, BlockEntityShulkerBox.class); BlockEntity.registerBlockEntity(BlockEntity.BANNER, BlockEntityBanner.class); BlockEntity.registerBlockEntity(BlockEntity.MUSIC, BlockEntityMusic.class); + BlockEntity.registerBlockEntity(BlockEntity.DISPENSER, BlockEntityDispenser.class); + BlockEntity.registerBlockEntity(BlockEntity.DROPPER, BlockEntityDropper.class); + BlockEntity.registerBlockEntity(BlockEntity.MOVING_BLOCK, BlockEntityMovingBlock.class); } public boolean isNetherAllowed() { diff --git a/src/main/java/cn/nukkit/block/Block.java b/src/main/java/cn/nukkit/block/Block.java index c14d908717d..bb4a2f193bb 100644 --- a/src/main/java/cn/nukkit/block/Block.java +++ b/src/main/java/cn/nukkit/block/Block.java @@ -179,7 +179,7 @@ public static void init() { list[DRAGON_EGG] = BlockDragonEgg.class; //122 list[REDSTONE_LAMP] = BlockRedstoneLamp.class; //123 list[LIT_REDSTONE_LAMP] = BlockRedstoneLampLit.class; //124 - //TODO: list[DROPPER] = BlockDropper.class; //125 + list[DROPPER] = BlockDropper.class; //125 list[ACTIVATOR_RAIL] = BlockRailActivator.class; //126 list[COCOA] = BlockCocoa.class; //127 list[SANDSTONE_STAIRS] = BlockStairsSandstone.class; //128 @@ -296,8 +296,7 @@ public static void init() { list[GLOWING_OBSIDIAN] = BlockObsidianGlowing.class; //246 //list[NETHER_REACTOR] = BlockNetherReactor.class; //247 Should not be removed - //TODO: list[PISTON_EXTENSION] = BlockPistonExtension.class; //250 - + list[MOVING_BLOCK] = BlockMoving.class; //250 list[OBSERVER] = BlockObserver.class; //251 for (int id = 0; id < 256; id++) { @@ -487,6 +486,18 @@ public boolean canBePushed() { return true; } + public boolean canBePulled() { + return true; + } + + public boolean breaksWhenMoved() { + return false; + } + + public boolean sticksToPiston() { + return true; + } + public boolean hasComparatorInputOverride() { return false; } @@ -548,10 +559,16 @@ public Item[] getDrops(Item item) { } } - private static double toolBreakTimeBonus0( - int toolType, int toolTier, boolean isWoolBlock, boolean isCobweb) { - if (toolType == ItemTool.TYPE_SWORD) return isCobweb ? 15.0 : 1.0; - if (toolType == ItemTool.TYPE_SHEARS) return isWoolBlock ? 5.0 : 15.0; + private static double toolBreakTimeBonus0(int toolType, int toolTier, int blockId) { + if (toolType == ItemTool.TYPE_SWORD) return blockId == Block.COBWEB ? 15.0 : 1.0; + if (toolType == ItemTool.TYPE_SHEARS) { + if (blockId == Block.WOOL || blockId == LEAVES || blockId == LEAVES2) { + return 5.0; + } else if (blockId == COBWEB) { + return 15.0; + } + return 1.0; + } if (toolType == ItemTool.TYPE_NONE) return 1.0; switch (toolTier) { case ItemTool.TIER_WOODEN: @@ -562,6 +579,8 @@ private static double toolBreakTimeBonus0( return 6.0; case ItemTool.TIER_DIAMOND: return 8.0; + case ItemTool.TIER_NETHERITE: + return 9.0; case ItemTool.TIER_GOLD: return 12.0; default: @@ -583,6 +602,7 @@ private static int toolType0(Item item) { if (item.isShovel()) return ItemTool.TYPE_SHOVEL; if (item.isPickaxe()) return ItemTool.TYPE_PICKAXE; if (item.isAxe()) return ItemTool.TYPE_AXE; + if (item.isHoe()) return ItemTool.TYPE_HOE; if (item.isShears()) return ItemTool.TYPE_SHEARS; return ItemTool.TYPE_NONE; } @@ -592,6 +612,7 @@ private static boolean correctTool0(int blockToolType, Item item) { (blockToolType == ItemTool.TYPE_SHOVEL && item.isShovel()) || (blockToolType == ItemTool.TYPE_PICKAXE && item.isPickaxe()) || (blockToolType == ItemTool.TYPE_AXE && item.isAxe()) || + (blockToolType == ItemTool.TYPE_HOE && item.isHoe()) || (blockToolType == ItemTool.TYPE_SHEARS && item.isShears()) || blockToolType == ItemTool.TYPE_NONE; } @@ -602,8 +623,7 @@ private static double breakTime0(double blockHardness, boolean correctTool, bool boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround) { double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; double speed = 1.0 / baseTime; - boolean isWoolBlock = blockId == Block.WOOL, isCobweb = blockId == Block.COBWEB; - if (correctTool) speed *= toolBreakTimeBonus0(toolType, toolTier, isWoolBlock, isCobweb); + if (correctTool) speed *= toolBreakTimeBonus0(toolType, toolTier, blockId); speed += speedBonusByEfficiencyLore0(efficiencyLoreLevel); speed *= speedRateByHasteLore0(hasteEffectLevel); if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; @@ -620,9 +640,10 @@ public double getBreakTime(Item item, Player player) { return 0; } - boolean correctTool = correctTool0(getToolType(), item); - boolean canHarvestWithHand = canHarvestWithHand(); int blockId = getId(); + boolean correctTool = correctTool0(getToolType(), item) + || item.isShears() && (blockId == COBWEB || blockId == LEAVES || blockId == LEAVES2); + boolean canHarvestWithHand = canHarvestWithHand(); int itemToolType = toolType0(item); int itemTier = item.getTier(); int efficiencyLoreLevel = Optional.ofNullable(item.getEnchantment(Enchantment.ID_EFFICIENCY)) @@ -651,7 +672,8 @@ public double getBreakTime(Item item) { } else if ( (this.getToolType() == ItemTool.TYPE_PICKAXE && item.isPickaxe()) || (this.getToolType() == ItemTool.TYPE_AXE && item.isAxe()) || - (this.getToolType() == ItemTool.TYPE_SHOVEL && item.isShovel()) + (this.getToolType() == ItemTool.TYPE_SHOVEL && item.isShovel()) || + (this.getToolType() == ItemTool.TYPE_HOE && item.isHoe()) ) { int tier = item.getTier(); switch (tier) { @@ -667,6 +689,9 @@ public double getBreakTime(Item item) { case ItemTool.TIER_DIAMOND: base /= 8; break; + case ItemTool.TIER_NETHERITE: + base /= 9; + break; case ItemTool.TIER_GOLD: base /= 12; break; @@ -695,6 +720,10 @@ public Block getSide(BlockFace face) { } public Block getSide(BlockFace face, int step) { + if (step == 0) { + return this; + } + if (this.isValid()) { if (step == 1) { return this.getLevel().getBlock((int) x + face.getXOffset(), (int) y + face.getYOffset(), (int) z + face.getZOffset()); diff --git a/src/main/java/cn/nukkit/block/BlockBanner.java b/src/main/java/cn/nukkit/block/BlockBanner.java index fd315acbd71..68352b124fb 100644 --- a/src/main/java/cn/nukkit/block/BlockBanner.java +++ b/src/main/java/cn/nukkit/block/BlockBanner.java @@ -133,6 +133,11 @@ public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + @Override + public boolean breaksWhenMoved() { + return true; + } + @Override public BlockColor getColor() { return this.getDyeColor().getColor(); diff --git a/src/main/java/cn/nukkit/block/BlockBeacon.java b/src/main/java/cn/nukkit/block/BlockBeacon.java index 8a52c099ef9..1ec4c92908f 100644 --- a/src/main/java/cn/nukkit/block/BlockBeacon.java +++ b/src/main/java/cn/nukkit/block/BlockBeacon.java @@ -105,4 +105,9 @@ public boolean canBePushed() { public BlockColor getColor() { return BlockColor.DIAMOND_BLOCK_COLOR; } + + @Override + public boolean canBePulled() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockBed.java b/src/main/java/cn/nukkit/block/BlockBed.java index 482a7bd45c8..1443cd11c77 100644 --- a/src/main/java/cn/nukkit/block/BlockBed.java +++ b/src/main/java/cn/nukkit/block/BlockBed.java @@ -205,4 +205,14 @@ public DyeColor getDyeColor() { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockBedrock.java b/src/main/java/cn/nukkit/block/BlockBedrock.java index 73e75f6ab2e..1f9392b5c87 100644 --- a/src/main/java/cn/nukkit/block/BlockBedrock.java +++ b/src/main/java/cn/nukkit/block/BlockBedrock.java @@ -41,6 +41,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java b/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java index 3f586ca815f..bb7bde361ca 100644 --- a/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java +++ b/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java @@ -47,6 +47,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); diff --git a/src/main/java/cn/nukkit/block/BlockButton.java b/src/main/java/cn/nukkit/block/BlockButton.java index 317d9e8c908..204a45425d6 100644 --- a/src/main/java/cn/nukkit/block/BlockButton.java +++ b/src/main/java/cn/nukkit/block/BlockButton.java @@ -55,15 +55,21 @@ public boolean onActivate(Item item, Player player) { return false; } - this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, 0, 15)); + this.level.scheduleUpdate(this, 30); + this.setDamage(this.getDamage() ^ 0x08); this.level.setBlock(this, this, true, false); - this.level.addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_POWER_ON, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); - this.level.scheduleUpdate(this, 30); - Vector3 pos = getLocation(); - level.updateAroundRedstone(pos, null); - level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + if (this.level.getServer().isRedstoneEnabled()) { + this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, 0, 15)); + + Vector3 pos = getLocation(); + + level.updateAroundRedstone(pos, null); + level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + } + + this.level.addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_POWER_ON, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); return true; } @@ -76,15 +82,17 @@ public int onUpdate(int type) { } } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { if (this.isActivated()) { - this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, 15, 0)); - this.setDamage(this.getDamage() ^ 0x08); this.level.setBlock(this, this, true, false); this.level.addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_POWER_OFF, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); - Vector3 pos = getLocation(); - level.updateAroundRedstone(pos, null); - level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + if (this.level.getServer().isRedstoneEnabled()) { + this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, 15, 0)); + + Vector3 pos = getLocation(); + level.updateAroundRedstone(pos, null); + level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + } } return Level.BLOCK_UPDATE_SCHEDULED; @@ -126,7 +134,7 @@ public boolean onBreak(Item item) { @Override public Item toItem() { - return Item.get(this.getId(), 5); + return Item.get(this.getId(), 0); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockCactus.java b/src/main/java/cn/nukkit/block/BlockCactus.java index 9e58ececd8f..e57bc7f0594 100644 --- a/src/main/java/cn/nukkit/block/BlockCactus.java +++ b/src/main/java/cn/nukkit/block/BlockCactus.java @@ -152,11 +152,21 @@ public String getName() { public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } - + @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.CACTUS, 0, 1) + Item.get(Item.CACTUS, 0, 1) }; } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCake.java b/src/main/java/cn/nukkit/block/BlockCake.java index b959b14266d..0da8998632e 100644 --- a/src/main/java/cn/nukkit/block/BlockCake.java +++ b/src/main/java/cn/nukkit/block/BlockCake.java @@ -136,4 +136,14 @@ public int getComparatorInputOverride() { public boolean hasComparatorInputOverride() { return true; } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockChorusFlower.java b/src/main/java/cn/nukkit/block/BlockChorusFlower.java index 46ad4e2a2c9..3c1e310295f 100644 --- a/src/main/java/cn/nukkit/block/BlockChorusFlower.java +++ b/src/main/java/cn/nukkit/block/BlockChorusFlower.java @@ -37,4 +37,14 @@ public int getToolType() { public Item[] getDrops(Item item) { return new Item[]{this.toItem()}; } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockChorusPlant.java b/src/main/java/cn/nukkit/block/BlockChorusPlant.java index f4363f5ecf3..b184c495601 100644 --- a/src/main/java/cn/nukkit/block/BlockChorusPlant.java +++ b/src/main/java/cn/nukkit/block/BlockChorusPlant.java @@ -42,6 +42,16 @@ public Item[] getDrops(Item item) { return ThreadLocalRandom.current().nextBoolean() ? new Item[]{Item.get(ItemID.CHORUS_FRUIT, 0, 1)} : new Item[0]; } + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } + @Override public BlockColor getColor() { return BlockColor.PURPLE_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockCobweb.java b/src/main/java/cn/nukkit/block/BlockCobweb.java index 17d1ff2c6b9..16ca8a689d8 100644 --- a/src/main/java/cn/nukkit/block/BlockCobweb.java +++ b/src/main/java/cn/nukkit/block/BlockCobweb.java @@ -51,7 +51,11 @@ public void onEntityCollide(Entity entity) { @Override public Item[] getDrops(Item item) { - if (item.isShears() || item.isSword()) { + if (item.isShears()) { + return new Item[]{ + this.toItem() + }; + } else if (item.isSword()) { return new Item[]{ new ItemString() }; diff --git a/src/main/java/cn/nukkit/block/BlockCocoa.java b/src/main/java/cn/nukkit/block/BlockCocoa.java index 66faef60934..ca844f1a346 100644 --- a/src/main/java/cn/nukkit/block/BlockCocoa.java +++ b/src/main/java/cn/nukkit/block/BlockCocoa.java @@ -239,4 +239,14 @@ public Item[] getDrops(Item item) { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockConcretePowder.java b/src/main/java/cn/nukkit/block/BlockConcretePowder.java index 049c5c1692f..111c8d1baec 100644 --- a/src/main/java/cn/nukkit/block/BlockConcretePowder.java +++ b/src/main/java/cn/nukkit/block/BlockConcretePowder.java @@ -67,7 +67,7 @@ public int onUpdate(int type) { for (int side = 1; side <= 5; side++) { Block block = this.getSide(BlockFace.fromIndex(side)); - if (block.getId() == Block.WATER || block.getId() == Block.STILL_WATER || block.getId() == Block.LAVA || block.getId() == Block.STILL_LAVA) { + if (block.getId() == Block.WATER || block.getId() == Block.STILL_WATER) { this.level.setBlock(this, Block.get(Block.CONCRETE, this.meta), true, true); } } @@ -83,7 +83,7 @@ public boolean place(Item item, Block b, Block target, BlockFace face, double fx for (int side = 1; side <= 5; side++) { Block block = this.getSide(BlockFace.fromIndex(side)); - if (block.getId() == Block.WATER || block.getId() == Block.STILL_WATER || block.getId() == Block.LAVA || block.getId() == Block.STILL_LAVA) { + if (block.getId() == Block.WATER || block.getId() == Block.STILL_WATER) { concrete = true; break; } diff --git a/src/main/java/cn/nukkit/block/BlockDirt.java b/src/main/java/cn/nukkit/block/BlockDirt.java index 6a7db24cd13..dd87fb80c7e 100644 --- a/src/main/java/cn/nukkit/block/BlockDirt.java +++ b/src/main/java/cn/nukkit/block/BlockDirt.java @@ -54,10 +54,17 @@ public String getName() { @Override public boolean onActivate(Item item, Player player) { if (item.isHoe()) { - item.useOn(this); - this.getLevel().setBlock(this, this.getDamage() == 0 ? get(FARMLAND) : get(DIRT), true); - - return true; + if (this.up() instanceof BlockAir) { + item.useOn(this); + this.getLevel().setBlock(this, this.getDamage() == 0 ? get(FARMLAND) : get(DIRT), true); + return true; + } + } else if (item.isShovel()) { + if (this.up() instanceof BlockAir) { + item.useOn(this); + this.getLevel().setBlock(this, get(GRASS_PATH)); + return true; + } } return false; diff --git a/src/main/java/cn/nukkit/block/BlockDispenser.java b/src/main/java/cn/nukkit/block/BlockDispenser.java index 3bd7bbd75f5..0d57b49a94e 100644 --- a/src/main/java/cn/nukkit/block/BlockDispenser.java +++ b/src/main/java/cn/nukkit/block/BlockDispenser.java @@ -1,11 +1,25 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityDispenser; +import cn.nukkit.dispenser.DispenseBehavior; +import cn.nukkit.dispenser.DispenseBehaviorRegister; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.Faceable; +import java.util.Map.Entry; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + /** * Created by CreeperFace on 15.4.2017. */ @@ -41,17 +55,13 @@ public Item toItem() { @Override public int getComparatorInputOverride() { - /*BlockEntity blockEntity = this.level.getBlockEntity(this); - - if(blockEntity instanceof BlockEntityDispenser) { - //return ContainerInventory.calculateRedstone(((BlockEntityDispenser) blockEntity).getInventory()); TODO: dispenser - }*/ + InventoryHolder blockEntity = this.getBlockEntity(); - return super.getComparatorInputOverride(); - } + if (blockEntity != null) { + return ContainerInventory.calculateRedstone(blockEntity.getInventory()); + } - public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage() & 7); + return 0; } public boolean isTriggered() { @@ -60,7 +70,7 @@ public boolean isTriggered() { public void setTriggered(boolean value) { int i = 0; - i |= getFacing().getIndex(); + i |= getBlockFace().getIndex(); if (value) { i |= 8; @@ -69,21 +79,190 @@ public void setTriggered(boolean value) { this.setDamage(i); } + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player == null) { + return false; + } + + InventoryHolder blockEntity = getBlockEntity(); + + if (blockEntity == null) { + return false; + } + + player.addWindow(blockEntity.getInventory()); + return true; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (player != null) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + double y = player.y + player.getEyeHeight(); + + if (y - this.y > 2) { + this.setDamage(BlockFace.UP.getIndex()); + } else if (this.y - y > 0) { + this.setDamage(BlockFace.DOWN.getIndex()); + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } + + this.getLevel().setBlock(block, this, true); + + createBlockEntity(); + return true; + } + + protected void createBlockEntity() { + new BlockEntityDispenser(this.level.getChunk(getChunkX(), getChunkZ()), + BlockEntity.getDefaultCompound(this, BlockEntity.DISPENSER)); + } + + protected InventoryHolder getBlockEntity() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDispenser)) { + return null; + } + + return (InventoryHolder) blockEntity; + } + + @Override + public int onUpdate(int type) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.setTriggered(false); + this.level.setBlock(this, this, false, false); + + dispense(); + return type; + } else if (type == Level.BLOCK_UPDATE_REDSTONE) { + Vector3 pos = this.add(0); + + boolean powered = level.isBlockPowered(pos) || level.isBlockPowered(pos.up()); + boolean triggered = isTriggered(); + + if (powered && !triggered) { + this.setTriggered(true); + this.level.setBlock(this, this, false, false); + level.scheduleUpdate(this, this, 4); + } + + return type; + } + + return 0; + } + + public void dispense() { + InventoryHolder blockEntity = getBlockEntity(); + + if (blockEntity == null) { + return; + } + + Random rand = ThreadLocalRandom.current(); + int r = 1; + int slot = -1; + Item target = null; + + Inventory inv = blockEntity.getInventory(); + for (Entry entry : inv.getContents().entrySet()) { + Item item = entry.getValue(); + + if (!item.isNull() && rand.nextInt(r++) == 0) { + target = item; + slot = entry.getKey(); + } + } + + LevelEventPacket pk = new LevelEventPacket(); + + BlockFace facing = getBlockFace(); + + pk.x = 0.5f + facing.getXOffset() * 0.7f; + pk.y = 0.5f + facing.getYOffset() * 0.7f; + pk.z = 0.5f + facing.getZOffset() * 0.7f; + + if (target == null) { + pk.evid = LevelEventPacket.EVENT_SOUND_CLICK_FAIL; + pk.data = 1200; + + this.level.addChunkPacket(getChunkX(), getChunkZ(), pk.clone()); + return; + } else { + pk.evid = LevelEventPacket.EVENT_SOUND_CLICK; + pk.data = 1000; + + //this.level.addChunkPacket(getChunkX(), getChunkZ(), pk.clone()); + } + + pk.evid = LevelEventPacket.EVENT_PARTICLE_SHOOT; + pk.data = 7; + this.level.addChunkPacket(getChunkX(), getChunkZ(), pk); + + Item origin = target; + target = target.clone(); + + DispenseBehavior behavior = getDispenseBehavior(target); + Item result = behavior.dispense(this, facing, target); + + + pk.evid = LevelEventPacket.EVENT_SOUND_CLICK; + + target.count--; + inv.setItem(slot, target); + + if (result != null) { + if (result.getId() != origin.getId() || result.getDamage() != origin.getDamage()) { + Item[] fit = inv.addItem(result); + + if (fit.length > 0) { + for (Item drop : fit) { + this.level.dropItem(this, drop); + } + } + } else { + inv.setItem(slot, result); + } + } + } + + protected DispenseBehavior getDispenseBehavior(Item item) { + return DispenseBehaviorRegister.getBehavior(item.getId()); + } + @Override public boolean canHarvestWithHand() { return false; } public Vector3 getDispensePosition() { - BlockFace facing = getFacing(); - double x = this.getX() + 0.7 * facing.getXOffset(); - double y = this.getY() + 0.7 * facing.getYOffset(); - double z = this.getZ() + 0.7 * facing.getZOffset(); - return new Vector3(x, y, z); + BlockFace facing = getBlockFace(); + return this.add( + 0.5 + 0.7 * facing.getXOffset(), + 0.5 + 0.7 * facing.getYOffset(), + 0.5 + 0.7 * facing.getZOffset() + ); } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromIndex(this.getDamage() & 0x07); } } diff --git a/src/main/java/cn/nukkit/block/BlockDoor.java b/src/main/java/cn/nukkit/block/BlockDoor.java index ace099a3bdf..6fbc266115c 100644 --- a/src/main/java/cn/nukkit/block/BlockDoor.java +++ b/src/main/java/cn/nukkit/block/BlockDoor.java @@ -210,6 +210,10 @@ public int onUpdate(int type) { } if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, isOpen() ? 15 : 0, isOpen() ? 0 : 15)); @@ -243,10 +247,12 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl this.getLevel().setBlock(block, this, true, false); //Bottom this.getLevel().setBlock(blockUp, Block.get(this.getId(), metaUp), true, true); //Top - if (!this.isOpen() && this.level.isBlockPowered(this.getLocation())) { - this.toggle(null); - metaUp |= DOOR_POWERED_BIT; - this.getLevel().setBlockDataAt(blockUp.getFloorX(), blockUp.getFloorY(), blockUp.getFloorZ(), metaUp); + if (this.level.getServer().isRedstoneEnabled()) { + if (!this.isOpen() && this.level.isBlockPowered(this.getLocation())) { + this.toggle(null); + metaUp |= DOOR_POWERED_BIT; + this.getLevel().setBlockDataAt(blockUp.getFloorX(), blockUp.getFloorY(), blockUp.getFloorZ(), metaUp); + } } return true; @@ -328,13 +334,23 @@ public boolean isTop(int meta) { public boolean isRightHinged() { if (isTop()) { - return (this.getDamage() & DOOR_HINGE_BIT ) >0; + return (this.getDamage() & DOOR_HINGE_BIT) > 0; } - return (this.up().getDamage() & DOOR_HINGE_BIT) >0; + return (this.up().getDamage() & DOOR_HINGE_BIT) > 0; } @Override public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlab.java b/src/main/java/cn/nukkit/block/BlockDoubleSlab.java index c23819a90cc..41a9c226f28 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlab.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlab.java @@ -75,11 +75,11 @@ public BlockColor getColor() { case BlockDoubleSlab.WOODEN: return BlockColor.WOOD_BLOCK_COLOR; default: - case BlockDoubleSlab.COBBLESTONE: - case BlockDoubleSlab.BRICK: - case BlockDoubleSlab.STONE_BRICK: - case BlockDoubleSlab.STONE: - return BlockColor.STONE_BLOCK_COLOR; + case BlockDoubleSlab.COBBLESTONE: + case BlockDoubleSlab.BRICK: + case BlockDoubleSlab.STONE_BRICK: + case BlockDoubleSlab.STONE: + return BlockColor.STONE_BLOCK_COLOR; case BlockDoubleSlab.SANDSTONE: return BlockColor.SAND_BLOCK_COLOR; case BlockDoubleSlab.QUARTZ: diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java index 6828f735a1e..8ce56d08646 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java @@ -82,11 +82,11 @@ public Item[] getDrops(Item item) { public BlockColor getColor() { switch (this.getDamage() & 0x07) { default: - case BlockDoubleSlabStone.STONE: - case BlockDoubleSlabStone.COBBLESTONE: - case BlockDoubleSlabStone.BRICK: - case BlockDoubleSlabStone.STONE_BRICK: - return BlockColor.STONE_BLOCK_COLOR; + case BlockDoubleSlabStone.STONE: + case BlockDoubleSlabStone.COBBLESTONE: + case BlockDoubleSlabStone.BRICK: + case BlockDoubleSlabStone.STONE_BRICK: + return BlockColor.STONE_BLOCK_COLOR; case BlockDoubleSlabStone.SANDSTONE: return BlockColor.SAND_BLOCK_COLOR; case BlockDoubleSlabStone.WOODEN: diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java index ab83b36c568..65e843c2604 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java @@ -67,10 +67,10 @@ public Item[] getDrops(Item item) { @Override public BlockColor getColor() { - switch(this.getDamage() & 0x07){ + switch (this.getDamage() & 0x07) { default: - case 0: //OAK - return BlockColor.WOOD_BLOCK_COLOR; + case 0: //OAK + return BlockColor.WOOD_BLOCK_COLOR; case 1: //SPRUCE return BlockColor.SPRUCE_BLOCK_COLOR; case 2: //BIRCH diff --git a/src/main/java/cn/nukkit/block/BlockDragonEgg.java b/src/main/java/cn/nukkit/block/BlockDragonEgg.java index a8cb7376ad0..96915058537 100644 --- a/src/main/java/cn/nukkit/block/BlockDragonEgg.java +++ b/src/main/java/cn/nukkit/block/BlockDragonEgg.java @@ -1,8 +1,10 @@ package cn.nukkit.block; +import cn.nukkit.event.block.BlockFromToEvent; import cn.nukkit.level.Level; import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; + import java.util.concurrent.ThreadLocalRandom; public class BlockDragonEgg extends BlockFallable { @@ -54,12 +56,18 @@ public int onUpdate(int type) { } public void teleport() { + ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < 1000; ++i) { - Block t = this.getLevel().getBlock(this.add(ThreadLocalRandom.current().nextInt(-16, 16), ThreadLocalRandom.current().nextInt(-16, 16), ThreadLocalRandom.current().nextInt(-16, 16))); - if (t.getId() == AIR) { - int diffX = this.getFloorX() - t.getFloorX(); - int diffY = this.getFloorY() - t.getFloorY(); - int diffZ = this.getFloorZ() - t.getFloorZ(); + Block to = this.getLevel().getBlock(this.add(random.nextInt(-16, 16), random.nextInt(-16, 16), random.nextInt(-16, 16))); + if (to.getId() == AIR) { + BlockFromToEvent event = new BlockFromToEvent(this, to); + this.level.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) return; + to = event.getTo(); + + int diffX = this.getFloorX() - to.getFloorX(); + int diffY = this.getFloorY() - to.getFloorY(); + int diffZ = this.getFloorZ() - to.getFloorZ(); LevelEventPacket pk = new LevelEventPacket(); pk.evid = LevelEventPacket.EVENT_PARTICLE_DRAGON_EGG_TELEPORT; pk.data = (((((Math.abs(diffX) << 16) | (Math.abs(diffY) << 8)) | Math.abs(diffZ)) | ((diffX < 0 ? 1 : 0) << 24)) | ((diffY < 0 ? 1 : 0) << 25)) | ((diffZ < 0 ? 1 : 0) << 26); @@ -68,9 +76,19 @@ public void teleport() { pk.z = this.getFloorZ(); this.getLevel().addChunkPacket(this.getFloorX() >> 4, this.getFloorZ() >> 4, pk); this.getLevel().setBlock(this, get(AIR), true); - this.getLevel().setBlock(t, this, true); + this.getLevel().setBlock(to, this, true); return; } } } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDropper.java b/src/main/java/cn/nukkit/block/BlockDropper.java new file mode 100644 index 00000000000..83665f74023 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDropper.java @@ -0,0 +1,56 @@ +package cn.nukkit.block; + +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityDropper; +import cn.nukkit.dispenser.DefaultDispenseBehavior; +import cn.nukkit.dispenser.DispenseBehavior; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; + +public class BlockDropper extends BlockDispenser { + + public BlockDropper() { + this(0); + } + + public BlockDropper(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dropper"; + } + + @Override + public int getId() { + return DROPPER; + } + + @Override + public void dispense() { + super.dispense(); + } + + @Override + protected void createBlockEntity() { + new BlockEntityDropper(this.level.getChunk(getChunkX(), getChunkZ()), + BlockEntity.getDefaultCompound(this, BlockEntity.DROPPER)); + } + + @Override + protected InventoryHolder getBlockEntity() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDropper)) { + return null; + } + + return (InventoryHolder) blockEntity; + } + + @Override + protected DispenseBehavior getDispenseBehavior(Item item) { + return new DefaultDispenseBehavior(); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockEndPortal.java b/src/main/java/cn/nukkit/block/BlockEndPortal.java index 8d2cf1ac1ee..685fbb14817 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortal.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortal.java @@ -59,11 +59,6 @@ public BlockColor getColor() { return BlockColor.BLACK_BLOCK_COLOR; } - @Override - public boolean canBePushed() { - return false; - } - @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java index 1180fd7c831..2dd387a94e2 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java @@ -9,6 +9,9 @@ import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; +import java.util.ArrayList; +import java.util.List; + /** * Created by Pub4Game on 26.12.2015. */ @@ -64,6 +67,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + public boolean hasComparatorInputOverride() { return true; } @@ -79,7 +87,7 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if((this.getDamage() & 0x04) == 0 && player != null && item.getId() == Item.ENDER_EYE) { + if ((this.getDamage() & 0x04) == 0 && player != null && item.getId() == Item.ENDER_EYE) { this.setDamage(this.getDamage() + 4); this.getLevel().setBlock(this, this, true, true); this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_END_PORTAL_FRAME_FILL); @@ -90,7 +98,7 @@ public boolean onActivate(Item item, Player player) { } public void createPortal() { - Vector3 centerSpot = this.searchCenter(); + Vector3 centerSpot = this.searchCenter(new ArrayList<>()); if(centerSpot != null) { for(int x = -2; x <= 2; x++) { for(int z = -2; z <= 2; z++) { @@ -116,15 +124,16 @@ public void createPortal() { } } - private Vector3 searchCenter() { + private Vector3 searchCenter(List visited) { for(int x = -2; x <= 2; x++) { if(x == 0) continue; Block block = this.getLevel().getBlock(this.add(x, 0, 0)); Block iBlock = this.getLevel().getBlock(this.add(x * 2, 0, 0)); - if(this.checkFrame(block)) { + if(this.checkFrame(block) && !visited.contains(block)) { + visited.add(block); if((x == -1 || x == 1) && this.checkFrame(iBlock)) - return ((BlockEndPortalFrame) block).searchCenter(); + return ((BlockEndPortalFrame) block).searchCenter(visited); for(int z = -4; z <= 4; z++) { if(z == 0) continue; @@ -140,9 +149,10 @@ private Vector3 searchCenter() { continue; Block block = this.getLevel().getBlock(this.add(0, 0, z)); Block iBlock = this.getLevel().getBlock(this.add(0, 0, z * 2)); - if(this.checkFrame(block)) { + if(this.checkFrame(block) && !visited.contains(block)) { + visited.add(block); if((z == -1 || z == 1) && this.checkFrame(iBlock)) - return ((BlockEndPortalFrame) block).searchCenter(); + return ((BlockEndPortalFrame) block).searchCenter(visited); for(int x = -4; x <= 4; x++) { if(x == 0) continue; diff --git a/src/main/java/cn/nukkit/block/BlockEndRod.java b/src/main/java/cn/nukkit/block/BlockEndRod.java index 2c4da5a0106..111e7f7dd6d 100644 --- a/src/main/java/cn/nukkit/block/BlockEndRod.java +++ b/src/main/java/cn/nukkit/block/BlockEndRod.java @@ -47,11 +47,6 @@ public int getLightLevel() { return 14; } - @Override - public boolean canBePushed() { - return true; - } - @Override public int getToolType() { return ItemTool.TYPE_PICKAXE; diff --git a/src/main/java/cn/nukkit/block/BlockEnderChest.java b/src/main/java/cn/nukkit/block/BlockEnderChest.java index a42d46c0d6f..26be034c942 100644 --- a/src/main/java/cn/nukkit/block/BlockEnderChest.java +++ b/src/main/java/cn/nukkit/block/BlockEnderChest.java @@ -178,6 +178,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockFarmland.java b/src/main/java/cn/nukkit/block/BlockFarmland.java index 2b4945d31d7..7301b15da03 100644 --- a/src/main/java/cn/nukkit/block/BlockFarmland.java +++ b/src/main/java/cn/nukkit/block/BlockFarmland.java @@ -48,7 +48,7 @@ public int getToolType() { @Override public double getMaxY() { - return this.y + 0.9375; + return this.y + 1; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockFence.java b/src/main/java/cn/nukkit/block/BlockFence.java index 3c6356511de..6865ace3e6e 100644 --- a/src/main/java/cn/nukkit/block/BlockFence.java +++ b/src/main/java/cn/nukkit/block/BlockFence.java @@ -99,7 +99,7 @@ public boolean canConnect(Block block) { @Override public BlockColor getColor() { - switch(this.getDamage() & 0x07){ + switch (this.getDamage() & 0x07) { default: case BlockFence.FENCE_OAK: //OAK return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockFenceGate.java b/src/main/java/cn/nukkit/block/BlockFenceGate.java index 15c18e7b44a..590218273da 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGate.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGate.java @@ -184,6 +184,10 @@ public boolean isOpen() { @Override public int onUpdate(int type) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if (type == Level.BLOCK_UPDATE_REDSTONE) { if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { this.toggle(null); diff --git a/src/main/java/cn/nukkit/block/BlockFlowable.java b/src/main/java/cn/nukkit/block/BlockFlowable.java index a0f2d7dd5b8..ea80adc1907 100644 --- a/src/main/java/cn/nukkit/block/BlockFlowable.java +++ b/src/main/java/cn/nukkit/block/BlockFlowable.java @@ -37,6 +37,16 @@ public boolean isSolid() { return false; } + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } + @Override protected AxisAlignedBB recalculateBoundingBox() { return null; diff --git a/src/main/java/cn/nukkit/block/BlockFlowerPot.java b/src/main/java/cn/nukkit/block/BlockFlowerPot.java index 08363acea48..1d8d7639957 100644 --- a/src/main/java/cn/nukkit/block/BlockFlowerPot.java +++ b/src/main/java/cn/nukkit/block/BlockFlowerPot.java @@ -32,7 +32,6 @@ protected static boolean canPlaceIntoFlowerPot(int id) { case RED_MUSHROOM: case BROWN_MUSHROOM: case CACTUS: - // TODO: 2016/2/4 case NETHER_WART: return true; default: return false; @@ -95,7 +94,25 @@ public boolean onActivate(Item item) { public boolean onActivate(Item item, Player player) { BlockEntity blockEntity = getLevel().getBlockEntity(this); if (!(blockEntity instanceof BlockEntityFlowerPot)) return false; - if (blockEntity.namedTag.getShort("item") != 0 || blockEntity.namedTag.getInt("mData") != 0) return false; + + if (blockEntity.namedTag.getShort("item") != AIR || blockEntity.namedTag.getInt("mData") != AIR) { + if (!canPlaceIntoFlowerPot(item.getId())) { + int id = blockEntity.namedTag.getShort("item"); + if (id == AIR) id = blockEntity.namedTag.getInt("mData"); + for (Item drop : player.getInventory().addItem(Item.get(id, blockEntity.namedTag.getInt("data")))) { + player.dropItem(drop); + } + + blockEntity.namedTag.putShort("item", AIR); + blockEntity.namedTag.putInt("data", 0); + this.setDamage(0); + this.level.setBlock(this, this, true); + ((BlockEntityFlowerPot) blockEntity).spawnToAll(); + return true; + } + return false; + } + int itemID; int itemMeta; if (!canPlaceIntoFlowerPot(item.getId())) { diff --git a/src/main/java/cn/nukkit/block/BlockGrassPath.java b/src/main/java/cn/nukkit/block/BlockGrassPath.java index 30a73cbf5bd..29d2442850c 100644 --- a/src/main/java/cn/nukkit/block/BlockGrassPath.java +++ b/src/main/java/cn/nukkit/block/BlockGrassPath.java @@ -31,7 +31,7 @@ public int getToolType() { @Override public double getMaxY() { - return this.y + 0.9375; + return this.y + 1; } @Override @@ -40,7 +40,9 @@ public double getResistance() { } @Override - public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } @Override public boolean canSilkTouch() { diff --git a/src/main/java/cn/nukkit/block/BlockHayBale.java b/src/main/java/cn/nukkit/block/BlockHayBale.java index 3f83d753ae5..a027f9a6907 100644 --- a/src/main/java/cn/nukkit/block/BlockHayBale.java +++ b/src/main/java/cn/nukkit/block/BlockHayBale.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -39,6 +40,11 @@ public double getResistance() { return 2.5; } + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + @Override public int getBurnChance() { return 60; diff --git a/src/main/java/cn/nukkit/block/BlockHopper.java b/src/main/java/cn/nukkit/block/BlockHopper.java index dc58c00cf10..1b27e268897 100644 --- a/src/main/java/cn/nukkit/block/BlockHopper.java +++ b/src/main/java/cn/nukkit/block/BlockHopper.java @@ -56,10 +56,12 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl this.setDamage(facing.getIndex()); - boolean powered = this.level.isBlockPowered(this.getLocation()); + if (this.level.getServer().isRedstoneEnabled()) { + boolean powered = this.level.isBlockPowered(this.getLocation()); - if (powered == this.isEnabled()) { - this.setEnabled(!powered); + if (powered == this.isEnabled()) { + this.setEnabled(!powered); + } } this.level.setBlock(this, this); @@ -122,12 +124,24 @@ public void setEnabled(boolean enabled) { @Override public int onUpdate(int type) { - if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + + if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) { boolean powered = this.level.isBlockPowered(this.getLocation()); if (powered == this.isEnabled()) { this.setEnabled(!powered); - this.level.setBlock(this, this, true, false); + this.level.setBlock(this, this, false, false); + + if (!powered) { + BlockEntity be = this.level.getBlockEntity(this); + + if (be != null) { + be.scheduleUpdate(); + } + } } return type; diff --git a/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java b/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java index 3ee1bb5d380..d4add63f196 100644 --- a/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java +++ b/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java @@ -61,5 +61,7 @@ public boolean canSilkTouch() { } @Override - public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } } diff --git a/src/main/java/cn/nukkit/block/BlockID.java b/src/main/java/cn/nukkit/block/BlockID.java index 9673b523c7d..2df1d9b9a04 100644 --- a/src/main/java/cn/nukkit/block/BlockID.java +++ b/src/main/java/cn/nukkit/block/BlockID.java @@ -302,7 +302,7 @@ public interface BlockID { int GLOWING_OBSIDIAN = 246; int NETHER_REACTOR = 247; //Should not be removed - int PISTON_EXTENSION = 250; + int MOVING_BLOCK = 250; int OBSERVER = 251; } diff --git a/src/main/java/cn/nukkit/block/BlockIce.java b/src/main/java/cn/nukkit/block/BlockIce.java index 50fe3aae032..fe039468082 100644 --- a/src/main/java/cn/nukkit/block/BlockIce.java +++ b/src/main/java/cn/nukkit/block/BlockIce.java @@ -3,6 +3,7 @@ import cn.nukkit.event.block.BlockFadeEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; import cn.nukkit.utils.BlockColor; @@ -57,15 +58,13 @@ public boolean onBreak(Item item) { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_RANDOM) { - if (this.getLevel().getDimension() != Level.DIMENSION_NETHER) { - if (this.getLevel().getBlockLightAt((int) this.x, (int) this.y, (int) this.z) >= 12) { - BlockFadeEvent event = new BlockFadeEvent(this, get(WATER)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); - } - return Level.BLOCK_UPDATE_NORMAL; + if (level.getBlockLightAt((int) this.x, (int) this.y, (int) this.z) >= 12) { + BlockFadeEvent event = new BlockFadeEvent(this, level.getDimension() == Level.DIMENSION_NETHER ? get(AIR) : get(WATER)); + level.getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + level.setBlock(this, event.getNewState(), true); } + return Level.BLOCK_UPDATE_RANDOM; } } return 0; @@ -73,6 +72,9 @@ public int onUpdate(int type) { @Override public Item[] getDrops(Item item) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[0]; } diff --git a/src/main/java/cn/nukkit/block/BlockItemFrame.java b/src/main/java/cn/nukkit/block/BlockItemFrame.java index 446188de73a..0203604fc7c 100644 --- a/src/main/java/cn/nukkit/block/BlockItemFrame.java +++ b/src/main/java/cn/nukkit/block/BlockItemFrame.java @@ -11,7 +11,7 @@ import cn.nukkit.nbt.tag.Tag; import cn.nukkit.network.protocol.LevelEventPacket; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; /** * Created by Pub4Game on 03.07.2016. @@ -114,8 +114,7 @@ public boolean onBreak(Item item) { public Item[] getDrops(Item item) { BlockEntity blockEntity = this.getLevel().getBlockEntity(this); BlockEntityItemFrame itemFrame = (BlockEntityItemFrame) blockEntity; - int chance = new Random().nextInt(100) + 1; - if (itemFrame != null && chance <= (itemFrame.getItemDropChance() * 100)) { + if (itemFrame != null && ThreadLocalRandom.current().nextFloat() <= itemFrame.getItemDropChance()) { return new Item[]{ toItem(), itemFrame.getItem().clone() }; @@ -171,4 +170,14 @@ public BlockFace getFacing() { public double getHardness() { return 0.25; } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockLadder.java b/src/main/java/cn/nukkit/block/BlockLadder.java index 014ac90b437..b0e2206e3f4 100644 --- a/src/main/java/cn/nukkit/block/BlockLadder.java +++ b/src/main/java/cn/nukkit/block/BlockLadder.java @@ -176,7 +176,7 @@ public BlockColor getColor() { @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.LADDER, 0, 1) + Item.get(Item.LADDER, 0, 1) }; } @@ -184,4 +184,14 @@ public Item[] getDrops(Item item) { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockLava.java b/src/main/java/cn/nukkit/block/BlockLava.java index 28c2f91d67b..1f551f4c048 100644 --- a/src/main/java/cn/nukkit/block/BlockLava.java +++ b/src/main/java/cn/nukkit/block/BlockLava.java @@ -153,6 +153,9 @@ public BlockLiquid getBlock(int meta) { @Override public int tickRate() { + if (this.level.getDimension() == Level.DIMENSION_NETHER) { + return 10; + } return 30; } diff --git a/src/main/java/cn/nukkit/block/BlockLeaves.java b/src/main/java/cn/nukkit/block/BlockLeaves.java index ab1dc1b1f86..24fbde2983d 100644 --- a/src/main/java/cn/nukkit/block/BlockLeaves.java +++ b/src/main/java/cn/nukkit/block/BlockLeaves.java @@ -45,7 +45,7 @@ public double getHardness() { @Override public int getToolType() { - return ItemTool.TYPE_SHEARS; + return ItemTool.TYPE_HOE; } @Override @@ -231,4 +231,14 @@ protected boolean canDropApple() { protected Item getSapling() { return Item.get(BlockID.SAPLING, this.getDamage() & 0x03); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockLever.java b/src/main/java/cn/nukkit/block/BlockLever.java index 98903e236de..cf86039a20f 100644 --- a/src/main/java/cn/nukkit/block/BlockLever.java +++ b/src/main/java/cn/nukkit/block/BlockLever.java @@ -5,6 +5,7 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; import cn.nukkit.math.BlockFace; import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; @@ -67,13 +68,21 @@ public boolean onActivate(Item item, Player player) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, isPowerOn() ? 15 : 0, isPowerOn() ? 0 : 15)); this.setDamage(this.getDamage() ^ 0x08); + boolean redstone = this.level.getServer().isRedstoneEnabled(); + this.getLevel().setBlock(this, this, false, true); this.getLevel().addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_BUTTON_CLICK, this.isPowerOn() ? 600 : 500); LeverOrientation orientation = LeverOrientation.byMetadata(this.isPowerOn() ? this.getDamage() ^ 0x08 : this.getDamage()); BlockFace face = orientation.getFacing(); - //this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(this.getLocation().getSide(face.getOpposite()), isPowerOn() ? face : null); + + if (redstone) { + Block target = this.getSide(face.getOpposite()); + target.onUpdate(Level.BLOCK_UPDATE_REDSTONE); + + this.level.updateAroundRedstone(this.getLocation(), isPowerOn() ? face.getOpposite() : null); + this.level.updateAroundRedstone(target.getLocation(), isPowerOn() ? face : null); + } return true; } diff --git a/src/main/java/cn/nukkit/block/BlockLiquid.java b/src/main/java/cn/nukkit/block/BlockLiquid.java index 7752283091f..2bf937a245f 100644 --- a/src/main/java/cn/nukkit/block/BlockLiquid.java +++ b/src/main/java/cn/nukkit/block/BlockLiquid.java @@ -280,7 +280,7 @@ protected void flowIntoBlock(Block block, int newFlowDecay) { level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { if (block.getId() > 0) { - this.level.useBreakOn(block); + this.level.useBreakOn(block, block.getId() == COBWEB ? Item.get(Item.WOODEN_SWORD) : null); } this.level.setBlock(block, getBlock(newFlowDecay), true, true); this.level.scheduleUpdate(block, this.tickRate()); @@ -447,4 +447,14 @@ protected boolean canFlowInto(Block block) { public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockMobSpawner.java b/src/main/java/cn/nukkit/block/BlockMobSpawner.java index 766b662146b..79ecb657346 100644 --- a/src/main/java/cn/nukkit/block/BlockMobSpawner.java +++ b/src/main/java/cn/nukkit/block/BlockMobSpawner.java @@ -46,6 +46,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockMoving.java b/src/main/java/cn/nukkit/block/BlockMoving.java new file mode 100644 index 00000000000..637e37dafec --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMoving.java @@ -0,0 +1,49 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockMoving extends BlockTransparent { + + public BlockMoving() { + this(0); + } + + public BlockMoving(int meta) { + super(); + } + + @Override + public String getName() { + return "MovingBlock"; + } + + @Override + public int getId() { + return BlockID.MOVING_BLOCK; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canBePulled() { + return false; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public boolean isSolid() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMushroom.java b/src/main/java/cn/nukkit/block/BlockMushroom.java index 0ab185efa3c..350229a9eef 100644 --- a/src/main/java/cn/nukkit/block/BlockMushroom.java +++ b/src/main/java/cn/nukkit/block/BlockMushroom.java @@ -1,8 +1,10 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.event.level.StructureGrowEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; +import cn.nukkit.level.ListChunkManager; import cn.nukkit.level.generator.object.mushroom.BigMushroom; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; @@ -70,7 +72,16 @@ public boolean grow() { BigMushroom generator = new BigMushroom(getType()); - if (generator.generate(this.level, new NukkitRandom(), this)) { + ListChunkManager chunkManager = new ListChunkManager(this.level); + if (generator.generate(chunkManager, new NukkitRandom(), this)) { + StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + for(Block block : ev.getBlockList()) { + this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); + } return true; } else { this.level.setBlock(this, this, true, false); diff --git a/src/main/java/cn/nukkit/block/BlockNetherPortal.java b/src/main/java/cn/nukkit/block/BlockNetherPortal.java index 861fede7118..708382a93be 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherPortal.java +++ b/src/main/java/cn/nukkit/block/BlockNetherPortal.java @@ -34,6 +34,11 @@ public int getId() { return NETHER_PORTAL; } + @Override + public boolean canBeFlowedInto() { + return false; + } + @Override public boolean canPassThrough() { return true; @@ -83,11 +88,6 @@ public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } - @Override - public boolean canBePushed() { - return false; - } - @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockObsidian.java b/src/main/java/cn/nukkit/block/BlockObsidian.java index 7ac74925ae3..bda2b6ec862 100644 --- a/src/main/java/cn/nukkit/block/BlockObsidian.java +++ b/src/main/java/cn/nukkit/block/BlockObsidian.java @@ -75,6 +75,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java b/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java index 8b57354b0e5..4565a349128 100644 --- a/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java +++ b/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java @@ -64,6 +64,11 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockPistonBase.java b/src/main/java/cn/nukkit/block/BlockPistonBase.java index 19c8a24b4ff..654e040f504 100644 --- a/src/main/java/cn/nukkit/block/BlockPistonBase.java +++ b/src/main/java/cn/nukkit/block/BlockPistonBase.java @@ -1,27 +1,34 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityMovingBlock; import cn.nukkit.blockentity.BlockEntityPistonArm; -import cn.nukkit.event.block.BlockPistonChangeEvent; +import cn.nukkit.event.block.BlockPistonEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockVector3; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.Faceable; +import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * @author CreeperFace */ public abstract class BlockPistonBase extends BlockSolidMeta implements Faceable { - public boolean sticky; + public boolean sticky = false; public BlockPistonBase() { this(0); @@ -43,7 +50,7 @@ public double getHardness() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + if (Math.abs(player.getFloorX() - this.x) <= 1 && Math.abs(player.getFloorZ() - this.z) <= 1) { double y = player.y + player.getEyeHeight(); if (y - this.y > 2) { @@ -56,19 +63,20 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } else { this.setDamage(player.getHorizontalFacing().getIndex()); } - this.level.setBlock(block, this, true, false); + this.level.setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag("") .putString("id", BlockEntity.PISTON_ARM) .putInt("x", (int) this.x) .putInt("y", (int) this.y) .putInt("z", (int) this.z) + .putInt("facing", this.getBlockFace().getIndex()) .putBoolean("Sticky", this.sticky); - BlockEntityPistonArm be = (BlockEntityPistonArm) BlockEntity.createBlockEntity(BlockEntity.PISTON_ARM, this.level.getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); + BlockEntityPistonArm piston = (BlockEntityPistonArm) BlockEntity.createBlockEntity(BlockEntity.PISTON_ARM, this.level.getChunk(getChunkX(), getChunkZ()), nbt); + piston.powered = isPowered(); - if (be == null) return false; - //this.checkState(); + this.checkState(piston.powered); return true; } @@ -76,32 +84,38 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl public boolean onBreak(Item item) { this.level.setBlock(this, Block.get(BlockID.AIR), true, true); - Block block = this.getSide(getFacing()); + Block block = this.getSide(getBlockFace()); - if (block instanceof BlockPistonHead && ((BlockPistonHead) block).getFacing() == this.getFacing()) { + if (block instanceof BlockPistonHead && ((BlockPistonHead) block).getBlockFace() == this.getBlockFace()) { block.onBreak(item); } return true; } public boolean isExtended() { - BlockFace face = getFacing(); + BlockFace face = getBlockFace(); Block block = getSide(face); - return block instanceof BlockPistonHead && ((BlockPistonHead) block).getFacing() == face; + + return block instanceof BlockPistonHead && ((BlockPistonHead) block).getBlockFace() == face; } @Override public int onUpdate(int type) { - if (type != 6 && type != 1) { + if (type != Level.BLOCK_UPDATE_NORMAL && type != Level.BLOCK_UPDATE_REDSTONE && type != Level.BLOCK_UPDATE_SCHEDULED) { return 0; } else { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + BlockEntity blockEntity = this.level.getBlockEntity(this); if (blockEntity instanceof BlockEntityPistonArm) { BlockEntityPistonArm arm = (BlockEntityPistonArm) blockEntity; boolean powered = this.isPowered(); - if (arm.powered != powered) { - this.level.getServer().getPluginManager().callEvent(new BlockPistonChangeEvent(this, powered ? 0 : 15, powered ? 15 : 0)); - arm.powered = !arm.powered; + + if (arm.state % 2 == 0 && arm.powered != powered && checkState(powered)) { + arm.powered = powered; + if (arm.chunk != null) { arm.chunk.setChanged(); } @@ -112,257 +126,319 @@ public int onUpdate(int type) { } } - private void checkState() { - BlockFace facing = getFacing(); - boolean isPowered = this.isPowered(); + private boolean checkState(Boolean isPowered) { + if (!this.level.getServer().isRedstoneEnabled()) { + return false; + } - if (isPowered && !isExtended()) { - if ((new BlocksCalculator(this.level, this, facing, true)).canMove()) { - if (!this.doMove(true)) { - return; - } + if (isPowered == null) { + isPowered = this.isPowered(); + } - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_PISTON_OUT); - } else { + if (isPowered && !isExtended()) { + if (!this.doMove(true)) { + return false; } - } else if (!isPowered && isExtended()) { - //this.level.setBlock() TODO: set piston extension? - if (this.sticky) { - Vector3 pos = this.add(facing.getXOffset() * 2, facing.getYOffset() * 2, facing.getZOffset() * 2); - Block block = this.level.getBlock(pos); - - if (block.getId() == AIR) { - this.level.setBlock(this.getLocation().getSide(facing), Block.get(BlockID.AIR), true, true); - } - if (canPush(block, facing.getOpposite(), false) && (!(block instanceof BlockFlowable) || block.getId() == PISTON || block.getId() == STICKY_PISTON)) { - this.doMove(false); - } - } else { - this.level.setBlock(getLocation().getSide(facing), Block.get(BlockID.AIR), true, false); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_PISTON_OUT); + return true; + } else if (!isPowered && isExtended()) { + if (!this.doMove(false)) { + return false; } this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_PISTON_IN); + return true; } - } - public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage()).getOpposite(); + return false; } private boolean isPowered() { - BlockFace face = getFacing(); + BlockFace face = getBlockFace(); for (BlockFace side : BlockFace.values()) { - if (side != face && this.level.isSidePowered(this.getLocation().getSide(side), side)) { - return true; + if (side == face) { + continue; } - } - if (this.level.isSidePowered(this, BlockFace.DOWN)) { - return true; - } else { - Vector3 pos = this.getLocation().up(); + Block b = this.getSide(side); - for (BlockFace side : BlockFace.values()) { - if (side != BlockFace.DOWN && this.level.isSidePowered(pos.getSide(side), side)) { - return true; - } + if (b.getId() == Block.REDSTONE_WIRE && b.getDamage() > 0) { + return true; } - return false; + if (this.level.isSidePowered(b, side)) { + return true; + } } + + return false; } private boolean doMove(boolean extending) { - Vector3 pos = this.getLocation(); - BlockFace direction = getFacing(); + BlockFace direction = getBlockFace(); + BlocksCalculator calculator = new BlocksCalculator(extending); + + boolean canMove = calculator.canMove(); - if (!extending) { - this.level.setBlock(pos.getSide(direction), Block.get(BlockID.AIR), true, false); + if (!canMove && extending) { + return false; } - BlocksCalculator calculator = new BlocksCalculator(this.level, this, direction, extending); + List attached = Collections.emptyList(); - if (!calculator.canMove()) { - return false; - } else { - List blocks = calculator.getBlocksToMove(); + BlockPistonEvent event = new BlockPistonEvent(this, direction, calculator.getBlocksToMove(), calculator.getBlocksToDestroy(), extending); + this.level.getServer().getPluginManager().callEvent(event); - List newBlocks = new ArrayList<>(blocks); + if (event.isCancelled()) { + return false; + } + if (canMove && (this.sticky || extending)) { List destroyBlocks = calculator.getBlocksToDestroy(); - BlockFace side = extending ? direction : direction.getOpposite(); - for (int i = destroyBlocks.size() - 1; i >= 0; --i) { Block block = destroyBlocks.get(i); - this.level.useBreakOn(block); + this.level.useBreakOn(block, null, null, false); } - for (int i = blocks.size() - 1; i >= 0; --i) { - Block block = blocks.get(i); - this.level.setBlock(block, Block.get(BlockID.AIR)); - Vector3 newPos = block.getLocation().getSide(side); + List newBlocks = calculator.getBlocksToMove(); + attached = newBlocks.stream().map(Vector3::asBlockVector3).collect(Collectors.toList()); + if(!isExtended())Collections.reverse(newBlocks); + if(!isExtended())Collections.reverse(attached); - //TODO: change this to block entity - this.level.setBlock(newPos, newBlocks.get(i)); - } + BlockFace side = extending ? direction : direction.getOpposite(); - Vector3 pistonHead = pos.getSide(direction); + for (Block newBlock : newBlocks) { + Vector3 oldPos = newBlock.add(0); + newBlock.position(newBlock.add(0).getSide(side)); - if (extending) { - //extension block entity + BlockEntity blockEntity = this.level.getBlockEntity(oldPos); + + this.level.setBlock(newBlock, Block.get(newBlock.getId()), true); + + CompoundTag nbt = BlockEntity.getDefaultCompound(newBlock, BlockEntity.MOVING_BLOCK) + .putInt("pistonPosX", this.getFloorX()) + .putInt("pistonPosY", this.getFloorY()) + .putInt("pistonPosZ", this.getFloorZ()) + .putCompound("movingBlock", new CompoundTag() + .putInt("id", newBlock.getId()) //only for nukkit purpose + .putInt("meta", newBlock.getDamage()) //only for nukkit purpose + .putShort("val", newBlock.getDamage()) + .putString("name", GlobalBlockPalette.getName(newBlock.getId())) + ); + + if (blockEntity != null && !(blockEntity instanceof BlockEntityMovingBlock)) { + blockEntity.saveNBT(); + + CompoundTag t = new CompoundTag(blockEntity.namedTag.getTags()); + + nbt.putCompound("movingEntity", t); + blockEntity.close(); + } - this.level.setBlock(pistonHead, Block.get(BlockID.PISTON_HEAD, this.getDamage())); + new BlockEntityMovingBlock(this.level.getChunk(newBlock.getChunkX(), newBlock.getChunkZ()), nbt); + + if (sticky) { + if(!(getLevel().getBlock(oldPos).getSide(side.getOpposite()).getLocation().equals(getLocation()))) { + this.level.setBlock(oldPos, Block.get(0)); + this.level.setBlock(oldPos.getSide(side), newBlock); + } + } } + } - return true; + if (extending) { + this.level.setBlock(this.getSide(direction), new BlockPistonHead(this.getDamage())); } + + BlockEntityPistonArm blockEntity = (BlockEntityPistonArm) this.level.getBlockEntity(this); + blockEntity.move(extending, attached); + return true; } - public static boolean canPush(Block block, BlockFace face, boolean destroyBlocks) { - if (block.canBePushed() && block.getY() >= 0 && (face != BlockFace.DOWN || block.getY() != 0) && - block.getY() <= 255 && (face != BlockFace.UP || block.getY() != 255)) { - if (!(block instanceof BlockPistonBase)) { + public static boolean canPush(Block block, BlockFace face, boolean destroyBlocks, boolean extending) { + if ( + block.getY() >= 0 && (face != BlockFace.DOWN || block.getY() != 0) && + block.getY() <= 255 && (face != BlockFace.UP || block.getY() != 255) + ) { + if (extending && !block.canBePushed() || !extending && !block.canBePulled()) { + return false; + } - if (block instanceof BlockFlowable) { - return destroyBlocks; - } - } else return !((BlockPistonBase) block).isExtended(); - return true; + if (block.breaksWhenMoved()) { + return destroyBlocks || block.sticksToPiston(); + } + + BlockEntity be = block.level.getBlockEntity(block); + return be == null || be.isMovable(); } - return false; + return false; } - public static class BlocksCalculator { + public class BlocksCalculator { - private final Level level; private final Vector3 pistonPos; + private Vector3 armPos; private final Block blockToMove; private final BlockFace moveDirection; + private final boolean extending; private final List toMove = new ArrayList<>(); private final List toDestroy = new ArrayList<>(); - public BlocksCalculator(Level level, Block pos, BlockFace facing, boolean extending) { - this.level = level; - this.pistonPos = pos.getLocation(); + public BlocksCalculator(boolean extending) { + this.pistonPos = getLocation(); + this.extending = extending; + + BlockFace face = getBlockFace(); + if (!extending) { + this.armPos = pistonPos.getSide(face); + } if (extending) { - this.moveDirection = facing; - this.blockToMove = pos.getSide(facing); + this.moveDirection = face; + this.blockToMove = getSide(face); } else { - this.moveDirection = facing.getOpposite(); - this.blockToMove = pos.getSide(facing, 2); + this.moveDirection = face.getOpposite(); + if (sticky) { + this.blockToMove = getSide(face, 2); + } else { + this.blockToMove = null; + } } } public boolean canMove() { + if (!sticky && !extending) { + return true; + } + this.toMove.clear(); this.toDestroy.clear(); Block block = this.blockToMove; - if (!canPush(block, this.moveDirection, false)) { - if (block instanceof BlockFlowable) { - this.toDestroy.add(this.blockToMove); - return true; - } else { - return false; - } - } else if (!this.addBlockLine(this.blockToMove)) { + if (!canPush(block, this.moveDirection, true, extending)) { return false; - } else { - for (Block b : this.toMove) { - if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { - return false; - } + } + + if (block.breaksWhenMoved()) { + if (extending || block.sticksToPiston()) { + this.toDestroy.add(this.blockToMove); } return true; } + + if (!this.addBlockLine(this.blockToMove, this.moveDirection)) { + return false; + } + + for (int i = 0; i < this.toMove.size(); ++i) { + Block b = this.toMove.get(i); + + if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { + return false; + } + } + + return true; } - private boolean addBlockLine(Block origin) { + private boolean addBlockLine(Block origin, BlockFace from) { Block block = origin.clone(); if (block.getId() == AIR) { return true; - } else if (!canPush(origin, this.moveDirection, false)) { + } + + if (!canPush(origin, this.moveDirection, false, extending)) { return true; - } else if (origin.equals(this.pistonPos)) { + } + + if (origin.equals(this.pistonPos)) { return true; - } else if (this.toMove.contains(origin)) { + } + + if (this.toMove.contains(origin)) { return true; - } else { - int count = 1; + } - if (count + this.toMove.size() > 12) { - return false; - } else { - while (block.getId() == SLIME_BLOCK) { - block = origin.getSide(this.moveDirection.getOpposite(), count); + if (this.toMove.size() >= 12) { + return false; + } - if (block.getId() == AIR || !canPush(block, this.moveDirection, false) || block.equals(this.pistonPos)) { - break; - } + this.toMove.add(block); - ++count; + int count = 1; + List sticked = new ArrayList<>(); - if (count + this.toMove.size() > 12) { - return false; - } - } + while (block.getId() == SLIME_BLOCK) { + block = origin.getSide(this.moveDirection.getOpposite(), count); - int blockCount = 0; + if (block.getId() == AIR || !canPush(block, this.moveDirection, false, extending) || block.equals(this.pistonPos)) { + break; + } - for (int step = count - 1; step >= 0; --step) { - this.toMove.add(block.getSide(this.moveDirection.getOpposite(), step)); - ++blockCount; - } + if (block.breaksWhenMoved() && block.sticksToPiston()) { + this.toDestroy.add(block); + break; + } - int steps = 1; + if (++count + this.toMove.size() > 12) { + return false; + } + + sticked.add(block); + } - while (true) { - Block nextBlock = block.getSide(this.moveDirection, steps); - int index = this.toMove.indexOf(nextBlock); + int stickedCount = sticked.size(); - if (index > -1) { - this.reorderListAtCollision(blockCount, index); + if (stickedCount > 0) { + this.toMove.addAll(Lists.reverse(sticked)); + } - for (int l = 0; l <= index + blockCount; ++l) { - Block b = this.toMove.get(l); + int step = 1; - if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { - return false; - } - } + while (true) { + Block nextBlock = origin.getSide(this.moveDirection, step); + int index = this.toMove.indexOf(nextBlock); - return true; - } + if (index > -1) { + this.reorderListAtCollision(stickedCount, index); - if (nextBlock.getId() == AIR) { - return true; - } + for (int i = 0; i <= index + stickedCount; ++i) { + Block b = this.toMove.get(i); - if (!canPush(nextBlock, this.moveDirection, true) || nextBlock.equals(this.pistonPos)) { + if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { return false; } + } - if (nextBlock instanceof BlockFlowable) { - this.toDestroy.add(nextBlock); - return true; - } + return true; + } - if (this.toMove.size() >= 12) { - return false; - } + if (nextBlock.getId() == AIR || nextBlock.equals(armPos)) { + return true; + } - this.toMove.add(nextBlock); - ++blockCount; - ++steps; - } + if (!canPush(nextBlock, this.moveDirection, true, extending) || nextBlock.equals(this.pistonPos)) { + return false; } + + if (nextBlock.breaksWhenMoved()) { + this.toDestroy.add(nextBlock); + return true; + } + + if (this.toMove.size() >= 12) { + return false; + } + + this.toMove.add(nextBlock); + ++stickedCount; + ++step; } } @@ -378,7 +454,7 @@ private void reorderListAtCollision(int count, int index) { private boolean addBranchingBlocks(Block block) { for (BlockFace face : BlockFace.values()) { - if (face.getAxis() != this.moveDirection.getAxis() && !this.addBlockLine(block.getSide(face))) { + if (face.getAxis() != this.moveDirection.getAxis() && !this.addBlockLine(block.getSide(face), face)) { return false; } } @@ -402,6 +478,8 @@ public Item toItem() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + BlockFace face = BlockFace.fromIndex(this.getDamage()); + + return face.getHorizontalIndex() >= 0 ? face.getOpposite() : face; } } diff --git a/src/main/java/cn/nukkit/block/BlockPistonHead.java b/src/main/java/cn/nukkit/block/BlockPistonHead.java index fb99b423307..37ae8dca3e4 100644 --- a/src/main/java/cn/nukkit/block/BlockPistonHead.java +++ b/src/main/java/cn/nukkit/block/BlockPistonHead.java @@ -3,11 +3,12 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; /** * @author CreeperFace */ -public class BlockPistonHead extends BlockTransparentMeta { +public class BlockPistonHead extends BlockTransparentMeta implements Faceable { public BlockPistonHead() { this(0); @@ -45,16 +46,19 @@ public Item[] getDrops(Item item) { @Override public boolean onBreak(Item item) { this.level.setBlock(this, Block.get(BlockID.AIR), true, true); - Block piston = getSide(getFacing().getOpposite()); + Block piston = getSide(getBlockFace().getOpposite()); - if (piston instanceof BlockPistonBase && ((BlockPistonBase) piston).getFacing() == this.getFacing()) { + if (piston instanceof BlockPistonBase && ((BlockPistonBase) piston).getBlockFace() == this.getBlockFace()) { piston.onBreak(item); } return true; } - public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage()).getOpposite(); + @Override + public BlockFace getBlockFace() { + BlockFace face = BlockFace.fromIndex(this.getDamage()); + + return face.getHorizontalIndex() >= 0 ? face.getOpposite() : face; } @Override @@ -62,6 +66,16 @@ public boolean canBePushed() { return false; } + @Override + public boolean canBePulled() { + return false; + } + + @Override + public boolean isSolid() { + return false; + } + @Override public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); diff --git a/src/main/java/cn/nukkit/block/BlockPlanks.java b/src/main/java/cn/nukkit/block/BlockPlanks.java index 409ae264815..0e05fbf3e19 100644 --- a/src/main/java/cn/nukkit/block/BlockPlanks.java +++ b/src/main/java/cn/nukkit/block/BlockPlanks.java @@ -70,7 +70,7 @@ public int getToolType() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { default: case OAK: return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java b/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java index 2cbdb4ecaa7..43b19bbbc1d 100644 --- a/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java @@ -116,6 +116,10 @@ protected AxisAlignedBB recalculateCollisionBoundingBox() { @Override public void onEntityCollide(Entity entity) { + if (!this.level.getServer().isRedstoneEnabled()) { + return; + } + int power = getRedstonePower(); if (power == 0) { diff --git a/src/main/java/cn/nukkit/block/BlockPrismarine.java b/src/main/java/cn/nukkit/block/BlockPrismarine.java index 6073aeffa0c..f81d71dbf92 100644 --- a/src/main/java/cn/nukkit/block/BlockPrismarine.java +++ b/src/main/java/cn/nukkit/block/BlockPrismarine.java @@ -68,7 +68,7 @@ public boolean canHarvestWithHand() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { case NORMAL: return BlockColor.CYAN_BLOCK_COLOR; case DARK: diff --git a/src/main/java/cn/nukkit/block/BlockPumpkin.java b/src/main/java/cn/nukkit/block/BlockPumpkin.java index 82aabab6b0b..92588e15e8f 100644 --- a/src/main/java/cn/nukkit/block/BlockPumpkin.java +++ b/src/main/java/cn/nukkit/block/BlockPumpkin.java @@ -64,7 +64,12 @@ public BlockColor getColor() { } @Override - public boolean canBePushed() { + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { return false; } diff --git a/src/main/java/cn/nukkit/block/BlockRail.java b/src/main/java/cn/nukkit/block/BlockRail.java index b2aa2baa35a..bdde591d0a2 100644 --- a/src/main/java/cn/nukkit/block/BlockRail.java +++ b/src/main/java/cn/nukkit/block/BlockRail.java @@ -72,7 +72,8 @@ public int getToolType() { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { Optional ascendingDirection = this.getOrientation().ascendingDirection(); - if (this.down().isTransparent() || (ascendingDirection.isPresent() && this.getSide(ascendingDirection.get()).isTransparent())) { + Block down = this.down(); + if ((down.isTransparent() && down.getId() != HOPPER_BLOCK) || (ascendingDirection.isPresent() && this.getSide(ascendingDirection.get()).isTransparent())) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; } @@ -99,7 +100,7 @@ public BlockColor getColor() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { Block down = this.down(); - if (down == null || down.isTransparent()) { + if (down == null || (down.isTransparent() && down.getId() != HOPPER_BLOCK)) { return false; } Map railsAround = this.checkRailsAroundAffected(); @@ -272,4 +273,14 @@ public Item[] getDrops(Item item) { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); } + + @Override + public boolean canBePushed() { + return true; + } + + @Override + public boolean canBePulled() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockRailPowered.java b/src/main/java/cn/nukkit/block/BlockRailPowered.java index 83a88072314..272a732d4d3 100644 --- a/src/main/java/cn/nukkit/block/BlockRailPowered.java +++ b/src/main/java/cn/nukkit/block/BlockRailPowered.java @@ -44,6 +44,10 @@ public int onUpdate(int type) { if (super.onUpdate(type) == Level.BLOCK_UPDATE_NORMAL) { return 0; // Already broken } + + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } boolean wasPowered = isActive(); boolean isPowered = level.isBlockPowered(this.getLocation()) || checkSurrounding(this, true, 0) @@ -70,7 +74,7 @@ public int onUpdate(int type) { * @param power The count of the rail that had been counted * @return Boolean of the surrounding area. Where the powered rail on! */ - protected boolean checkSurrounding(Vector3 pos, boolean relative, int power) { + private boolean checkSurrounding(Vector3 pos, boolean relative, int power) { // The powered rail can power up to 8 blocks only if (power >= 8) { return false; diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java b/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java index 11d5711c0cd..4955c25a6d1 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java @@ -70,7 +70,6 @@ public void updateState() { this.level.scheduleUpdate(this, this, 2, 0); }*/ - //System.out.println("schedule update 0"); this.level.scheduleUpdate(this, this, 2); } } @@ -138,6 +137,10 @@ public int onUpdate(int type) { } private void onChange() { + if (!this.level.getServer().isRedstoneEnabled()) { + return; + } + int output = this.calculateOutput(); BlockEntity blockEntity = this.level.getBlockEntity(this); int currentOutput = 0; diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java b/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java index 668d3853bfb..eebd87c8005 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java @@ -4,7 +4,9 @@ import cn.nukkit.event.redstone.RedstoneUpdateEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; +import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -29,8 +31,10 @@ public boolean onBreak(Item item) { Vector3 pos = getLocation(); this.level.setBlock(this, Block.get(BlockID.AIR), true, true); - for (BlockFace face : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(face), null); + if (this.level.getServer().isRedstoneEnabled()) { + for (BlockFace face : BlockFace.values()) { + this.level.updateAroundRedstone(pos.getSide(face), null); + } } return true; } @@ -44,8 +48,10 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl this.setDamage(player != null ? player.getDirection().getOpposite().getHorizontalIndex() : 0); this.level.setBlock(block, this, true, true); - if (shouldBePowered()) { - this.level.scheduleUpdate(this, 1); + if (this.level.getServer().isRedstoneEnabled()) { + if (shouldBePowered()) { + this.level.scheduleUpdate(this, 1); + } } return true; } @@ -53,6 +59,10 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_SCHEDULED) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if (!this.isLocked()) { Vector3 pos = getLocation(); boolean shouldBePowered = this.shouldBePowered(); @@ -66,22 +76,22 @@ public int onUpdate(int type) { this.level.updateAroundRedstone(this.getLocation().getSide(getFacing().getOpposite()), null); if (!shouldBePowered) { -// System.out.println("schedule update 2"); level.scheduleUpdate(getPowered(), this, this.getDelay()); } } } } else if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) { - // Redstone event - RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); - getLevel().getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return 0; - } if (type == Level.BLOCK_UPDATE_NORMAL && this.getSide(BlockFace.DOWN).isTransparent()) { this.level.useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; - } else { + } else if (this.level.getServer().isRedstoneEnabled()) { + // Redstone event + RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); + getLevel().getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return 0; + } + this.updateState(); return Level.BLOCK_UPDATE_NORMAL; } @@ -200,6 +210,11 @@ public boolean isFacingTowardsRepeater() { return block instanceof BlockRedstoneDiode && ((BlockRedstoneDiode) block).getFacing() != side; } + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return new SimpleAxisAlignedBB(this.x, this.y, this.z, this.x + 1, this.y + 0.125, this.z + 1); + } + @Override public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java b/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java index d812b27b55a..f27192ad11a 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java @@ -55,6 +55,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } // Redstone event RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); getLevel().getServer().getPluginManager().callEvent(ev); diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java b/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java index 876e847bb82..010afcc54ca 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java @@ -35,6 +35,10 @@ public Item toItem() { @Override public int onUpdate(int type) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && !this.level.isBlockPowered(this.getLocation())) { // Redstone event RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java b/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java index 4619dd35f48..cf8feba388b 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java @@ -43,20 +43,22 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } -// if (!checkState()) { -// BlockFace facing = getFacing().getOpposite(); -// Vector3 pos = getLocation(); -// -// for (BlockFace side : BlockFace.values()) { -// if (facing == side) { -// continue; -// } -// -// this.level.updateAround(pos.getSide(side)); -// } -// } - - checkState(); + if (this.level.getServer().isRedstoneEnabled()) { + if (!checkState()) { + BlockFace facing = getBlockFace().getOpposite(); + Vector3 pos = getLocation(); + + for (BlockFace side : BlockFace.values()) { + if (facing == side) { + continue; + } + + this.level.updateAroundRedstone(pos.getSide(side), null); + } + } + + checkState(); + } return true; } @@ -79,12 +81,14 @@ public boolean onBreak(Item item) { BlockFace face = getBlockFace().getOpposite(); - for (BlockFace side : BlockFace.values()) { - if (side == face) { - continue; - } + if (this.level.getServer().isRedstoneEnabled()) { + for (BlockFace side : BlockFace.values()) { + if (side == face) { + continue; + } - this.level.updateAroundRedstone(pos.getSide(side), null); + this.level.updateAroundRedstone(pos.getSide(side), null); + } } return true; } @@ -92,6 +96,10 @@ public boolean onBreak(Item item) { @Override public int onUpdate(int type) { if (super.onUpdate(type) == 0) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) { this.level.scheduleUpdate(this, tickRate()); } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { @@ -111,7 +119,7 @@ public int onUpdate(int type) { return 0; } - protected boolean checkState() { + private boolean checkState() { if (isPoweredFromSide()) { BlockFace face = getBlockFace().getOpposite(); Vector3 pos = getLocation(); diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java b/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java index eb0c46d6de4..faaaa6d12a2 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java @@ -53,6 +53,10 @@ public Item toItem() { @Override public int onUpdate(int type) { if (super.onUpdate(type) == 0) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) { this.level.scheduleUpdate(this, tickRate()); } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { @@ -71,7 +75,7 @@ public int onUpdate(int type) { return 0; } - protected boolean checkState() { + private boolean checkState() { BlockFace face = getBlockFace().getOpposite(); Vector3 pos = getLocation(); diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java index ae22f200c3d..544047ff546 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java @@ -220,7 +220,7 @@ public int onUpdate(int type) { public boolean canBePlacedOn(Vector3 v) { Block b = this.level.getBlock(v); - return b.isSolid() && !b.isTransparent() && b.getId() != Block.GLOWSTONE; + return (b.isSolid() && !b.isTransparent() && b.getId() != Block.GLOWSTONE) || b.getId() == HOPPER_BLOCK; } public int getStrongPower(BlockFace side) { @@ -326,4 +326,4 @@ private int getStrongPower(Vector3 pos, BlockFace direction) { return block.getStrongPower(direction); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockSapling.java b/src/main/java/cn/nukkit/block/BlockSapling.java index f6335b4366d..71c8bab42ee 100644 --- a/src/main/java/cn/nukkit/block/BlockSapling.java +++ b/src/main/java/cn/nukkit/block/BlockSapling.java @@ -1,16 +1,22 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.event.level.StructureGrowEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; +import cn.nukkit.level.ListChunkManager; import cn.nukkit.level.generator.object.BasicGenerator; import cn.nukkit.level.generator.object.tree.*; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector2; import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** @@ -117,41 +123,31 @@ private void grow() { BasicGenerator generator = null; boolean bigTree = false; - int x = 0; - int z = 0; + Vector3 vector3 = new Vector3(); switch (this.getDamage() & 0x07) { case JUNGLE: - loop: - for (; x >= -1; --x) { - for (; z >= -1; --z) { - if (this.findSaplings(x, z, JUNGLE)) { - generator = new ObjectJungleBigTree(10, 20, Block.get(BlockID.WOOD, BlockWood.JUNGLE), Block.get(BlockID.LEAVES, BlockLeaves.JUNGLE)); - bigTree = true; - break loop; - } - } + Vector2 vector2; + if ((vector2 = this.findSaplings(JUNGLE)) != null) { + vector3 = this.add(vector2.getFloorX(), 0, vector2.getFloorY()); + generator = new ObjectJungleBigTree(10, 20, Block.get(BlockID.WOOD, BlockWood.JUNGLE), Block.get(BlockID.LEAVES, BlockLeaves.JUNGLE)); + bigTree = true; } if (!bigTree) { - x = 0; - z = 0; generator = new NewJungleTree(4, 7); + vector3 = this.add(0,0,0); } break; case ACACIA: generator = new ObjectSavannaTree(); + vector3 = this.add(0,0,0); break; case DARK_OAK: - loop: - for (; x >= -1; --x) { - for (; z >= -1; --z) { - if (this.findSaplings(x, z, DARK_OAK)) { - generator = new ObjectDarkOakTree(); - bigTree = true; - break loop; - } - } + if ((vector2 = this.findSaplings(DARK_OAK)) != null) { + vector3 = this.add(vector2.getFloorX(), 0, vector2.getFloorY()); + generator = new ObjectDarkOakTree(); + bigTree = true; } if (!bigTree) { @@ -160,33 +156,73 @@ private void grow() { break; //TODO: big spruce default: - ObjectTree.growTree(this.level, this.getFloorX(), this.getFloorY(), this.getFloorZ(), new NukkitRandom(), this.getDamage() & 0x07); + ListChunkManager chunkManager = new ListChunkManager(this.level); + ObjectTree.growTree(chunkManager, this.getFloorX(), this.getFloorY(), this.getFloorZ(), new NukkitRandom(), this.getDamage() & 0x07); + StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } + for(Block block : ev.getBlockList()) { + this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); + } return; } if (bigTree) { - this.level.setBlock(this.add(x, 0, z), get(AIR), true, false); - this.level.setBlock(this.add(x + 1, 0, z), get(AIR), true, false); - this.level.setBlock(this.add(x, 0, z + 1), get(AIR), true, false); - this.level.setBlock(this.add(x + 1, 0, z + 1), get(AIR), true, false); + this.level.setBlock(vector3, get(AIR), true, false); + this.level.setBlock(vector3.add(1, 0, 0), get(AIR), true, false); + this.level.setBlock(vector3.add(0, 0, 1), get(AIR), true, false); + this.level.setBlock(vector3.add(1, 0, 1), get(AIR), true, false); } else { this.level.setBlock(this, get(AIR), true, false); } - if (!generator.generate(this.level, new NukkitRandom(), this.add(x, 0, z))) { + ListChunkManager chunkManager = new ListChunkManager(this.level); + boolean success = generator.generate(chunkManager, new NukkitRandom(), vector3); + StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled() || !success) { if (bigTree) { - this.level.setBlock(this.add(x, 0, z), this, true, false); - this.level.setBlock(this.add(x + 1, 0, z), this, true, false); - this.level.setBlock(this.add(x, 0, z + 1), this, true, false); - this.level.setBlock(this.add(x + 1, 0, z + 1), this, true, false); + this.level.setBlock(vector3, this, true, false); + this.level.setBlock(vector3.add(1, 0, 0), this, true, false); + this.level.setBlock(vector3.add(0, 0, 1), this, true, false); + this.level.setBlock(vector3.add(1, 0, 1), this, true, false); } else { this.level.setBlock(this, this, true, false); } + return; + } + for(Block block : ev.getBlockList()) { + this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); } } - private boolean findSaplings(int x, int z, int type) { - return this.isSameType(this.add(x, 0, z), type) && this.isSameType(this.add(x + 1, 0, z), type) && this.isSameType(this.add(x, 0, z + 1), type) && this.isSameType(this.add(x + 1, 0, z + 1), type); + private Vector2 findSaplings(int type) { + List> validVectorsList = new ArrayList<>(); + validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1))); + validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, -1), new Vector2(-1, -1))); + validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, -1), new Vector2(1, -1))); + validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, 1), new Vector2(-1, 1))); + for(List validVectors : validVectorsList) { + boolean correct = true; + for(Vector2 vector2 : validVectors) { + if(!this.isSameType(this.add(vector2.x, 0, vector2.y), type)) + correct = false; + } + if(correct) { + int lowestX = 0; + int lowestZ = 0; + for(Vector2 vector2 : validVectors) { + if(vector2.getFloorX() < lowestX) + lowestX = vector2.getFloorX(); + if(vector2.getFloorY() < lowestZ) + lowestZ = vector2.getFloorY(); + } + return new Vector2(lowestX, lowestZ); + } + } + return null; } public boolean isSameType(Vector3 pos, int type) { diff --git a/src/main/java/cn/nukkit/block/BlockSignPost.java b/src/main/java/cn/nukkit/block/BlockSignPost.java index deb236fb5d7..49b6339fb7d 100644 --- a/src/main/java/cn/nukkit/block/BlockSignPost.java +++ b/src/main/java/cn/nukkit/block/BlockSignPost.java @@ -127,4 +127,9 @@ public BlockColor getColor() { public BlockFace getBlockFace() { return BlockFace.fromIndex(this.getDamage() & 0x07); } + + @Override + public boolean breaksWhenMoved() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSkull.java b/src/main/java/cn/nukkit/block/BlockSkull.java index f43892fbd2b..7f0b72f30dd 100644 --- a/src/main/java/cn/nukkit/block/BlockSkull.java +++ b/src/main/java/cn/nukkit/block/BlockSkull.java @@ -124,4 +124,13 @@ public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockSlabWood.java b/src/main/java/cn/nukkit/block/BlockSlabWood.java index 6f9b968e7be..b0fc413c824 100644 --- a/src/main/java/cn/nukkit/block/BlockSlabWood.java +++ b/src/main/java/cn/nukkit/block/BlockSlabWood.java @@ -68,7 +68,7 @@ public Item toItem() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { default: case 0: //OAK return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockSnow.java b/src/main/java/cn/nukkit/block/BlockSnow.java index 2124e6e8c9e..2ca14c92264 100644 --- a/src/main/java/cn/nukkit/block/BlockSnow.java +++ b/src/main/java/cn/nukkit/block/BlockSnow.java @@ -1,5 +1,6 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemSnowball; import cn.nukkit.item.ItemTool; @@ -12,7 +13,7 @@ public BlockSnow() { @Override public String getName() { - return "Snow Block"; + return "Snow"; } @Override @@ -61,4 +62,19 @@ public boolean canHarvestWithHand() { public boolean canSilkTouch() { return true; } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.isShovel()) { + item.useOn(this); + this.level.useBreakOn(this, item.clone().clearNamedTag(), null, true); + return true; + } + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSnowLayer.java b/src/main/java/cn/nukkit/block/BlockSnowLayer.java index 7cda0ad1a8e..31e589e8d5c 100644 --- a/src/main/java/cn/nukkit/block/BlockSnowLayer.java +++ b/src/main/java/cn/nukkit/block/BlockSnowLayer.java @@ -5,8 +5,8 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemSnowball; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.GameRule; import cn.nukkit.level.Level; -import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; @@ -26,6 +26,11 @@ public BlockSnowLayer(int meta) { this.meta = meta; } + @Override + public final int getFullId() { + return (this.getId() << 4) + this.getDamage(); + } + @Override public final int getDamage() { return this.meta; @@ -38,7 +43,7 @@ public final void setDamage(int meta) { @Override public String getName() { - return "Snow Layer"; + return "Top Snow"; } @Override @@ -63,13 +68,12 @@ public int getToolType() { @Override public boolean canBeReplaced() { - return true; + return (this.getDamage() & 0x7) != 0x7; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - Block down = this.down(); - if (down.isSolid()) { + if (this.canSurvive()) { this.getLevel().setBlock(block, this, true); return true; @@ -77,12 +81,27 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } + private boolean canSurvive() { + Block below = this.down(); + return below.getId() != ICE && below.getId() != PACKED_ICE && below.getId() != ICE_FROSTED && (below.isSolid() || (this.getDamage() & 0x7) == 0x7); + } + @Override public int onUpdate(int type) { - super.onUpdate(type); - if (type == Level.BLOCK_UPDATE_RANDOM) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if ((this.getDamage() & 0x7) != 0x7 || this.up().getId() != SNOW_LAYER) { + super.onUpdate(type); + } + + if (this.level.getBlockIdAt(this.getFloorX(), this.getFloorY(), this.getFloorZ()) == SNOW_LAYER && !this.canSurvive()) { + this.level.useBreakOn(this, null, null, true); + if (this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { + this.level.dropItem(this, this.toItem()); + } + } + } else if (type == Level.BLOCK_UPDATE_RANDOM) { if (this.getLevel().getBlockLightAt((int) this.x, (int) this.y, (int) this.z) >= 10) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); + BlockFadeEvent event = new BlockFadeEvent(this, (this.getDamage() & 0x7) > 0 ? get(SNOW_LAYER, this.getDamage() - 1) : get(AIR)); level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { level.setBlock(this, event.getNewState(), true); @@ -101,9 +120,10 @@ public Item toItem() { @Override public Item[] getDrops(Item item) { if (item.isShovel() && item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{ - this.toItem() - }; + Item drop = this.toItem(); + int height = this.getDamage() & 0x7; + drop.setCount(height < 3 ? 1 : height < 5 ? 2 : height == 7 ? 4 : 3); + return new Item[]{drop}; } else { return new Item[0]; } @@ -121,7 +141,7 @@ public boolean canHarvestWithHand() { @Override public boolean isTransparent() { - return true; + return (this.getDamage() & 0x7) != 0x7; } @Override @@ -130,19 +150,40 @@ public boolean canBeFlowedInto() { } @Override - public boolean canPassThrough() { - return true; + public boolean isSolid() { + return (this.getDamage() & 0x7) == 0x7; } @Override - public boolean isSolid() { - return false; + public double getMaxY() { + int height = this.getDamage() & 0x7; + return height < 3 ? this.y : height == 7 ? this.y + 1 : this.y + 0.5; } @Override - protected AxisAlignedBB recalculateBoundingBox() { - return null; + public boolean canBeActivated() { + return true; } -} + @Override + public boolean onActivate(Item item, Player player) { + if (item.isShovel()) { + item.useOn(this); + this.level.useBreakOn(this, item.clone().clearNamedTag(), null, true); + return true; + } else if (item.getId() == SNOW_LAYER) { + if ((this.getDamage() & 0x7) != 0x7) { + this.setDamage(this.getDamage() + 1); + this.level.setBlock(this ,this, true); + if (player != null && (player.gamemode & 0x1) == 0) { + item.count--; + } + return true; + } else { + this.level.setBlock(this ,this, true); + } + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSponge.java b/src/main/java/cn/nukkit/block/BlockSponge.java index 7552ee93031..db77752a515 100644 --- a/src/main/java/cn/nukkit/block/BlockSponge.java +++ b/src/main/java/cn/nukkit/block/BlockSponge.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.Level; import cn.nukkit.level.particle.SmokeParticle; @@ -11,6 +12,7 @@ import java.util.ArrayDeque; import java.util.Queue; +import java.util.concurrent.ThreadLocalRandom; /** * author: Angelic47 @@ -48,6 +50,11 @@ public double getResistance() { return 3; } + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + @Override public String getName() { return NAMES[this.getDamage() & 0b1]; @@ -60,34 +67,32 @@ public BlockColor getColor() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - Level level = block.getLevel(); - boolean blockSet = level.setBlock(block, this); - - if (blockSet) { - if (this.getDamage() == WET && level.getDimension() == Level.DIMENSION_NETHER) { - level.setBlock(block, Block.get(BlockID.SPONGE, DRY)); - this.getLevel().addLevelEvent(block.add(0.5, 0.875, 0.5), LevelEventPacket.EVENT_SOUND_EXPLODE); - - for (int i = 0; i < 8; ++i) { - this.getLevel().addParticle( - //TODO: Use correct smoke particle - new SmokeParticle(block.getLocation().add(Math.random(), 1, Math.random()))); - } - } else if (this.getDamage() == DRY && performWaterAbsorb(block)) { - level.setBlock(block, Block.get(BlockID.SPONGE, WET)); - - for (int i = 0; i < 4; i++) { - LevelEventPacket packet = new LevelEventPacket(); - packet.evid = 2001; - packet.x = (float) block.getX(); - packet.y = (float) block.getY(); - packet.z = (float) block.getZ(); - packet.data = GlobalBlockPalette.getOrCreateRuntimeId(BlockID.WATER, 0); - level.addChunkPacket(getChunkX(), getChunkZ(), packet); - } + if (this.getDamage() == WET && level.getDimension() == Level.DIMENSION_NETHER) { + level.setBlock(block, Block.get(BlockID.SPONGE, DRY), true, true); + this.getLevel().addLevelEvent(block.add(0.5, 0.875, 0.5), LevelEventPacket.EVENT_SOUND_EXPLODE); + + for (int i = 0; i < 8; ++i) { + level.addParticle(new SmokeParticle(block.getLocation().add(ThreadLocalRandom.current().nextDouble(), 1, ThreadLocalRandom.current().nextDouble()))); + } + + return true; + } else if (this.getDamage() == DRY && block instanceof BlockWater && performWaterAbsorb(block)) { + level.setBlock(block, Block.get(BlockID.SPONGE, WET), true, true); + + for (int i = 0; i < 4; i++) { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_DESTROY; + packet.x = (float) block.getX() + 0.5f; + packet.y = (float) block.getY() + 1f; + packet.z = (float) block.getZ() + 0.5f; + packet.data = GlobalBlockPalette.getOrCreateRuntimeId(BlockID.WATER, 0); + level.addChunkPacket(getChunkX(), getChunkZ(), packet); } + + return true; } - return blockSet; + + return super.place(item, block, target, face, fx, fy, fz, player); } private boolean performWaterAbsorb(Block block) { diff --git a/src/main/java/cn/nukkit/block/BlockSugarcane.java b/src/main/java/cn/nukkit/block/BlockSugarcane.java index 4948a709b43..4eaf9e1f903 100644 --- a/src/main/java/cn/nukkit/block/BlockSugarcane.java +++ b/src/main/java/cn/nukkit/block/BlockSugarcane.java @@ -134,7 +134,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl if (down.getId() == SUGARCANE_BLOCK) { this.getLevel().setBlock(block, Block.get(BlockID.SUGARCANE_BLOCK), true); return true; - } else if (down.getId() == GRASS || down.getId() == DIRT || down.getId() == SAND) { + } else if (down.getId() == GRASS || down.getId() == DIRT || down.getId() == SAND || down.getId() == PODZOL) { Block block0 = down.north(); Block block1 = down.south(); Block block2 = down.west(); diff --git a/src/main/java/cn/nukkit/block/BlockTNT.java b/src/main/java/cn/nukkit/block/BlockTNT.java index 1b16648af4e..05d99d8522a 100644 --- a/src/main/java/cn/nukkit/block/BlockTNT.java +++ b/src/main/java/cn/nukkit/block/BlockTNT.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; import cn.nukkit.math.NukkitRandom; import cn.nukkit.nbt.tag.CompoundTag; @@ -93,6 +94,10 @@ public void prime(int fuse, Entity source) { @Override public int onUpdate(int type) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && this.level.isBlockPowered(this.getLocation())) { this.prime(); } @@ -106,9 +111,12 @@ public boolean onActivate(Item item, Player player) { item.useOn(this); this.prime(80, player); return true; - } - if (item.getId() == Item.FIRE_CHARGE) { - if (!player.isCreative()) player.getInventory().removeItem(Item.get(Item.FIRE_CHARGE, 0, 1)); + } else if (item.getId() == Item.FIRE_CHARGE) { + if (!player.isCreative()) item.count--; + this.prime(80, player); + return true; + } else if (item.hasEnchantment(Enchantment.ID_FIRE_ASPECT)) { + item.useOn(this); this.prime(80, player); return true; } diff --git a/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java b/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java index 3e7f4ec62b1..2c7f72b0bad 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java +++ b/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java @@ -25,5 +25,7 @@ public String getName() { return "Lime Glazed Terracotta"; } - public DyeColor getDyeColor() { return DyeColor.LIME; } + public DyeColor getDyeColor() { + return DyeColor.LIME; + } } diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoor.java b/src/main/java/cn/nukkit/block/BlockTrapdoor.java index 14a6a92f101..19428112876 100644 --- a/src/main/java/cn/nukkit/block/BlockTrapdoor.java +++ b/src/main/java/cn/nukkit/block/BlockTrapdoor.java @@ -166,7 +166,7 @@ public double getMaxZ() { @Override public int onUpdate(int type) { - if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (type == Level.BLOCK_UPDATE_REDSTONE && this.level.getServer().isRedstoneEnabled()) { if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, isOpen() ? 15 : 0, isOpen() ? 0 : 15)); this.setDamage(this.getDamage() ^ 0x04); diff --git a/src/main/java/cn/nukkit/block/BlockTripWire.java b/src/main/java/cn/nukkit/block/BlockTripWire.java index 8d63ebf0564..1de92651c40 100644 --- a/src/main/java/cn/nukkit/block/BlockTripWire.java +++ b/src/main/java/cn/nukkit/block/BlockTripWire.java @@ -11,7 +11,7 @@ /** * @author CreeperFace */ -public class BlockTripWire extends BlockFlowable { +public class BlockTripWire extends BlockTransparentMeta { public BlockTripWire(int meta) { super(meta); @@ -88,6 +88,10 @@ public void setDisarmed(boolean value) { @Override public void onEntityCollide(Entity entity) { + if (!this.level.getServer().isRedstoneEnabled()) { + return; + } + if (!entity.doesTriggerPressurePlate()) { return; } @@ -103,7 +107,11 @@ public void onEntityCollide(Entity entity) { } } - public void updateHook(boolean scheduleUpdate) { + private void updateHook(boolean scheduleUpdate) { + if (!this.level.getServer().isRedstoneEnabled()) { + return; + } + for (BlockFace side : new BlockFace[]{BlockFace.SOUTH, BlockFace.WEST}) { for (int i = 1; i < 42; ++i) { Block block = this.getSide(side, i); @@ -130,6 +138,10 @@ public void updateHook(boolean scheduleUpdate) { @Override public int onUpdate(int type) { + if (!this.level.getServer().isRedstoneEnabled()) { + return 0; + } + if (type == Level.BLOCK_UPDATE_SCHEDULED) { if (!isPowered()) { return type; diff --git a/src/main/java/cn/nukkit/block/BlockTripWireHook.java b/src/main/java/cn/nukkit/block/BlockTripWireHook.java index c286e1be24c..6af9d689bca 100644 --- a/src/main/java/cn/nukkit/block/BlockTripWireHook.java +++ b/src/main/java/cn/nukkit/block/BlockTripWireHook.java @@ -12,7 +12,7 @@ /** * @author CreeperFace */ -public class BlockTripWireHook extends BlockFlowable { +public class BlockTripWireHook extends BlockTransparentMeta { public BlockTripWireHook() { this(0); @@ -89,6 +89,10 @@ public boolean onBreak(Item item) { } public void calculateState(boolean onBreak, boolean updateAround, int pos, Block block) { + if (!this.level.getServer().isRedstoneEnabled()) { + return; + } + BlockFace facing = getFacing(); Vector3 v = this.getLocation(); boolean attached = isAttached(); diff --git a/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java b/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java index 15f33862f1d..c2d87bf451b 100644 --- a/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java +++ b/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java @@ -8,6 +8,8 @@ import cn.nukkit.Player; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityShulkerBox; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.inventory.ShulkerBoxInventory; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; @@ -157,8 +159,34 @@ public boolean onActivate(Item item, Player player) { return true; } + @Override + public boolean hasComparatorInputOverride() { + return true; + } + + @Override + public int getComparatorInputOverride() { + BlockEntity be = this.getLevel().getBlockEntity(this); + + if (!(be instanceof InventoryHolder)) { + return 0; + } + + return ContainerInventory.calculateRedstone(((InventoryHolder) be).getInventory()); + } + @Override public BlockColor getColor() { return BlockColor.PURPLE_BLOCK_COLOR; } + + @Override + public boolean breaksWhenMoved() { + return true; + } + + @Override + public boolean sticksToPiston() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockVine.java b/src/main/java/cn/nukkit/block/BlockVine.java index b6e26f15751..bc097954203 100644 --- a/src/main/java/cn/nukkit/block/BlockVine.java +++ b/src/main/java/cn/nukkit/block/BlockVine.java @@ -2,6 +2,8 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.event.block.BlockSpreadEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; @@ -11,6 +13,11 @@ import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.utils.BlockColor; +import java.util.EnumSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + /** * Created by Pub4Game on 15.01.2016. */ @@ -131,7 +138,7 @@ protected AxisAlignedBB recalculateBoundingBox() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (target.isSolid() && face.getHorizontalIndex() != -1) { + if (block.getId() != VINE && target.isSolid() && face.getHorizontalIndex() != -1) { this.setDamage(getMetaFromFace(face.getOpposite())); this.getLevel().setBlock(block, this, true, true); return true; @@ -159,33 +166,166 @@ public Item toItem() { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { - if (!this.getSide(getFace()).isSolid()) { - Block up = this.up(); - if (up.getId() != this.getId() || up.getDamage() != this.getDamage()) { - this.getLevel().useBreakOn(this, null, null, true); - return Level.BLOCK_UPDATE_NORMAL; + Block up = this.up(); + Set upFaces = up instanceof BlockVine ? ((BlockVine) up).getFaces() : null; + Set faces = this.getFaces(); + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + if (!this.getSide(face).isSolid() && (upFaces == null || !upFaces.contains(face))) { + faces.remove(face); + } + } + if (faces.isEmpty() && !up.isSolid()) { + this.getLevel().useBreakOn(this, null, null, true); + return Level.BLOCK_UPDATE_NORMAL; + } + int meta = getMetaFromFaces(faces); + if (meta != this.getDamage()) { + this.level.setBlock(this, Block.get(VINE, meta), true); + return Level.BLOCK_UPDATE_NORMAL; + } + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + Random random = ThreadLocalRandom.current(); + if (random.nextInt(4) == 0) { + BlockFace face = BlockFace.random(random); + Block block = this.getSide(face); + int faceMeta = getMetaFromFace(face); + int meta = this.getDamage(); + + if (this.y < 255 && face == BlockFace.UP && block.getId() == AIR) { + if (this.canSpread()) { + for (BlockFace horizontalFace : BlockFace.Plane.HORIZONTAL) { + if (random.nextBoolean() || !this.getSide(horizontalFace).getSide(face).isSolid()) { + meta &= ~getMetaFromFace(horizontalFace); + } + } + putVineOnHorizontalFace(block, meta, this); + } + } else if (face.getHorizontalIndex() != -1 && (meta & faceMeta) != faceMeta) { + if (this.canSpread()) { + if (block.getId() == AIR) { + BlockFace cwFace = face.rotateY(); + BlockFace ccwFace = face.rotateYCCW(); + Block cwBlock = block.getSide(cwFace); + Block ccwBlock = block.getSide(ccwFace); + int cwMeta = getMetaFromFace(cwFace); + int ccwMeta = getMetaFromFace(ccwFace); + boolean onCw = (meta & cwMeta) == cwMeta; + boolean onCcw = (meta & ccwMeta) == ccwMeta; + + if (onCw && cwBlock.isSolid()) { + putVine(block, getMetaFromFace(cwFace), this); + } else if (onCcw && ccwBlock.isSolid()) { + putVine(block, getMetaFromFace(ccwFace), this); + } else if (onCw && cwBlock.getId() == AIR && this.getSide(cwFace).isSolid()) { + putVine(cwBlock, getMetaFromFace(face.getOpposite()), this); + } else if (onCcw && ccwBlock.getId() == AIR && this.getSide(ccwFace).isSolid()) { + putVine(ccwBlock, getMetaFromFace(face.getOpposite()), this); + } else if (block.up().isSolid()) { + putVine(block, 0, this); + } + } else if (!block.isTransparent()) { + meta |= getMetaFromFace(face); + putVine(this, meta, null); + } + } + } else if (this.y > 0) { + Block below = this.down(); + int id = below.getId(); + if (id == AIR || id == VINE) { + for (BlockFace horizontalFace : BlockFace.Plane.HORIZONTAL) { + if (random.nextBoolean()) { + meta &= ~getMetaFromFace(horizontalFace); + } + } + putVineOnHorizontalFace(below, below.getDamage() | meta, id == AIR ? this : null); + } } + return Level.BLOCK_UPDATE_RANDOM; } } return 0; } - private BlockFace getFace() { + private boolean canSpread() { + int blockX = this.getFloorX(); + int blockY = this.getFloorY(); + int blockZ = this.getFloorZ(); + + int count = 0; + for (int x = blockX - 4; x <= blockX + 4; x++) { + for (int z = blockZ - 4; z <= blockZ + 4; z++) { + for (int y = blockY - 1; y <= blockY + 1; y++) { + if (this.level.getBlock(x, y, z).getId() == VINE) { + if (++count >= 5) return false; + } + } + } + } + return true; + } + + private void putVine(Block block, int meta, Block source) { + if (block.getId() == VINE && block.getDamage() == meta) return; + Block vine = get(VINE, meta); + BlockGrowEvent event; + if (source != null) { + event = new BlockSpreadEvent(block, source, vine); + } else { + event = new BlockGrowEvent(block, vine); + } + this.level.getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.level.setBlock(block, vine, true); + } + } + + private void putVineOnHorizontalFace(Block block, int meta, Block source) { + if (block.getId() == VINE && block.getDamage() == meta) return; + boolean isOnHorizontalFace = false; + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + int faceMeta = getMetaFromFace(face); + if ((meta & faceMeta) == faceMeta) { + isOnHorizontalFace = true; + break; + } + } + if (isOnHorizontalFace) { + putVine(block, meta, source); + } + } + + private Set getFaces() { + Set faces = EnumSet.noneOf(BlockFace.class); + int meta = this.getDamage(); if ((meta & 1) > 0) { - return BlockFace.SOUTH; - } else if ((meta & 2) > 0) { - return BlockFace.WEST; - } else if ((meta & 4) > 0) { - return BlockFace.NORTH; - } else if ((meta & 8) > 0) { - return BlockFace.EAST; + faces.add(BlockFace.SOUTH); + } + if ((meta & 2) > 0) { + faces.add(BlockFace.WEST); + } + if ((meta & 4) > 0) { + faces.add(BlockFace.NORTH); + } + if ((meta & 8) > 0) { + faces.add(BlockFace.EAST); } - return BlockFace.SOUTH; + return faces; } - private int getMetaFromFace(BlockFace face) { + private static int getMetaFromFaces(Set faces) { + int meta = 0; + + for (BlockFace face : faces) { + meta |= getMetaFromFace(face); + + } + + return meta; + } + + private static int getMetaFromFace(BlockFace face) { switch (face) { case SOUTH: default: @@ -201,11 +341,16 @@ private int getMetaFromFace(BlockFace face) { @Override public int getToolType() { - return ItemTool.TYPE_SHEARS; + return ItemTool.TYPE_AXE; } @Override public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } + + @Override + public boolean canSilkTouch() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockWood.java b/src/main/java/cn/nukkit/block/BlockWood.java index c9aebf02a9f..68ebf3f5612 100644 --- a/src/main/java/cn/nukkit/block/BlockWood.java +++ b/src/main/java/cn/nukkit/block/BlockWood.java @@ -92,7 +92,7 @@ public int getToolType() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { default: case OAK: return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockWood2.java b/src/main/java/cn/nukkit/block/BlockWood2.java index 1de3f2c7eaf..dcafbeca362 100644 --- a/src/main/java/cn/nukkit/block/BlockWood2.java +++ b/src/main/java/cn/nukkit/block/BlockWood2.java @@ -37,7 +37,7 @@ public String getName() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { case ACACIA: return BlockColor.ORANGE_BLOCK_COLOR; case DARK_OAK: diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntity.java b/src/main/java/cn/nukkit/blockentity/BlockEntity.java index cbc97c453e7..6178640767e 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntity.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntity.java @@ -2,6 +2,7 @@ import cn.nukkit.Server; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; import cn.nukkit.level.Position; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.Vector3; @@ -42,6 +43,8 @@ public abstract class BlockEntity extends Position { public static final String JUKEBOX = "Jukebox"; public static final String SHULKER_BOX = "ShulkerBox"; public static final String BANNER = "Banner"; + public static final String DISPENSER = "Dispenser"; + public static final String DROPPER = "Dropper"; public static long count = 1; @@ -52,7 +55,7 @@ public abstract class BlockEntity extends Position { public String name; public long id; - public boolean movable = true; + public boolean movable; public boolean closed = false; public CompoundTag namedTag; @@ -76,7 +79,13 @@ public BlockEntity(FullChunk chunk, CompoundTag nbt) { this.x = this.namedTag.getInt("x"); this.y = this.namedTag.getInt("y"); this.z = this.namedTag.getInt("z"); - this.movable = this.namedTag.getBoolean("isMovable"); + + if (namedTag.contains("isMovable")) { + this.movable = this.namedTag.getBoolean("isMovable"); + } else { + this.movable = true; + namedTag.putBoolean("isMovable", true); + } this.initBlockEntity(); @@ -199,6 +208,10 @@ public void onBreak() { public void setDirty() { chunk.setChanged(); + + if (this.getLevelBlock().getId() != BlockID.AIR) { + this.level.updateComparatorOutputLevel(this); + } } public String getName() { @@ -210,7 +223,7 @@ public boolean isMovable() { } public static CompoundTag getDefaultCompound(Vector3 pos, String id) { - return new CompoundTag("") + return new CompoundTag() .putString("id", id) .putInt("x", pos.getFloorX()) .putInt("y", pos.getFloorY()) diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java index c2dfefffc4d..f84df308af3 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java @@ -112,9 +112,9 @@ public boolean onUpdate() { //If secondary is selected as the primary too, apply 2 amplification if (getSecondaryPower() == getPrimaryPower()) { - e.setAmplifier(2); - } else { e.setAmplifier(1); + } else { + e.setAmplifier(0); } //Hide particles diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java b/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java new file mode 100644 index 00000000000..0f19bec7ab1 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java @@ -0,0 +1,142 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.BlockAir; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.DispenserInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +public class BlockEntityDispenser extends BlockEntitySpawnable implements BlockEntityContainer, BlockEntityNameable, InventoryHolder { + + protected DispenserInventory inventory; + + public BlockEntityDispenser(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + protected void initBlockEntity() { + this.inventory = new DispenserInventory(this); + + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + + for (int i = 0; i < this.getSize(); i++) { + this.inventory.setItem(i, this.getItem(i)); + } + + super.initBlockEntity(); + } + + @Override + public int getSize() { + return 9; + } + + protected int getSlotIndex(int index) { + ListTag list = this.namedTag.getList("Items", CompoundTag.class); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getByte("Slot") == index) { + return i; + } + } + + return -1; + } + + @Override + public Item getItem(int index) { + int i = this.getSlotIndex(index); + if (i < 0) { + return new ItemBlock(new BlockAir(), 0, 0); + } else { + CompoundTag data = (CompoundTag) this.namedTag.getList("Items").get(i); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + int i = this.getSlotIndex(index); + + CompoundTag d = NBTIO.putItemHelper(item, index); + + if (item.getId() == Item.AIR || item.getCount() <= 0) { + if (i >= 0) { + this.namedTag.getList("Items").getAll().remove(i); + } + } else if (i < 0) { + (this.namedTag.getList("Items", CompoundTag.class)).add(d); + } else { + (this.namedTag.getList("Items", CompoundTag.class)).add(i, d); + } + } + + @Override + public DispenserInventory getInventory() { + return inventory; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.DISPENSER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } + + @Override + public void saveNBT() { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } + + super.saveNBT(); + } + + @Override + public boolean isBlockEntityValid() { + return this.getLevelBlock().getId() == BlockID.DISPENSER; + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Dispenser"; + } + + @Override + public boolean hasName() { + return this.namedTag.contains("CustomName"); + } + + @Override + public void setName(String name) { + if (name == null || name.equals("")) { + this.namedTag.remove("CustomName"); + return; + } + + this.namedTag.putString("CustomName", name); + } + + @Override + public void onBreak() { + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java b/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java new file mode 100644 index 00000000000..5cae1660f38 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java @@ -0,0 +1,142 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.BlockAir; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.DropperInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +public class BlockEntityDropper extends BlockEntitySpawnable implements BlockEntityContainer, InventoryHolder, BlockEntityNameable { + + protected DropperInventory inventory; + + public BlockEntityDropper(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + protected void initBlockEntity() { + this.inventory = new DropperInventory(this); + + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + + for (int i = 0; i < this.getSize(); i++) { + this.inventory.setItem(i, this.getItem(i)); + } + + super.initBlockEntity(); + } + + @Override + public int getSize() { + return 9; + } + + protected int getSlotIndex(int index) { + ListTag list = this.namedTag.getList("Items", CompoundTag.class); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getByte("Slot") == index) { + return i; + } + } + + return -1; + } + + @Override + public Item getItem(int index) { + int i = this.getSlotIndex(index); + if (i < 0) { + return new ItemBlock(new BlockAir(), 0, 0); + } else { + CompoundTag data = (CompoundTag) this.namedTag.getList("Items").get(i); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + int i = this.getSlotIndex(index); + + CompoundTag d = NBTIO.putItemHelper(item, index); + + if (item.getId() == Item.AIR || item.getCount() <= 0) { + if (i >= 0) { + this.namedTag.getList("Items").getAll().remove(i); + } + } else if (i < 0) { + (this.namedTag.getList("Items", CompoundTag.class)).add(d); + } else { + (this.namedTag.getList("Items", CompoundTag.class)).add(i, d); + } + } + + @Override + public DropperInventory getInventory() { + return inventory; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.DROPPER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } + + @Override + public void saveNBT() { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } + + super.saveNBT(); + } + + @Override + public boolean isBlockEntityValid() { + return this.getLevelBlock().getId() == BlockID.DROPPER; + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Dropper"; + } + + @Override + public boolean hasName() { + return this.namedTag.contains("CustomName"); + } + + @Override + public void setName(String name) { + if (name == null || name.isEmpty()) { + this.namedTag.remove("CustomName"); + return; + } + + this.namedTag.putString("CustomName", name); + } + + @Override + public void onBreak() { + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java b/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java index db34f1f3bf9..f1306f46cb1 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java @@ -164,16 +164,18 @@ public boolean onUpdate() { } if (!this.isOnTransferCooldown()) { + if ((this.level.getBlockDataAt(getFloorX(), getFloorY(), getFloorZ()) & 0x08) == 8) { //is hopper disabled? + return false; + } + BlockEntity blockEntity = this.level.getBlockEntity(this.up()); boolean changed = pushItems(); - if (!changed) { - if (!(blockEntity instanceof BlockEntityContainer)) { - changed = pickupItems(); - } else { - changed = pullItems(); - } + if (blockEntity instanceof InventoryHolder) { + changed = pullItems() || changed; + } else { + changed = pickupItems() || changed; } if (changed) { diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java b/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java index 9c7e6ea86b0..fc4f8be2a24 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java @@ -1,12 +1,17 @@ package cn.nukkit.blockentity; +import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; +import cn.nukkit.event.block.ItemFrameDropItemEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.LevelEventPacket; + +import java.util.concurrent.ThreadLocalRandom; /** * Created by Pub4Game on 03.07.2016. @@ -108,4 +113,27 @@ public CompoundTag getSpawnCompound() { public int getAnalogOutput() { return this.getItem() == null || this.getItem().getId() == 0 ? 0 : this.getItemRotation() % 8 + 1; } + + public boolean dropItem(Player player) { + Item item = this.getItem(); + if (item != null && item.getId() != Item.AIR) { + if (player != null) { + ItemFrameDropItemEvent event = new ItemFrameDropItemEvent(player, this.getBlock(), this, item); + this.level.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.spawnTo(player); + return true; + } + } + + if (this.getItemDropChance() > ThreadLocalRandom.current().nextFloat()) { + this.level.dropItem(this.add(0.5, 0, 0.5), item); + } + this.setItem(Item.get(Item.AIR)); + this.setItemRotation(0); + this.level.addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_ITEM_REMOVED); + return true; + } + return false; + } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java b/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java index 564a9649fb4..64bd3214224 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java @@ -1,7 +1,11 @@ package cn.nukkit.blockentity; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.entity.Entity; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockVector3; import cn.nukkit.nbt.tag.CompoundTag; @@ -10,10 +14,10 @@ */ public class BlockEntityMovingBlock extends BlockEntitySpawnable { - public Block block; + protected String blockString; + protected Block block; - public BlockVector3 piston; - public int progress; + protected BlockVector3 piston; public BlockEntityMovingBlock(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -21,8 +25,11 @@ public BlockEntityMovingBlock(FullChunk chunk, CompoundTag nbt) { @Override protected void initBlockEntity() { - if (namedTag.contains("movingBlockData") && namedTag.contains("movingBlockId")) { - this.block = Block.get(namedTag.getInt("movingBlockId"), namedTag.getInt("movingBlockData")); + if (namedTag.contains("movingBlock")) { + CompoundTag blockData = namedTag.getCompound("movingBlock"); + + this.blockString = blockData.getString("name"); + this.block = Block.get(blockData.getInt("id"), blockData.getInt("meta")); } else { this.close(); } @@ -30,28 +37,50 @@ protected void initBlockEntity() { if (namedTag.contains("pistonPosX") && namedTag.contains("pistonPosY") && namedTag.contains("pistonPosZ")) { this.piston = new BlockVector3(namedTag.getInt("pistonPosX"), namedTag.getInt("pistonPosY"), namedTag.getInt("pistonPosZ")); } else { - this.close(); + this.piston = new BlockVector3(0, -1, 0); } super.initBlockEntity(); } - public Block getBlock() { + public CompoundTag getBlockEntity() { + if (this.namedTag.contains("movingEntity")) { + return this.namedTag.getCompound("movingEntity"); + } + + return null; + } + + public Block getMovingBlock() { return this.block; } - @Override - public boolean isBlockEntityValid() { - return true; + public String getMovingBlockString() { + return this.blockString; + } + + public void moveCollidedEntities(BlockEntityPistonArm piston, BlockFace moveDirection) { + AxisAlignedBB bb = block.getBoundingBox(); + + if (bb == null) { + return; + } + + bb = bb.getOffsetBoundingBox( + this.x + (piston.progress * moveDirection.getXOffset()) - moveDirection.getXOffset(), + this.y + (piston.progress * moveDirection.getYOffset()) - moveDirection.getYOffset(), + this.z + (piston.progress * moveDirection.getZOffset()) - moveDirection.getZOffset() + ); + + Entity[] entities = this.level.getCollidingEntities(bb); + + for (Entity entity : entities) { + piston.moveEntity(entity, moveDirection); + } } @Override - public CompoundTag getSpawnCompound() { - return getDefaultCompound(this, MOVING_BLOCK) - .putFloat("movingBlockId", this.block.getId()) - .putFloat("movingBlockData", this.block.getDamage()) - .putInt("pistonPosX", this.piston.x) - .putInt("pistonPosY", this.piston.y) - .putInt("pistonPosZ", this.piston.z); + public boolean isBlockEntityValid() { + return this.level.getBlockIdAt(getFloorX(), getFloorY(), getFloorZ()) == BlockID.MOVING_BLOCK; } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java b/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java index c722c423bbc..7e2db2ab9ef 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java @@ -1,30 +1,39 @@ package cn.nukkit.blockentity; +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockAir; +import cn.nukkit.block.BlockID; import cn.nukkit.entity.Entity; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockVector3; import cn.nukkit.math.SimpleAxisAlignedBB; -import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.IntTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.Faceable; + +import java.util.ArrayList; +import java.util.List; /** * @author CreeperFace */ -public class BlockEntityPistonArm extends BlockEntity { +public class BlockEntityPistonArm extends BlockEntitySpawnable { + + public static final float MOVE_STEP = Float.valueOf(0.5f); - public float progress = 1.0F; - public float lastProgress = 1.0F; + public float progress; + public float lastProgress = 1; public BlockFace facing; - public boolean extending = false; - public boolean sticky = false; - public byte state = 1; - public byte newState = 1; - public Vector3 attachedBlock = null; - public boolean isMovable = true; - public boolean powered = false; + public boolean extending; + public boolean sticky; + public int state; + public int newState = 1; + public List attachedBlocks; + public boolean powered; public BlockEntityPistonArm(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -40,45 +49,159 @@ protected void initBlockEntity() { this.lastProgress = (float) namedTag.getInt("LastProgress"); } - if (namedTag.contains("Sticky")) { - this.sticky = namedTag.getBoolean("Sticky"); - } + this.sticky = namedTag.getBoolean("Sticky"); + this.extending = namedTag.getBoolean("Extending"); + this.powered = namedTag.getBoolean("powered"); - if (namedTag.contains("Extending")) { - this.extending = namedTag.getBoolean("Extending"); - } - if (namedTag.contains("powered")) { - this.powered = namedTag.getBoolean("powered"); + if (namedTag.contains("facing")) { + this.facing = BlockFace.fromIndex(namedTag.getInt("facing")); + } else { + Block b = this.getLevelBlock(); + + if (b instanceof Faceable) { + this.facing = ((Faceable) b).getBlockFace(); + } else { + this.facing = BlockFace.NORTH; + } } + attachedBlocks = new ArrayList<>(); + if (namedTag.contains("AttachedBlocks")) { ListTag blocks = namedTag.getList("AttachedBlocks", IntTag.class); if (blocks != null && blocks.size() > 0) { - this.attachedBlock = new Vector3((double) ((IntTag) blocks.get(0)).getData(), (double) ((IntTag) blocks.get(1)).getData(), (double) ((IntTag) blocks.get(2)).getData()); + for (int i = 0; i < blocks.size(); i += 3) { + this.attachedBlocks.add(new BlockVector3( + ((IntTag) blocks.get(i)).data, + ((IntTag) blocks.get(i + 1)).data, + ((IntTag) blocks.get(i + 1)).data + )); + } } } else { - namedTag.putList(new ListTag("AttachedBlocks")); + namedTag.putList(new ListTag<>("AttachedBlocks")); } super.initBlockEntity(); } - private void pushEntities() { - float lastProgress = this.getExtendedProgress(this.lastProgress); - double x = lastProgress * (float) this.facing.getXOffset(); - double y = lastProgress * (float) this.facing.getYOffset(); - double z = lastProgress * (float) this.facing.getZOffset(); - AxisAlignedBB bb = new SimpleAxisAlignedBB(x, y, z, x + 1.0D, y + 1.0D, z + 1.0D); + private void moveCollidedEntities() { + BlockFace pushDir = this.extending ? facing : facing.getOpposite(); + for (BlockVector3 pos : this.attachedBlocks) { + BlockEntity blockEntity = this.level.getBlockEntity(pos.getSide(pushDir)); + + if (blockEntity instanceof BlockEntityMovingBlock) { + ((BlockEntityMovingBlock) blockEntity).moveCollidedEntities(this, pushDir); + } + } + + AxisAlignedBB bb = new SimpleAxisAlignedBB(0, 0, 0, 1, 1, 1).getOffsetBoundingBox( + this.x + (pushDir.getXOffset() * progress), + this.y + (pushDir.getYOffset() * progress), + this.z + (pushDir.getZOffset() * progress) + ); + Entity[] entities = this.level.getCollidingEntities(bb); - if (entities.length != 0) { + for (Entity entity : entities) { + moveEntity(entity, pushDir); + } + } + + void moveEntity(Entity entity, BlockFace moveDirection) { + if (!entity.canBePushed()) { + return; + } + + //TODO: event + + if (entity instanceof Player) { + return; } + entity.onPushByPiston(this); + + if (!entity.closed) { + float diff = Math.abs(this.progress - this.lastProgress); + + entity.move( + diff * moveDirection.getXOffset(), + diff * moveDirection.getYOffset(), + diff * moveDirection.getZOffset() + ); + } + } + + public void move(boolean extending, List attachedBlocks) { + this.extending = extending; + this.lastProgress = this.progress = extending ? 0 : 1; + this.state = this.newState = extending ? 1 : 3; + this.attachedBlocks = attachedBlocks; + this.movable = false; + + this.level.addChunkPacket(getChunkX(), getChunkZ(), getSpawnPacket()); + this.lastProgress = extending ? -MOVE_STEP : 1 + MOVE_STEP; + this.moveCollidedEntities(); + this.scheduleUpdate(); + } + + @Override + public boolean onUpdate() { + boolean hasUpdate = true; + + if (this.extending) { + this.progress = Math.min(1, this.progress + MOVE_STEP); + this.lastProgress = Math.min(1, this.lastProgress + MOVE_STEP); + } else { + this.progress = Math.max(0, this.progress - MOVE_STEP); + this.lastProgress = Math.max(0, this.lastProgress - MOVE_STEP); + } + + this.moveCollidedEntities(); + + if (this.progress == this.lastProgress) { + this.state = this.newState = extending ? 2 : 0; + + BlockFace pushDir = this.extending ? facing : facing.getOpposite(); + + for (BlockVector3 pos : this.attachedBlocks) { + BlockEntity movingBlock = this.level.getBlockEntity(pos.getSide(pushDir)); + + if (movingBlock instanceof BlockEntityMovingBlock) { + movingBlock.close(); + Block moved = ((BlockEntityMovingBlock) movingBlock).getMovingBlock(); + + CompoundTag blockEntity = ((BlockEntityMovingBlock) movingBlock).getBlockEntity(); + + if (blockEntity != null) { + blockEntity.putInt("x", movingBlock.getFloorX()); + blockEntity.putInt("y", movingBlock.getFloorY()); + blockEntity.putInt("z", movingBlock.getFloorZ()); + BlockEntity.createBlockEntity(blockEntity.getString("id"), this.level.getChunk(movingBlock.getChunkX(), movingBlock.getChunkZ()), blockEntity); + } + + this.level.setBlock(movingBlock, moved); + } + } + + if (!extending && this.level.getBlock(getSide(facing)).getId() == BlockID.PISTON_HEAD) { + this.level.setBlock(getSide(facing), new BlockAir()); + this.movable = true; + } + + this.level.scheduleUpdate(this.getLevelBlock(), 1); + this.attachedBlocks.clear(); + hasUpdate = false; + } + + this.level.addChunkPacket(getChunkX(), getChunkZ(), getSpawnPacket()); + + return super.onUpdate() || hasUpdate; } private float getExtendedProgress(float progress) { - return this.extending ? progress - 1.0F : 1.0F - progress; + return this.extending ? progress - 1 : 1 - progress; } public boolean isBlockEntityValid() { @@ -87,15 +210,39 @@ public boolean isBlockEntityValid() { public void saveNBT() { super.saveNBT(); - this.namedTag.putBoolean("isMovable", this.isMovable); this.namedTag.putByte("State", this.state); this.namedTag.putByte("NewState", this.newState); this.namedTag.putFloat("Progress", this.progress); this.namedTag.putFloat("LastProgress", this.lastProgress); this.namedTag.putBoolean("powered", this.powered); + this.namedTag.putList(getAttachedBlocks()); + this.namedTag.putInt("facing", this.facing.getIndex()); } public CompoundTag getSpawnCompound() { - return (new CompoundTag()).putString("id", "PistonArm").putInt("x", (int) this.x).putInt("y", (int) this.y).putInt("z", (int) this.z); + return new CompoundTag() + .putString("id", BlockEntity.PISTON_ARM) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putFloat("Progress", this.progress) + .putFloat("LastProgress", this.lastProgress) + .putBoolean("isMovable", this.movable) + .putList(getAttachedBlocks()) + .putList(new ListTag<>("BreakBlocks")) + .putBoolean("Sticky", this.sticky) + .putByte("State", this.state) + .putByte("NewState", this.newState); + } + + private ListTag getAttachedBlocks() { + ListTag attachedBlocks = new ListTag<>("AttachedBlocks"); + for (BlockVector3 block : this.attachedBlocks) { + attachedBlocks.add(new IntTag("", block.x)); + attachedBlocks.add(new IntTag("", block.y)); + attachedBlocks.add(new IntTag("", block.z)); + } + + return attachedBlocks; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java index efcc91b9eb8..a9362fb6b43 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java @@ -53,10 +53,6 @@ public void close() { for (Player player : new HashSet<>(this.getInventory().getViewers())) { player.removeWindow(this.getInventory()); } - - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getRealInventory()); - } super.close(); } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java index e0d8651346d..7ac3146ec35 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java @@ -26,24 +26,38 @@ protected void initBlockEntity() { this.spawnToAll(); } - public abstract CompoundTag getSpawnCompound(); + public CompoundTag getSpawnCompound() { + return this.namedTag; + } public void spawnTo(Player player) { if (this.closed) { return; } - CompoundTag tag = this.getSpawnCompound(); + player.dataPacket(getSpawnPacket()); + } + + public BlockEntityDataPacket getSpawnPacket() { + return getSpawnPacket(null); + } + + public BlockEntityDataPacket getSpawnPacket(CompoundTag nbt) { + if (nbt == null) { + nbt = this.getSpawnCompound(); + } + BlockEntityDataPacket pk = new BlockEntityDataPacket(); pk.x = (int) this.x; pk.y = (int) this.y; pk.z = (int) this.z; try { - pk.namedTag = NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN, true); + pk.namedTag = NBTIO.write(nbt, ByteOrder.LITTLE_ENDIAN, true); } catch (IOException e) { throw new RuntimeException(e); } - player.dataPacket(pk); + + return pk; } public void spawnToAll() { diff --git a/src/main/java/cn/nukkit/command/Command.java b/src/main/java/cn/nukkit/command/Command.java index 6b6e6897413..fbbd4954eb7 100644 --- a/src/main/java/cn/nukkit/command/Command.java +++ b/src/main/java/cn/nukkit/command/Command.java @@ -68,7 +68,7 @@ public Command(String name, String description, String usageMessage, String[] al this.aliases = aliases; this.activeAliases = aliases; this.timing = Timings.getCommandTiming(this); - this.commandParameters.put("default", new CommandParameter[]{new CommandParameter("args", CommandParamType.RAWTEXT, true)}); + this.commandParameters.put("default", new CommandParameter[]{CommandParameter.newType("args", true, CommandParamType.RAWTEXT)}); } /** diff --git a/src/main/java/cn/nukkit/command/SimpleCommandMap.java b/src/main/java/cn/nukkit/command/SimpleCommandMap.java index c4d8fe06194..366dfb16933 100644 --- a/src/main/java/cn/nukkit/command/SimpleCommandMap.java +++ b/src/main/java/cn/nukkit/command/SimpleCommandMap.java @@ -144,7 +144,7 @@ public void registerSimpleCommands(Object object) { if (commandParameters != null) { Map map = Arrays.stream(commandParameters.parameters()) .collect(Collectors.toMap(Parameters::name, parameters -> Arrays.stream(parameters.parameters()) - .map(parameter -> new CommandParameter(parameter.name(), parameter.type(), parameter.optional())) + .map(parameter -> CommandParameter.newType(parameter.name(), parameter.optional(), parameter.type())) .distinct() .toArray(CommandParameter[]::new))); diff --git a/src/main/java/cn/nukkit/command/data/CommandEnum.java b/src/main/java/cn/nukkit/command/data/CommandEnum.java index 20f482c0de3..6e18e36039b 100644 --- a/src/main/java/cn/nukkit/command/data/CommandEnum.java +++ b/src/main/java/cn/nukkit/command/data/CommandEnum.java @@ -1,5 +1,11 @@ package cn.nukkit.command.data; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.ItemID; +import com.google.common.collect.ImmutableList; + +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.List; /** @@ -7,9 +13,34 @@ */ public class CommandEnum { + public static final CommandEnum ENUM_BOOLEAN = new CommandEnum("Boolean", ImmutableList.of("true", "false")); + public static final CommandEnum ENUM_GAMEMODE = new CommandEnum("GameMode", + ImmutableList.of("survival", "creative", "s", "c", "adventure", "a", "spectator", "view", "v", "spc")); + public static final CommandEnum ENUM_BLOCK; + public static final CommandEnum ENUM_ITEM; + + static { + ImmutableList.Builder blocks = ImmutableList.builder(); + for (Field field : BlockID.class.getDeclaredFields()) { + blocks.add(field.getName().toLowerCase()); + } + ENUM_BLOCK = new CommandEnum("Block", blocks.build()); + + ImmutableList.Builder items = ImmutableList.builder(); + for (Field field : ItemID.class.getDeclaredFields()) { + items.add(field.getName().toLowerCase()); + } + items.addAll(ENUM_BLOCK.getValues()); + ENUM_ITEM = new CommandEnum("Item", items.build()); + } + private String name; private List values; + public CommandEnum(String name, String... values) { + this(name, Arrays.asList(values)); + } + public CommandEnum(String name, List values) { this.name = name; this.values = values; diff --git a/src/main/java/cn/nukkit/command/data/CommandParameter.java b/src/main/java/cn/nukkit/command/data/CommandParameter.java index befe39246ab..a657455d1b0 100644 --- a/src/main/java/cn/nukkit/command/data/CommandParameter.java +++ b/src/main/java/cn/nukkit/command/data/CommandParameter.java @@ -1,28 +1,9 @@ package cn.nukkit.command.data; - import java.util.ArrayList; -import java.util.Arrays; public class CommandParameter { - public final static String ARG_TYPE_STRING = "string"; - public final static String ARG_TYPE_STRING_ENUM = "stringenum"; - public final static String ARG_TYPE_BOOL = "bool"; - public final static String ARG_TYPE_TARGET = "target"; - public final static String ARG_TYPE_PLAYER = "target"; - public final static String ARG_TYPE_BLOCK_POS = "blockpos"; - public final static String ARG_TYPE_RAW_TEXT = "rawtext"; - public final static String ARG_TYPE_INT = "int"; - - public static final String ENUM_TYPE_ITEM_LIST = "itemType"; - public static final String ENUM_TYPE_BLOCK_LIST = "blockType"; - public static final String ENUM_TYPE_COMMAND_LIST = "commandName"; - public static final String ENUM_TYPE_ENCHANTMENT_LIST = "enchantmentType"; - public static final String ENUM_TYPE_ENTITY_LIST = "entityType"; - public static final String ENUM_TYPE_EFFECT_LIST = "effectType"; - public static final String ENUM_TYPE_PARTICLE_LIST = "particleType"; - public String name; public CommandParamType type; public boolean optional; @@ -31,25 +12,44 @@ public class CommandParameter { public CommandEnum enumData; public String postFix; + /** + * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead + */ @Deprecated public CommandParameter(String name, String type, boolean optional) { this(name, fromString(type), optional); } + /** + * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead + */ + @Deprecated public CommandParameter(String name, CommandParamType type, boolean optional) { this.name = name; this.type = type; this.optional = optional; } + /** + * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead + */ + @Deprecated public CommandParameter(String name, boolean optional) { this(name, CommandParamType.RAWTEXT, optional); } + /** + * @deprecated use {@link #newType(String, CommandParamType)} instead + */ + @Deprecated public CommandParameter(String name) { this(name, false); } + /** + * @deprecated use {@link #newEnum(String, boolean, String)} instead + */ + @Deprecated public CommandParameter(String name, boolean optional, String enumType) { this.name = name; this.type = CommandParamType.RAWTEXT; @@ -57,21 +57,81 @@ public CommandParameter(String name, boolean optional, String enumType) { this.enumData = new CommandEnum(enumType, new ArrayList<>()); } + /** + * @deprecated use {@link #newEnum(String, boolean, String[])} instead + */ + @Deprecated public CommandParameter(String name, boolean optional, String[] enumValues) { this.name = name; this.type = CommandParamType.RAWTEXT; this.optional = optional; - this.enumData = new CommandEnum(name + "Enums", Arrays.asList(enumValues)); + this.enumData = new CommandEnum(name + "Enums", enumValues); } + /** + * @deprecated use {@link #newEnum(String, String)} instead + */ + @Deprecated public CommandParameter(String name, String enumType) { this(name, false, enumType); } + /** + * @deprecated use {@link #newEnum(String, String[])} instead + */ + @Deprecated public CommandParameter(String name, String[] enumValues) { this(name, false, enumValues); } + private CommandParameter(String name, boolean optional, CommandParamType type, CommandEnum enumData, String postFix) { + this.name = name; + this.optional = optional; + this.type = type; + this.enumData = enumData; + this.postFix = postFix; + } + + public static CommandParameter newType(String name, CommandParamType type) { + return newType(name, false, type); + } + + public static CommandParameter newType(String name, boolean optional, CommandParamType type) { + return new CommandParameter(name, optional, type, null, null); + } + + public static CommandParameter newEnum(String name, String[] values) { + return newEnum(name, false, values); + } + + public static CommandParameter newEnum(String name, boolean optional, String[] values) { + return newEnum(name, optional, new CommandEnum(name + "Enums", values)); + } + + public static CommandParameter newEnum(String name, String type) { + return newEnum(name, false, type); + } + + public static CommandParameter newEnum(String name, boolean optional, String type) { + return newEnum(name, optional, new CommandEnum(type, new ArrayList<>())); + } + + public static CommandParameter newEnum(String name, CommandEnum data) { + return newEnum(name, false, data); + } + + public static CommandParameter newEnum(String name, boolean optional, CommandEnum data) { + return new CommandParameter(name, optional, CommandParamType.RAWTEXT, data, null); + } + + public static CommandParameter newPostfix(String name, String postfix) { + return newPostfix(name, false, postfix); + } + + public static CommandParameter newPostfix(String name, boolean optional, String postfix) { + return new CommandParameter(name, optional, CommandParamType.RAWTEXT, null, postfix); + } + protected static CommandParamType fromString(String param) { switch (param) { case "string": diff --git a/src/main/java/cn/nukkit/command/defaults/BanCommand.java b/src/main/java/cn/nukkit/command/defaults/BanCommand.java index 9ae05584693..ca437e55712 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanCommand.java @@ -20,8 +20,8 @@ public BanCommand(String name) { this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("reason", CommandParamType.STRING, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("reason", true, CommandParamType.STRING) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java b/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java index 3ce50c08016..7a1b48bb89e 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java @@ -30,8 +30,12 @@ public BanIpCommand(String name) { this.setAliases(new String[]{"banip"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("reason", CommandParamType.STRING, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("reason", true, CommandParamType.STRING) + }); + this.commandParameters.put("byIp", new CommandParameter[]{ + CommandParameter.newType("ip", CommandParamType.STRING), + CommandParameter.newType("reason", true, CommandParamType.STRING) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/BanListCommand.java b/src/main/java/cn/nukkit/command/defaults/BanListCommand.java index 2ba24980801..0ca654c4e88 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanListCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanListCommand.java @@ -1,6 +1,7 @@ package cn.nukkit.command.defaults; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.permission.BanEntry; @@ -18,7 +19,7 @@ public BanListCommand(String name) { this.setPermission("nukkit.command.ban.list"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("ips|players", true) + CommandParameter.newEnum("type", true, new CommandEnum("BanListType", "ips", "players")) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java b/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java index 1617db80816..57e765d08bb 100644 --- a/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java @@ -18,6 +18,7 @@ public class DebugPasteCommand extends VanillaCommand { public DebugPasteCommand(String name) { super(name, "%nukkit.command.debug.description", "%nukkit.command.debug.usage"); this.setPermission("nukkit.command.debug.perform"); + this.commandParameters.clear(); } @Override diff --git a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java index 8594186b679..73604ce3cd3 100644 --- a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java @@ -2,6 +2,7 @@ import cn.nukkit.Server; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -17,11 +18,10 @@ public DefaultGamemodeCommand(String name) { this.setPermission("nukkit.command.defaultgamemode"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("mode", CommandParamType.INT, false) + CommandParameter.newType("gameMode", CommandParamType.INT) }); this.commandParameters.put("byString", new CommandParameter[]{ - new CommandParameter("mode", new String[]{"survival", "creative", "s", "c", - "adventure", "a", "spectator", "view", "v"}) + CommandParameter.newEnum("gameMode", CommandEnum.ENUM_GAMEMODE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/DeopCommand.java b/src/main/java/cn/nukkit/command/defaults/DeopCommand.java index 358599f3786..4934da84e69 100644 --- a/src/main/java/cn/nukkit/command/defaults/DeopCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DeopCommand.java @@ -18,7 +18,7 @@ public DeopCommand(String name) { super(name, "%nukkit.command.deop.description", "%commands.deop.description"); this.setPermission("nukkit.command.op.take"); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false) + CommandParameter.newType("player", CommandParamType.TARGET) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java b/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java index 081312233df..4fad9e3b8fc 100644 --- a/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Server; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -21,11 +22,10 @@ public DifficultyCommand(String name) { this.setPermission("nukkit.command.difficulty"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("difficulty", CommandParamType.INT, false) + CommandParameter.newType("difficulty", CommandParamType.INT) }); this.commandParameters.put("byString", new CommandParameter[]{ - new CommandParameter("difficulty", new String[]{"peaceful", "p", "easy", "e", - "normal", "n", "hard", "h"}) + CommandParameter.newEnum("difficulty", new CommandEnum("Difficulty", "peaceful", "p", "easy", "e", "normal", "n", "hard", "h")) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java index 78dbfe90849..760db564e36 100644 --- a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -11,6 +12,11 @@ import cn.nukkit.utils.ServerException; import cn.nukkit.utils.TextFormat; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + /** * Created by Snake1999 and Pub4Game on 2016/1/23. * Package cn.nukkit.command.defaults in project nukkit. @@ -20,16 +26,24 @@ public EffectCommand(String name) { super(name, "%nukkit.command.effect.description", "%commands.effect.usage"); this.setPermission("nukkit.command.effect"); this.commandParameters.clear(); + + List effects = new ArrayList<>(); + for (Field field : Effect.class.getDeclaredFields()) { + if (field.getType() == int.class && field.getModifiers() == (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)) { + effects.add(field.getName().toLowerCase()); + } + } + this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("effect", CommandParamType.STRING, false), //Do not use Enum here because of buggy behavior - new CommandParameter("seconds", CommandParamType.INT, true), - new CommandParameter("amplifier", true), - new CommandParameter("hideParticle", true, new String[]{"true", "false"}) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("effect", new CommandEnum("Effect", effects)), + CommandParameter.newType("seconds", true, CommandParamType.INT), + CommandParameter.newType("amplifier", true, CommandParamType.INT), + CommandParameter.newEnum("hideParticle", true, CommandEnum.ENUM_BOOLEAN) }); this.commandParameters.put("clear", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("clear", new String[]{"clear"}) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("clear", new CommandEnum("ClearEffects", "clear")) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java b/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java index 5fe9bf8f123..e262a9ca62a 100644 --- a/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; @@ -20,14 +21,19 @@ public EnchantCommand(String name) { this.setPermission("nukkit.command.enchant"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("enchantment ID", CommandParamType.INT, false), - new CommandParameter("level", CommandParamType.INT, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("enchantmentId", CommandParamType.INT), + CommandParameter.newType("level", true, CommandParamType.INT) }); this.commandParameters.put("byName", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("id", false, CommandParameter.ENUM_TYPE_ENCHANTMENT_LIST), - new CommandParameter("level", CommandParamType.INT, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("enchantmentName", new CommandEnum("Enchant", + "protection", "fire_protection", "feather_falling", "blast_protection", "projectile_projection", "thorns", "respiration", + "aqua_affinity", "depth_strider", "sharpness", "smite", "bane_of_arthropods", "knockback", "fire_aspect", "looting", "efficiency", + "silk_touch", "durability", "fortune", "power", "punch", "flame", "infinity", "luck_of_the_sea", "lure", "frost_walker", "mending", + "binding_curse", "vanishing_curse", "impaling", "loyality", "riptide", "channeling", "multishot", "piercing", "quick_charge", + "soul_speed")), + CommandParameter.newType("level", true, CommandParamType.INT) }); } @@ -139,6 +145,14 @@ public int getIdByName(String value) throws NumberFormatException { return 31; case "channeling": return 32; + case "multishot": + return 33; + case "piercing": + return 34; + case "quick_charge": + return 35; + case "soul_speed": + return 36; default: return Integer.parseInt(value); } diff --git a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java index 3dd8a1891e5..3f07ae5b1a1 100644 --- a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java @@ -4,6 +4,7 @@ import cn.nukkit.Server; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -25,13 +26,12 @@ public GamemodeCommand(String name) { "nukkit.command.gamemode.other"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("mode", CommandParamType.INT, false), - new CommandParameter("player", CommandParamType.TARGET, true) + CommandParameter.newType("gameMode", CommandParamType.INT), + CommandParameter.newType("player", true, CommandParamType.TARGET) }); this.commandParameters.put("byString", new CommandParameter[]{ - new CommandParameter("mode", new String[]{"survival", "s", "creative", "c", - "adventure", "a", "spectator", "spc", "view", "v"}), - new CommandParameter("player", CommandParamType.TARGET, true) + CommandParameter.newEnum("gameMode", CommandEnum.ENUM_GAMEMODE), + CommandParameter.newType("player", true, CommandParamType.TARGET) }); } @@ -79,8 +79,8 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (target.equals(sender)) { Command.broadcastCommandMessage(sender, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(gameMode))); } else { - target.sendMessage(new TranslationContainer("gameMode.changed")); - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.gamemode.success.other", Server.getGamemodeString(gameMode), target.getName())); + target.sendMessage(new TranslationContainer("gameMode.changed", Server.getGamemodeString(gameMode))); + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.gamemode.success.other", target.getName(), Server.getGamemodeString(gameMode))); } } diff --git a/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java b/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java index 26a7d180633..1015a4cc662 100644 --- a/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java @@ -2,13 +2,16 @@ import cn.nukkit.Player; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.level.GameRule; import cn.nukkit.level.GameRules; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.StringJoiner; @@ -18,10 +21,55 @@ public GameruleCommand(String name) { super(name, "%nukkit.command.gamerule.description", "%nukkit.command.gamerule.usage"); this.setPermission("nukkit.command.gamerule"); this.commandParameters.clear(); - this.commandParameters.put("byString", new CommandParameter[]{ - new CommandParameter("gamerule", true , GameRule.getNames()), - new CommandParameter("value", CommandParamType.STRING, true) + + GameRules rules = GameRules.getDefault(); + List boolGameRules = new ArrayList<>(); + List intGameRules = new ArrayList<>(); + List floatGameRules = new ArrayList<>(); + List unknownGameRules = new ArrayList<>(); + + rules.getGameRules().forEach((rule, value) -> { + switch (value.getType()) { + case BOOLEAN: + boolGameRules.add(rule.getName().toLowerCase()); + break; + case INTEGER: + intGameRules.add(rule.getName().toLowerCase()); + break; + case FLOAT: + floatGameRules.add(rule.getName().toLowerCase()); + break; + case UNKNOWN: + default: + unknownGameRules.add(rule.getName().toLowerCase()); + break; + } }); + + if (!boolGameRules.isEmpty()) { + this.commandParameters.put("boolGameRules", new CommandParameter[]{ + CommandParameter.newEnum("rule", new CommandEnum("BoolGameRule", boolGameRules)), + CommandParameter.newEnum("value", true, CommandEnum.ENUM_BOOLEAN) + }); + } + if (!intGameRules.isEmpty()) { + this.commandParameters.put("intGameRules", new CommandParameter[]{ + CommandParameter.newEnum("rule", new CommandEnum("IntGameRule", intGameRules)), + CommandParameter.newType("value", true, CommandParamType.INT) + }); + } + if (!floatGameRules.isEmpty()) { + this.commandParameters.put("floatGameRules", new CommandParameter[]{ + CommandParameter.newEnum("rule", new CommandEnum("FloatGameRule", floatGameRules)), + CommandParameter.newType("value", true, CommandParamType.FLOAT) + }); + } + if (!unknownGameRules.isEmpty()) { + this.commandParameters.put("unknownGameRules", new CommandParameter[]{ + CommandParameter.newEnum("rule", new CommandEnum("UnknownGameRule", unknownGameRules)), + CommandParameter.newType("value", true, CommandParamType.STRING) + }); + } } @Override @@ -51,7 +99,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - sender.sendMessage(gameRule.get().getName() + " = " + rules.getString(gameRule.get())); + sender.sendMessage(gameRule.get().getName() .toLowerCase()+ " = " + rules.getString(gameRule.get())); return true; default: Optional optionalRule = GameRule.parseString(args[0]); @@ -64,7 +112,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) try { rules.setGameRules(optionalRule.get(), args[1]); - sender.sendMessage(new TranslationContainer("commands.gamerule.success", optionalRule.get().getName(), args[1])); + sender.sendMessage(new TranslationContainer("commands.gamerule.success", optionalRule.get().getName().toLowerCase(), args[1])); } catch (IllegalArgumentException e) { sender.sendMessage(new TranslationContainer("commands.generic.syntax", "/gamerule " + args[0] + " ", args[1], " " + String.join(" ", Arrays.copyOfRange(args, 2, args.length)))); } diff --git a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java index e3ca0b16209..d3cef0469d9 100644 --- a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; @@ -19,23 +20,22 @@ public GiveCommand(String name) { this.setPermission("nukkit.command.give"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("item", false, CommandParameter.ENUM_TYPE_ITEM_LIST), - new CommandParameter("amount", CommandParamType.INT, true), - new CommandParameter("meta", CommandParamType.INT, true), - new CommandParameter("tags...", CommandParamType.RAWTEXT, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("itemName", CommandEnum.ENUM_ITEM), + CommandParameter.newType("amount", true, CommandParamType.INT), + CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) }); this.commandParameters.put("toPlayerById", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("item ID", CommandParamType.INT, false), - new CommandParameter("amount", CommandParamType.INT, true), - new CommandParameter("tags...", CommandParamType.RAWTEXT, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("itemId", CommandParamType.INT), + CommandParameter.newType("amount", true, CommandParamType.INT), + CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) }); this.commandParameters.put("toPlayerByIdMeta", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("item ID:meta", CommandParamType.RAWTEXT, false), - new CommandParameter("amount", CommandParamType.INT, true), - new CommandParameter("tags...", CommandParamType.RAWTEXT, true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("itemAndData", CommandParamType.STRING), + CommandParameter.newType("amount", true, CommandParamType.INT), + CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/HelpCommand.java b/src/main/java/cn/nukkit/command/defaults/HelpCommand.java index 5c8f7b718ee..124c35b46f1 100644 --- a/src/main/java/cn/nukkit/command/defaults/HelpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/HelpCommand.java @@ -22,7 +22,7 @@ public HelpCommand(String name) { this.setPermission("nukkit.command.help"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("page", CommandParamType.INT, true) + CommandParameter.newType("page", true, CommandParamType.INT) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/KickCommand.java b/src/main/java/cn/nukkit/command/defaults/KickCommand.java index 01045116f8b..1caf85ac982 100644 --- a/src/main/java/cn/nukkit/command/defaults/KickCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/KickCommand.java @@ -20,8 +20,8 @@ public KickCommand(String name) { this.setPermission("nukkit.command.kick"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("reason", true) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("reason", true, CommandParamType.MESSAGE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/KillCommand.java b/src/main/java/cn/nukkit/command/defaults/KillCommand.java index 2f35f45dc2d..a6fa78e8563 100644 --- a/src/main/java/cn/nukkit/command/defaults/KillCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/KillCommand.java @@ -27,7 +27,7 @@ public KillCommand(String name) { + "nukkit.command.kill.other"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, true) + CommandParameter.newType("player", true, CommandParamType.TARGET) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/MeCommand.java b/src/main/java/cn/nukkit/command/defaults/MeCommand.java index fb701d91d5d..57a35cfc010 100644 --- a/src/main/java/cn/nukkit/command/defaults/MeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/MeCommand.java @@ -18,7 +18,7 @@ public MeCommand(String name) { this.setPermission("nukkit.command.me"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("action ...", CommandParamType.RAWTEXT, false) + CommandParameter.newType("message", CommandParamType.MESSAGE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/OpCommand.java b/src/main/java/cn/nukkit/command/defaults/OpCommand.java index 2c8493340c3..6afc9fdcc6c 100644 --- a/src/main/java/cn/nukkit/command/defaults/OpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/OpCommand.java @@ -20,7 +20,7 @@ public OpCommand(String name) { this.setPermission("nukkit.command.op.give"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false) + CommandParameter.newType("player", CommandParamType.TARGET) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/PardonCommand.java b/src/main/java/cn/nukkit/command/defaults/PardonCommand.java index d97dd30d0ca..0ffd5296b72 100644 --- a/src/main/java/cn/nukkit/command/defaults/PardonCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/PardonCommand.java @@ -18,7 +18,7 @@ public PardonCommand(String name) { this.setAliases(new String[]{"unban"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false) + CommandParameter.newType("player", CommandParamType.TARGET) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java b/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java index bc61968664f..d3ae0695a85 100644 --- a/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java @@ -2,6 +2,7 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -21,7 +22,7 @@ public PardonIpCommand(String name) { this.setAliases(new String[]{"unbanip", "unban-ip", "pardonip"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("ip") + CommandParameter.newType("ip", CommandParamType.STRING) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java b/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java index d4812d7b117..aea1e78c51e 100644 --- a/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; @@ -12,6 +13,7 @@ import cn.nukkit.math.Vector3; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; /** * Created on 2015/11/12 by xtypr. @@ -27,10 +29,10 @@ public ParticleCommand(String name) { this.setPermission("nukkit.command.particle"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("name", false, ENUM_VALUES), - new CommandParameter("position", CommandParamType.POSITION, false), - new CommandParameter("count", CommandParamType.INT, true), - new CommandParameter("data", true) + CommandParameter.newEnum("effect", new CommandEnum("Particle", ENUM_VALUES)), + CommandParameter.newType("position", CommandParamType.POSITION), + CommandParameter.newType("count", true, CommandParamType.INT), + CommandParameter.newType("data", true, CommandParamType.INT) }); } @@ -98,7 +100,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.particle.success", name, String.valueOf(count))); - Random random = new Random(System.currentTimeMillis()); + Random random = ThreadLocalRandom.current(); for (int i = 0; i < count; i++) { particle.setComponents( diff --git a/src/main/java/cn/nukkit/command/defaults/SayCommand.java b/src/main/java/cn/nukkit/command/defaults/SayCommand.java index 738bbac5bf1..a55d3c459cf 100644 --- a/src/main/java/cn/nukkit/command/defaults/SayCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SayCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.CommandSender; import cn.nukkit.command.ConsoleCommandSender; +import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.utils.TextFormat; @@ -18,7 +19,7 @@ public SayCommand(String name) { this.setPermission("nukkit.command.say"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("message") + CommandParameter.newType("message", CommandParamType.MESSAGE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java b/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java index 83720b6a3d7..fcb93688eb2 100644 --- a/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java @@ -21,7 +21,7 @@ public SetWorldSpawnCommand(String name) { this.setPermission("nukkit.command.setworldspawn"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("blockPos", CommandParamType.POSITION, true) + CommandParameter.newType("spawnPoint", true, CommandParamType.POSITION) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java b/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java index 8b98b7ab06e..bf10543d072 100644 --- a/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java @@ -22,7 +22,8 @@ public SpawnpointCommand(String name) { this.setPermission("nukkit.command.spawnpoint"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("blockPos", CommandParamType.POSITION, true), + CommandParameter.newType("player", true, CommandParamType.TARGET), + CommandParameter.newType("spawnPos", true, CommandParamType.POSITION), }); } diff --git a/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java b/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java index 364e9f46963..e69aa948808 100644 --- a/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java @@ -21,18 +21,22 @@ public TeleportCommand(String name) { this.setPermission("nukkit.command.teleport"); this.commandParameters.clear(); this.commandParameters.put("->Player", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), + CommandParameter.newType("destination", CommandParamType.TARGET), }); this.commandParameters.put("Player->Player", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("target", CommandParamType.TARGET, false), + CommandParameter.newType("victim", CommandParamType.TARGET), + CommandParameter.newType("destination", CommandParamType.TARGET) }); this.commandParameters.put("Player->Pos", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("blockPos", CommandParamType.POSITION, false), + CommandParameter.newType("victim", CommandParamType.TARGET), + CommandParameter.newType("destination", CommandParamType.POSITION), + CommandParameter.newType("yRot", true, CommandParamType.VALUE), + CommandParameter.newType("xRot", true, CommandParamType.VALUE) }); this.commandParameters.put("->Pos", new CommandParameter[]{ - new CommandParameter("blockPos", CommandParamType.POSITION, false), + CommandParameter.newType("destination", CommandParamType.POSITION), + CommandParameter.newType("yRot", true, CommandParamType.VALUE), + CommandParameter.newType("xRot", true, CommandParamType.VALUE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/TellCommand.java b/src/main/java/cn/nukkit/command/defaults/TellCommand.java index fa5640bfe9e..37dc1426bd7 100644 --- a/src/main/java/cn/nukkit/command/defaults/TellCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TellCommand.java @@ -20,8 +20,8 @@ public TellCommand(String name) { this.setPermission("nukkit.command.tell"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("message") + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newType("message", CommandParamType.MESSAGE) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/TimeCommand.java b/src/main/java/cn/nukkit/command/defaults/TimeCommand.java index b0816b164e8..5d6f39ad95d 100644 --- a/src/main/java/cn/nukkit/command/defaults/TimeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TimeCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -23,15 +24,19 @@ public TimeCommand(String name) { "nukkit.command.time.stop"); this.commandParameters.clear(); this.commandParameters.put("1arg", new CommandParameter[]{ - new CommandParameter("start|stop", CommandParamType.STRING, false) + CommandParameter.newEnum("mode", new CommandEnum("TimeMode", "query", "start", "stop")) }); - this.commandParameters.put("2args", new CommandParameter[]{ - new CommandParameter("add|set", CommandParamType.STRING, false), - new CommandParameter("value", CommandParamType.INT, false) + this.commandParameters.put("add", new CommandParameter[]{ + CommandParameter.newEnum("mode", new CommandEnum("TimeModeAdd", "add")), + CommandParameter.newType("amount", CommandParamType.INT) }); - this.commandParameters.put("2args_", new CommandParameter[]{ - new CommandParameter("add|set", CommandParamType.STRING, false), - new CommandParameter("value", CommandParamType.STRING, false) + this.commandParameters.put("setAmount", new CommandParameter[]{ + CommandParameter.newEnum("mode", false, new CommandEnum("TimeModeSet", "set")), + CommandParameter.newType("amount", CommandParamType.INT) + }); + this.commandParameters.put("setTime", new CommandParameter[]{ + CommandParameter.newEnum("mode", new CommandEnum("TimeModeSet", "set")), + CommandParameter.newEnum("time", new CommandEnum("TimeSpec", "day", "night", "midnight", "noon", "sunrise", "sunset")) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java b/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java index e9b81c64f58..beb9354e2ba 100644 --- a/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java @@ -1,6 +1,7 @@ package cn.nukkit.command.defaults; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import co.aikar.timings.Timings; @@ -17,7 +18,7 @@ public TimingsCommand(String name) { this.setPermission("nukkit.command.timings"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("on|off|paste") + CommandParameter.newEnum("action", new CommandEnum("TimingsAction", "on", "off", "paste", "verbon", "verboff", "reset", "report")) }); } @@ -71,4 +72,3 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } } - diff --git a/src/main/java/cn/nukkit/command/defaults/TitleCommand.java b/src/main/java/cn/nukkit/command/defaults/TitleCommand.java index 09e0fa7d870..2bdf150c741 100644 --- a/src/main/java/cn/nukkit/command/defaults/TitleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TitleCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -18,34 +19,24 @@ public TitleCommand(String name) { this.commandParameters.clear(); this.commandParameters.put("clear", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("clear", new String[]{"clear"}) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("clear", new CommandEnum("TitleClear", "clear")) }); this.commandParameters.put("reset", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("reset", new String[]{"reset"}) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("reset", new CommandEnum("TitleReset", "reset")) }); - this.commandParameters.put("title", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("title", new String[]{"title"}), - new CommandParameter("titleText", CommandParamType.STRING, false) - }); - this.commandParameters.put("subtitle", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("subtitle", new String[]{"subtitle"}), - new CommandParameter("titleText", CommandParamType.STRING, false) - }); - this.commandParameters.put("actionbar", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("actionbar", new String[]{"actionbar"}), - new CommandParameter("titleText", CommandParamType.STRING, false) + this.commandParameters.put("set", new CommandParameter[]{ + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("titleLocation", new CommandEnum("TitleSet", "title", "subtitle", "actionbar")), + CommandParameter.newType("titleText", CommandParamType.MESSAGE) }); this.commandParameters.put("times", new CommandParameter[]{ - new CommandParameter("player", CommandParamType.TARGET, false), - new CommandParameter("times", new String[]{"times"}), - new CommandParameter("fadeIn", CommandParamType.INT, false), - new CommandParameter("stay", CommandParamType.INT, false), - new CommandParameter("fadeOut", CommandParamType.INT, false) + CommandParameter.newType("player", CommandParamType.TARGET), + CommandParameter.newEnum("times", new CommandEnum("TitleTimes", "times")), + CommandParameter.newType("fadeIn", CommandParamType.INT), + CommandParameter.newType("stay", CommandParamType.INT), + CommandParameter.newType("fadeOut", CommandParamType.INT) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/VersionCommand.java b/src/main/java/cn/nukkit/command/defaults/VersionCommand.java index 17d439c77fe..0fbfe8a6949 100644 --- a/src/main/java/cn/nukkit/command/defaults/VersionCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/VersionCommand.java @@ -1,6 +1,8 @@ package cn.nukkit.command.defaults; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandParamType; +import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.network.protocol.ProtocolInfo; import cn.nukkit.plugin.Plugin; @@ -23,6 +25,9 @@ public VersionCommand(String name) { ); this.setPermission("nukkit.command.version"); this.commandParameters.clear(); + this.commandParameters.put("default", new CommandParameter[]{ + CommandParameter.newType("pluginName", true, CommandParamType.STRING) + }); } @Override diff --git a/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java b/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java index 24069cc7b3d..f85de3c6d19 100644 --- a/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -14,15 +15,13 @@ */ public class WeatherCommand extends VanillaCommand { - private java.util.Random rand = new java.util.Random(); - public WeatherCommand(String name) { super(name, "%nukkit.command.weather.description", "%commands.weather.usage"); this.setPermission("nukkit.command.weather"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("clear|rain|thunder", CommandParamType.STRING, false), - new CommandParameter("duration in seconds", CommandParamType.INT, true) + CommandParameter.newEnum("type", new CommandEnum("WeatherType", "clear", "rain", "thunder")), + CommandParameter.newType("duration", true, CommandParamType.INT) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java b/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java index 0e30f93987d..39b3a487e21 100644 --- a/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java @@ -2,6 +2,7 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -25,15 +26,14 @@ public WhitelistCommand(String name) { ); this.commandParameters.clear(); this.commandParameters.put("1arg", new CommandParameter[]{ - new CommandParameter("on|off|list|reload", CommandParamType.STRING, false) + CommandParameter.newEnum("action", new CommandEnum("WhitelistAction", "on", "off", "list", "reload")) }); this.commandParameters.put("2args", new CommandParameter[]{ - new CommandParameter("add|remove", CommandParamType.STRING, false), - new CommandParameter("player", CommandParamType.TARGET, false) + CommandParameter.newEnum("action", new CommandEnum("WhitelistPlayerAction", "add", "remove")), + CommandParameter.newType("player", CommandParamType.TARGET) }); } - @Override public boolean execute(CommandSender sender, String commandLabel, String[] args) { if (!this.testPermission(sender)) { @@ -107,7 +107,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } private boolean badPerm(CommandSender sender, String perm) { - if (!sender.hasPermission("nukkit.command.whitelist" + perm)) { + if (!sender.hasPermission("nukkit.command.whitelist." + perm)) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); return true; diff --git a/src/main/java/cn/nukkit/command/defaults/XpCommand.java b/src/main/java/cn/nukkit/command/defaults/XpCommand.java index 31ac7f49414..26a5a5f9323 100644 --- a/src/main/java/cn/nukkit/command/defaults/XpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/XpCommand.java @@ -18,8 +18,12 @@ public XpCommand(String name) { this.setPermission("nukkit.command.xp"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - new CommandParameter("amount|level", CommandParamType.INT, false), - new CommandParameter("player", CommandParamType.TARGET, true) + CommandParameter.newType("amount", CommandParamType.INT), + CommandParameter.newType("player", true, CommandParamType.TARGET) + }); + this.commandParameters.put("level", new CommandParameter[]{ + CommandParameter.newPostfix("amount", "l"), + CommandParameter.newType("player", true, CommandParamType.TARGET) }); } diff --git a/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java new file mode 100644 index 00000000000..b4eb9aeb5fb --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java @@ -0,0 +1,39 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityBoat; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class BoatDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).multiply(1.125); + + Block target = block.getSide(face); + + if (target instanceof BlockWater) { + pos.y += 1; + } else if (target.getId() != BlockID.AIR || !(target.down() instanceof BlockWater)) { + return super.dispense(block, face, item); + } + + pos = target.getLocation().setYaw(face.getHorizontalAngle()); + + EntityBoat boat = new EntityBoat(block.level.getChunk(target.getChunkX(), target.getChunkZ()), + Entity.getDefaultNBT(pos) + .putByte("woodID", item.getDamage()) + ); + + boat.spawnToAll(); + + return null; + } + +} diff --git a/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java index 73cf483bd85..58a1c214804 100644 --- a/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java @@ -1,15 +1,37 @@ package cn.nukkit.dispenser; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockAir; import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockLiquid; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBucket; +import cn.nukkit.item.ItemID; +import cn.nukkit.math.BlockFace; /** * @author CreeperFace */ -public class BucketDispenseBehavior implements DispenseBehavior { +public class BucketDispenseBehavior extends DefaultDispenseBehavior { @Override - public void dispense(BlockDispenser block, Item stack) { + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + if (item.getDamage() > 0) { + if (target.canBeFlowedInto()) { + Block replace = Block.get(ItemBucket.getDamageByTarget(item.getDamage())); + + if (replace instanceof BlockLiquid) { + block.level.setBlock(target, replace); + return Item.get(ItemID.BUCKET); + } + } + } else if (target instanceof BlockLiquid && target.getDamage() == 0) { + target.level.setBlock(target, new BlockAir()); + return new ItemBucket(ItemBucket.getDamageByTarget(target.getId())); + } + + return super.dispense(block, face, item); } } diff --git a/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java index c13002af48e..e24a39006b2 100644 --- a/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java @@ -3,15 +3,47 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.item.Item; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockFace.Axis; +import cn.nukkit.math.Vector3; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; /** * @author CreeperFace */ public class DefaultDispenseBehavior implements DispenseBehavior { + public boolean success = true; + @Override - public void dispense(BlockDispenser block, Item stack) { + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 dispensePos = block.getDispensePosition(); + + if (face.getAxis() == Axis.Y) { + dispensePos.y -= 0.125; + } else { + dispensePos.y -= 0.15625; + } + + Random rand = ThreadLocalRandom.current(); + Vector3 motion = new Vector3(); + + double offset = rand.nextDouble() * 0.1 + 0.2; + + motion.x = face.getXOffset() * offset; + motion.y = 0.20000000298023224; + motion.z = face.getZOffset() * offset; + + motion.x += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.y += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.z += rand.nextGaussian() * 0.007499999832361937 * 6; + + Item clone = item.clone(); + clone.count = 1; + block.level.dropItem(dispensePos, clone, motion); + return null; } private int getParticleMetadataForFace(BlockFace face) { diff --git a/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java index 9019b20ff16..ebb94f2c36a 100644 --- a/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java @@ -2,12 +2,13 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; /** * @author CreeperFace */ public interface DispenseBehavior { - void dispense(BlockDispenser block, Item item); + Item dispense(BlockDispenser block, BlockFace face, Item item); } diff --git a/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java b/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java index 9cf02b8558b..1209b7cced7 100644 --- a/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java +++ b/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java @@ -1,12 +1,15 @@ package cn.nukkit.dispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.ItemID; + import java.util.HashMap; import java.util.Map; /** * @author CreeperFace */ -public class DispenseBehaviorRegister { +public final class DispenseBehaviorRegister { private static final Map behaviors = new HashMap<>(); private static DispenseBehavior defaultBehavior = new DefaultDispenseBehavior(); @@ -22,4 +25,59 @@ public static DispenseBehavior getBehavior(int id) { public static void removeDispenseBehavior(int id) { behaviors.remove(id); } + + public static void init() { + registerBehavior(ItemID.BOAT, new BoatDispenseBehavior()); + registerBehavior(ItemID.BUCKET, new BucketDispenseBehavior()); + registerBehavior(ItemID.DYE, new DyeDispenseBehavior()); + registerBehavior(ItemID.FIREWORKS, new FireworksDispenseBehavior()); + registerBehavior(ItemID.FLINT_AND_STEEL, new FlintAndSteelDispenseBehavior()); + registerBehavior(BlockID.SHULKER_BOX, new ShulkerBoxDispenseBehavior()); + registerBehavior(ItemID.SPAWN_EGG, new SpawnEggDispenseBehavior()); + registerBehavior(BlockID.TNT, new TNTDispenseBehavior()); + registerBehavior(ItemID.ARROW, new ProjectileDispenseBehavior("Arrow") { + @Override + protected double getMotion() { + return super.getMotion() * 1.5; + } + }); + //TODO: tipped arrow + //TODO: spectral arrow + registerBehavior(ItemID.EGG, new ProjectileDispenseBehavior("Egg")); + registerBehavior(ItemID.SNOWBALL, new ProjectileDispenseBehavior("Snowball")); + registerBehavior(ItemID.EXPERIENCE_BOTTLE, new ProjectileDispenseBehavior("ThrownExpBottle") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + registerBehavior(ItemID.SPLASH_POTION, new ProjectileDispenseBehavior("ThrownPotion") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); +// registerBehavior(ItemID.LINGERING_POTION, new ProjectileDispenseBehavior("LingeringPotion")); //TODO + registerBehavior(ItemID.TRIDENT, new ProjectileDispenseBehavior("ThrownTrident") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + } } diff --git a/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java new file mode 100644 index 00000000000..c065af3b646 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java @@ -0,0 +1,28 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.*; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.DyeColor; + +public class DyeDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (DyeColor.getByDyeData(item.getDamage()) == DyeColor.WHITE) { + if (target instanceof BlockCrops || target instanceof BlockSapling || target instanceof BlockTallGrass + || target instanceof BlockDoublePlant || target instanceof BlockMushroom) { + target.onActivate(item); + + } else { + this.success = false; + } + + return null; + } + + return super.dispense(block, face, item); + } +} diff --git a/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java index f01f4e1619f..750213c35ae 100644 --- a/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java @@ -1,15 +1,28 @@ package cn.nukkit.dispenser; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockAir; import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockLiquid; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBucket; +import cn.nukkit.math.BlockFace; /** * @author CreeperFace */ -public class EmptyBucketDispenseBehavior implements DispenseBehavior { +public class EmptyBucketDispenseBehavior extends DefaultDispenseBehavior { @Override - public void dispense(BlockDispenser block, Item item) { + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + if (target instanceof BlockLiquid && target.getDamage() == 0) { + target.level.setBlock(target, new BlockAir()); + return new ItemBucket(ItemBucket.getDamageByTarget(target.getId())); + } + + super.dispense(block, face, item); + return null; } } diff --git a/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java new file mode 100644 index 00000000000..6edfaa09655 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java @@ -0,0 +1,14 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; + +public class FireChargeDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + //TODO: firecharge + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java new file mode 100644 index 00000000000..d1133ba15cb --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java @@ -0,0 +1,22 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityFirework; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class FireworksDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).add(0, 0.2); + + EntityFirework firework = new EntityFirework(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos)); + firework.spawnToAll(); + + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java new file mode 100644 index 00000000000..3b098800d5d --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java @@ -0,0 +1,25 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; + +public class FlintAndSteelDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (target.getId() == BlockID.AIR) { + block.level.setBlock(target, Block.get(BlockID.FIRE)); + } else if (target.getId() == BlockID.TNT) { + target.onActivate(item); + } else { + this.success = false; + } + + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java index 52c2006408c..cac498f5793 100644 --- a/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java @@ -2,8 +2,8 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.item.Item; -import cn.nukkit.level.Position; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; @@ -11,33 +11,46 @@ /** * @author CreeperFace */ -public class ProjectileDispenseBehavior implements DispenseBehavior { +public class ProjectileDispenseBehavior extends DefaultDispenseBehavior { - private String entityType; - - public ProjectileDispenseBehavior() { - - } + private final String entityType; public ProjectileDispenseBehavior(String entity) { this.entityType = entity; } @Override - public void dispense(BlockDispenser source, Item item) { - Position dispensePos = Position.fromObject(source.getDispensePosition(), source.getLevel()); + public Item dispense(BlockDispenser source, BlockFace face, Item item) { + Vector3 dispensePos = source.getDispensePosition(); + CompoundTag nbt = Entity.getDefaultNBT(dispensePos); this.correctNBT(nbt); - BlockFace face = source.getFacing(); + Entity projectile = Entity.createEntity(getEntityType(), source.level.getChunk(dispensePos.getChunkX(), dispensePos.getChunkZ()), nbt); - Entity projectile = Entity.createEntity(getEntityType(), dispensePos.getLevel().getChunk(dispensePos.getFloorX(), dispensePos.getFloorZ()), nbt); - if (projectile == null) { - return; + if (!(projectile instanceof EntityProjectile)) { + return super.dispense(source, face, item); } - projectile.setMotion(new Vector3(face.getXOffset(), face.getYOffset() + 0.1f, face.getZOffset()).multiply(6)); + Vector3 motion = new Vector3(face.getXOffset(), face.getYOffset() + 0.1f, face.getZOffset()) + .normalize(); + + projectile.setMotion(motion); + ((EntityProjectile) projectile).inaccurate(getAccuracy()); + projectile.setMotion(projectile.getMotion().multiply(getMotion())); + + ((EntityProjectile) projectile).updateRotation(); + projectile.spawnToAll(); + return null; + } + + protected double getMotion() { + return 1.1; + } + + protected float getAccuracy() { + return 6; } protected String getEntityType() { diff --git a/src/main/java/cn/nukkit/dispenser/PumpkinDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/PumpkinDispenseBehavior.java new file mode 100644 index 00000000000..0803372fa92 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/PumpkinDispenseBehavior.java @@ -0,0 +1,17 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; + +public class PumpkinDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + //TODO: snowman / golem + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java new file mode 100644 index 00000000000..550bda8d5ca --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java @@ -0,0 +1,46 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockShulkerBox; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityShulkerBox; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; + +public class ShulkerBoxDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block shulkerBox = new BlockShulkerBox(); + Block target = block.getSide(face); + + this.success = block.level.getCollidingEntities(shulkerBox.getBoundingBox()).length == 0; + + if (this.success) { + BlockFace shulkerBoxFace = target.down().getId() == BlockID.AIR ? face : BlockFace.UP; + + CompoundTag nbt = BlockEntity.getDefaultCompound(target, BlockEntity.SHULKER_BOX); + nbt.putByte("facing", shulkerBoxFace.getIndex()); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + CompoundTag tag = item.getNamedTag(); + + if (tag != null) { + if (tag.contains("Items")) { + nbt.putList(tag.getList("Items")); + } + } + + new BlockEntityShulkerBox(block.level.getChunk(target.getChunkX(), target.getChunkZ()), nbt); + block.level.updateComparatorOutputLevel(target); + } + + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java new file mode 100644 index 00000000000..04a36857f72 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java @@ -0,0 +1,32 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityLiving; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class SpawnEggDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).add(0.5, 0.7, 0.5); + + Entity entity = Entity.createEntity(item.getDamage(), block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos)); + + this.success = entity != null; + + if (this.success) { + if (item.hasCustomName() && entity instanceof EntityLiving) { + entity.setNameTag(item.getCustomName()); + } + + entity.spawnToAll(); + return null; + } + + return super.dispense(block, face, item); + } +} diff --git a/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java new file mode 100644 index 00000000000..bbdb9aaf266 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java @@ -0,0 +1,23 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityPrimedTNT; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class TNTDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).add(0.5, 0, 0.5); + + EntityPrimedTNT tnt = new EntityPrimedTNT(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos)); + tnt.spawnToAll(); + + return null; + } + +} diff --git a/src/main/java/cn/nukkit/entity/Entity.java b/src/main/java/cn/nukkit/entity/Entity.java index 52a2f7070be..9d5ecc980fe 100644 --- a/src/main/java/cn/nukkit/entity/Entity.java +++ b/src/main/java/cn/nukkit/entity/Entity.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.*; +import cn.nukkit.blockentity.BlockEntityPistonArm; import cn.nukkit.entity.data.*; import cn.nukkit.event.Event; import cn.nukkit.event.entity.*; @@ -12,6 +13,7 @@ import cn.nukkit.event.player.PlayerInteractEvent.Action; import cn.nukkit.event.player.PlayerTeleportEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.level.*; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.*; @@ -117,66 +119,69 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_RIDER_ROTATION_LOCKED = 57; //byte public static final int DATA_RIDER_MAX_ROTATION = 58; //float public static final int DATA_RIDER_MIN_ROTATION = 59; //float - public static final int DATA_AREA_EFFECT_CLOUD_RADIUS = 60; //float - public static final int DATA_AREA_EFFECT_CLOUD_WAITING = 61; //int - public static final int DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 62; //int - public static final int DATA_SHULKER_PEEK_ID = 63; //int - public static final int DATA_SHULKER_ATTACH_FACE = 64; //byte - public static final int DATA_SHULKER_ATTACHED = 65; //short - public static final int DATA_SHULKER_ATTACH_POS = 66; //block coords - public static final int DATA_TRADING_PLAYER_EID = 67; //long - public static final int DATA_TRADING_CAREER = 68; - public static final int DATA_HAS_COMMAND_BLOCK = 69; - public static final int DATA_COMMAND_BLOCK_COMMAND = 70; //string - public static final int DATA_COMMAND_BLOCK_LAST_OUTPUT = 71; //string - public static final int DATA_COMMAND_BLOCK_TRACK_OUTPUT = 72; //byte - public static final int DATA_CONTROLLING_RIDER_SEAT_NUMBER = 73; //byte - public static final int DATA_STRENGTH = 74; //int - public static final int DATA_MAX_STRENGTH = 75; //int - public static final int DATA_SPELL_CASTING_COLOR = 76; //int - public static final int DATA_LIMITED_LIFE = 77; - public static final int DATA_ARMOR_STAND_POSE_INDEX = 78; // int - public static final int DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; // int - public static final int DATA_ALWAYS_SHOW_NAMETAG = 80; // byte - public static final int DATA_COLOR_2 = 81; // byte - public static final int DATA_NAME_AUTHOR = 82; - public static final int DATA_SCORE_TAG = 83; //String - public static final int DATA_BALLOON_ATTACHED_ENTITY = 84; // long - public static final int DATA_PUFFERFISH_SIZE = 85; - public static final int DATA_BUBBLE_TIME = 86; - public static final int DATA_AGENT = 87; - public static final int DATA_SITTING_AMOUNT = 88; - public static final int DATA_SITTING_AMOUNT_PREVIOUS = 89; - public static final int DATA_EATING_COUNTER = 90; - public static final int DATA_FLAGS_EXTENDED = 91; - public static final int DATA_LAYING_AMOUNT = 92; - public static final int DATA_LAYING_AMOUNT_PREVIOUS = 93; - public static final int DATA_DURATION = 94; - public static final int DATA_SPAWN_TIME = 95; - public static final int DATA_CHANGE_RATE = 96; - public static final int DATA_CHANGE_ON_PICKUP = 97; - public static final int DATA_PICKUP_COUNT = 98; - public static final int DATA_INTERACT_TEXT = 99; - public static final int DATA_TRADE_TIER = 100; - public static final int DATA_MAX_TRADE_TIER = 101; - public static final int DATA_TRADE_EXPERIENCE = 102; - public static final int DATA_SKIN_ID = 103; // int ??? - public static final int DATA_SPAWNING_FRAMES = 104; - public static final int DATA_COMMAND_BLOCK_TICK_DELAY = 105; - public static final int DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 106; - public static final int DATA_AMBIENT_SOUND_INTERVAL = 107; - public static final int DATA_AMBIENT_SOUND_INTERVAL_RANGE = 108; - public static final int DATA_AMBIENT_SOUND_EVENT_NAME = 109; - public static final int DATA_FALL_DAMAGE_MULTIPLIER = 110; - public static final int DATA_NAME_RAW_TEXT = 111; - public static final int DATA_CAN_RIDE_TARGET = 112; - public static final int DATA_LOW_TIER_CURED_DISCOUNT = 113; - public static final int DATA_HIGH_TIER_CURED_DISCOUNT = 114; - public static final int DATA_NEARBY_CURED_DISCOUNT = 115; - public static final int DATA_NEARBY_CURED_DISCOUNT_TIMESTAMP = 116; - public static final int DATA_HITBOX = 117; - public static final int DATA_IS_BUOYANT = 118; - public static final int DATA_BUOYANCY_DATA = 119; + public static final int DATA_RIDER_ROTATION_OFFSET = 60; + public static final int DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float + public static final int DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int + public static final int DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int + public static final int DATA_SHULKER_PEEK_ID = 64; //int + public static final int DATA_SHULKER_ATTACH_FACE = 65; //byte + public static final int DATA_SHULKER_ATTACHED = 66; //short + public static final int DATA_SHULKER_ATTACH_POS = 67; //block coords + public static final int DATA_TRADING_PLAYER_EID = 68; //long + public static final int DATA_TRADING_CAREER = 69; + public static final int DATA_HAS_COMMAND_BLOCK = 70; + public static final int DATA_COMMAND_BLOCK_COMMAND = 71; //string + public static final int DATA_COMMAND_BLOCK_LAST_OUTPUT = 72; //string + public static final int DATA_COMMAND_BLOCK_TRACK_OUTPUT = 73; //byte + public static final int DATA_CONTROLLING_RIDER_SEAT_NUMBER = 74; //byte + public static final int DATA_STRENGTH = 75; //int + public static final int DATA_MAX_STRENGTH = 76; //int + public static final int DATA_SPELL_CASTING_COLOR = 77; //int + public static final int DATA_LIMITED_LIFE = 78; + public static final int DATA_ARMOR_STAND_POSE_INDEX = 79; // int + public static final int DATA_ENDER_CRYSTAL_TIME_OFFSET = 80; // int + public static final int DATA_ALWAYS_SHOW_NAMETAG = 81; // byte + public static final int DATA_COLOR_2 = 82; // byte + public static final int DATA_NAME_AUTHOR = 83; + public static final int DATA_SCORE_TAG = 84; // String + public static final int DATA_BALLOON_ATTACHED_ENTITY = 85; // long + public static final int DATA_PUFFERFISH_SIZE = 86; + public static final int DATA_BUBBLE_TIME = 87; + public static final int DATA_AGENT = 88; + public static final int DATA_SITTING_AMOUNT = 89; + public static final int DATA_SITTING_AMOUNT_PREVIOUS = 90; + public static final int DATA_EATING_COUNTER = 91; + public static final int DATA_FLAGS_EXTENDED = 92; + public static final int DATA_LAYING_AMOUNT = 93; + public static final int DATA_LAYING_AMOUNT_PREVIOUS = 94; + public static final int DATA_DURATION = 95; + public static final int DATA_SPAWN_TIME = 96; + public static final int DATA_CHANGE_RATE = 97; + public static final int DATA_CHANGE_ON_PICKUP = 98; + public static final int DATA_PICKUP_COUNT = 99; + public static final int DATA_INTERACT_TEXT = 100; + public static final int DATA_TRADE_TIER = 101; + public static final int DATA_MAX_TRADE_TIER = 102; + public static final int DATA_TRADE_EXPERIENCE = 103; + public static final int DATA_SKIN_ID = 104; // int ??? + public static final int DATA_SPAWNING_FRAMES = 105; + public static final int DATA_COMMAND_BLOCK_TICK_DELAY = 106; + public static final int DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 107; + public static final int DATA_AMBIENT_SOUND_INTERVAL = 108; + public static final int DATA_AMBIENT_SOUND_INTERVAL_RANGE = 109; + public static final int DATA_AMBIENT_SOUND_EVENT_NAME = 110; + public static final int DATA_FALL_DAMAGE_MULTIPLIER = 111; + public static final int DATA_NAME_RAW_TEXT = 112; + public static final int DATA_CAN_RIDE_TARGET = 113; + public static final int DATA_LOW_TIER_CURED_DISCOUNT = 114; + public static final int DATA_HIGH_TIER_CURED_DISCOUNT = 115; + public static final int DATA_NEARBY_CURED_DISCOUNT = 116; + public static final int DATA_NEARBY_CURED_DISCOUNT_TIMESTAMP = 117; + public static final int DATA_HITBOX = 118; + public static final int DATA_IS_BUOYANT = 119; + public static final int DATA_FREEZING_EFFECT_STRENGTH = 120; + public static final int DATA_BUOYANCY_DATA = 121; + public static final int DATA_GOAT_HORN_COUNT = 122; // Flags public static final int DATA_FLAG_ONFIRE = 0; @@ -274,6 +279,7 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_FLAG_CELEBRATING = 92; public static final int DATA_FLAG_ADMIRING = 93; public static final int DATA_FLAG_CELEBRATING_SPECIAL = 94; + public static final int DATA_FLAG_RAM_ATTACK = 96; public static long entityCount = 1; @@ -431,7 +437,7 @@ protected void initEntity() { continue; } - effect.setAmplifier(e.getByte("Amplifier")).setDuration(e.getInt("Duration")).setVisible(e.getBoolean("showParticles")); + effect.setAmplifier(e.getByte("Amplifier")).setDuration(e.getInt("Duration")).setVisible(e.getBoolean("ShowParticles")); this.addEffect(effect); } @@ -1089,7 +1095,43 @@ public boolean attack(EntityDamageEvent source) { this.setAbsorption(Math.max(0, this.getAbsorption() + source.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION))); } setLastDamageCause(source); - setHealth(getHealth() - source.getFinalDamage()); + + float newHealth = getHealth() - source.getFinalDamage(); + if (newHealth < 1 && this instanceof Player) { + if (source.getCause() != DamageCause.VOID && source.getCause() != DamageCause.SUICIDE) { + Player p = (Player) this; + boolean totem = false; + if (p.getOffhandInventory().getItem(0).getId() == ItemID.TOTEM) { + p.getOffhandInventory().clear(0); + totem = true; + } else if (p.getInventory().getItemInHand().getId() == ItemID.TOTEM) { + p.getInventory().clear(p.getInventory().getHeldItemIndex()); + totem = true; + } + if (totem) { + this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_TOTEM); + this.getLevel().addParticleEffect(this, ParticleEffect.TOTEM); + + this.extinguish(); + this.removeAllEffects(); + this.setHealth(1); + + this.addEffect(Effect.getEffect(Effect.REGENERATION).setDuration(800).setAmplifier(1)); + this.addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(800)); + this.addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(100).setAmplifier(1)); + + EntityEventPacket pk = new EntityEventPacket(); + pk.eid = this.getId(); + pk.event = EntityEventPacket.CONSUME_TOTEM; + p.dataPacket(pk); + + source.setCancelled(true); + return false; + } + } + } + + setHealth(newHealth); return true; } @@ -1302,7 +1344,7 @@ public boolean entityBaseTick(int tickDiff) { if (this.y <= -16 && this.isAlive()) { if (this instanceof Player) { Player player = (Player) this; - if (player.getGamemode() != 1) this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); + if (!player.isCreative()) this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); } else { this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); hasUpdate = true; @@ -1586,6 +1628,10 @@ public void setAbsorption(float absorption) { } } + public boolean canBePushed() { + return true; + } + public BlockFace getDirection() { double rotation = this.yaw % 360; if (rotation < 0) { @@ -1641,6 +1687,13 @@ public void fall(float fallDistance) { } float damage = (float) Math.floor(fallDistance - 3 - (this.hasEffect(Effect.JUMP) ? this.getEffect(Effect.JUMP).getAmplifier() + 1 : 0)); + + Block down = this.level.getBlock(this.floor().down()); + + if(down instanceof BlockHayBale) { + damage -= (damage * 0.8f); + } + if (damage > 0) { if (!this.isPlayer || level.getGameRules().getBoolean(GameRule.FALL_DAMAGE)) { this.attack(new EntityDamageEvent(this, DamageCause.FALL, damage)); @@ -1648,7 +1701,6 @@ public void fall(float fallDistance) { } if (fallDistance > 0.75) { - Block down = this.level.getBlock(this.floor().down()); if (down.getId() == Item.FARMLAND) { Event ev; @@ -1732,6 +1784,10 @@ public void onStruckByLightning(Entity entity) { } } + public void onPushByPiston(BlockEntityPistonArm piston) { + + } + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { return onInteract(player, item); } diff --git a/src/main/java/cn/nukkit/entity/EntityCreature.java b/src/main/java/cn/nukkit/entity/EntityCreature.java index 0c8ac3d46fc..705468fc943 100644 --- a/src/main/java/cn/nukkit/entity/EntityCreature.java +++ b/src/main/java/cn/nukkit/entity/EntityCreature.java @@ -1,6 +1,9 @@ package cn.nukkit.entity; +import cn.nukkit.Player; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; /** @@ -11,4 +14,30 @@ public abstract class EntityCreature extends EntityLiving { public EntityCreature(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + + // Armor stands, when implemented, should also check this. + @Override + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { + if (item.getId() == Item.NAME_TAG) { + return applyNameTag(player, item); + } + return false; + } + + // Structured like this so I can override nametags in player and dragon classes + // without overriding onInteract. + protected boolean applyNameTag(Player player, Item item){ + if (item.hasCustomName()) { + this.setNameTag(item.getCustomName()); + this.setNameTagVisible(true); + + if(!player.isCreative()) { + player.getInventory().removeItem(item); + } + // Set entity as persistent. + return true; + } + return false; + } + } diff --git a/src/main/java/cn/nukkit/entity/EntityHuman.java b/src/main/java/cn/nukkit/entity/EntityHuman.java index 9209aa514c8..d271fe782b0 100644 --- a/src/main/java/cn/nukkit/entity/EntityHuman.java +++ b/src/main/java/cn/nukkit/entity/EntityHuman.java @@ -107,6 +107,11 @@ protected void initEntity() { if (skinTag.contains("ModelId")) { newSkin.setSkinId(skinTag.getString("ModelId")); } + + if (skinTag.contains("PlayFabID")) { + newSkin.setPlayFabId(skinTag.getString("PlayFabID")); + } + if (skinTag.contains("Data")) { byte[] data = skinTag.getByteArray("Data"); if (skinTag.contains("SkinImageWidth") && skinTag.contains("SkinImageHeight")) { @@ -159,7 +164,8 @@ protected void initEntity() { byte[] image = animationTag.getByteArray("Image"); int width = animationTag.getInt("ImageWidth"); int height = animationTag.getInt("ImageHeight"); - newSkin.getAnimations().add(new SkinAnimation(new SerializedImage(width, height, image), type, frames)); + int expression = animationTag.getInt("AnimationExpression"); + newSkin.getAnimations().add(new SkinAnimation(new SerializedImage(width, height, image), type, frames, expression)); } } if (skinTag.contains("ArmSize")) { @@ -240,6 +246,7 @@ public void saveNBT() { .putInt("Type", animation.type) .putInt("ImageWidth", animation.image.width) .putInt("ImageHeight", animation.image.height) + .putInt("AnimationExpression", animation.expression) .putByteArray("Image", animation.image.data)); } skinTag.putList(animationsTag); @@ -266,6 +273,10 @@ public void saveNBT() { .putList(colors)); } } + + if (!this.getSkin().getPlayFabId().isEmpty()) { + skinTag.putString("PlayFabID", this.getSkin().getPlayFabId()); + } this.namedTag.putCompound("Skin", skinTag); } } @@ -285,7 +296,7 @@ public void spawnTo(Player player) { } if (this instanceof Player) - this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getName(), this.skin, ((Player) this).getLoginChainData().getXUID(), new Player[]{player}); + this.server.updatePlayerListData(this.getUniqueId(), this.getId(), ((Player) this).getDisplayName(), this.skin, ((Player) this).getLoginChainData().getXUID(), new Player[]{player}); else this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getName(), this.skin, new Player[]{player}); @@ -320,7 +331,7 @@ public void spawnTo(Player player) { } if (!(this instanceof Player)) { - this.server.removePlayerListData(this.getUniqueId(), new Player[]{player}); + this.server.removePlayerListData(this.getUniqueId(), player); } } } diff --git a/src/main/java/cn/nukkit/entity/EntityHumanType.java b/src/main/java/cn/nukkit/entity/EntityHumanType.java index 32dc4aa4385..46445ac67c7 100644 --- a/src/main/java/cn/nukkit/entity/EntityHumanType.java +++ b/src/main/java/cn/nukkit/entity/EntityHumanType.java @@ -244,4 +244,9 @@ public void setOnFire(int seconds) { super.setOnFire(seconds); } + + @Override + protected boolean applyNameTag(Player player, Item item) { + return false; + } } diff --git a/src/main/java/cn/nukkit/entity/EntityLiving.java b/src/main/java/cn/nukkit/entity/EntityLiving.java index c086e2588fb..baae0c55054 100644 --- a/src/main/java/cn/nukkit/entity/EntityLiving.java +++ b/src/main/java/cn/nukkit/entity/EntityLiving.java @@ -56,7 +56,7 @@ protected float getDrag() { protected float movementSpeed = 0.1f; - protected int turtleTicks = 200; + protected int turtleTicks = 0; @Override protected void initEntity() { @@ -206,18 +206,17 @@ public boolean entityBaseTick() { public boolean entityBaseTick(int tickDiff) { Timings.livingEntityBaseTickTimer.startTiming(); boolean isBreathing = !this.isInsideOfWater(); - if (this instanceof Player && (((Player) this).isCreative() || ((Player) this).isSpectator())) { - isBreathing = true; - } if (this instanceof Player) { - if (!isBreathing && ((Player) this).getInventory().getHelmet() instanceof ItemTurtleShell) { - if (turtleTicks > 0) { - isBreathing = true; - turtleTicks--; - } - } else { + if (isBreathing && ((Player) this).getInventory().getHelmet() instanceof ItemTurtleShell) { turtleTicks = 200; + } else if (turtleTicks > 0) { + isBreathing = true; + turtleTicks--; + } + + if ((((Player) this).isCreative() || ((Player) this).isSpectator())) { + isBreathing = true; } } @@ -232,7 +231,7 @@ public boolean entityBaseTick(int tickDiff) { this.attack(new EntityDamageEvent(this, DamageCause.SUFFOCATION, 1)); } - if (this.isOnLadder() || this.hasEffect(Effect.LEVITATION)) { + if (this.isOnLadder() || this.hasEffect(Effect.LEVITATION) || this.hasEffect(Effect.SLOW_FALLING)) { this.resetFallDistance(); } diff --git a/src/main/java/cn/nukkit/entity/data/Skin.java b/src/main/java/cn/nukkit/entity/data/Skin.java index 94c7281a9af..5c15ea1f637 100644 --- a/src/main/java/cn/nukkit/entity/data/Skin.java +++ b/src/main/java/cn/nukkit/entity/data/Skin.java @@ -33,6 +33,7 @@ public class Skin { private final String fullSkinId = UUID.randomUUID().toString(); private String skinId; + private String playFabId = ""; private String skinResourcePatch = GEOMETRY_CUSTOM; private SerializedImage skinData; private final List animations = new ArrayList<>(); @@ -262,6 +263,14 @@ public String getFullSkinId() { return fullSkinId; } + public void setPlayFabId(String playFabId) { + this.playFabId = playFabId; + } + + public String getPlayFabId() { + return this.playFabId; + } + private static SerializedImage parseBufferedImage(BufferedImage image) { FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream(); for (int y = 0; y < image.getHeight(); y++) { diff --git a/src/main/java/cn/nukkit/entity/item/EntityBoat.java b/src/main/java/cn/nukkit/entity/item/EntityBoat.java index cf01f678ce8..e4bbcf68264 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityBoat.java +++ b/src/main/java/cn/nukkit/entity/item/EntityBoat.java @@ -16,7 +16,6 @@ import cn.nukkit.level.GameRule; import cn.nukkit.level.Location; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.particle.SmokeParticle; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.NukkitMath; import cn.nukkit.math.Vector3; @@ -34,8 +33,6 @@ public class EntityBoat extends EntityVehicle { public static final int NETWORK_ID = 90; - public static final int DATA_WOOD_ID = 20; - public static final Vector3f RIDER_PLAYER_OFFSET = new Vector3f(0, 1.02001f, 0); public static final Vector3f RIDER_OFFSET = new Vector3f(0, -0.2f, 0); @@ -50,6 +47,7 @@ public class EntityBoat extends EntityVehicle { public static final double SINKING_MAX_SPEED = 0.005; protected boolean sinking = true; + public int woodID; public EntityBoat(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -62,7 +60,7 @@ public EntityBoat(FullChunk chunk, CompoundTag nbt) { protected void initEntity() { super.initEntity(); - this.dataProperties.putByte(DATA_WOOD_ID, this.namedTag.getByte("woodID")); + this.dataProperties.putInt(DATA_VARIANT, woodID = this.namedTag.getByte("woodID")); } @Override @@ -119,9 +117,6 @@ public void close() { for (Entity linkedEntity : this.passengers) { linkedEntity.riding = null; } - - SmokeParticle particle = new SmokeParticle(this); - this.level.addParticle(particle); } @Override @@ -374,7 +369,7 @@ public void onPaddle(AnimatePacket.Action animation, float value) { public void applyEntityCollision(Entity entity) { if (this.riding == null && entity.riding != this && !entity.passengers.contains(this)) { if (!entity.boundingBox.intersectsWith(this.boundingBox.grow(0.20000000298023224, -0.1, 0.20000000298023224)) - || entity instanceof Player && ((Player) entity).getGamemode() == Player.SPECTATOR) { + || entity instanceof Player && ((Player) entity).isSpectator()) { return; } @@ -414,7 +409,14 @@ public void kill() { super.kill(); if (level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - this.level.dropItem(this, new ItemBoat()); + this.level.dropItem(this, new ItemBoat(this.woodID)); } } + + @Override + public void saveNBT() { + super.saveNBT(); + + this.namedTag.putByte("woodID", this.woodID); + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java b/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java index 9126f222e8e..992397dad99 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java +++ b/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java @@ -36,6 +36,7 @@ protected void initEntity() { } this.fireProof = true; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); } @Override diff --git a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java index 3d40871c72b..574b5be0674 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java @@ -1,6 +1,7 @@ package cn.nukkit.entity.item; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLiquid; import cn.nukkit.entity.Entity; import cn.nukkit.entity.data.IntEntityData; import cn.nukkit.event.entity.EntityBlockChangeEvent; @@ -86,6 +87,7 @@ protected void initEntity() { } this.fireProof = true; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); setDataProperty(new IntEntityData(DATA_VARIANT, GlobalBlockPalette.getOrCreateRuntimeId(this.getBlock(), this.getDamage()))); } @@ -133,9 +135,37 @@ public boolean onUpdate(int currentTick) { if (onGround) { close(); Block block = level.getBlock(pos); - if (block.getId() > 0 && block.isTransparent() && !block.canBeReplaced()) { - if (this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - getLevel().dropItem(this, Item.get(this.getBlock(), this.getDamage(), 1)); + + Vector3 floorPos = (new Vector3(x - 0.5, y, z - 0.5)).floor(); + Block floorBlock = this.level.getBlock(floorPos); + if (this.getBlock() == Block.SNOW_LAYER && floorBlock.getId() == Block.SNOW_LAYER && (floorBlock.getDamage() & 0x7) != 0x7) { + int mergedHeight = (floorBlock.getDamage() & 0x7) + 1 + (this.getDamage() & 0x7) + 1; + if (mergedHeight > 8) { + EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, 0x7)); + this.server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.level.setBlock(floorPos, event.getTo(), true); + + Vector3 abovePos = floorPos.up(); + Block aboveBlock = this.level.getBlock(abovePos); + if (aboveBlock.getId() == Block.AIR) { + EntityBlockChangeEvent event2 = new EntityBlockChangeEvent(this, aboveBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 8 - 1)); + this.server.getPluginManager().callEvent(event2); + if (!event2.isCancelled()) { + this.level.setBlock(abovePos, event2.getTo(), true); + } + } + } + } else { + EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 1)); + this.server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.level.setBlock(floorPos, event.getTo(), true); + } + } + } else if (block.getId() > 0 && block.isTransparent() && !block.canBeReplaced() || this.getBlock() == Block.SNOW_LAYER && block instanceof BlockLiquid) { + if (this.getBlock() != Block.SNOW_LAYER ? this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS) : this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { + getLevel().dropItem(this, Block.get(this.getBlock(), this.getDamage()).toItem()); } } else { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, block, Block.get(getBlock(), getDamage())); diff --git a/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java b/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java index 57c1fdac81b..d535ead7113 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java @@ -19,9 +19,6 @@ import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.DoubleTag; -import cn.nukkit.nbt.tag.FloatTag; -import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.network.protocol.EntityEventPacket; @@ -159,17 +156,17 @@ public void fishBites() { EntityEventPacket pk = new EntityEventPacket(); pk.eid = this.getId(); pk.event = EntityEventPacket.FISH_HOOK_HOOK; - Server.broadcastPacket(this.level.getPlayers().values(), pk); + Server.broadcastPacket(this.getViewers().values(), pk); EntityEventPacket bubblePk = new EntityEventPacket(); bubblePk.eid = this.getId(); bubblePk.event = EntityEventPacket.FISH_HOOK_BUBBLE; - Server.broadcastPacket(this.level.getPlayers().values(), bubblePk); + Server.broadcastPacket(this.getViewers().values(), bubblePk); EntityEventPacket teasePk = new EntityEventPacket(); teasePk.eid = this.getId(); teasePk.event = EntityEventPacket.FISH_HOOK_TEASE; - Server.broadcastPacket(this.level.getPlayers().values(), teasePk); + Server.broadcastPacket(this.getViewers().values(), teasePk); Random random = new Random(); for (int i = 0; i < 5; i++) { @@ -238,7 +235,7 @@ public void reelLine() { EntityEventPacket pk = new EntityEventPacket(); pk.eid = this.getId(); pk.event = EntityEventPacket.FISH_HOOK_TEASE; - Server.broadcastPacket(this.level.getPlayers().values(), pk); + Server.broadcastPacket(this.getViewers().values(), pk); } if (!this.closed) { this.kill(); diff --git a/src/main/java/cn/nukkit/entity/item/EntityItem.java b/src/main/java/cn/nukkit/entity/item/EntityItem.java index 5b3284066c6..4f2234c403a 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityItem.java +++ b/src/main/java/cn/nukkit/entity/item/EntityItem.java @@ -105,6 +105,11 @@ protected void initEntity() { this.item = NBTIO.getItemHelper(this.namedTag.getCompound("Item")); this.setDataFlag(DATA_FLAGS, DATA_FLAG_GRAVITY, true); + int id = this.item.getId(); + if (id >= Item.NETHERITE_INGOT && id <= Item.NETHERITE_SCRAP) { + this.fireProof = true; // Netherite items are fireproof + } + this.server.getPluginManager().callEvent(new ItemSpawnEvent(this)); } @@ -134,7 +139,7 @@ public boolean onUpdate(int currentTick) { this.lastUpdate = currentTick; this.timing.startTiming(); - + if (this.age % 60 == 0 && this.onGround && this.getItem() != null && this.isAlive()) { if (this.getItem().getCount() < this.getItem().getMaxStackSize()) { for (Entity entity : this.getLevel().getNearbyEntities(getBoundingBox().grow(1, 1, 1), this, false)) { @@ -146,7 +151,7 @@ public boolean onUpdate(int currentTick) { if (!closeItem.equals(getItem(), true, true)) { continue; } - if(!entity.isOnGround()) { + if (!entity.isOnGround()) { continue; } int newAmount = this.getItem().getCount() + closeItem.getCount(); @@ -159,7 +164,7 @@ public boolean onUpdate(int currentTick) { packet.eid = getId(); packet.data = newAmount; packet.event = EntityEventPacket.MERGE_ITEMS; - Server.broadcastPacket(this.getLevel().getPlayers().values(), packet); + Server.broadcastPacket(this.getViewers().values(), packet); } } } @@ -167,7 +172,7 @@ public boolean onUpdate(int currentTick) { boolean hasUpdate = this.entityBaseTick(tickDiff); - if (isInsideOfFire()) { + if (!this.fireProof && this.isInsideOfFire()) { this.kill(); } diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java index 3341469cbea..6eb7a70ecaa 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java @@ -20,7 +20,6 @@ import cn.nukkit.level.GameRule; import cn.nukkit.level.Location; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.particle.SmokeParticle; import cn.nukkit.math.MathHelper; import cn.nukkit.math.NukkitMath; import cn.nukkit.math.Vector3; @@ -261,9 +260,6 @@ public void close() { entity.riding = null; } } - - SmokeParticle particle = new SmokeParticle(this); - level.addParticle(particle); } @Override @@ -281,7 +277,7 @@ public boolean onInteract(Player p, Item item, Vector3 clickedPos) { @Override public void applyEntityCollision(cn.nukkit.entity.Entity entity) { - if (entity != riding && !(entity instanceof Player && ((Player) entity).getGamemode() == Player.SPECTATOR)) { + if (entity != riding && !(entity instanceof Player && ((Player) entity).isSpectator())) { if (entity instanceof EntityLiving && !(entity instanceof EntityHuman) && motionX * motionX + motionZ * motionZ > 0.01D diff --git a/src/main/java/cn/nukkit/entity/item/EntityPainting.java b/src/main/java/cn/nukkit/entity/item/EntityPainting.java index f20602f61b3..25aba1f81a3 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityPainting.java +++ b/src/main/java/cn/nukkit/entity/item/EntityPainting.java @@ -1,6 +1,7 @@ package cn.nukkit.entity.item; import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntityPistonArm; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityHanging; import cn.nukkit.event.entity.EntityDamageByEntityEvent; @@ -8,6 +9,10 @@ import cn.nukkit.item.ItemPainting; import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockFace.Axis; +import cn.nukkit.math.SimpleAxisAlignedBB; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.AddPaintingPacket; import cn.nukkit.network.protocol.DataPacket; @@ -26,6 +31,10 @@ public class EntityPainting extends EntityHanging { public final static Motive[] motives = Motive.values(); private Motive motive; + private float width; + private float length; + private float height; + public EntityPainting(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -34,6 +43,21 @@ public static Motive getMotive(String name) { return Motive.BY_NAME.getOrDefault(name, Motive.KEBAB); } + @Override + public float getWidth() { + return width; + } + + @Override + public float getLength() { + return length; + } + + @Override + public float getHeight() { + return height; + } + @Override public int getNetworkId() { return NETWORK_ID; @@ -41,8 +65,38 @@ public int getNetworkId() { @Override protected void initEntity() { - super.initEntity(); this.motive = getMotive(this.namedTag.getString("Motive")); + + if (this.motive != null) { + BlockFace face = getHorizontalFacing(); + + Vector3 size = new Vector3(this.motive.width, this.motive.height, this.motive.width).multiply(0.5); + + if (face.getAxis() == Axis.Z) { + size.z = 0.5; + } else { + size.x = 0.5; + } + + this.width = (float) size.x; + this.length = (float) size.z; + this.height = (float) size.y; + + this.boundingBox = new SimpleAxisAlignedBB( + this.x - size.x, + this.y - size.y, + this.z - size.z, + this.x + size.x, + this.y + size.y, + this.z + size.z + ); + } else { + this.width = 0; + this.height = 0; + this.length = 0; + } + + super.initEntity(); } @Override @@ -63,7 +117,7 @@ public boolean attack(EntityDamageEvent source) { if (super.attack(source)) { if (source instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); - if (damager instanceof Player && ((Player) damager).isSurvival() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { + if (damager instanceof Player && (((Player) damager).isAdventure() || ((Player) damager).isSurvival()) && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { this.level.dropItem(this, new ItemPainting()); } } @@ -80,6 +134,15 @@ public void saveNBT() { this.namedTag.putString("Motive", this.motive.title); } + @Override + public void onPushByPiston(BlockEntityPistonArm piston) { + if (this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { + this.level.dropItem(this, new ItemPainting()); + } + + this.close(); + } + public Motive getArt() { return getMotive(); } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java b/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java index 31320312d8f..230808a5bae 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java @@ -1,5 +1,7 @@ package cn.nukkit.entity.mob; +import cn.nukkit.Player; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; @@ -35,6 +37,11 @@ public void initEntity() { this.setMaxHealth(200); } + @Override + protected boolean applyNameTag(Player player, Item item) { + return false; + } + @Override public String getName() { return "EnderDragon"; diff --git a/src/main/java/cn/nukkit/entity/mob/EntityMob.java b/src/main/java/cn/nukkit/entity/mob/EntityMob.java index 1a953a33857..f80fb439067 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityMob.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityMob.java @@ -1,10 +1,7 @@ package cn.nukkit.entity.mob; -import cn.nukkit.Player; import cn.nukkit.entity.EntityCreature; -import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; /** @@ -17,16 +14,4 @@ public EntityMob(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - @Override - public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - if (item.getId() == Item.NAME_TAG) { - if (item.hasCustomName()) { - this.setNameTag(item.getCustomName()); - this.setNameTagVisible(true); - player.getInventory().removeItem(item); - return true; - } - } - return false; - } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java index aa735018517..a8943d2b50c 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java @@ -1,12 +1,10 @@ package cn.nukkit.entity.passive; -import cn.nukkit.Player; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityAgeable; import cn.nukkit.entity.EntityCreature; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; /** @@ -27,16 +25,4 @@ public boolean isBreedingItem(Item item) { return item.getId() == Item.WHEAT; //default } - @Override - public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - if (item.getId() == Item.NAME_TAG) { - if (item.hasCustomName()) { - this.setNameTag(item.getCustomName()); - this.setNameTagVisible(true); - player.getInventory().removeItem(item); - return true; - } - } - return false; - } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java b/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java index 8660ef827a6..890b11d3c2a 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java @@ -51,9 +51,6 @@ public float getDrag() { return 0.01f; } - protected float gravity = 0.05f; - protected float drag = 0.01f; - public EntityArrow(FullChunk chunk, CompoundTag nbt) { this(chunk, nbt, null); } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java b/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java index 719bfaac2a6..3f38522a2cf 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java @@ -4,6 +4,7 @@ import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.data.LongEntityData; import cn.nukkit.entity.item.EntityEndCrystal; +import cn.nukkit.entity.mob.EntityBlaze; import cn.nukkit.event.entity.*; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.level.MovingObjectPosition; @@ -13,6 +14,9 @@ import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + /** * author: MagicDroidX * Nukkit Project @@ -59,7 +63,7 @@ public boolean attack(EntityDamageEvent source) { public void onCollideWithEntity(Entity entity) { this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromEntity(entity))); - float damage = this.getResultDamage(); + float damage = this instanceof EntitySnowball && entity instanceof EntityBlaze ? 3 : this.getResultDamage(); EntityDamageEvent ev; if (this.shootingEntity == null) { @@ -125,6 +129,8 @@ public boolean onUpdate(int currentTick) { if (!this.isCollided) { this.motionY -= this.getGravity(); + this.motionX *= 1 - this.getDrag(); + this.motionZ *= 1 - this.getDrag(); } Vector3 moveVector = new Vector3(this.x + this.motionX, this.y + this.motionY, this.z + this.motionZ); @@ -137,7 +143,7 @@ public boolean onUpdate(int currentTick) { for (Entity entity : list) { if (/*!entity.canCollideWith(this) or */ (entity == this.shootingEntity && this.ticksLived < 5) - ) { + ) { continue; } @@ -183,9 +189,7 @@ public boolean onUpdate(int currentTick) { } if (!this.hadCollision || Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionY) > 0.00001 || Math.abs(this.motionZ) > 0.00001) { - double f = Math.sqrt((this.motionX * this.motionX) + (this.motionZ * this.motionZ)); - this.yaw = Math.atan2(this.motionX, this.motionZ) * 180 / Math.PI; - this.pitch = Math.atan2(this.motionY, f) * 180 / Math.PI; + updateRotation(); hasUpdate = true; } @@ -195,4 +199,18 @@ public boolean onUpdate(int currentTick) { return hasUpdate; } + + public void updateRotation() { + double f = Math.sqrt((this.motionX * this.motionX) + (this.motionZ * this.motionZ)); + this.yaw = Math.atan2(this.motionX, this.motionZ) * 180 / Math.PI; + this.pitch = Math.atan2(this.motionY, f) * 180 / Math.PI; + } + + public void inaccurate(float modifier) { + Random rand = ThreadLocalRandom.current(); + + this.motionX += rand.nextGaussian() * 0.007499999832361937 * modifier; + this.motionY += rand.nextGaussian() * 0.007499999832361937 * modifier; + this.motionZ += rand.nextGaussian() * 0.007499999832361937 * modifier; + } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java b/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java index 6830b186e03..c012fc6251f 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java @@ -14,9 +14,6 @@ import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.DoubleTag; -import cn.nukkit.nbt.tag.FloatTag; -import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; @@ -150,11 +147,6 @@ public boolean onUpdate(int currentTick) { this.setCritical(false); } - if (this.age > 1200) { - this.close(); - hasUpdate = true; - } - this.timing.stopTiming(); return hasUpdate; diff --git a/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java b/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java new file mode 100644 index 00000000000..aee8e00eefe --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java @@ -0,0 +1,53 @@ +package cn.nukkit.event.block; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +public class AnvilDamageEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final int oldDamage; + private int newDamage; + private DamageCause cause; + private final Player player; + + public AnvilDamageEvent(Block block, int oldDamage, int newDamage, DamageCause cause, Player player) { + super(block); + this.oldDamage = oldDamage; + this.newDamage = newDamage; + this.cause = cause; + this.player = player; + } + + public int getOldDamage() { + return this.oldDamage; + } + + public int getNewDamage() { + return this.newDamage; + } + + public void setNewDamage(int newDamage) { + this.newDamage = newDamage; + } + + public DamageCause getCause() { + return this.cause; + } + + public Player getPlayer() { + return this.player; + } + + public enum DamageCause { + USE, + FALL + } +} diff --git a/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java b/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java index c2b7995cae9..b499f8d23d8 100644 --- a/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java @@ -77,9 +77,13 @@ public void setDrops(Item[] drops) { this.blockDrops = drops; } - public int getDropExp() { return this.blockXP; } + public int getDropExp() { + return this.blockXP; + } - public void setDropExp(int xp) { this.blockXP = xp; } + public void setDropExp(int xp) { + this.blockXP = xp; + } public void setInstaBreak(boolean instaBreak) { this.instaBreak = instaBreak; diff --git a/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java b/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java index ab5d0184f07..81930ae1ae7 100644 --- a/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java @@ -5,7 +5,10 @@ /** * Created by CreeperFace on 2.8.2017. + * + * @deprecated Use BlockPistonEvent */ +@Deprecated public class BlockPistonChangeEvent extends BlockEvent { private static final HandlerList handlers = new HandlerList(); diff --git a/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java b/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java new file mode 100644 index 00000000000..2d1cc3debd7 --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java @@ -0,0 +1,53 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockPistonBase; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.math.BlockFace; + +import java.util.ArrayList; +import java.util.List; + +public class BlockPistonEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final BlockFace direction; + private final List blocks; + private final List destroyedBlocks; + private final boolean extending; + + public BlockPistonEvent(BlockPistonBase piston, BlockFace direction, List blocks, List destroyedBlocks, boolean extending) { + super(piston); + this.direction = direction; + this.blocks = blocks; + this.destroyedBlocks = destroyedBlocks; + this.extending = extending; + } + + public BlockFace getDirection() { + return direction; + } + + public List getBlocks() { + return new ArrayList<>(blocks); + } + + public List getDestroyedBlocks() { + return destroyedBlocks; + } + + public boolean isExtending() { + return extending; + } + + @Override + public BlockPistonBase getBlock() { + return (BlockPistonBase) super.getBlock(); + } +} diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryPickupTridentEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryPickupTridentEvent.java new file mode 100644 index 00000000000..8514d04c890 --- /dev/null +++ b/src/main/java/cn/nukkit/event/inventory/InventoryPickupTridentEvent.java @@ -0,0 +1,26 @@ +package cn.nukkit.event.inventory; + +import cn.nukkit.entity.projectile.EntityThrownTrident; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.inventory.Inventory; + +public class InventoryPickupTridentEvent extends InventoryEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final EntityThrownTrident trident; + + public InventoryPickupTridentEvent(Inventory inventory, EntityThrownTrident trident) { + super(inventory); + this.trident = trident; + } + + public EntityThrownTrident getTrident() { + return trident; + } +} diff --git a/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java b/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java new file mode 100644 index 00000000000..dd7acdc3df3 --- /dev/null +++ b/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java @@ -0,0 +1,55 @@ +package cn.nukkit.event.inventory; + +import cn.nukkit.Player; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.inventory.AnvilInventory; +import cn.nukkit.item.Item; + +public class RepairItemEvent extends InventoryEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private Item oldItem; + private Item newItem; + private Item materialItem; + private int cost; + private Player player; + + public RepairItemEvent(AnvilInventory inventory, Item oldItem, Item newItem, Item materialItem, int cost, Player player) { + super(inventory); + this.oldItem = oldItem; + this.newItem = newItem; + this.materialItem = materialItem; + this.cost = cost; + this.player = player; + } + + public Item getOldItem() { + return this.oldItem; + } + + public Item getNewItem() { + return this.newItem; + } + + public Item getMaterialItem() { + return this.materialItem; + } + + public int getCost() { + return this.cost; + } + + public void setCost(int cost) { + this.cost = cost; + } + + public Player getPlayer() { + return this.player; + } +} diff --git a/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java b/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java new file mode 100644 index 00000000000..1bea3eeb218 --- /dev/null +++ b/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java @@ -0,0 +1,44 @@ +package cn.nukkit.event.level; + +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +import java.util.List; +import java.util.Objects; + +/** + * @author KCodeYT (Nukkit Project) + */ +public class StructureGrowEvent extends LevelEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Block block; + private final List blocks; + + public StructureGrowEvent(Block block, List blocks) { + super(Objects.requireNonNull(block.getLevel())); + this.block = block; + this.blocks = blocks; + } + + public Block getBlock() { + return this.block; + } + + public List getBlockList() { + return this.blocks; + } + + public void setBlockList(List blocks) { + this.blocks.clear(); + if(blocks != null) + this.blocks.addAll(blocks); + } + +} diff --git a/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java b/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java index ba7b49ed705..9bc11dfa819 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java @@ -1,7 +1,10 @@ package cn.nukkit.event.player; +import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.entity.data.Skin; import cn.nukkit.event.HandlerList; +import cn.nukkit.utils.LoginChainData; import java.util.ArrayList; import java.util.List; @@ -23,6 +26,8 @@ public static HandlerList getHandlers() { private final String name; private final UUID uuid; + private final LoginChainData chainData; + private Skin skin; private final String address; private final int port; @@ -31,31 +36,54 @@ public static HandlerList getHandlers() { private final List> scheduledActions = new ArrayList<>(); - public PlayerAsyncPreLoginEvent(String name, UUID uuid, String address, int port) { + public PlayerAsyncPreLoginEvent(String name, UUID uuid, LoginChainData chainData, Skin skin, String address, int port) { this.name = name; this.uuid = uuid; + this.chainData = chainData; + this.skin = skin; this.address = address; this.port = port; } + @Override + public Player getPlayer() { + throw new UnsupportedOperationException("Could not get player instance in an async event"); + } + public String getName() { - return name; + return this.name; } public UUID getUuid() { - return uuid; + return this.uuid; + } + + public LoginChainData getChainData() { + return this.chainData; + } + + public String getXuid() { + return this.chainData.getXUID(); + } + + public Skin getSkin() { + return this.skin; + } + + public void setSkin(Skin skin) { + this.skin = skin; } public String getAddress() { - return address; + return this.address; } public int getPort() { - return port; + return this.port; } public LoginResult getLoginResult() { - return loginResult; + return this.loginResult; } public void setLoginResult(LoginResult loginResult) { diff --git a/src/main/java/cn/nukkit/event/server/ServerStopEvent.java b/src/main/java/cn/nukkit/event/server/ServerStopEvent.java new file mode 100644 index 00000000000..65b46996c57 --- /dev/null +++ b/src/main/java/cn/nukkit/event/server/ServerStopEvent.java @@ -0,0 +1,16 @@ +package cn.nukkit.event.server; + +import cn.nukkit.event.HandlerList; + +/** + * author: NycuRO + * NukkitX Project + */ +public class ServerStopEvent extends ServerEvent { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } +} diff --git a/src/main/java/cn/nukkit/inventory/AnvilInventory.java b/src/main/java/cn/nukkit/inventory/AnvilInventory.java index 78b8e26a1ac..b359a10ab47 100644 --- a/src/main/java/cn/nukkit/inventory/AnvilInventory.java +++ b/src/main/java/cn/nukkit/inventory/AnvilInventory.java @@ -2,12 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Position; -import cn.nukkit.network.protocol.LevelSoundEventPacket; - -import java.util.ArrayList; -import java.util.Arrays; /** * author: MagicDroidX @@ -15,111 +10,20 @@ */ public class AnvilInventory extends FakeBlockUIComponent { + public static final int ANVIL_INPUT_UI_SLOT = 1; + public static final int ANVIL_MATERIAL_UI_SLOT = 2; + public static final int ANVIL_OUTPUT_UI_SLOT = CREATED_ITEM_OUTPUT_UI_SLOT; + public static final int TARGET = 0; public static final int SACRIFICE = 1; - public static final int RESULT = 50; + public static final int RESULT = ANVIL_OUTPUT_UI_SLOT - 1; //1: offset + + private int cost; public AnvilInventory(PlayerUIInventory playerUI, Position position) { super(playerUI, InventoryType.ANVIL, 1, position); } - public boolean onRename(Player player, Item resultItem) { - Item local = getItem(TARGET); - Item second = getItem(SACRIFICE); - - if (!resultItem.equals(local, true, false) || resultItem.getCount() != local.getCount()) { - //Item does not match target item. Everything must match except the tags. - return false; - } - - if (local.equals(resultItem)) { - //just item transaction - return true; - } - - if (local.getId() != 0 && second.getId() == 0) { //only rename - local.setCustomName(resultItem.getCustomName()); - setItem(RESULT, local); - player.getInventory().addItem(local); - clearAll(); - player.getInventory().sendContents(player); - sendContents(player); - - player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_RANDOM_ANVIL_USE); - return true; - } else if (local.getId() != 0 && second.getId() != 0) { //enchants combining - if (!local.equals(second, true, false)) { - return false; - } - - if (local.getId() != 0 && second.getId() != 0) { - Item result = local.clone(); - int enchants = 0; - - ArrayList enchantments = new ArrayList<>(Arrays.asList(second.getEnchantments())); - - ArrayList baseEnchants = new ArrayList<>(); - - for (Enchantment ench : local.getEnchantments()) { - if (ench.isMajor()) { - baseEnchants.add(ench); - } - } - - for (Enchantment enchantment : enchantments) { - if (enchantment.getLevel() < 0 || enchantment.getId() < 0) { - continue; - } - - if (enchantment.isMajor()) { - boolean same = false; - boolean another = false; - - for (Enchantment baseEnchant : baseEnchants) { - if (baseEnchant.getId() == enchantment.getId()) - same = true; - else { - another = true; - } - } - - if (!same && another) { - continue; - } - } - - Enchantment localEnchantment = local.getEnchantment(enchantment.getId()); - - if (localEnchantment != null) { - int level = Math.max(localEnchantment.getLevel(), enchantment.getLevel()); - - if (localEnchantment.getLevel() == enchantment.getLevel()) - level++; - - enchantment.setLevel(level); - result.addEnchantment(enchantment); - continue; - } - - result.addEnchantment(enchantment); - enchants++; - } - - result.setCustomName(resultItem.getCustomName()); - - player.getInventory().addItem(result); - player.getInventory().sendContents(player); - clearAll(); - sendContents(player); - - player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_RANDOM_ANVIL_USE); - return true; - } - } - - return false; - } - @Override public void onClose(Player who) { super.onClose(who); @@ -137,4 +41,24 @@ public void onOpen(Player who) { super.onOpen(who); who.craftingType = Player.CRAFTING_ANVIL; } + + public Item getInputSlot() { + return this.getItem(TARGET); + } + + public Item getMaterialSlot() { + return this.getItem(SACRIFICE); + } + + public Item getOutputSlot() { + return this.getItem(RESULT); + } + + public int getCost() { + return this.cost; + } + + public void setCost(int cost) { + this.cost = cost; + } } diff --git a/src/main/java/cn/nukkit/inventory/BaseInventory.java b/src/main/java/cn/nukkit/inventory/BaseInventory.java index c6b110ec34d..1cc928d972b 100644 --- a/src/main/java/cn/nukkit/inventory/BaseInventory.java +++ b/src/main/java/cn/nukkit/inventory/BaseInventory.java @@ -37,6 +37,8 @@ public abstract class BaseInventory implements Inventory { protected InventoryHolder holder; + private List listeners; + public BaseInventory(InventoryHolder holder, InventoryType type) { this(holder, type, new HashMap<>()); } @@ -380,9 +382,6 @@ public boolean clear(int index, boolean send) { } item = ev.getNewItem(); } - if (holder instanceof BlockEntity) { - ((BlockEntity) holder).setDirty(); - } if (item.getId() != Item.AIR) { this.slots.put(index, item.clone()); @@ -451,6 +450,16 @@ public void onSlotChange(int index, Item before, boolean send) { if (send) { this.sendSlot(index, this.getViewers()); } + + if (holder instanceof BlockEntity) { + ((BlockEntity) holder).setDirty(); + } + + if (this.listeners != null) { + for (InventoryListener listener : listeners) { + listener.onInventoryChanged(this, before, index); + } + } } @Override @@ -557,6 +566,22 @@ public void sendSlot(int index, Collection players) { this.sendSlot(index, players.toArray(new Player[0])); } + @Override + public void addListener(InventoryListener listener) { + if (this.listeners == null) { + this.listeners = new ArrayList<>(); + } + + this.listeners.add(listener); + } + + @Override + public void removeListener(InventoryListener listener) { + if (this.listeners != null) { + this.listeners.remove(listener); + } + } + @Override public InventoryType getType() { return type; diff --git a/src/main/java/cn/nukkit/inventory/ContainerInventory.java b/src/main/java/cn/nukkit/inventory/ContainerInventory.java index 5bb977844cd..0b8af477475 100644 --- a/src/main/java/cn/nukkit/inventory/ContainerInventory.java +++ b/src/main/java/cn/nukkit/inventory/ContainerInventory.java @@ -58,6 +58,7 @@ public void onOpen(Player who) { public void onClose(Player who) { ContainerClosePacket pk = new ContainerClosePacket(); pk.windowId = who.getWindowId(this); + pk.wasServerInitiated = who.getClosingWindowId() != pk.windowId; who.dataPacket(pk); super.onClose(who); } diff --git a/src/main/java/cn/nukkit/inventory/CraftingManager.java b/src/main/java/cn/nukkit/inventory/CraftingManager.java index 00580ff33a0..e7cc23e9ace 100644 --- a/src/main/java/cn/nukkit/inventory/CraftingManager.java +++ b/src/main/java/cn/nukkit/inventory/CraftingManager.java @@ -2,8 +2,8 @@ import cn.nukkit.Server; import cn.nukkit.item.Item; -import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.CraftingDataPacket; +import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.Config; import cn.nukkit.utils.MainLogger; @@ -15,7 +15,6 @@ import java.io.File; import java.io.InputStream; import java.util.*; -import java.util.zip.Deflater; /** * author: MagicDroidX @@ -26,11 +25,13 @@ public class CraftingManager { public final Collection recipes = new ArrayDeque<>(); - public static BatchPacket packet = null; + public static DataPacket packet = null; protected final Map> shapedRecipes = new Int2ObjectOpenHashMap<>(); public final Map furnaceRecipes = new Int2ObjectOpenHashMap<>(); + public final Map multiRecipes = new HashMap<>(); + public final Map brewingRecipes = new Int2ObjectOpenHashMap<>(); public final Map containerRecipes = new Int2ObjectOpenHashMap<>(); @@ -152,6 +153,9 @@ private void loadRecipes(Config config) { } this.registerRecipe(new FurnaceRecipe(resultItem, inputItem)); break; + case 4: + this.registerRecipe(new MultiRecipe(UUID.fromString((String) recipe.get("uuid")))); + break; default: break; } @@ -188,7 +192,6 @@ private void loadRecipes(Config config) { public void rebuildPacket() { CraftingDataPacket pk = new CraftingDataPacket(); pk.cleanRecipes = true; - for (Recipe recipe : this.getRecipes()) { if (recipe instanceof ShapedRecipe) { pk.addShapedRecipe((ShapedRecipe) recipe); @@ -201,6 +204,10 @@ public void rebuildPacket() { pk.addFurnaceRecipe(recipe); } + for (MultiRecipe recipe : this.multiRecipes.values()) { + pk.addMultiRecipe(recipe); + } + for (BrewingRecipe recipe : brewingRecipes.values()) { pk.addBrewingRecipe(recipe); } @@ -209,9 +216,10 @@ public void rebuildPacket() { pk.addContainerRecipe(recipe); } - pk.encode(); - - packet = pk.compress(Deflater.BEST_COMPRESSION); + pk.tryEncode(); + // TODO: find out whats wrong with compression + // packet = pk.compress(Deflater.BEST_COMPRESSION); + packet = pk; } public Collection getRecipes() { @@ -380,6 +388,10 @@ private boolean matchItemsAccumulation(CraftingRecipe recipe, List inputLi return false; } + public void registerMultiRecipe(MultiRecipe recipe) { + this.multiRecipes.put(recipe.getId(), recipe); + } + public static class Entry { final int resultItemId; final int resultMeta; diff --git a/src/main/java/cn/nukkit/inventory/DispenserInventory.java b/src/main/java/cn/nukkit/inventory/DispenserInventory.java new file mode 100644 index 00000000000..55f651447fd --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/DispenserInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityDispenser; + +public class DispenserInventory extends ContainerInventory { + + public DispenserInventory(BlockEntityDispenser blockEntity) { + super(blockEntity, InventoryType.DISPENSER); + } + + @Override + public BlockEntityDispenser getHolder() { + return (BlockEntityDispenser) super.getHolder(); + } +} diff --git a/src/main/java/cn/nukkit/inventory/DropperInventory.java b/src/main/java/cn/nukkit/inventory/DropperInventory.java new file mode 100644 index 00000000000..c349445a45b --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/DropperInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityDropper; + +public class DropperInventory extends ContainerInventory { + + public DropperInventory(BlockEntityDropper blockEntity) { + super(blockEntity, InventoryType.DROPPER); + } + + @Override + public BlockEntityDropper getHolder() { + return (BlockEntityDropper) super.getHolder(); + } +} diff --git a/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java b/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java index 6a04f160be2..0c802b22460 100644 --- a/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java +++ b/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java @@ -67,6 +67,7 @@ public void onOpen(Player who) { public void onClose(Player who) { ContainerClosePacket pk = new ContainerClosePacket(); pk.windowId = who.getWindowId(this); + pk.wasServerInitiated = who.getClosingWindowId() != pk.windowId; who.dataPacket(pk); super.onClose(who); } diff --git a/src/main/java/cn/nukkit/inventory/Fuel.java b/src/main/java/cn/nukkit/inventory/Fuel.java index 376b2503af2..87bffb2c581 100644 --- a/src/main/java/cn/nukkit/inventory/Fuel.java +++ b/src/main/java/cn/nukkit/inventory/Fuel.java @@ -59,7 +59,7 @@ public abstract class Fuel { duration.put(Item.BROWN_MUSHROOM_BLOCK, (short) 300); duration.put(Item.RED_MUSHROOM_BLOCK, (short) 300); duration.put(Item.FISHING_ROD, (short) 300); - duration.put(Item.WOODEN_BUTTON, (short) 100); + duration.put(Item.WOODEN_BUTTON, (short) 300); duration.put(Item.WOODEN_DOOR, (short) 200); duration.put(Item.SPRUCE_DOOR, (short) 200); duration.put(Item.BIRCH_DOOR, (short) 200); @@ -67,5 +67,7 @@ public abstract class Fuel { duration.put(Item.ACACIA_DOOR, (short) 200); duration.put(Item.DARK_OAK_DOOR, (short) 200); duration.put(Item.BANNER, (short) 300); + duration.put(Item.DEAD_BUSH, (short) 100); + duration.put(Item.SIGN, (short) 200); } } diff --git a/src/main/java/cn/nukkit/inventory/Inventory.java b/src/main/java/cn/nukkit/inventory/Inventory.java index 9b62bfd3bf8..1d8c90feeae 100644 --- a/src/main/java/cn/nukkit/inventory/Inventory.java +++ b/src/main/java/cn/nukkit/inventory/Inventory.java @@ -98,4 +98,8 @@ default boolean clear(int index) { void onClose(Player who); void onSlotChange(int index, Item before, boolean send); + + void addListener(InventoryListener listener); + + void removeListener(InventoryListener listener); } diff --git a/src/main/java/cn/nukkit/inventory/InventoryListener.java b/src/main/java/cn/nukkit/inventory/InventoryListener.java new file mode 100644 index 00000000000..889b24bfc26 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/InventoryListener.java @@ -0,0 +1,8 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; + +public interface InventoryListener { + + void onInventoryChanged(Inventory inventory, Item oldItem, int slot); +} diff --git a/src/main/java/cn/nukkit/inventory/InventoryType.java b/src/main/java/cn/nukkit/inventory/InventoryType.java index 1b16ae7b9a6..b9f776b6762 100644 --- a/src/main/java/cn/nukkit/inventory/InventoryType.java +++ b/src/main/java/cn/nukkit/inventory/InventoryType.java @@ -15,7 +15,7 @@ public enum InventoryType { BREWING_STAND(5, "Brewing", 4), //1 INPUT, 3 POTION, 1 fuel ANVIL(3, "Anvil", 5), //2 INPUT, 1 OUTPUT ENCHANT_TABLE(2, "Enchant", 3), //1 INPUT/OUTPUT, 1 LAPIS - DISPENSER(0, "Dispenser", 6), //9 CONTAINER + DISPENSER(9, "Dispenser", 6), //9 CONTAINER DROPPER(9, "Dropper", 7), //9 CONTAINER HOPPER(5, "Hopper", 8), //5 CONTAINER UI(1, "UI", -1), diff --git a/src/main/java/cn/nukkit/inventory/MultiRecipe.java b/src/main/java/cn/nukkit/inventory/MultiRecipe.java new file mode 100644 index 00000000000..adf791436b9 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/MultiRecipe.java @@ -0,0 +1,33 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; + +import java.util.UUID; + +public class MultiRecipe implements Recipe { + + private final UUID id; + + public MultiRecipe(UUID id) { + this.id = id; + } + + @Override + public Item getResult() { + throw new UnsupportedOperationException(); + } + + @Override + public void registerToCraftingManager(CraftingManager manager) { + manager.registerMultiRecipe(this); + } + + @Override + public RecipeType getType() { + return RecipeType.MULTI; + } + + public UUID getId() { + return this.id; + } +} diff --git a/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java b/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java index 459c04c2f33..faf894b73f6 100755 --- a/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java @@ -63,6 +63,7 @@ public void onOpen(Player who) { public void onClose(Player who) { ContainerClosePacket containerClosePacket = new ContainerClosePacket(); containerClosePacket.windowId = who.getWindowId(this); + containerClosePacket.wasServerInitiated = who.getClosingWindowId() != containerClosePacket.windowId; who.dataPacket(containerClosePacket); super.onClose(who); diff --git a/src/main/java/cn/nukkit/inventory/PlayerInventory.java b/src/main/java/cn/nukkit/inventory/PlayerInventory.java index 73fcc1da0aa..6febe54ea13 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerInventory.java @@ -338,8 +338,7 @@ public void sendArmorContents(Player[] players) { MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); pk.eid = this.getHolder().getId(); pk.slots = armor; - pk.encode(); - pk.isEncoded = true; + pk.tryEncode(); for (Player player : players) { if (player.equals(this.getHolder())) { @@ -387,8 +386,7 @@ public void sendArmorSlot(int index, Player[] players) { MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); pk.eid = this.getHolder().getId(); pk.slots = armor; - pk.encode(); - pk.isEncoded = true; + pk.tryEncode(); for (Player player : players) { if (player.equals(this.getHolder())) { @@ -482,7 +480,6 @@ public void sendCreativeContents() { } Player p = (Player) this.getHolder(); - //InventoryContentPacket pk = new InventoryContentPacket(); CreativeContentPacket pk = new CreativeContentPacket(); if (!p.isSpectator()) { //fill it for all gamemodes except spectator @@ -514,6 +511,7 @@ public void onOpen(Player who) { public void onClose(Player who) { ContainerClosePacket pk = new ContainerClosePacket(); pk.windowId = who.getWindowId(this); + pk.wasServerInitiated = who.getClosingWindowId() != pk.windowId; who.dataPacket(pk); // player can never stop viewing their own inventory if (who != holder) { diff --git a/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java b/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java index dd17cbda0fb..7e2d3e5ae43 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java @@ -71,8 +71,7 @@ private MobEquipmentPacket createMobEquipmentPacket(Item item) { pk.item = item; pk.inventorySlot = 1; pk.windowId = ContainerIds.OFFHAND; - pk.encode(); - pk.isEncoded = true; + pk.tryEncode(); return pk; } diff --git a/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java b/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java index 0b272324dde..41bc32d7a5f 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java +++ b/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java @@ -8,6 +8,9 @@ import java.util.Set; public class PlayerUIComponent extends BaseInventory { + + public static final int CREATED_ITEM_OUTPUT_UI_SLOT = 50; + private final PlayerUIInventory playerUI; private final int offset; private final int size; diff --git a/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java new file mode 100644 index 00000000000..8dbdba1e6f5 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java @@ -0,0 +1,396 @@ +package cn.nukkit.inventory.transaction; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.event.block.AnvilDamageEvent; +import cn.nukkit.event.block.AnvilDamageEvent.DamageCause; +import cn.nukkit.event.inventory.RepairItemEvent; +import cn.nukkit.inventory.AnvilInventory; +import cn.nukkit.inventory.FakeBlockMenu; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.transaction.action.RepairItemAction; +import cn.nukkit.inventory.transaction.action.InventoryAction; +import cn.nukkit.item.Item; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.types.NetworkInventoryAction; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class RepairItemTransaction extends InventoryTransaction { + + private Item inputItem; + private Item materialItem; + private Item outputItem; + + private int cost; + + public RepairItemTransaction(Player source, List actions) { + super(source, actions); + } + + @Override + public boolean canExecute() { + Inventory inventory = getSource().getWindowById(Player.ANVIL_WINDOW_ID); + if (inventory == null) { + return false; + } + AnvilInventory anvilInventory = (AnvilInventory) inventory; + return this.inputItem != null && this.outputItem != null && this.inputItem.equals(anvilInventory.getInputSlot(), true, true) + && (!this.hasMaterial() || this.materialItem.equals(anvilInventory.getMaterialSlot(), true, true)) + && this.checkRecipeValid(); + } + + @Override + public boolean execute() { + if (this.hasExecuted() || !this.canExecute()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return false; + } + AnvilInventory inventory = (AnvilInventory) getSource().getWindowById(Player.ANVIL_WINDOW_ID); + + if (inventory.getCost() != this.cost && !this.source.isCreative()) { + this.source.getServer().getLogger().debug("Got unexpected cost " + inventory.getCost() + " from " + this.source.getName() + "(expected " + this.cost + ")"); + } + + RepairItemEvent event = new RepairItemEvent(inventory, this.inputItem, this.outputItem, this.materialItem, this.cost, this.source); + this.source.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return true; + } + + for (InventoryAction action : this.actions) { + if (action.execute(this.source)) { + action.onExecuteSuccess(this.source); + } else { + action.onExecuteFail(this.source); + } + } + + FakeBlockMenu holder = inventory.getHolder(); + Block block = this.source.level.getBlock(holder.getFloorX(), holder.getFloorY(), holder.getFloorZ()); + if (block.getId() == Block.ANVIL) { + int oldDamage = block.getDamage() >= 8 ? 2 : block.getDamage() >= 4 ? 1 : 0; + int newDamage = !this.source.isCreative() && ThreadLocalRandom.current().nextInt(100) < 12 ? oldDamage + 1 : oldDamage; + + AnvilDamageEvent ev = new AnvilDamageEvent(block, oldDamage, newDamage, DamageCause.USE, this.source); + ev.setCancelled(oldDamage == newDamage); + this.source.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + newDamage = ev.getNewDamage(); + if (newDamage > 2) { + this.source.level.setBlock(block, Block.get(Block.AIR), true); + this.source.level.addLevelEvent(block, LevelEventPacket.EVENT_SOUND_ANVIL_BREAK); + } else { + if (newDamage < 0) { + newDamage = 0; + } + if (newDamage != oldDamage) { + block.setDamage((newDamage << 2) | (block.getDamage() & 0x3)); + this.source.level.setBlock(block, block, true); + } + this.source.level.addLevelEvent(block, LevelEventPacket.EVENT_SOUND_ANVIL_USE); + } + } else { + this.source.level.addLevelEvent(block, LevelEventPacket.EVENT_SOUND_ANVIL_USE); + } + } + + if (!this.source.isCreative()) { + this.source.setExperience(this.source.getExperience(), this.source.getExperienceLevel() - event.getCost()); + } + return true; + } + + @Override + public void addAction(InventoryAction action) { + super.addAction(action); + if (action instanceof RepairItemAction) { + switch (((RepairItemAction) action).getType()) { + case NetworkInventoryAction.SOURCE_TYPE_ANVIL_INPUT: + this.inputItem = action.getTargetItem(); + break; + case NetworkInventoryAction.SOURCE_TYPE_ANVIL_RESULT: + this.outputItem = action.getSourceItem(); + break; + case NetworkInventoryAction.SOURCE_TYPE_ANVIL_MATERIAL: + this.materialItem = action.getTargetItem(); + break; + } + } + } + + private boolean checkRecipeValid() { + int cost = 0; + int baseRepairCost = this.inputItem.getRepairCost(); + + if (this.isMapRecipe()) { + if (!this.matchMapRecipe()) { + return false; + } + baseRepairCost = 0; + } else if (this.hasMaterial()) { + baseRepairCost += this.materialItem.getRepairCost(); + + if (this.inputItem.getMaxDurability() != -1 && this.matchRepairItem()) { + int maxRepairDamage = this.inputItem.getMaxDurability() / 4; + int repairDamage = Math.min(this.inputItem.getDamage(), maxRepairDamage); + if (repairDamage <= 0) { + return false; + } + + int damage = this.inputItem.getDamage(); + for (; repairDamage > 0 && cost < this.materialItem.getCount(); cost++) { + damage = damage - repairDamage; + repairDamage = Math.min(damage, maxRepairDamage); + } + if (this.outputItem.getDamage() != damage) { + return false; + } + } else { + boolean consumeEnchantedBook = this.materialItem.getId() == Item.ENCHANTED_BOOK && this.materialItem.hasEnchantments(); + if (!consumeEnchantedBook && (this.inputItem.getMaxDurability() == -1 || this.inputItem.getId() != this.materialItem.getId())) { + return false; + } + + if (!consumeEnchantedBook && this.inputItem.getMaxDurability() != -1) { + int damage = this.inputItem.getDamage() - this.inputItem.getMaxDurability() + this.materialItem.getDamage() - this.inputItem.getMaxDurability() * 12 / 100 + 1; + if (damage < 0) { + damage = 0; + } + + if (damage < this.inputItem.getDamage()) { + if (this.outputItem.getDamage() != damage) { + return false; + } + cost += 2; + } + } + + Int2IntMap enchantments = new Int2IntOpenHashMap(); + enchantments.defaultReturnValue(-1); + for (Enchantment enchantment : this.inputItem.getEnchantments()) { + enchantments.put(enchantment.getId(), enchantment.getLevel()); + } + + boolean hasCompatibleEnchantments = false; + boolean hasIncompatibleEnchantments = false; + for (Enchantment materialEnchantment : this.materialItem.getEnchantments()) { + Enchantment enchantment = this.inputItem.getEnchantment(materialEnchantment.getId()); + int inputLevel = enchantment != null ? enchantment.getLevel() : 0; + int materialLevel = materialEnchantment.getLevel(); + int outputLevel = inputLevel == materialLevel ? materialLevel + 1 : Math.max(materialLevel, inputLevel); + + boolean canEnchant = materialEnchantment.canEnchant(this.inputItem) || this.inputItem.getId() == Item.ENCHANTED_BOOK; + for (Enchantment inputEnchantment : this.inputItem.getEnchantments()) { + if (inputEnchantment.getId() != materialEnchantment.getId() && !materialEnchantment.isCompatibleWith(inputEnchantment)) { + canEnchant = false; + cost++; + } + } + + if (!canEnchant) { + hasIncompatibleEnchantments = true; + } else { + hasCompatibleEnchantments = true; + if (outputLevel > materialEnchantment.getMaxLevel()) { + outputLevel = materialEnchantment.getMaxLevel(); + } + + enchantments.put(materialEnchantment.getId(), outputLevel); + int rarityFactor; + switch (materialEnchantment.getRarity()) { + case COMMON: + rarityFactor = 1; + break; + case UNCOMMON: + rarityFactor = 2; + break; + case RARE: + rarityFactor = 4; + break; + case VERY_RARE: + default: + rarityFactor = 8; + break; + } + + if (consumeEnchantedBook) { + rarityFactor = Math.max(1, rarityFactor / 2); + } + + cost += rarityFactor * Math.max(0, outputLevel - inputLevel); + if (this.inputItem.getCount() > 1) { + cost = 40; + } + } + } + + Enchantment[] outputEnchantments = this.outputItem.getEnchantments(); + if (hasIncompatibleEnchantments && !hasCompatibleEnchantments || enchantments.size() != outputEnchantments.length) { + return false; + } + + for (Enchantment enchantment : outputEnchantments) { + if (enchantments.get(enchantment.getId()) != enchantment.getLevel()) { + return false; + } + } + } + } + + int renameCost = 0; + if (!this.inputItem.getCustomName().equals(this.outputItem.getCustomName())) { + if (this.outputItem.getCustomName().length() > 30) { + return false; + } + renameCost = 1; + cost += renameCost; + } + + this.cost = baseRepairCost + cost; + if (renameCost == cost && renameCost > 0 && this.cost >= 40) { + this.cost = 39; + } + if (baseRepairCost < 0 || cost < 0 || cost == 0 && !this.isMapRecipe() || this.cost > 39 && !this.source.isCreative()) { + return false; + } + + int nextBaseRepairCost = this.inputItem.getRepairCost(); + if (!this.isMapRecipe()) { + if (this.hasMaterial() && nextBaseRepairCost < this.materialItem.getRepairCost()) { + nextBaseRepairCost = this.materialItem.getRepairCost(); + } + if (renameCost == 0 || renameCost != cost) { + nextBaseRepairCost = 2 * nextBaseRepairCost + 1; + } + } + if (this.outputItem.getRepairCost() != nextBaseRepairCost) { + this.source.getServer().getLogger().debug("Got unexpected base cost " + this.outputItem.getRepairCost() + " from " + this.source.getName() + "(expected " + nextBaseRepairCost + ")"); + return false; + } + + return true; + } + + private boolean hasMaterial() { + return this.materialItem != null && !this.materialItem.isNull(); + } + + private boolean isMapRecipe() { + return this.hasMaterial() && (this.inputItem.getId() == Item.MAP || this.inputItem.getId() == Item.EMPTY_MAP) + && (this.materialItem.getId() == Item.EMPTY_MAP || this.materialItem.getId() == Item.PAPER || this.materialItem.getId() == Item.COMPASS); + } + + private boolean matchMapRecipe() { + if (this.inputItem.getId() == Item.EMPTY_MAP) { + return this.inputItem.getDamage() != 2 && this.materialItem.getId() == Item.COMPASS // locator + && this.outputItem.getId() == Item.EMPTY_MAP && this.outputItem.getDamage() == 2 && this.outputItem.getCount() == 1; + } else if (this.inputItem.getId() == Item.MAP && this.outputItem.getDamage() == this.inputItem.getDamage()) { + if (this.materialItem.getId() == Item.COMPASS) { // locator + return this.inputItem.getDamage() != 2 && this.outputItem.getId() == Item.MAP && this.outputItem.getCount() == 1; + } else if (this.materialItem.getId() == Item.EMPTY_MAP) { // clone + return this.outputItem.getId() == Item.MAP && this.outputItem.getCount() == 2; + } else if (this.materialItem.getId() == Item.PAPER && this.materialItem.getCount() >= 8) { // zoom out + return this.inputItem.getDamage() < 3 && this.outputItem.getId() == Item.MAP && this.outputItem.getCount() == 1; + } + } + return false; + } + + private boolean matchRepairItem() { + switch (this.inputItem.getId()) { + case Item.LEATHER_CAP: + case Item.LEATHER_TUNIC: + case Item.LEATHER_PANTS: + case Item.LEATHER_BOOTS: + return this.materialItem.getId() == Item.LEATHER; + case Item.WOODEN_SWORD: + case Item.WOODEN_PICKAXE: + case Item.WOODEN_SHOVEL: + case Item.WOODEN_AXE: + case Item.WOODEN_HOE: + return this.materialItem.getId() == Item.PLANKS; + case Item.IRON_SWORD: + case Item.IRON_PICKAXE: + case Item.IRON_SHOVEL: + case Item.IRON_AXE: + case Item.IRON_HOE: + case Item.IRON_HELMET: + case Item.IRON_CHESTPLATE: + case Item.IRON_LEGGINGS: + case Item.IRON_BOOTS: + case Item.CHAIN_HELMET: + case Item.CHAIN_CHESTPLATE: + case Item.CHAIN_LEGGINGS: + case Item.CHAIN_BOOTS: + return this.materialItem.getId() == Item.IRON_INGOT; + case Item.GOLD_SWORD: + case Item.GOLD_PICKAXE: + case Item.GOLD_SHOVEL: + case Item.GOLD_AXE: + case Item.GOLD_HOE: + case Item.GOLD_HELMET: + case Item.GOLD_CHESTPLATE: + case Item.GOLD_LEGGINGS: + case Item.GOLD_BOOTS: + return this.materialItem.getId() == Item.GOLD_INGOT; + case Item.DIAMOND_SWORD: + case Item.DIAMOND_PICKAXE: + case Item.DIAMOND_SHOVEL: + case Item.DIAMOND_AXE: + case Item.DIAMOND_HOE: + case Item.DIAMOND_HELMET: + case Item.DIAMOND_CHESTPLATE: + case Item.DIAMOND_LEGGINGS: + case Item.DIAMOND_BOOTS: + return this.materialItem.getId() == Item.DIAMOND; + case Item.NETHERITE_SWORD: + case Item.NETHERITE_PICKAXE: + case Item.NETHERITE_SHOVEL: + case Item.NETHERITE_AXE: + case Item.NETHERITE_HOE: + case Item.NETHERITE_HELMET: + case Item.NETHERITE_CHESTPLATE: + case Item.NETHERITE_LEGGINGS: + case Item.NETHERITE_BOOTS: + return this.materialItem.getId() == Item.NETHERITE_INGOT; + case Item.TURTLE_SHELL: + return this.materialItem.getId() == Item.SCUTE; + case Item.ELYTRA: + return this.materialItem.getId() == Item.PHANTOM_MEMBRANE; + } + return false; + } + + public Item getInputItem() { + return this.inputItem; + } + + public Item getMaterialItem() { + return this.materialItem; + } + + public Item getOutputItem() { + return this.outputItem; + } + + public int getCost() { + return this.cost; + } + + public static boolean checkForRepairItemPart(List actions) { + for (InventoryAction action : actions) { + if (action instanceof RepairItemAction) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java new file mode 100644 index 00000000000..b126b2b2552 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java @@ -0,0 +1,38 @@ +package cn.nukkit.inventory.transaction.action; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; + +public class RepairItemAction extends InventoryAction { + + private int type; + + public RepairItemAction(Item sourceItem, Item targetItem, int type) { + super(sourceItem, targetItem); + this.type = type; + } + + @Override + public boolean isValid(Player source) { + return source.getWindowById(Player.ANVIL_WINDOW_ID) != null; + } + + @Override + public boolean execute(Player source) { + return true; + } + + @Override + public void onExecuteSuccess(Player source) { + + } + + @Override + public void onExecuteFail(Player source) { + + } + + public int getType() { + return this.type; + } +} diff --git a/src/main/java/cn/nukkit/item/Item.java b/src/main/java/cn/nukkit/item/Item.java index f6357b44a18..a0312e772e3 100644 --- a/src/main/java/cn/nukkit/item/Item.java +++ b/src/main/java/cn/nukkit/item/Item.java @@ -12,6 +12,7 @@ import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.IntTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.StringTag; import cn.nukkit.nbt.tag.Tag; @@ -247,7 +248,7 @@ public static void init() { list[GOLD_HORSE_ARMOR] = ItemHorseArmorGold.class; //418 list[DIAMOND_HORSE_ARMOR] = ItemHorseArmorDiamond.class; //419 //TODO: list[LEAD] = ItemLead.class; //420 - //TODO: list[NAME_TAG] = ItemNameTag.class; //421 + list[NAME_TAG] = ItemNameTag.class; //421 list[PRISMARINE_CRYSTALS] = ItemPrismarineCrystals.class; //422 list[RAW_MUTTON] = ItemMuttonRaw.class; //423 list[COOKED_MUTTON] = ItemMuttonCooked.class; //424 @@ -288,6 +289,8 @@ public static void init() { list[TURTLE_SHELL] = ItemTurtleShell.class; //469 + list[CROSSBOW] = ItemCrossbow.class; //471 + list[SWEET_BERRIES] = ItemSweetBerries.class; //477 list[RECORD_11] = ItemRecord11.class; @@ -308,6 +311,18 @@ public static void init() { list[HONEYCOMB] = ItemHoneycomb.class; //736 list[HONEY_BOTTLE] = ItemHoneyBottle.class; //737 + list[NETHERITE_INGOT] = ItemIngotNetherite.class; //742 + list[NETHERITE_SWORD] = ItemSwordNetherite.class; //743 + list[NETHERITE_SHOVEL] = ItemShovelNetherite.class; //744 + list[NETHERITE_PICKAXE] = ItemPickaxeNetherite.class; //745 + list[NETHERITE_AXE] = ItemAxeNetherite.class; //746 + list[NETHERITE_HOE] = ItemHoeNetherite.class; //747 + list[NETHERITE_HELMET] = ItemHelmetNetherite.class; //748 + list[NETHERITE_CHESTPLATE] = ItemChestplateNetherite.class; //749 + list[NETHERITE_LEGGINGS] = ItemLeggingsNetherite.class; //750 + list[NETHERITE_BOOTS] = ItemBootsNetherite.class; //751 + list[NETHERITE_SCRAP] = ItemScrapNetherite.class; //752 + list[RECORD_PIGSTEP] = ItemRecordPigstep.class; //759 for (int i = 0; i < 256; ++i) { @@ -332,7 +347,10 @@ private static void initCreativeItems() { for (Map map : list) { try { - addCreativeItem(fromJson(map)); + Item item = fromJson(map, true); + if (item != null) { + addCreativeItem(item); + } } catch (Exception e) { MainLogger.getLogger().logException(e); } @@ -442,6 +460,10 @@ public static Item fromString(String str) { } public static Item fromJson(Map data) { + return fromJson(data, false); + } + + private static Item fromJson(Map data, boolean ignoreUnsupported) { String nbt = (String) data.get("nbt_b64"); byte[] nbtBytes; if (nbt != null) { @@ -455,7 +477,10 @@ public static Item fromJson(Map data) { } } - return get(Utils.toInt(data.get("id")), Utils.toInt(data.getOrDefault("damage", 0)), Utils.toInt(data.getOrDefault("count", 1)), nbtBytes); + int id = Utils.toInt(data.get("id")); + if (ignoreUnsupported && id < 0) return null; + + return get(id, Utils.toInt(data.getOrDefault("damage", 0)), Utils.toInt(data.getOrDefault("count", 1)), nbtBytes); } public static Item[] fromStringMultiple(String str) { @@ -642,6 +667,37 @@ public Enchantment[] getEnchantments() { return enchantments.toArray(new Enchantment[0]); } + public boolean hasEnchantment(int id) { + return this.getEnchantment(id) != null; + } + + public int getRepairCost() { + if (this.hasCompoundTag()) { + CompoundTag tag = this.getNamedTag(); + if (tag.contains("RepairCost")) { + Tag repairCost = tag.get("RepairCost"); + if (repairCost instanceof IntTag) { + return ((IntTag) repairCost).data; + } + } + } + return 0; + } + + public Item setRepairCost(int cost) { + if (cost <= 0 && this.hasCompoundTag()) { + return this.setNamedTag(this.getNamedTag().remove("RepairCost")); + } + + CompoundTag tag; + if (!this.hasCompoundTag()) { + tag = new CompoundTag(); + } else { + tag = this.getNamedTag(); + } + return this.setNamedTag(tag.putInt("RepairCost", cost)); + } + public boolean hasCustomName() { if (!this.hasCompoundTag()) { return false; @@ -840,6 +896,10 @@ public Block getBlock() { } } + public Block getBlockUnsafe() { + return this.block; + } + public int getId() { return id; } @@ -1047,4 +1107,8 @@ public Item clone() { return null; } } + + public final int getNetworkId() { + return RuntimeItems.getNetworkId(RuntimeItems.getRuntimeMapping().getNetworkFullId(this)); + } } diff --git a/src/main/java/cn/nukkit/item/ItemArmor.java b/src/main/java/cn/nukkit/item/ItemArmor.java index 696a4c5049e..de91f4e626f 100644 --- a/src/main/java/cn/nukkit/item/ItemArmor.java +++ b/src/main/java/cn/nukkit/item/ItemArmor.java @@ -1,6 +1,7 @@ package cn.nukkit.item; import cn.nukkit.Player; +import cn.nukkit.level.Sound; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.ByteTag; import cn.nukkit.nbt.tag.Tag; @@ -17,7 +18,8 @@ abstract public class ItemArmor extends Item implements ItemDurable { public static final int TIER_CHAIN = 3; public static final int TIER_GOLD = 4; public static final int TIER_DIAMOND = 5; - public static final int TIER_OTHER = 6; + public static final int TIER_NETHERITE = 6; + public static final int TIER_OTHER = 7; public ItemArmor(int id) { super(id); @@ -48,25 +50,30 @@ public boolean isArmor() { @Override public boolean onClickAir(Player player, Vector3 directionVector) { boolean equip = false; - if (this.isHelmet() && player.getInventory().getHelmet().isNull()) { + Item oldSlotItem = Item.get(AIR); + if (this.isHelmet()) { + oldSlotItem = player.getInventory().getHelmet(); if (player.getInventory().setHelmet(this)) { equip = true; } - } else if (this.isChestplate() && player.getInventory().getChestplate().isNull()) { + } else if (this.isChestplate()) { + oldSlotItem = player.getInventory().getChestplate(); if (player.getInventory().setChestplate(this)) { equip = true; } - } else if (this.isLeggings() && player.getInventory().getLeggings().isNull()) { + } else if (this.isLeggings()) { + oldSlotItem = player.getInventory().getLeggings(); if (player.getInventory().setLeggings(this)) { equip = true; } - } else if (this.isBoots() && player.getInventory().getBoots().isNull()) { + } else if (this.isBoots()) { + oldSlotItem = player.getInventory().getBoots(); if (player.getInventory().setBoots(this)) { equip = true; } } if (equip) { - player.getInventory().clear(player.getInventory().getHeldItemIndex()); + player.getInventory().setItem(player.getInventory().getHeldItemIndex(), oldSlotItem); switch (this.getTier()) { case TIER_CHAIN: player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_ARMOR_EQUIP_CHAIN); @@ -83,6 +90,8 @@ public boolean onClickAir(Player player, Vector3 directionVector) { case TIER_LEATHER: player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_ARMOR_EQUIP_LEATHER); break; + case TIER_NETHERITE: + player.getLevel().addSound(player, Sound.ARMOR_EQUIP_NETHERITE); case TIER_OTHER: default: player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_ARMOR_EQUIP_GENERIC); diff --git a/src/main/java/cn/nukkit/item/ItemAxeNetherite.java b/src/main/java/cn/nukkit/item/ItemAxeNetherite.java new file mode 100644 index 00000000000..d1060ad868e --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemAxeNetherite.java @@ -0,0 +1,36 @@ +package cn.nukkit.item; + +public class ItemAxeNetherite extends ItemTool { + + public ItemAxeNetherite() { + this(0, 1); + } + + public ItemAxeNetherite(Integer meta) { + this(meta, 1); + } + + public ItemAxeNetherite(Integer meta, int count) { + super(NETHERITE_AXE, meta, count, "Netherite Axe"); + } + + @Override + public boolean isAxe() { + return true; + } + + @Override + public int getAttackDamage() { + return 7; + } + + @Override + public int getTier() { + return ItemTool.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_NETHERITE; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemBoat.java b/src/main/java/cn/nukkit/item/ItemBoat.java index 8afcdabcb2e..d7c81ef1607 100644 --- a/src/main/java/cn/nukkit/item/ItemBoat.java +++ b/src/main/java/cn/nukkit/item/ItemBoat.java @@ -57,7 +57,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemBootsNetherite.java b/src/main/java/cn/nukkit/item/ItemBootsNetherite.java new file mode 100644 index 00000000000..6761c3a26bb --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemBootsNetherite.java @@ -0,0 +1,41 @@ +package cn.nukkit.item; + +public class ItemBootsNetherite extends ItemArmor { + + public ItemBootsNetherite() { + this(0, 1); + } + + public ItemBootsNetherite(Integer meta) { + this(meta, 1); + } + + public ItemBootsNetherite(Integer meta, int count) { + super(NETHERITE_BOOTS, meta, count, "Netherite Boots"); + } + + @Override + public boolean isBoots() { + return true; + } + + @Override + public int getTier() { + return ItemArmor.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return 481; + } + + @Override + public int getArmorPoints() { + return 3; + } + + @Override + public int getToughness() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemBow.java b/src/main/java/cn/nukkit/item/ItemBow.java index af5f230612b..dc953ec9ab1 100644 --- a/src/main/java/cn/nukkit/item/ItemBow.java +++ b/src/main/java/cn/nukkit/item/ItemBow.java @@ -57,7 +57,7 @@ public boolean onRelease(Player player, int ticksUsed) { Inventory inventory = player.getOffhandInventory(); - if (!inventory.contains(itemArrow) && !(inventory = player.getInventory()).contains(itemArrow) && player.isSurvival()) { + if (!inventory.contains(itemArrow) && !(inventory = player.getInventory()).contains(itemArrow) && (player.isAdventure() || player.isSurvival())) { player.getOffhandInventory().sendContents(player); inventory.sendContents(player); return false; @@ -116,7 +116,7 @@ public boolean onRelease(Player player, int ticksUsed) { if (infinity && (projectile = entityShootBowEvent.getProjectile()) instanceof EntityArrow) { ((EntityArrow) projectile).setPickupMode(EntityArrow.PICKUP_CREATIVE); } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { if (!infinity) { inventory.removeItem(itemArrow); } diff --git a/src/main/java/cn/nukkit/item/ItemBucket.java b/src/main/java/cn/nukkit/item/ItemBucket.java index 2b899ca0965..2322a551ab4 100644 --- a/src/main/java/cn/nukkit/item/ItemBucket.java +++ b/src/main/java/cn/nukkit/item/ItemBucket.java @@ -2,10 +2,12 @@ import cn.nukkit.Player; import cn.nukkit.block.*; +import cn.nukkit.entity.Entity; import cn.nukkit.event.player.PlayerBucketEmptyEvent; import cn.nukkit.event.player.PlayerBucketFillEvent; import cn.nukkit.event.player.PlayerItemConsumeEvent; import cn.nukkit.level.Level; +import cn.nukkit.level.particle.ExplodeParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockFace.Plane; import cn.nukkit.math.Vector3; @@ -80,6 +82,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + Block targetBlock = Block.get(getDamageByTarget(this.meta)); if (targetBlock instanceof BlockAir) { @@ -100,10 +106,18 @@ public boolean onActivate(Level level, Player player, Block block, Block target, } if (player.isSurvival()) { - Item clone = this.clone(); - clone.setCount(this.getCount() - 1); - player.getInventory().setItemInHand(clone); - player.getInventory().addItem(ev.getItem()); + if (this.getCount() - 1 <= 0) { + player.getInventory().setItemInHand(ev.getItem()); + } else { + Item clone = this.clone(); + clone.setCount(this.getCount() - 1); + player.getInventory().setItemInHand(clone); + if (player.getInventory().canAddItem(ev.getItem())) { + player.getInventory().addItem(ev.getItem()); + } else { + player.dropItem(ev.getItem()); + } + } } if (target instanceof BlockLava) { @@ -120,10 +134,14 @@ public boolean onActivate(Level level, Player player, Block block, Block target, } else if (targetBlock instanceof BlockLiquid) { Item result = Item.get(BUCKET, 0, 1); PlayerBucketEmptyEvent ev = new PlayerBucketEmptyEvent(player, block, face, this, result); - ev.setCancelled(!block.canBeFlowedInto()); + if (!block.canBeFlowedInto()) { + ev.setCancelled(true); + } + boolean nether = false; if (player.getLevel().getDimension() == Level.DIMENSION_NETHER && this.getDamage() != 10) { ev.setCancelled(true); + nether = true; } player.getServer().getPluginManager().callEvent(ev); @@ -131,10 +149,18 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (!ev.isCancelled()) { player.getLevel().setBlock(block, targetBlock, true, true); if (player.isSurvival()) { - Item clone = this.clone(); - clone.setCount(this.getCount() - 1); - player.getInventory().setItemInHand(clone); - player.getInventory().addItem(ev.getItem()); + if (this.getCount() - 1 <= 0) { + player.getInventory().setItemInHand(ev.getItem()); + } else { + Item clone = this.clone(); + clone.setCount(this.getCount() - 1); + player.getInventory().setItemInHand(clone); + if (player.getInventory().canAddItem(ev.getItem())) { + player.getInventory().addItem(ev.getItem()); + } else { + player.dropItem(ev.getItem()); + } + } } if (this.getDamage() == 10) { @@ -143,7 +169,33 @@ public boolean onActivate(Level level, Player player, Block block, Block target, level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_BUCKET_EMPTY_WATER); } + switch (this.getDamage()) { + case 2: + Entity e2 = Entity.createEntity("Cod", block); + if (e2 != null) e2.spawnToAll(); + break; + case 3: + Entity e3 = Entity.createEntity("Salmon", block); + if (e3 != null) e3.spawnToAll(); + break; + case 4: + Entity e4 = Entity.createEntity("TropicalFish", block); + if (e4 != null) e4.spawnToAll(); + break; + case 5: + Entity e5 = Entity.createEntity("Pufferfish", block); + if (e5 != null) e5.spawnToAll(); + break; + } + return true; + } else if (nether) { + if (!player.isCreative()) { + this.setDamage(0); // Empty bucket + player.getInventory().setItemInHand(this); + } + player.getLevel().addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_FIZZ); + player.getLevel().addParticle(new ExplodeParticle(target.add(0.5, 1, 0.5))); } else { player.getLevel().sendBlocks(new Player[]{player}, new Block[]{Block.get(Block.AIR, 0, block)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); player.getInventory().sendContents(player); @@ -160,6 +212,10 @@ public boolean onClickAir(Player player, Vector3 directionVector) { @Override public boolean onUse(Player player, int ticksUsed) { + if (player.isSpectator() || this.getDamage() != 1) { + return false; + } + PlayerItemConsumeEvent consumeEvent = new PlayerItemConsumeEvent(player, this); player.getServer().getPluginManager().callEvent(consumeEvent); @@ -168,10 +224,8 @@ public boolean onUse(Player player, int ticksUsed) { return false; } - if (player.isSurvival()) { - this.count--; - player.getInventory().setItemInHand(this); - player.getInventory().addItem(new ItemBucket()); + if (!player.isCreative()) { + player.getInventory().setItemInHand(new ItemBucket()); } player.removeAllEffects(); diff --git a/src/main/java/cn/nukkit/item/ItemCake.java b/src/main/java/cn/nukkit/item/ItemCake.java index efce9bca8c4..d3f0021ed00 100644 --- a/src/main/java/cn/nukkit/item/ItemCake.java +++ b/src/main/java/cn/nukkit/item/ItemCake.java @@ -24,6 +24,6 @@ public ItemCake(Integer meta, int count) { @Override public int getMaxStackSize() { - return 1; + return 64; } } diff --git a/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java b/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java new file mode 100644 index 00000000000..09674778ac9 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java @@ -0,0 +1,41 @@ +package cn.nukkit.item; + +public class ItemChestplateNetherite extends ItemArmor { + + public ItemChestplateNetherite() { + this(0, 1); + } + + public ItemChestplateNetherite(Integer meta) { + this(meta, 1); + } + + public ItemChestplateNetherite(Integer meta, int count) { + super(NETHERITE_CHESTPLATE, meta, count, "Netherite Chestplate"); + } + + @Override + public boolean isChestplate() { + return true; + } + + @Override + public int getTier() { + return ItemArmor.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return 592; + } + + @Override + public int getArmorPoints() { + return 8; + } + + @Override + public int getToughness() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemCrossbow.java b/src/main/java/cn/nukkit/item/ItemCrossbow.java new file mode 100644 index 00000000000..6abd3526271 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemCrossbow.java @@ -0,0 +1,26 @@ +package cn.nukkit.item; + +public class ItemCrossbow extends ItemTool { + + public ItemCrossbow() { + this(0, 1); + } + + public ItemCrossbow(Integer meta) { + this(meta, 1); + } + + public ItemCrossbow(Integer meta, int count) { + super(CROSSBOW, meta, count, "Crossbow"); + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_CROSSBOW; + } + + @Override + public int getEnchantAbility() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemEdible.java b/src/main/java/cn/nukkit/item/ItemEdible.java index 26abaaae27e..fd59d149e6d 100644 --- a/src/main/java/cn/nukkit/item/ItemEdible.java +++ b/src/main/java/cn/nukkit/item/ItemEdible.java @@ -46,7 +46,7 @@ public boolean onUse(Player player, int ticksUsed) { } Food food = Food.getByRelative(this); - if (player.isSurvival() && food != null && food.eatenBy(player)) { + if ((player.isAdventure() || player.isSurvival()) && food != null && food.eatenBy(player)) { --this.count; player.getInventory().setItemInHand(this); } diff --git a/src/main/java/cn/nukkit/item/ItemEndCrystal.java b/src/main/java/cn/nukkit/item/ItemEndCrystal.java index 2bd2f3d55f2..97a6984ab7e 100644 --- a/src/main/java/cn/nukkit/item/ItemEndCrystal.java +++ b/src/main/java/cn/nukkit/item/ItemEndCrystal.java @@ -1,18 +1,21 @@ package cn.nukkit.item; -import cn.nukkit.block.BlockObsidian; +import cn.nukkit.Player; +import cn.nukkit.block.Block; import cn.nukkit.block.BlockBedrock; -import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockObsidian; import cn.nukkit.entity.Entity; -import java.util.Random; -import cn.nukkit.nbt.tag.FloatTag; -import cn.nukkit.nbt.tag.DoubleTag; -import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.math.BlockFace; -import cn.nukkit.block.Block; -import cn.nukkit.Player; import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +import java.util.Random; public class ItemEndCrystal extends Item { @@ -35,18 +38,20 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { - if ((!(target instanceof BlockBedrock) && !(target instanceof BlockObsidian)) || face != BlockFace.UP) return false; + if (!(target instanceof BlockBedrock) && !(target instanceof BlockObsidian)) return false; FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); + Entity[] entities = level.getNearbyEntities(new SimpleAxisAlignedBB(target.x, target.y, target.z, target.x + 1, target.y + 2, target.z + 1)); + Block up = target.up(); - if (chunk == null) { + if (chunk == null || up.getId() != BlockID.AIR || up.up().getId() != BlockID.AIR || entities.length != 0) { return false; } CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") - .add(new DoubleTag("", block.getX() + 0.5)) - .add(new DoubleTag("", block.getY())) - .add(new DoubleTag("", block.getZ() + 0.5))) + .add(new DoubleTag("", target.x + 0.5)) + .add(new DoubleTag("", up.y)) + .add(new DoubleTag("", target.z + 0.5))) .putList(new ListTag("Motion") .add(new DoubleTag("", 0)) .add(new DoubleTag("", 0)) @@ -62,7 +67,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, Entity entity = Entity.createEntity("EndCrystal", chunk, nbt); if (entity != null) { - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemFireCharge.java b/src/main/java/cn/nukkit/item/ItemFireCharge.java index 7c529f2fc72..a06735bbe9f 100644 --- a/src/main/java/cn/nukkit/item/ItemFireCharge.java +++ b/src/main/java/cn/nukkit/item/ItemFireCharge.java @@ -33,6 +33,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta)) { if (target.getId() == OBSIDIAN) { if (level.createPortal(target)) { diff --git a/src/main/java/cn/nukkit/item/ItemFirework.java b/src/main/java/cn/nukkit/item/ItemFirework.java index 69c1931e7f7..2bc7e4ca8f5 100644 --- a/src/main/java/cn/nukkit/item/ItemFirework.java +++ b/src/main/java/cn/nukkit/item/ItemFirework.java @@ -62,6 +62,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + if (block.canPassThrough()) { this.spawnFirework(level, block); diff --git a/src/main/java/cn/nukkit/item/ItemFlintSteel.java b/src/main/java/cn/nukkit/item/ItemFlintSteel.java index c6b3b957a7d..c214b903a2b 100644 --- a/src/main/java/cn/nukkit/item/ItemFlintSteel.java +++ b/src/main/java/cn/nukkit/item/ItemFlintSteel.java @@ -34,6 +34,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta)) { if (target.getId() == OBSIDIAN) { if (level.createPortal(target)) { diff --git a/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java b/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java new file mode 100644 index 00000000000..4f6b45146fb --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java @@ -0,0 +1,41 @@ +package cn.nukkit.item; + +public class ItemHelmetNetherite extends ItemArmor { + + public ItemHelmetNetherite() { + this(0, 1); + } + + public ItemHelmetNetherite(Integer meta) { + this(meta, 1); + } + + public ItemHelmetNetherite(Integer meta, int count) { + super(NETHERITE_HELMET, meta, count, "Netherite Helmet"); + } + + @Override + public boolean isHelmet() { + return true; + } + + @Override + public int getTier() { + return ItemArmor.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return 407; + } + + @Override + public int getArmorPoints() { + return 3; + } + + @Override + public int getToughness() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemHoeNetherite.java b/src/main/java/cn/nukkit/item/ItemHoeNetherite.java new file mode 100644 index 00000000000..a808c6c80b5 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemHoeNetherite.java @@ -0,0 +1,36 @@ +package cn.nukkit.item; + +public class ItemHoeNetherite extends ItemTool { + + public ItemHoeNetherite() { + this(0, 1); + } + + public ItemHoeNetherite(Integer meta) { + this(meta, 1); + } + + public ItemHoeNetherite(Integer meta, int count) { + super(NETHERITE_HOE, meta, count, "Netherite Hoe"); + } + + @Override + public boolean isHoe() { + return true; + } + + @Override + public int getAttackDamage() { + return 6; + } + + @Override + public int getTier() { + return ItemTool.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_NETHERITE; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemID.java b/src/main/java/cn/nukkit/item/ItemID.java index 53e0314ca71..f4e7b379cfe 100644 --- a/src/main/java/cn/nukkit/item/ItemID.java +++ b/src/main/java/cn/nukkit/item/ItemID.java @@ -229,6 +229,7 @@ public interface ItemID { int SCUTE = 468; int TURTLE_SHELL = 469; int PHANTOM_MEMBRANE = 470; + int CROSSBOW = 471; int SWEET_BERRIES = 477; @@ -251,6 +252,17 @@ public interface ItemID { int HONEY_BOTTLE = 737; int LODESTONECOMPASS = 741; + int NETHERITE_INGOT = 742; + int NETHERITE_SWORD = 743; + int NETHERITE_SHOVEL = 744; + int NETHERITE_PICKAXE = 745; + int NETHERITE_AXE = 746; + int NETHERITE_HOE = 747; + int NETHERITE_HELMET = 748; + int NETHERITE_CHESTPLATE = 749; + int NETHERITE_LEGGINGS = 750; + int NETHERITE_BOOTS = 751; + int NETHERITE_SCRAP = 752; int WARPED_FUNGUS_ON_A_STICK = 757; diff --git a/src/main/java/cn/nukkit/item/ItemIngotNetherite.java b/src/main/java/cn/nukkit/item/ItemIngotNetherite.java new file mode 100644 index 00000000000..e7bf8de4c70 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemIngotNetherite.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemIngotNetherite extends Item { + + public ItemIngotNetherite() { + this(0, 1); + } + + public ItemIngotNetherite(Integer meta) { + this(meta, 1); + } + + public ItemIngotNetherite(Integer meta, int count) { + super(NETHERITE_INGOT, 0, count, "Netherite Ingot"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java b/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java new file mode 100644 index 00000000000..a7e0298fec9 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java @@ -0,0 +1,41 @@ +package cn.nukkit.item; + +public class ItemLeggingsNetherite extends ItemArmor { + + public ItemLeggingsNetherite() { + this(0, 1); + } + + public ItemLeggingsNetherite(Integer meta) { + this(meta, 1); + } + + public ItemLeggingsNetherite(Integer meta, int count) { + super(NETHERITE_LEGGINGS, meta, count, "Netherite Leggings"); + } + + @Override + public boolean isLeggings() { + return true; + } + + @Override + public int getTier() { + return ItemArmor.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return 555; + } + + @Override + public int getArmorPoints() { + return 6; + } + + @Override + public int getToughness() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemMap.java b/src/main/java/cn/nukkit/item/ItemMap.java index f4d03343ba1..5ed327ea55b 100644 --- a/src/main/java/cn/nukkit/item/ItemMap.java +++ b/src/main/java/cn/nukkit/item/ItemMap.java @@ -32,7 +32,7 @@ public ItemMap(Integer meta) { } public ItemMap(Integer meta, int count) { - super(MAP, 0, count, "Map"); + super(MAP, meta, count, "Map"); if (!hasCompoundTag() || !getNamedTag().contains("map_uuid")) { CompoundTag tag = new CompoundTag(); diff --git a/src/main/java/cn/nukkit/item/ItemMinecart.java b/src/main/java/cn/nukkit/item/ItemMinecart.java index 122b59b516e..5a523cd88cd 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecart.java +++ b/src/main/java/cn/nukkit/item/ItemMinecart.java @@ -63,7 +63,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemMinecartChest.java b/src/main/java/cn/nukkit/item/ItemMinecartChest.java index 37d2503e1d4..847a0feb6d6 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartChest.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartChest.java @@ -59,7 +59,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemMinecartHopper.java b/src/main/java/cn/nukkit/item/ItemMinecartHopper.java index b7534975ab7..84c052b3376 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartHopper.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartHopper.java @@ -59,7 +59,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemMinecartTNT.java b/src/main/java/cn/nukkit/item/ItemMinecartTNT.java index db854023e4f..20f0e6eb2fe 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartTNT.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartTNT.java @@ -59,7 +59,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (player.isSurvival()) { + if (player.isAdventure() || player.isSurvival()) { Item item = player.getInventory().getItemInHand(); item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item); diff --git a/src/main/java/cn/nukkit/item/ItemNameTag.java b/src/main/java/cn/nukkit/item/ItemNameTag.java new file mode 100644 index 00000000000..ef898d71a5c --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemNameTag.java @@ -0,0 +1,17 @@ +package cn.nukkit.item; + +public class ItemNameTag extends Item { + + public ItemNameTag() { + this(0, 1); + } + + public ItemNameTag(Integer meta) { + this(meta, 1); + } + + public ItemNameTag(Integer meta, int count) { + super(NAME_TAG, meta, count, "Name Tag"); + } + +} diff --git a/src/main/java/cn/nukkit/item/ItemPainting.java b/src/main/java/cn/nukkit/item/ItemPainting.java index 550b95e7fb8..3419bfc2086 100644 --- a/src/main/java/cn/nukkit/item/ItemPainting.java +++ b/src/main/java/cn/nukkit/item/ItemPainting.java @@ -45,6 +45,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); if (chunk == null || target.isTransparent() || face.getHorizontalIndex() == -1 || block.isSolid()) { diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeNetherite.java b/src/main/java/cn/nukkit/item/ItemPickaxeNetherite.java new file mode 100644 index 00000000000..7c58e92b989 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemPickaxeNetherite.java @@ -0,0 +1,36 @@ +package cn.nukkit.item; + +public class ItemPickaxeNetherite extends ItemTool { + + public ItemPickaxeNetherite() { + this(0, 1); + } + + public ItemPickaxeNetherite(Integer meta) { + this(meta, 1); + } + + public ItemPickaxeNetherite(Integer meta, int count) { + super(NETHERITE_PICKAXE, meta, count, "Netherite Pickaxe"); + } + + @Override + public boolean isPickaxe() { + return true; + } + + @Override + public int getAttackDamage() { + return 6; + } + + @Override + public int getTier() { + return ItemTool.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_NETHERITE; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemPotion.java b/src/main/java/cn/nukkit/item/ItemPotion.java index debcae3fe5a..70d9bb6d6b9 100644 --- a/src/main/java/cn/nukkit/item/ItemPotion.java +++ b/src/main/java/cn/nukkit/item/ItemPotion.java @@ -5,8 +5,6 @@ import cn.nukkit.math.Vector3; import cn.nukkit.potion.Potion; -import static cn.nukkit.Player.SURVIVAL; - public class ItemPotion extends Item { public static final int NO_EFFECTS = 0; @@ -78,7 +76,7 @@ public boolean onUse(Player player, int ticksUsed) { } Potion potion = Potion.getPotion(this.getDamage()).setSplash(false); - if (player.getGamemode() == SURVIVAL) { + if (player.isAdventure() || player.isSurvival()) { --this.count; player.getInventory().setItemInHand(this); player.getInventory().addItem(new ItemGlassBottle()); diff --git a/src/main/java/cn/nukkit/item/ItemScrapNetherite.java b/src/main/java/cn/nukkit/item/ItemScrapNetherite.java new file mode 100644 index 00000000000..3f5426fc0b0 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemScrapNetherite.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemScrapNetherite extends Item { + + public ItemScrapNetherite() { + this(0, 1); + } + + public ItemScrapNetherite(Integer meta) { + this(meta, 1); + } + + public ItemScrapNetherite(Integer meta, int count) { + super(NETHERITE_SCRAP, 0, count, "Netherite Scrap"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemShovelNetherite.java b/src/main/java/cn/nukkit/item/ItemShovelNetherite.java new file mode 100644 index 00000000000..99e5dd766af --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemShovelNetherite.java @@ -0,0 +1,36 @@ +package cn.nukkit.item; + +public class ItemShovelNetherite extends ItemTool { + + public ItemShovelNetherite() { + this(0, 1); + } + + public ItemShovelNetherite(Integer meta) { + this(meta, 1); + } + + public ItemShovelNetherite(Integer meta, int count) { + super(NETHERITE_SHOVEL, meta, count, "Netherite Shovel"); + } + + @Override + public boolean isShovel() { + return true; + } + + @Override + public int getAttackDamage() { + return 5; + } + + @Override + public int getTier() { + return ItemTool.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_NETHERITE; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSpawnEgg.java b/src/main/java/cn/nukkit/item/ItemSpawnEgg.java index a9d34e79369..3afd9eb6440 100644 --- a/src/main/java/cn/nukkit/item/ItemSpawnEgg.java +++ b/src/main/java/cn/nukkit/item/ItemSpawnEgg.java @@ -40,6 +40,10 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (player.isAdventure()) { + return false; + } + FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); if (chunk == null) { diff --git a/src/main/java/cn/nukkit/item/ItemSwordNetherite.java b/src/main/java/cn/nukkit/item/ItemSwordNetherite.java new file mode 100644 index 00000000000..b1c6c21266a --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSwordNetherite.java @@ -0,0 +1,36 @@ +package cn.nukkit.item; + +public class ItemSwordNetherite extends ItemTool { + + public ItemSwordNetherite() { + this(0, 1); + } + + public ItemSwordNetherite(Integer meta) { + this(meta, 1); + } + + public ItemSwordNetherite(Integer meta, int count) { + super(NETHERITE_SWORD, meta, count, "Netherite Sword"); + } + + @Override + public boolean isSword() { + return true; + } + + @Override + public int getAttackDamage() { + return 8; + } + + @Override + public int getTier() { + return ItemTool.TIER_NETHERITE; + } + + @Override + public int getMaxDurability() { + return ItemTool.DURABILITY_NETHERITE; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemTool.java b/src/main/java/cn/nukkit/item/ItemTool.java index 072576ff02f..cad0e61332d 100644 --- a/src/main/java/cn/nukkit/item/ItemTool.java +++ b/src/main/java/cn/nukkit/item/ItemTool.java @@ -18,6 +18,7 @@ public abstract class ItemTool extends Item implements ItemDurable { public static final int TIER_STONE = 3; public static final int TIER_IRON = 4; public static final int TIER_DIAMOND = 5; + public static final int TIER_NETHERITE = 6; public static final int TYPE_NONE = 0; public static final int TYPE_SWORD = 1; @@ -25,17 +26,20 @@ public abstract class ItemTool extends Item implements ItemDurable { public static final int TYPE_PICKAXE = 3; public static final int TYPE_AXE = 4; public static final int TYPE_SHEARS = 5; + public static final int TYPE_HOE = 6; public static final int DURABILITY_WOODEN = 60; public static final int DURABILITY_GOLD = 33; public static final int DURABILITY_STONE = 132; public static final int DURABILITY_IRON = 251; public static final int DURABILITY_DIAMOND = 1562; + public static final int DURABILITY_NETHERITE = 2032; public static final int DURABILITY_FLINT_STEEL = 65; public static final int DURABILITY_SHEARS = 239; public static final int DURABILITY_BOW = 385; public static final int DURABILITY_TRIDENT = 251; public static final int DURABILITY_FISHING_ROD = 65; + public static final int DURABILITY_CROSSBOW = 465; public ItemTool(int id) { this(id, 0, 1, UNKNOWN_STR); @@ -67,8 +71,9 @@ public boolean useOn(Block block) { if (block.getToolType() == ItemTool.TYPE_PICKAXE && this.isPickaxe() || block.getToolType() == ItemTool.TYPE_SHOVEL && this.isShovel() || block.getToolType() == ItemTool.TYPE_AXE && this.isAxe() || + block.getToolType() == ItemTool.TYPE_HOE && this.isHoe() || block.getToolType() == ItemTool.TYPE_SWORD && this.isSword() || - block.getToolType() == ItemTool.SHEARS && this.isShears() + block.getToolType() == ItemTool.TYPE_SHEARS && this.isShears() ) { this.meta++; } else if (!this.isShears() && block.getBreakTime(this) > 0) { @@ -145,7 +150,7 @@ public boolean isShears() { @Override public boolean isTool() { - return (this.id == FLINT_STEEL || this.id == SHEARS || this.id == BOW || this.isPickaxe() || this.isAxe() || this.isShovel() || this.isSword() || this.isHoe()); + return (this.id == FLINT_STEEL || this.id == SHEARS || this.id == BOW || this.id == CROSSBOW || this.isPickaxe() || this.isAxe() || this.isShovel() || this.isSword() || this.isHoe()); } @Override diff --git a/src/main/java/cn/nukkit/item/RuntimeItemMapping.java b/src/main/java/cn/nukkit/item/RuntimeItemMapping.java new file mode 100644 index 00000000000..20ca3fd00fa --- /dev/null +++ b/src/main/java/cn/nukkit/item/RuntimeItemMapping.java @@ -0,0 +1,43 @@ +package cn.nukkit.item; + +import it.unimi.dsi.fastutil.ints.Int2IntMap; + +public class RuntimeItemMapping { + + private final Int2IntMap legacyNetworkMap; + private final Int2IntMap networkLegacyMap; + private final byte[] itemDataPalette; + + public RuntimeItemMapping(byte[] itemDataPalette, Int2IntMap legacyNetworkMap, Int2IntMap networkLegacyMap) { + this.itemDataPalette = itemDataPalette; + this.legacyNetworkMap = legacyNetworkMap; + this.networkLegacyMap = networkLegacyMap; + this.legacyNetworkMap.defaultReturnValue(-1); + this.networkLegacyMap.defaultReturnValue(-1); + } + + public int getNetworkFullId(Item item) { + int fullId = RuntimeItems.getFullId(item.getId(), item.hasMeta() ? item.getDamage() : -1); + int networkId = this.legacyNetworkMap.get(fullId); + if (networkId == -1) { + networkId = this.legacyNetworkMap.get(RuntimeItems.getFullId(item.getId(), 0)); + } + if (networkId == -1) { + throw new IllegalArgumentException("Unknown item mapping " + item); + } + + return networkId; + } + + public int getLegacyFullId(int networkId) { + int fullId = networkLegacyMap.get(networkId); + if (fullId == -1) { + throw new IllegalArgumentException("Unknown network mapping: " + networkId); + } + return fullId; + } + + public byte[] getItemDataPalette() { + return this.itemDataPalette; + } +} diff --git a/src/main/java/cn/nukkit/item/RuntimeItems.java b/src/main/java/cn/nukkit/item/RuntimeItems.java new file mode 100644 index 00000000000..3aca8e84078 --- /dev/null +++ b/src/main/java/cn/nukkit/item/RuntimeItems.java @@ -0,0 +1,91 @@ +package cn.nukkit.item; + +import cn.nukkit.Server; +import cn.nukkit.utils.BinaryStream; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.experimental.UtilityClass; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; + +@UtilityClass +public class RuntimeItems { + + private static final Gson GSON = new Gson(); + private static final Type ENTRY_TYPE = new TypeToken>(){}.getType(); + + private static final RuntimeItemMapping itemPalette; + + static { + Server.getInstance().getLogger().debug("Loading runtime items..."); + InputStream stream = Server.class.getClassLoader().getResourceAsStream("runtime_item_ids.json"); + if (stream == null) { + throw new AssertionError("Unable to load runtime_item_ids.json"); + } + + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + Collection entries = GSON.fromJson(reader, ENTRY_TYPE); + + BinaryStream paletteBuffer = new BinaryStream(); + paletteBuffer.putUnsignedVarInt(entries.size()); + + Int2IntMap legacyNetworkMap = new Int2IntOpenHashMap(); + Int2IntMap networkLegacyMap = new Int2IntOpenHashMap(); + for (Entry entry : entries) { + paletteBuffer.putString(entry.name); + paletteBuffer.putLShort(entry.id); + paletteBuffer.putBoolean(false); // Component item + if (entry.oldId != null) { + boolean hasData = entry.oldData != null; + int fullId = getFullId(entry.oldId, hasData ? entry.oldData : 0); + legacyNetworkMap.put(fullId, (entry.id << 1) | (hasData ? 1 : 0)); + networkLegacyMap.put(entry.id, fullId | (hasData ? 1 : 0)); + } + } + + byte[] itemDataPalette = paletteBuffer.getBuffer(); + itemPalette = new RuntimeItemMapping(itemDataPalette, legacyNetworkMap, networkLegacyMap); + } + + public static RuntimeItemMapping getRuntimeMapping() { + return itemPalette; + } + + public static int getId(int fullId) { + return (short) (fullId >> 16); + } + + public static int getData(int fullId) { + return ((fullId >> 1) & 0x7fff); + } + + public static int getFullId(int id, int data) { + return (((short) id) << 16) | ((data & 0x7fff) << 1); + } + + public static int getNetworkId(int networkFullId) { + return networkFullId >> 1; + } + + public static boolean hasData(int id) { + return (id & 0x1) != 0; + } + + @ToString + @RequiredArgsConstructor + static class Entry { + String name; + int id; + Integer oldId; + Integer oldData; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/Enchantment.java b/src/main/java/cn/nukkit/item/enchantment/Enchantment.java index 8d2e9b6bba0..bcfc27b492b 100644 --- a/src/main/java/cn/nukkit/item/enchantment/Enchantment.java +++ b/src/main/java/cn/nukkit/item/enchantment/Enchantment.java @@ -7,6 +7,9 @@ import cn.nukkit.item.enchantment.bow.EnchantmentBowInfinity; import cn.nukkit.item.enchantment.bow.EnchantmentBowKnockback; import cn.nukkit.item.enchantment.bow.EnchantmentBowPower; +import cn.nukkit.item.enchantment.crossbow.EnchantmentCrossbowMultishot; +import cn.nukkit.item.enchantment.crossbow.EnchantmentCrossbowPiercing; +import cn.nukkit.item.enchantment.crossbow.EnchantmentCrossbowQuickCharge; import cn.nukkit.item.enchantment.damage.EnchantmentDamageAll; import cn.nukkit.item.enchantment.damage.EnchantmentDamageArthropods; import cn.nukkit.item.enchantment.damage.EnchantmentDamageSmite; @@ -67,6 +70,10 @@ public abstract class Enchantment implements Cloneable { public static final int ID_TRIDENT_RIPTIDE = 30; public static final int ID_TRIDENT_LOYALTY = 31; public static final int ID_TRIDENT_CHANNELING = 32; + public static final int ID_CROSSBOW_MULTISHOT = 33; + public static final int ID_CROSSBOW_PIERCING = 34; + public static final int ID_CROSSBOW_QUICK_CHARGE = 35; + public static final int ID_SOUL_SPEED = 36; public static void init() { enchantments = new Enchantment[256]; @@ -104,6 +111,10 @@ public static void init() { enchantments[ID_TRIDENT_RIPTIDE] = new EnchantmentTridentRiptide(); enchantments[ID_TRIDENT_LOYALTY] = new EnchantmentTridentLoyalty(); enchantments[ID_TRIDENT_CHANNELING] = new EnchantmentTridentChanneling(); + enchantments[ID_CROSSBOW_MULTISHOT] = new EnchantmentCrossbowMultishot(); + enchantments[ID_CROSSBOW_PIERCING] = new EnchantmentCrossbowPiercing(); + enchantments[ID_CROSSBOW_QUICK_CHARGE] = new EnchantmentCrossbowQuickCharge(); + enchantments[ID_SOUL_SPEED] = new EnchantmentSoulSpeed(); } public static Enchantment get(int id) { @@ -135,16 +146,16 @@ public static Enchantment[] getEnchantments() { } public final int id; - private final int weight; + private final Rarity rarity; public EnchantmentType type; protected int level = 1; protected final String name; - protected Enchantment(int id, String name, int weight, EnchantmentType type) { + protected Enchantment(int id, String name, Rarity rarity, EnchantmentType type) { this.id = id; - this.weight = weight; + this.rarity = rarity; this.type = type; this.name = name; @@ -173,8 +184,16 @@ public int getId() { return id; } + public Rarity getRarity() { + return this.rarity; + } + + /** + * @deprecated use {@link Rarity#getWeight()} instead + */ + @Deprecated public int getWeight() { - return weight; + return this.rarity.getWeight(); } public int getMinLevel() { @@ -213,7 +232,11 @@ public void doPostHurt(Entity attacker, Entity entity) { } - public boolean isCompatibleWith(Enchantment enchantment) { + public final boolean isCompatibleWith(Enchantment enchantment) { + return this.checkCompatibility(enchantment) && enchantment.checkCompatibility(this); + } + + protected boolean checkCompatibility(Enchantment enchantment) { return this != enchantment; } @@ -254,7 +277,35 @@ public static String getRandomName() { private static class UnknownEnchantment extends Enchantment { protected UnknownEnchantment(int id) { - super(id, "unknown", 0, EnchantmentType.ALL); + super(id, "unknown", Rarity.VERY_RARE, EnchantmentType.ALL); + } + } + + public enum Rarity { + COMMON(10), + UNCOMMON(5), + RARE(2), + VERY_RARE(1); + + private final int weight; + + Rarity(int weight) { + this.weight = weight; + } + + public int getWeight() { + return this.weight; + } + + public static Rarity fromWeight(int weight) { + if (weight < 2) { + return VERY_RARE; + } else if (weight < 5) { + return RARE; + } else if (weight < 10) { + return UNCOMMON; + } + return COMMON; } } } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java index 6ede1d48b6d..7d511415002 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java @@ -2,7 +2,7 @@ public class EnchantmentBindingCurse extends Enchantment { protected EnchantmentBindingCurse() { - super(ID_BINDING_CURSE, "bindingCurse", 1, EnchantmentType.WEARABLE); + super(ID_BINDING_CURSE, "curse.binding", Rarity.VERY_RARE, EnchantmentType.WEARABLE); } @Override @@ -12,11 +12,6 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return 50; - } - - @Override - public int getMaxLevel() { - return 1; + return 30; } } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java index eb6ed37b3ef..8cd4140069a 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java @@ -10,7 +10,7 @@ */ public class EnchantmentDurability extends Enchantment { protected EnchantmentDurability() { - super(ID_DURABILITY, "durability", 5, EnchantmentType.BREAKABLE); + super(ID_DURABILITY, "durability", Rarity.UNCOMMON, EnchantmentType.BREAKABLE); } @Override @@ -20,7 +20,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override @@ -29,8 +29,8 @@ public int getMaxLevel() { } @Override - public boolean isCompatibleWith(Enchantment enchantment) { - return super.isCompatibleWith(enchantment) && enchantment.id != ID_FORTUNE_DIGGING; + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_FORTUNE_DIGGING; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java index 9274f92509c..35581c0cf3f 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java @@ -8,7 +8,7 @@ */ public class EnchantmentEfficiency extends Enchantment { protected EnchantmentEfficiency() { - super(ID_EFFICIENCY, "digging", 10, EnchantmentType.DIGGER); + super(ID_EFFICIENCY, "digging", Rarity.COMMON, EnchantmentType.DIGGER); } @Override @@ -18,7 +18,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override @@ -30,5 +30,4 @@ public int getMaxLevel() { public boolean canEnchant(Item item) { return item.isShears() || super.canEnchant(item); } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java index 55f831d2536..2b72e7f91d6 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java @@ -9,7 +9,7 @@ */ public class EnchantmentFireAspect extends Enchantment { protected EnchantmentFireAspect() { - super(ID_FIRE_ASPECT, "fire", 2, EnchantmentType.SWORD); + super(ID_FIRE_ASPECT, "fire", Rarity.RARE, EnchantmentType.SWORD); } @Override @@ -19,7 +19,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java index ab0a22848ca..7f75d0e2d35 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java @@ -2,12 +2,7 @@ public class EnchantmentFrostWalker extends Enchantment { protected EnchantmentFrostWalker() { - super(ID_FROST_WALKER, "frostWalker", 2, EnchantmentType.ARMOR_FEET); - } - - @Override - public int getMinEnchantAbility(int level) { - return level * 10; + super(ID_FROST_WALKER, "frostwalker", Rarity.RARE, EnchantmentType.ARMOR_FEET); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java index 99fd7523f54..e920eb91f67 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java @@ -6,7 +6,7 @@ */ public class EnchantmentKnockback extends Enchantment { protected EnchantmentKnockback() { - super(ID_KNOCKBACK, "knockback", 5, EnchantmentType.SWORD); + super(ID_KNOCKBACK, "knockback", Rarity.UNCOMMON, EnchantmentType.SWORD); } @Override @@ -16,7 +16,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java index fd74ac0cc67..d8890a6e053 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java @@ -6,22 +6,16 @@ */ public class EnchantmentLure extends Enchantment { protected EnchantmentLure() { - super(ID_LURE, "fishingSpeed", 2, EnchantmentType.FISHING_ROD); + super(ID_LURE, "fishingSpeed", Rarity.RARE, EnchantmentType.FISHING_ROD); } @Override public int getMinEnchantAbility(int level) { - return 15 + (level - 1) * 9; - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return level + 8 * level + 6; } @Override public int getMaxLevel() { return 3; } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java index acc024e6673..2b9e9fc3b48 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java @@ -5,13 +5,12 @@ */ public class EnchantmentMending extends Enchantment { protected EnchantmentMending() { - - super(ID_MENDING, "mending", 2, EnchantmentType.ALL); + super(ID_MENDING, "mending", Rarity.RARE, EnchantmentType.BREAKABLE); } @Override public int getMinEnchantAbility(int level) { - return 25 + (level - 1) * 9; + return 25 * level; } @Override @@ -20,10 +19,7 @@ public int getMaxEnchantAbility(int level) { } @Override - public int getMaxLevel() { return 1; } - - @Override - public boolean isCompatibleWith(Enchantment enchantment) { - return super.isCompatibleWith(enchantment) && enchantment.id != ID_BOW_INFINITY; + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_BOW_INFINITY; } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java index 481f8c16ccd..550dea32eba 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java @@ -8,7 +8,7 @@ */ public class EnchantmentSilkTouch extends Enchantment { protected EnchantmentSilkTouch() { - super(ID_SILK_TOUCH, "untouching", 1, EnchantmentType.DIGGER); + super(ID_SILK_TOUCH, "untouching", Rarity.VERY_RARE, EnchantmentType.DIGGER); } @Override @@ -18,17 +18,12 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override - public int getMaxLevel() { - return 1; - } - - @Override - public boolean isCompatibleWith(Enchantment enchantment) { - return super.isCompatibleWith(enchantment) && enchantment.id != ID_FORTUNE_DIGGING; + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_FORTUNE_DIGGING; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java new file mode 100644 index 00000000000..e5d8e1356b2 --- /dev/null +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java @@ -0,0 +1,18 @@ +package cn.nukkit.item.enchantment; + +public class EnchantmentSoulSpeed extends Enchantment { + + protected EnchantmentSoulSpeed() { + super(ID_SOUL_SPEED, "soul_speed", Rarity.VERY_RARE, EnchantmentType.ARMOR_FEET); + } + + @Override + public int getMinEnchantAbility(int level) { + return 10 * level; + } + + @Override + public int getMaxLevel() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java index 34ce2926557..cf85ed2cb97 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java @@ -14,7 +14,7 @@ */ public class EnchantmentThorns extends Enchantment { protected EnchantmentThorns() { - super(ID_THORNS, "thorns", 2, EnchantmentType.ARMOR); + super(ID_THORNS, "thorns", Rarity.RARE, EnchantmentType.ARMOR); } @Override @@ -24,7 +24,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return super.getMinEnchantAbility(level) + 50; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java index 37aa44fbd3c..cf5eeee9477 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java @@ -1,10 +1,9 @@ package cn.nukkit.item.enchantment; -import cn.nukkit.block.BlockPumpkin; import cn.nukkit.item.Item; import cn.nukkit.item.ItemArmor; import cn.nukkit.item.ItemBow; -import cn.nukkit.item.ItemElytra; +import cn.nukkit.item.ItemCrossbow; import cn.nukkit.item.ItemFishingRod; import cn.nukkit.item.ItemSkull; import cn.nukkit.item.ItemTrident; @@ -26,7 +25,8 @@ public enum EnchantmentType { BREAKABLE, BOW, WEARABLE, - TRIDENT; + TRIDENT, + CROSSBOW; public boolean canEnchantItem(Item item) { if (this == ALL) { @@ -36,7 +36,7 @@ public boolean canEnchantItem(Item item) { return true; } else if (item instanceof ItemArmor) { - if (this == ARMOR) { + if (this == ARMOR || this == WEARABLE) { return true; } @@ -58,15 +58,17 @@ public boolean canEnchantItem(Item item) { case SWORD: return item.isSword(); case DIGGER: - return item.isPickaxe() || item.isShovel() || item.isAxe(); + return item.isPickaxe() || item.isShovel() || item.isAxe() || item.isHoe(); case BOW: return item instanceof ItemBow; case FISHING_ROD: return item instanceof ItemFishingRod; case WEARABLE: - return item instanceof ItemArmor || item instanceof ItemElytra || item instanceof ItemSkull || item.getBlock() instanceof BlockPumpkin; + return item instanceof ItemSkull; case TRIDENT: return item instanceof ItemTrident; + case CROSSBOW: + return item instanceof ItemCrossbow; default: return false; } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java index ec8d8aca14e..928398ee5fb 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java @@ -1,22 +1,14 @@ package cn.nukkit.item.enchantment; +import cn.nukkit.item.Item; + public class EnchantmentVanishingCurse extends Enchantment { protected EnchantmentVanishingCurse() { - super(ID_VANISHING_CURSE, "vanishingCurse", 1, EnchantmentType.ALL); - } - - @Override - public int getMinEnchantAbility(int level) { - return 25; - } - - @Override - public int getMaxEnchantAbility(int level) { - return 50; + super(ID_VANISHING_CURSE, "curse.vanishing", Rarity.VERY_RARE, EnchantmentType.BREAKABLE); } @Override - public int getMaxLevel() { - return 1; + public boolean canEnchant(Item item) { + return item.getId() == Item.SKULL || item.getId() == Item.COMPASS || super.canEnchant(item); } } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java index 2374fa718a1..694b59ee54d 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java @@ -6,17 +6,17 @@ */ public class EnchantmentWaterBreath extends Enchantment { protected EnchantmentWaterBreath() { - super(ID_WATER_BREATHING, "oxygen", 2, EnchantmentType.ARMOR_HEAD); + super(ID_WATER_BREATHING, "oxygen", Rarity.RARE, EnchantmentType.ARMOR_HEAD); } @Override public int getMinEnchantAbility(int level) { - return 10 + (level - 1) * 20; + return 10 * level; } @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return this.getMinEnchantAbility(level) + 30; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java index a17e7f9548b..4a6afdba2fd 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java @@ -6,7 +6,7 @@ */ public class EnchantmentWaterWalker extends Enchantment { protected EnchantmentWaterWalker() { - super(ID_WATER_WALKER, "waterWalker", 2, EnchantmentType.ARMOR_FEET); + super(ID_WATER_WALKER, "waterWalker", Rarity.RARE, EnchantmentType.ARMOR_FEET); } @Override @@ -16,7 +16,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 15; + return this.getMinEnchantAbility(level) + 10; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java index 364fb2dcc67..baa9e4542c0 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java @@ -6,7 +6,7 @@ */ public class EnchantmentWaterWorker extends Enchantment { protected EnchantmentWaterWorker() { - super(ID_WATER_WORKER, "waterWorker", 2, EnchantmentType.ARMOR_HEAD); + super(ID_WATER_WORKER, "waterWorker", Rarity.RARE, EnchantmentType.ARMOR_HEAD); } @Override @@ -18,9 +18,4 @@ public int getMinEnchantAbility(int level) { public int getMaxEnchantAbility(int level) { return this.getMinEnchantAbility(level) + 40; } - - @Override - public int getMaxLevel() { - return 1; - } } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java index 15cccf0ed6a..062eaf35df2 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java @@ -8,8 +8,7 @@ * Nukkit Project */ public abstract class EnchantmentBow extends Enchantment { - protected EnchantmentBow(int id, String name, int weight) { - super(id, name, weight, EnchantmentType.BOW); + protected EnchantmentBow(int id, String name, Rarity rarity) { + super(id, name, rarity, EnchantmentType.BOW); } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java index 904795064d7..62b9f48d54a 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java @@ -8,7 +8,7 @@ */ public class EnchantmentBowFlame extends EnchantmentBow { public EnchantmentBowFlame() { - super(Enchantment.ID_BOW_FLAME, "arrowFire", 2); + super(Enchantment.ID_BOW_FLAME, "arrowFire", Rarity.RARE); } @Override @@ -20,9 +20,4 @@ public int getMinEnchantAbility(int level) { public int getMaxEnchantAbility(int level) { return 50; } - - @Override - public int getMaxLevel() { - return 1; - } } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java index 53e2e0597b6..f630c80af95 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java @@ -8,7 +8,7 @@ */ public class EnchantmentBowInfinity extends EnchantmentBow { public EnchantmentBowInfinity() { - super(Enchantment.ID_BOW_INFINITY, "arrowInfinite", 1); + super(Enchantment.ID_BOW_INFINITY, "arrowInfinite", Rarity.VERY_RARE); } @Override @@ -20,9 +20,4 @@ public int getMinEnchantAbility(int level) { public int getMaxEnchantAbility(int level) { return 50; } - - @Override - public int getMaxLevel() { - return 1; - } } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java index 9ef2359788a..457e14715f3 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java @@ -8,7 +8,7 @@ */ public class EnchantmentBowKnockback extends EnchantmentBow { public EnchantmentBowKnockback() { - super(Enchantment.ID_BOW_KNOCKBACK, "arrowKnockback", 2); + super(Enchantment.ID_BOW_KNOCKBACK, "arrowKnockback", Rarity.RARE); } @Override @@ -18,7 +18,7 @@ public int getMinEnchantAbility(int level) { @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; + return this.getMinEnchantAbility(level) + 25; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java index cb15c3a5863..77b25b7172b 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java @@ -8,12 +8,12 @@ */ public class EnchantmentBowPower extends EnchantmentBow { public EnchantmentBowPower() { - super(Enchantment.ID_BOW_POWER, "arrowDamage", 10); + super(Enchantment.ID_BOW_POWER, "arrowDamage", Rarity.COMMON); } @Override public int getMinEnchantAbility(int level) { - return 1 + (level - 1) * 20; + return 1 + (level - 1) * 10; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbow.java b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbow.java new file mode 100644 index 00000000000..9afc026ef60 --- /dev/null +++ b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbow.java @@ -0,0 +1,11 @@ +package cn.nukkit.item.enchantment.crossbow; + +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.item.enchantment.EnchantmentType; + +public abstract class EnchantmentCrossbow extends Enchantment { + + protected EnchantmentCrossbow(int id, String name, Rarity rarity) { + super(id, name, rarity, EnchantmentType.CROSSBOW); + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowMultishot.java b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowMultishot.java new file mode 100644 index 00000000000..939c8f4caf3 --- /dev/null +++ b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowMultishot.java @@ -0,0 +1,30 @@ +package cn.nukkit.item.enchantment.crossbow; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentCrossbowMultishot extends EnchantmentCrossbow { + + public EnchantmentCrossbowMultishot() { + super(Enchantment.ID_CROSSBOW_MULTISHOT, "crossbowMultishot", Rarity.RARE); + } + + @Override + public int getMinEnchantAbility(int level) { + return 20; + } + + @Override + public int getMaxEnchantAbility(int level) { + return 50 + this.getMinEnchantAbility(level); + } + + @Override + public int getMaxLevel() { + return 1; + } + + @Override + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_CROSSBOW_PIERCING; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowPiercing.java b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowPiercing.java new file mode 100644 index 00000000000..83853f3f5a3 --- /dev/null +++ b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowPiercing.java @@ -0,0 +1,30 @@ +package cn.nukkit.item.enchantment.crossbow; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentCrossbowPiercing extends EnchantmentCrossbow { + + public EnchantmentCrossbowPiercing() { + super(Enchantment.ID_CROSSBOW_PIERCING, "crossbowPiercing", Rarity.COMMON); + } + + @Override + public int getMinEnchantAbility(int level) { + return 1 + 10 * (level - 1); + } + + @Override + public int getMaxEnchantAbility(int level) { + return 50 + this.getMinEnchantAbility(level); + } + + @Override + public int getMaxLevel() { + return 4; + } + + @Override + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_CROSSBOW_MULTISHOT; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowQuickCharge.java b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowQuickCharge.java new file mode 100644 index 00000000000..c0ff904d2b3 --- /dev/null +++ b/src/main/java/cn/nukkit/item/enchantment/crossbow/EnchantmentCrossbowQuickCharge.java @@ -0,0 +1,25 @@ +package cn.nukkit.item.enchantment.crossbow; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentCrossbowQuickCharge extends EnchantmentCrossbow { + + public EnchantmentCrossbowQuickCharge() { + super(Enchantment.ID_CROSSBOW_QUICK_CHARGE, "crossbowQuickCharge", Rarity.UNCOMMON); + } + + @Override + public int getMinEnchantAbility(int level) { + return 12 + 20 * (level - 1); + } + + @Override + public int getMaxEnchantAbility(int level) { + return 50 + this.getMinEnchantAbility(level); + } + + @Override + public int getMaxLevel() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java index b5e965a1ed3..a2a1e5a612b 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java @@ -18,13 +18,13 @@ public enum TYPE { protected final TYPE damageType; - protected EnchantmentDamage(int id, String name, int weight, TYPE type) { - super(id, name, weight, EnchantmentType.SWORD); + protected EnchantmentDamage(int id, String name, Rarity rarity, TYPE type) { + super(id, name, rarity, EnchantmentType.SWORD); this.damageType = type; } @Override - public boolean isCompatibleWith(Enchantment enchantment) { + public boolean checkCompatibility(Enchantment enchantment) { return !(enchantment instanceof EnchantmentDamage); } diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java index 91eb677c0ca..903eb864825 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java @@ -9,7 +9,7 @@ public class EnchantmentDamageAll extends EnchantmentDamage { public EnchantmentDamageAll() { - super(ID_DAMAGE_ALL, "all", 10, TYPE.ALL); + super(ID_DAMAGE_ALL, "all", Rarity.COMMON, TYPE.ALL); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java index 7612a8da99f..3966e261d23 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java @@ -13,7 +13,7 @@ public class EnchantmentDamageArthropods extends EnchantmentDamage { public EnchantmentDamageArthropods() { - super(ID_DAMAGE_ARTHROPODS, "arthropods", 5, TYPE.SMITE); + super(ID_DAMAGE_ARTHROPODS, "arthropods", Rarity.UNCOMMON, TYPE.SMITE); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java index eb4cb753724..ddc155a6cae 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java @@ -10,7 +10,7 @@ public class EnchantmentDamageSmite extends EnchantmentDamage { public EnchantmentDamageSmite() { - super(ID_DAMAGE_SMITE, "undead", 5, TYPE.SMITE); + super(ID_DAMAGE_SMITE, "undead", Rarity.UNCOMMON, TYPE.SMITE); } @Override @@ -25,7 +25,7 @@ public int getMaxEnchantAbility(int level) { @Override public double getDamageBonus(Entity entity) { - if(entity instanceof EntitySmite) { + if (entity instanceof EntitySmite) { return getLevel() * 2.5; } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java index 8312de6bab5..3be23fe9686 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java @@ -9,8 +9,8 @@ */ public abstract class EnchantmentLoot extends Enchantment { - protected EnchantmentLoot(int id, String name, int weight, EnchantmentType type) { - super(id, name, weight, type); + protected EnchantmentLoot(int id, String name, Rarity rarity, EnchantmentType type) { + super(id, name, rarity, type); } @Override @@ -29,7 +29,7 @@ public int getMaxLevel() { } @Override - public boolean isCompatibleWith(Enchantment enchantment) { - return super.isCompatibleWith(enchantment) && enchantment.id != Enchantment.ID_SILK_TOUCH; + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != Enchantment.ID_SILK_TOUCH; } } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java index be2b4a4fecc..83d116a69e0 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java @@ -9,6 +9,6 @@ */ public class EnchantmentLootDigging extends EnchantmentLoot { public EnchantmentLootDigging() { - super(Enchantment.ID_FORTUNE_DIGGING, "lootBonusDigger", 2, EnchantmentType.DIGGER); + super(Enchantment.ID_FORTUNE_DIGGING, "lootBonusDigger", Rarity.RARE, EnchantmentType.DIGGER); } } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java index 4005a0f0c01..1016b6f3137 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java @@ -9,6 +9,6 @@ */ public class EnchantmentLootFishing extends EnchantmentLoot { public EnchantmentLootFishing() { - super(Enchantment.ID_FORTUNE_FISHING, "lootBonusFishing", 2, EnchantmentType.FISHING_ROD); + super(Enchantment.ID_FORTUNE_FISHING, "lootBonusFishing", Rarity.RARE, EnchantmentType.FISHING_ROD); } } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java index 758f073186e..d0f438b2794 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java @@ -9,6 +9,6 @@ */ public class EnchantmentLootWeapon extends EnchantmentLoot { public EnchantmentLootWeapon() { - super(Enchantment.ID_LOOTING, "lootBonus", 2, EnchantmentType.SWORD); + super(Enchantment.ID_LOOTING, "lootBonus", Rarity.RARE, EnchantmentType.SWORD); } } diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java index 9729464dcdd..57949b75d1c 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java @@ -19,8 +19,8 @@ public enum TYPE { protected final TYPE protectionType; - protected EnchantmentProtection(int id, String name, int weight, EnchantmentProtection.TYPE type) { - super(id, name, weight, EnchantmentType.ARMOR); + protected EnchantmentProtection(int id, String name, Rarity rarity, EnchantmentProtection.TYPE type) { + super(id, name, rarity, EnchantmentType.ARMOR); this.protectionType = type; if (protectionType == TYPE.FALL) { this.type = EnchantmentType.ARMOR_FEET; @@ -28,14 +28,14 @@ protected EnchantmentProtection(int id, String name, int weight, EnchantmentProt } @Override - public boolean isCompatibleWith(Enchantment enchantment) { + public boolean checkCompatibility(Enchantment enchantment) { if (enchantment instanceof EnchantmentProtection) { if (((EnchantmentProtection) enchantment).protectionType == this.protectionType) { return false; } return ((EnchantmentProtection) enchantment).protectionType == TYPE.FALL || this.protectionType == TYPE.FALL; } - return super.isCompatibleWith(enchantment); + return super.checkCompatibility(enchantment); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java index a4769cf1fc9..06575f7f03d 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java @@ -11,7 +11,7 @@ public class EnchantmentProtectionAll extends EnchantmentProtection { public EnchantmentProtectionAll() { - super(Enchantment.ID_PROTECTION_ALL, "all", 10, TYPE.ALL); + super(Enchantment.ID_PROTECTION_ALL, "all", Rarity.COMMON, TYPE.ALL); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java index 8d290474ae3..ca2d74693e2 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java @@ -10,7 +10,7 @@ public class EnchantmentProtectionExplosion extends EnchantmentProtection { public EnchantmentProtectionExplosion() { - super(ID_PROTECTION_EXPLOSION, "explosion", 2, TYPE.EXPLOSION); + super(ID_PROTECTION_EXPLOSION, "explosion", Rarity.RARE, TYPE.EXPLOSION); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java index bae3ce2be73..2fc1d91803d 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java @@ -10,7 +10,7 @@ public class EnchantmentProtectionFall extends EnchantmentProtection { public EnchantmentProtectionFall() { - super(ID_PROTECTION_FALL, "fall", 5, TYPE.FALL); + super(ID_PROTECTION_FALL, "fall", Rarity.UNCOMMON, TYPE.FALL); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java index 793a259f680..f5aeac6a93b 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java @@ -10,7 +10,7 @@ public class EnchantmentProtectionFire extends EnchantmentProtection { public EnchantmentProtectionFire() { - super(ID_PROTECTION_FIRE, "fire", 5, TYPE.FIRE); + super(ID_PROTECTION_FIRE, "fire", Rarity.UNCOMMON, TYPE.FIRE); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java index 24c2100256b..5a05696bd7e 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java @@ -10,7 +10,7 @@ public class EnchantmentProtectionProjectile extends EnchantmentProtection { public EnchantmentProtectionProjectile() { - super(ID_PROTECTION_PROJECTILE, "projectile", 5, TYPE.PROJECTILE); + super(ID_PROTECTION_PROJECTILE, "projectile", Rarity.UNCOMMON, TYPE.PROJECTILE); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java index 72be2e52509..4e9b8e066a4 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java @@ -4,8 +4,7 @@ import cn.nukkit.item.enchantment.EnchantmentType; public abstract class EnchantmentTrident extends Enchantment { - protected EnchantmentTrident(int id, String name, int weight) { - super(id, name, weight, EnchantmentType.TRIDENT); + protected EnchantmentTrident(int id, String name, Rarity rarity) { + super(id, name, rarity, EnchantmentType.TRIDENT); } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java index 0a09f65d9fa..61956f8db4f 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java @@ -4,21 +4,6 @@ public class EnchantmentTridentChanneling extends EnchantmentTrident { public EnchantmentTridentChanneling() { - super(Enchantment.ID_TRIDENT_CHANNELING, "channeling", 1); - } - - @Override - public int getMinEnchantAbility(int level) { - return 20; - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 50; - } - - @Override - public int getMaxLevel() { - return 1; + super(Enchantment.ID_TRIDENT_CHANNELING, "tridentChanneling", Rarity.VERY_RARE); } } diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java index 69d09cf6b5b..384ed212cd2 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java @@ -1,24 +1,34 @@ package cn.nukkit.item.enchantment.trident; +import cn.nukkit.entity.Entity; import cn.nukkit.item.enchantment.Enchantment; public class EnchantmentTridentImpaling extends EnchantmentTrident { public EnchantmentTridentImpaling() { - super(Enchantment.ID_TRIDENT_IMPALING, "impaling", 2); + super(Enchantment.ID_TRIDENT_IMPALING, "tridentImpaling", Rarity.RARE); } @Override public int getMinEnchantAbility(int level) { - return 1 + (level - 1) * 10; + return 8 * level - 7; } @Override public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 15; + return this.getMinEnchantAbility(level) + 20; } @Override public int getMaxLevel() { return 5; } + + @Override + public double getDamageBonus(Entity entity) { + if (entity.isInsideOfWater() || (entity.getLevel().isRaining() && entity.getLevel().canBlockSeeSky(entity))) { + return 2.5 * getLevel(); + } + + return 0; + } } diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java index 812d982f515..be37f596a15 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java @@ -4,17 +4,12 @@ public class EnchantmentTridentLoyalty extends EnchantmentTrident { public EnchantmentTridentLoyalty() { - super(Enchantment.ID_TRIDENT_LOYALTY, "loyalty", 5); + super(Enchantment.ID_TRIDENT_LOYALTY, "tridentLoyalty", Rarity.UNCOMMON); } @Override public int getMinEnchantAbility(int level) { - return level * 10; - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 15; + return 7 * level + 5; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java index f964859b81b..e226fb7d766 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java @@ -4,17 +4,12 @@ public class EnchantmentTridentRiptide extends EnchantmentTrident { public EnchantmentTridentRiptide() { - super(Enchantment.ID_TRIDENT_RIPTIDE, "riptide", 2); + super(Enchantment.ID_TRIDENT_RIPTIDE, "tridentRiptide", Rarity.RARE); } @Override public int getMinEnchantAbility(int level) { - return level * 10; - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 15; + return 7 * level + 10; } @Override diff --git a/src/main/java/cn/nukkit/level/EnumLevel.java b/src/main/java/cn/nukkit/level/EnumLevel.java index ea03b054d15..3423d84c9bd 100644 --- a/src/main/java/cn/nukkit/level/EnumLevel.java +++ b/src/main/java/cn/nukkit/level/EnumLevel.java @@ -2,6 +2,7 @@ import cn.nukkit.Server; import cn.nukkit.level.generator.Generator; +import cn.nukkit.math.NukkitMath; public enum EnumLevel { OVERWORLD, @@ -61,9 +62,9 @@ public static Position moveToNether(Position current) { return null; } else { if (current.level == OVERWORLD.level) { - return new Position(mRound(current.getFloorX() >> 3, 128), mRound(current.getFloorY(), 32), mRound(current.getFloorZ() >> 3, 128), NETHER.level); + return new Position(mRound(current.getFloorX() >> 3, 128), NukkitMath.clamp(mRound(current.getFloorY(), 32), 70, 128 - 10), mRound(current.getFloorZ() >> 3, 128), NETHER.level); } else if (current.level == NETHER.level) { - return new Position(mRound(current.getFloorX() << 3, 1024), mRound(current.getFloorY(), 32), mRound(current.getFloorZ() << 3, 1024), OVERWORLD.level); + return new Position(mRound(current.getFloorX() << 3, 1024), NukkitMath.clamp(mRound(current.getFloorY(), 32), 70, 256 - 10), mRound(current.getFloorZ() << 3, 1024), OVERWORLD.level); } else { throw new IllegalArgumentException("Neither overworld nor nether given!"); } diff --git a/src/main/java/cn/nukkit/level/GlobalBlockPalette.java b/src/main/java/cn/nukkit/level/GlobalBlockPalette.java index a09e08195df..e78e79255c0 100644 --- a/src/main/java/cn/nukkit/level/GlobalBlockPalette.java +++ b/src/main/java/cn/nukkit/level/GlobalBlockPalette.java @@ -8,10 +8,14 @@ import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import lombok.extern.log4j.Log4j2; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; import java.nio.ByteOrder; import java.util.List; import java.util.NoSuchElementException; @@ -21,8 +25,9 @@ public class GlobalBlockPalette { private static final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap(); private static final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap(); + private static final Int2ObjectMap legacyIdToString = new Int2ObjectOpenHashMap<>(); + private static final Map stringToLegacyId = new HashMap<>(); private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0); - public static final byte[] BLOCK_PALETTE; static { legacyToRuntimeId.defaultReturnValue(-1); @@ -44,22 +49,19 @@ public class GlobalBlockPalette { if (!state.contains("LegacyStates")) continue; List legacyStates = state.getList("LegacyStates", CompoundTag.class).getAll(); + String name = state.getCompound("block").getString("name"); // Resolve to first legacy id CompoundTag firstState = legacyStates.get(0); runtimeIdToLegacy.put(runtimeId, firstState.getInt("id") << 6 | firstState.getShort("val")); + stringToLegacyId.put(name, firstState.getInt("id")); + legacyIdToString.put(firstState.getInt("id"), name); + for (CompoundTag legacyState : legacyStates) { int legacyId = legacyState.getInt("id") << 6 | legacyState.getShort("val"); legacyToRuntimeId.put(legacyId, runtimeId); } - state.remove("meta"); // No point in sending this since the client doesn't use it. - } - - try { - BLOCK_PALETTE = NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN, true); - } catch (IOException e) { - throw new AssertionError("Unable to write block palette", e); } } @@ -81,4 +83,12 @@ public static int getOrCreateRuntimeId(int id, int meta) { public static int getOrCreateRuntimeId(int legacyId) throws NoSuchElementException { return getOrCreateRuntimeId(legacyId >> 4, legacyId & 0xf); } + + public static int getLegacyId(int runtimeId) { + return runtimeIdToLegacy.get(runtimeId); + } + + public static String getName(int id) { + return legacyIdToString.get(id); + } } diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 66ce39189be..292216bd25b 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -132,6 +132,7 @@ public class Level implements ChunkManager, Metadatable { randomTickBlocks[Block.FIRE] = true; randomTickBlocks[Block.GLOWING_REDSTONE_ORE] = true; randomTickBlocks[Block.COCOA_BLOCK] = true; + randomTickBlocks[Block.VINE] = true; } private final Long2ObjectOpenHashMap blockEntities = new Long2ObjectOpenHashMap<>(); @@ -187,7 +188,7 @@ public int size() { private final BlockUpdateScheduler updateQueue; - private final Queue normalUpdateQueue = new ConcurrentLinkedDeque<>(); + private final Queue normalUpdateQueue = new ConcurrentLinkedDeque<>(); // private final TreeSet updateQueue = new TreeSet<>(); // private final List nextTickUpdates = Lists.newArrayList(); //private final Map updateQueueIndex = new HashMap<>(); @@ -410,6 +411,10 @@ public void initLevel() { Generator generator = generators.get(); this.dimension = generator.getDimension(); this.gameRules = this.provider.getGamerules(); + + this.server.getLogger().info("Preparing start region for level \"" + this.getFolderName() + "\""); + Position spawn = this.getSpawnLocation(); + this.populateChunk(spawn.getChunkX(), spawn.getChunkZ(), true); } public Generator getGenerator() { @@ -759,7 +764,7 @@ public void doTick(int currentTick) { } // Tick Weather - if (gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) { + if (this.dimension != DIMENSION_NETHER && this.dimension != DIMENSION_THE_END && gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) { this.rainTime--; if (this.rainTime <= 0) { if (!this.setRaining(!this.raining)) { @@ -811,9 +816,14 @@ public void doTick(int currentTick) { this.updateQueue.tick(this.getCurrentTick()); this.timings.doTickPending.stopTiming(); - Block block; - while ((block = this.normalUpdateQueue.poll()) != null) { - block.onUpdate(BLOCK_UPDATE_NORMAL); + while (!this.normalUpdateQueue.isEmpty()) { + Block block = getBlock(this.normalUpdateQueue.poll()); + BlockUpdateEvent event = new BlockUpdateEvent(block); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + block.onUpdate(BLOCK_UPDATE_NORMAL); + } } TimingsHistory.entityTicks += this.updateEntities.size(); @@ -1246,46 +1256,13 @@ public void updateComparatorOutputLevel(Vector3 v) { } public void updateAround(Vector3 pos) { - updateAround((int) pos.x, (int) pos.y, (int) pos.z); + for (BlockFace face : BlockFace.values()) { + normalUpdateQueue.add(pos.getSide(face)); + } } public void updateAround(int x, int y, int z) { - BlockUpdateEvent ev; - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y - 1, z))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } - - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y + 1, z))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } - - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x - 1, y, z))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } - - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x + 1, y, z))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } - - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y, z - 1))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } - - this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y, z + 1))); - if (!ev.isCancelled()) { - normalUpdateQueue.add(ev.getBlock()); - } + updateAround(new Vector3(x, y, z)); } public void scheduleUpdate(Block pos, int delay) { @@ -1754,6 +1731,10 @@ public synchronized boolean setBlock(int x, int y, int z, Block block, boolean d block = ev.getBlock(); block.onUpdate(BLOCK_UPDATE_NORMAL); this.updateAround(x, y, z); + + if (block.hasComparatorInputOverride()) { + this.updateComparatorOutputLevel(block); + } } } return true; @@ -2333,10 +2314,14 @@ public Map getLoaders() { } public BlockEntity getBlockEntity(Vector3 pos) { - FullChunk chunk = this.getChunk((int) pos.x >> 4, (int) pos.z >> 4, false); + return getBlockEntity(pos.asBlockVector3()); + } + + public BlockEntity getBlockEntity(BlockVector3 pos) { + FullChunk chunk = this.getChunk(pos.x >> 4, pos.z >> 4, false); if (chunk != null) { - return chunk.getTile((int) pos.x & 0x0f, (int) pos.y & 0xff, (int) pos.z & 0x0f); + return chunk.getTile(pos.x & 0x0f, pos.y & 0xff, pos.z & 0x0f); } return null; @@ -2928,8 +2913,8 @@ public Position getSafeSpawn(Vector3 spawn) { FullChunk chunk = this.getChunk((int) v.x >> 4, (int) v.z >> 4, false); int x = (int) v.x & 0x0f; int z = (int) v.z & 0x0f; - if (chunk != null) { - int y = (int) NukkitMath.clamp(v.y, 0, 254); + if (chunk != null && chunk.isGenerated()) { + int y = (int) NukkitMath.clamp(v.y, 1, 254); boolean wasAir = chunk.getBlockId(x, y - 1, z) == 0; for (; y > 0; --y) { int b = chunk.getFullBlock(x, y, z); @@ -3471,7 +3456,15 @@ public boolean isSidePowered(Vector3 pos, BlockFace face) { } public int getRedstonePower(Vector3 pos, BlockFace face) { - Block block = this.getBlock(pos); + Block block; + + if (pos instanceof Block) { + block = (Block) pos; + pos = pos.add(0); + } else { + block = this.getBlock(pos); + } + return block.isNormalBlock() ? this.getStrongPower(pos) : block.getWeakPower(face); } diff --git a/src/main/java/cn/nukkit/level/ListChunkManager.java b/src/main/java/cn/nukkit/level/ListChunkManager.java new file mode 100644 index 00000000000..1df86a9a595 --- /dev/null +++ b/src/main/java/cn/nukkit/level/ListChunkManager.java @@ -0,0 +1,85 @@ +package cn.nukkit.level; + +import cn.nukkit.block.Block; +import cn.nukkit.level.format.generic.BaseFullChunk; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ListChunkManager implements ChunkManager { + + private ChunkManager parent; + private List blocks; + + public ListChunkManager(ChunkManager parent) { + this.parent = parent; + this.blocks = new ArrayList<>(); + } + + @Override + public int getBlockIdAt(int x, int y, int z) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); + return optionalBlock.map(Block::getId).orElseGet(() -> this.parent.getBlockIdAt(x, y, z)); + } + + @Override + public void setBlockFullIdAt(int x, int y, int z, int fullId) { + this.blocks.removeIf(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z); + this.blocks.add(Block.get(fullId, null, x, y, z)); + } + + @Override + public void setBlockIdAt(int x, int y, int z, int id) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); + Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + this.blocks.remove(block); + this.blocks.add(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + } + + @Override + public void setBlockAt(int x, int y, int z, int id, int data) { + this.blocks.removeIf(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z); + this.blocks.add(Block.get(id, data, new Position(x, y, z))); + } + + @Override + public int getBlockDataAt(int x, int y, int z) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); + return optionalBlock.map(Block::getDamage).orElseGet(() -> this.parent.getBlockDataAt(x, y, z)); + } + + @Override + public void setBlockDataAt(int x, int y, int z, int data) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); + Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + this.blocks.remove(block); + block.setDamage(data); + this.blocks.add(block); + } + + @Override + public BaseFullChunk getChunk(int chunkX, int chunkZ) { + return this.parent.getChunk(chunkX, chunkZ); + } + + @Override + public void setChunk(int chunkX, int chunkZ) { + this.parent.setChunk(chunkX, chunkZ); + } + + @Override + public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk) { + this.parent.setChunk(chunkX, chunkZ, chunk); + } + + @Override + public long getSeed() { + return this.parent.getSeed(); + } + + public List getBlocks() { + return this.blocks; + } + +} diff --git a/src/main/java/cn/nukkit/level/Location.java b/src/main/java/cn/nukkit/level/Location.java index d6f0fc863ad..44a62d9bc9b 100644 --- a/src/main/java/cn/nukkit/level/Location.java +++ b/src/main/java/cn/nukkit/level/Location.java @@ -73,6 +73,16 @@ public double getPitch() { return this.pitch; } + public Location setYaw(double yaw) { + this.yaw = yaw; + return this; + } + + public Location setPitch(double pitch) { + this.pitch = pitch; + return this; + } + @Override public String toString() { return "Location (level=" + (this.isValid() ? this.getLevel().getName() : "null") + ", x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", yaw=" + this.yaw + ", pitch=" + this.pitch + ")"; diff --git a/src/main/java/cn/nukkit/level/Sound.java b/src/main/java/cn/nukkit/level/Sound.java index c6e39757dd2..6c0c27cc608 100644 --- a/src/main/java/cn/nukkit/level/Sound.java +++ b/src/main/java/cn/nukkit/level/Sound.java @@ -4,22 +4,32 @@ * @author CreeperFace */ public enum Sound { + AMBIENT_BASALT_DELTAS_ADDITIONS("ambient.basalt_deltas.additions"), + AMBIENT_BASALT_DELTAS_LOOP("ambient.basalt_deltas.loop"), AMBIENT_BASALT_DELTAS_MOOD("ambient.basalt_deltas.mood"), AMBIENT_CAVE("ambient.cave"), + AMBIENT_CRIMSON_FOREST_ADDITIONS("ambient.crimson_forest.additions"), + AMBIENT_CRIMSON_FOREST_LOOP("ambient.crimson_forest.loop"), AMBIENT_CRIMSON_FOREST_MOOD("ambient.crimson_forest.mood"), + AMBIENT_NETHER_WASTES_ADDITIONS("ambient.nether_wastes.additions"), + AMBIENT_NETHER_WASTES_LOOP("ambient.nether_wastes.loop"), AMBIENT_NETHER_WASTES_MOOD("ambient.nether_wastes.mood"), + AMBIENT_SOULSAND_VALLEY_ADDITIONS("ambient.soulsand_valley.additions"), + AMBIENT_SOULSAND_VALLEY_LOOP("ambient.soulsand_valley.loop"), AMBIENT_SOULSAND_VALLEY_MOOD("ambient.soulsand_valley.mood"), + AMBIENT_WARPED_FOREST_ADDITIONS("ambient.warped_forest.additions"), + AMBIENT_WARPED_FOREST_LOOP("ambient.warped_forest.loop"), AMBIENT_WARPED_FOREST_MOOD("ambient.warped_forest.mood"), AMBIENT_WEATHER_LIGHTNING_IMPACT("ambient.weather.lightning.impact"), AMBIENT_WEATHER_RAIN("ambient.weather.rain"), AMBIENT_WEATHER_THUNDER("ambient.weather.thunder"), - ARMOR_EQUIP_NETHERITE("armor.equip_netherite"), ARMOR_EQUIP_CHAIN("armor.equip_chain"), ARMOR_EQUIP_DIAMOND("armor.equip_diamond"), ARMOR_EQUIP_GENERIC("armor.equip_generic"), ARMOR_EQUIP_GOLD("armor.equip_gold"), ARMOR_EQUIP_IRON("armor.equip_iron"), ARMOR_EQUIP_LEATHER("armor.equip_leather"), + ARMOR_EQUIP_NETHERITE("armor.equip_netherite"), BEACON_ACTIVATE("beacon.activate"), BEACON_AMBIENT("beacon.ambient"), BEACON_DEACTIVATE("beacon.deactivate"), @@ -125,6 +135,7 @@ public enum Sound { DIG_GRASS("dig.grass"), DIG_GRAVEL("dig.gravel"), DIG_HONEY_BLOCK("dig.honey_block"), + DIG_LODESTONE("dig.lodestone"), DIG_NETHER_BRICK("dig.nether_brick"), DIG_NETHER_GOLD_ORE("dig.nether_gold_ore"), DIG_NETHER_SPROUTS("dig.nether_sprouts"), @@ -284,7 +295,6 @@ public enum Sound { LIQUID_LAVAPOP("liquid.lavapop"), LIQUID_WATER("liquid.water"), LODESTONE_COMPASS_LINK_COMPASS_TO_LODESTONE("lodestone_compass.link_compass_to_lodestone"), - DIG_LODESTONE("dig.lodestone"), MINECART_BASE("minecart.base"), MINECART_INSIDE("minecart.inside"), MOB_AGENT_SPAWN("mob.agent.spawn"), @@ -473,6 +483,7 @@ public enum Sound { MOB_PARROT_STEP("mob.parrot.step"), MOB_PHANTOM_BITE("mob.phantom.bite"), MOB_PHANTOM_DEATH("mob.phantom.death"), + MOB_PHANTOM_FLAP("mob.phantom.flap"), MOB_PHANTOM_HURT("mob.phantom.hurt"), MOB_PHANTOM_IDLE("mob.phantom.idle"), MOB_PHANTOM_SWOOP("mob.phantom.swoop"), @@ -493,8 +504,8 @@ public enum Sound { MOB_PIGLIN_BRUTE_AMBIENT("mob.piglin_brute.ambient"), MOB_PIGLIN_BRUTE_ANGRY("mob.piglin_brute.angry"), MOB_PIGLIN_BRUTE_CONVERTED_TO_ZOMBIFIED("mob.piglin_brute.converted_to_zombified"), - MOB_PIGLIN_BRUTE_HURT("mob.piglin_brute.hurt"), MOB_PIGLIN_BRUTE_DEATH("mob.piglin_brute.death"), + MOB_PIGLIN_BRUTE_HURT("mob.piglin_brute.hurt"), MOB_PIGLIN_BRUTE_STEP("mob.piglin_brute.step"), MOB_PILLAGER_CELEBRATE("mob.pillager.celebrate"), MOB_PILLAGER_DEATH("mob.pillager.death"), @@ -620,11 +631,11 @@ public enum Sound { MOB_WOLF_STEP("mob.wolf.step"), MOB_WOLF_WHINE("mob.wolf.whine"), MOB_ZOGLIN_ANGRY("mob.zoglin.angry"), + MOB_ZOGLIN_ATTACK("mob.zoglin.attack"), MOB_ZOGLIN_DEATH("mob.zoglin.death"), - MOB_ZOGLIN_IDLE("mob.zoglin.idle"), MOB_ZOGLIN_HURT("mob.zoglin.hurt"), + MOB_ZOGLIN_IDLE("mob.zoglin.idle"), MOB_ZOGLIN_STEP("mob.zoglin.step"), - MOB_ZOGLIN_ATTACK("mob.zoglin.attack"), MOB_ZOMBIE_DEATH("mob.zombie.death"), MOB_ZOMBIE_HURT("mob.zombie.hurt"), MOB_ZOMBIE_REMEDY("mob.zombie.remedy"), @@ -724,12 +735,14 @@ public enum Sound { RESPAWN_ANCHOR_CHARGE("respawn_anchor.charge"), RESPAWN_ANCHOR_DEPLETE("respawn_anchor.deplete"), RESPAWN_ANCHOR_SET_SPAWN("respawn_anchor.set_spawn"), + SIGN_DYE_USE("sign.dye.use"), + SIGN_INK_SAC_USE("sign.ink_sac.use"), SMITHING_TABLE_USE("smithing_table.use"), STEP_ANCIENT_DEBRIS("step.ancient_debris"), STEP_BASALT("step.basalt"), STEP_BONE_BLOCK("step.bone_block"), - STEP_CLOTH("step.cloth"), STEP_CHAIN("step.chain"), + STEP_CLOTH("step.cloth"), STEP_CORAL("step.coral"), STEP_GRASS("step.grass"), STEP_GRAVEL("step.gravel"), diff --git a/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java index b9e3706034b..a859e7414eb 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java @@ -1,6 +1,5 @@ package cn.nukkit.level.biome.impl.mushroom; -import cn.nukkit.block.Block; import cn.nukkit.level.biome.type.GrassyBiome; import cn.nukkit.level.generator.populator.impl.MushroomPopulator; diff --git a/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java b/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java index e7b854e0ae8..751e5e6b99f 100644 --- a/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java @@ -1,7 +1,5 @@ package cn.nukkit.level.biome.type; -import cn.nukkit.block.Block; - /** * author: MagicDroidX * Nukkit Project diff --git a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java index 71d1a9c5e78..2a1e8af6494 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java +++ b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java @@ -131,19 +131,6 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException { } } - Map extra = chunk.getBlockExtraDataArray(); - BinaryStream extraData; - if (!extra.isEmpty()) { - extraData = new BinaryStream(); - extraData.putVarInt(extra.size()); - for (Map.Entry entry : extra.entrySet()) { - extraData.putVarInt(entry.getKey()); - extraData.putLShort(entry.getValue()); - } - } else { - extraData = null; - } - BinaryStream stream = ThreadCache.binaryStream.get().reset(); int count = 0; cn.nukkit.level.format.ChunkSection[] sections = chunk.getSections(); @@ -153,20 +140,13 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException { break; } } -// stream.putByte((byte) count); count is now sent in packet + for (int i = 0; i < count; i++) { sections[i].writeTo(stream); } -// for (byte height : chunk.getHeightMapArray()) { -// stream.putByte(height); -// } computed client side? + stream.put(chunk.getBiomeIdArray()); - stream.putByte((byte) 0); - if (extraData != null) { - stream.put(extraData.getBuffer()); - } else { - stream.putVarInt(0); - } + stream.putByte((byte) 0); // Border blocks stream.put(blockEntities); this.getLevel().chunkRequestCallback(timestamp, x, z, count, stream.getBuffer()); diff --git a/src/main/java/cn/nukkit/level/format/anvil/Chunk.java b/src/main/java/cn/nukkit/level/format/anvil/Chunk.java index 4f0d6dec7a2..2085a1c48c4 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/Chunk.java +++ b/src/main/java/cn/nukkit/level/format/anvil/Chunk.java @@ -275,7 +275,7 @@ public byte[] toFastBinary() { if (section instanceof EmptyChunkSection) { continue; } - CompoundTag s = new CompoundTag(null); + CompoundTag s = new CompoundTag(); s.putByte("Y", section.getY()); s.putByteArray("Blocks", section.getIdArray()); s.putByteArray("Data", section.getDataArray()); @@ -358,7 +358,7 @@ public byte[] toBinary() { if (section instanceof EmptyChunkSection) { continue; } - CompoundTag s = new CompoundTag(null); + CompoundTag s = new CompoundTag(); s.putByte("Y", (section.getY())); s.putByteArray("Blocks", section.getIdArray()); s.putByteArray("Data", section.getDataArray()); diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java index 7ccddbedc51..f9b1cfc579e 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java @@ -210,9 +210,7 @@ private boolean isAirBlock(ChunkManager level, BlockVector3 v) { } private int getCocoaMeta(int age, int side) { - int meta = 0; - - meta *= age; + int meta = age * 4; //3 4 2 5 switch (side) { diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java index b34290591ca..3feec7fa3b1 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java @@ -1,12 +1,10 @@ package cn.nukkit.level.generator.populator.impl; -import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.biome.EnumBiome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.biome.type.CoveredBiome; -import cn.nukkit.level.generator.Normal; import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java index 472d587354e..0bbaec8dac6 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java @@ -1,6 +1,5 @@ package cn.nukkit.level.generator.populator.impl; -import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.object.ore.OreType; diff --git a/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java b/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java index 2932c326299..458e2debf30 100644 --- a/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java +++ b/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java @@ -1,14 +1,34 @@ package cn.nukkit.level.particle; import cn.nukkit.item.Item; +import cn.nukkit.item.RuntimeItems; import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.DataPacket; +import cn.nukkit.network.protocol.LevelEventPacket; /** * Created on 2015/11/21 by xtypr. * Package cn.nukkit.level.particle in project Nukkit . */ -public class ItemBreakParticle extends GenericParticle { +public class ItemBreakParticle extends Particle { + + private final int data; + public ItemBreakParticle(Vector3 pos, Item item) { - super(pos, Particle.TYPE_ITEM_BREAK, (item.getId() << 16) | item.getDamage()); + super(pos.x, pos.y, pos.z); + int networkFullId = RuntimeItems.getRuntimeMapping().getNetworkFullId(item); + int networkId = RuntimeItems.getNetworkId(networkFullId); + this.data = (networkId << 16 | item.getDamage()); + } + + @Override + public DataPacket[] encode() { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = (short) (LevelEventPacket.EVENT_ADD_PARTICLE_MASK | Particle.TYPE_ITEM_BREAK); + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/Particle.java b/src/main/java/cn/nukkit/level/particle/Particle.java index dc9b8cf4a8a..7f529f66c75 100644 --- a/src/main/java/cn/nukkit/level/particle/Particle.java +++ b/src/main/java/cn/nukkit/level/particle/Particle.java @@ -3,6 +3,8 @@ import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.DataPacket; +import java.lang.reflect.Field; + /** * author: MagicDroidX * Nukkit Project @@ -10,7 +12,7 @@ public abstract class Particle extends Vector3 { public static final int TYPE_BUBBLE = 1; - // 2 same as 1 + public static final int TYPE_BUBBLE_MANUAL = 2; public static final int TYPE_CRITICAL = 3; public static final int TYPE_BLOCK_FORCE_FIELD = 4; public static final int TYPE_SMOKE = 5; @@ -21,7 +23,6 @@ public abstract class Particle extends Vector3 { public static final int TYPE_LARGE_SMOKE = 10; public static final int TYPE_REDSTONE = 11; public static final int TYPE_RISING_RED_DUST = 12; - public static final int TYPE_ITEM_BREAK = 13; public static final int TYPE_SNOWBALL_POOF = 14; public static final int TYPE_HUGE_EXPLODE = 15; @@ -30,56 +31,87 @@ public abstract class Particle extends Vector3 { public static final int TYPE_HEART = 18; public static final int TYPE_TERRAIN = 19; public static final int TYPE_SUSPENDED_TOWN = 20, TYPE_TOWN_AURA = 20; - // 61 same as 20 public static final int TYPE_PORTAL = 21; // 22 same as 21 public static final int TYPE_SPLASH = 23, TYPE_WATER_SPLASH = 23; - // 24 same as 23 + public static final int TYPE_WATER_SPLASH_MANUAL = 24; public static final int TYPE_WATER_WAKE = 25; public static final int TYPE_DRIP_WATER = 26; public static final int TYPE_DRIP_LAVA = 27; public static final int TYPE_DRIP_HONEY = 28; - public static final int TYPE_FALLING_DUST = 29, TYPE_DUST = 29; - public static final int TYPE_MOB_SPELL = 30; - public static final int TYPE_MOB_SPELL_AMBIENT = 31; - public static final int TYPE_MOB_SPELL_INSTANTANEOUS = 32; - public static final int TYPE_NOTE_AND_DUST = 33; + public static final int TYPE_STALACTITE_DRIP_WATER = 29; + public static final int TYPE_STALACTITE_DRIP_LAVA = 30; + public static final int TYPE_FALLING_DUST = 31, TYPE_DUST = 31; + public static final int TYPE_MOB_SPELL = 32; + public static final int TYPE_MOB_SPELL_AMBIENT = 33; + public static final int TYPE_MOB_SPELL_INSTANTANEOUS = 34; + public static final int TYPE_INK = 35; + public static final int TYPE_SLIME = 36; + public static final int TYPE_RAIN_SPLASH = 37; + public static final int TYPE_VILLAGER_ANGRY = 38; + public static final int TYPE_VILLAGER_HAPPY = 39; + public static final int TYPE_ENCHANTMENT_TABLE = 40; + public static final int TYPE_TRACKING_EMITTER = 41; + public static final int TYPE_NOTE = 42; + public static final int TYPE_WITCH_SPELL = 43; + public static final int TYPE_CARROT = 44; + public static final int TYPE_MOB_APPEARANCE = 45; + public static final int TYPE_END_ROD = 46; + public static final int TYPE_RISING_DRAGONS_BREATH = 47; + public static final int TYPE_SPIT = 48; + public static final int TYPE_TOTEM = 49; + public static final int TYPE_FOOD = 50; + public static final int TYPE_FIREWORKS_STARTER = 51; + public static final int TYPE_FIREWORKS_SPARK = 52; + public static final int TYPE_FIREWORKS_OVERLAY = 53; + public static final int TYPE_BALLOON_GAS = 54; + public static final int TYPE_COLORED_FLAME = 55; + public static final int TYPE_SPARKLER = 56; + public static final int TYPE_CONDUIT = 57; + public static final int TYPE_BUBBLE_COLUMN_UP = 58; + public static final int TYPE_BUBBLE_COLUMN_DOWN = 59; + public static final int TYPE_SNEEZE = 60; + public static final int TYPE_SHULKER_BULLET = 61; + public static final int TYPE_BLEACH = 62; + public static final int TYPE_LARGE_EXPLOSION = 63; + public static final int TYPE_MYCELIUM_DUST = 64; + public static final int TYPE_FALLING_RED_DUST = 65; + public static final int TYPE_CAMPFIRE_SMOKE = 66; + public static final int TYPE_TALL_CAMPFIRE_SMOKE = 67; + public static final int TYPE_FALLING_DRAGONS_BREATH = 68; + public static final int TYPE_DRAGONS_BREATH = 69; + public static final int TYPE_BLUE_FLAME = 70; + public static final int TYPE_SOUL = 71; + public static final int TYPE_OBSIDIAN_TEAR = 72; + public static final int TYPE_PORTAL_REVERSE = 73; + public static final int TYPE_SNOWFLAKE = 74; + public static final int TYPE_VIBRATION_SIGNAL = 75; + public static final int TYPE_SCULK_SENSOR_REDSTONE = 76; + public static final int TYPE_SPORE_BLOSSOM_SHOWER = 77; + public static final int TYPE_SPORE_BLOSSOM_AMBIENT = 78; + public static final int TYPE_WAX = 79; + public static final int TYPE_ELECTRIC_SPARK = 80; + + public static final Integer getParticleIdByName(String name) { + name = name.toUpperCase(); + + try { + Field field = Particle.class.getField((name.startsWith("TYPE_") == true ? name : ("TYPE_" + name))); - public static final int TYPE_SLIME = 34; - public static final int TYPE_RAIN_SPLASH = 35; - public static final int TYPE_VILLAGER_ANGRY = 36; - // 60 same as 36 - public static final int TYPE_VILLAGER_HAPPY = 37; - public static final int TYPE_ENCHANTMENT_TABLE = 38; - public static final int TYPE_TRACKING_EMITTER = 39; - public static final int TYPE_NOTE = 40; - public static final int TYPE_WITCH_SPELL = 41; - public static final int TYPE_CARROT = 42; - // 43 unknown - public static final int TYPE_END_ROD = 44; - // 59 same as 44 - public static final int TYPE_RISING_DRAGONS_BREATH = 45; - public static final int TYPE_SPIT = 46; - public static final int TYPE_TOTEM = 47; - public static final int TYPE_FOOD = 48; - public static final int TYPE_FIREWORKS_STARTER = 49; - public static final int TYPE_FIREWORKS_SPARK = 50; - public static final int TYPE_FIREWORKS_OVERLAY = 51; - public static final int TYPE_BALLOON_GAS = 52; - public static final int TYPE_COLORED_FLAME = 53; - public static final int TYPE_SPARKLER = 54; - public static final int TYPE_CONDUIT = 55; - public static final int TYPE_BUBBLE_COLUMN_UP = 56; - public static final int TYPE_BUBBLE_COLUMN_DOWN = 57; - public static final int TYPE_SNEEZE = 58; + Class type = field.getType(); - public static final int TYPE_LARGE_EXPLOSION = 61; - public static final int TYPE_INK = 62; - public static final int TYPE_FALLING_RED_DUST = 63; - public static final int TYPE_CAMPFIRE_SMOKE = 64; - //65 same as 64 - public static final int TYPE_FALLING_DRAGONS_BREATH = 66; - public static final int TYPE_DRAGONS_BREATH = 67; + if(type==int.class) { + return field.getInt(null); + } + } catch(NoSuchFieldException | IllegalAccessException e) { + // ignore + } + return null; + } + + public static final boolean particleExists(String name) { + return getParticleIdByName(name) != null; + } public Particle() { super(0, 0, 0); diff --git a/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java b/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java index 964f43b1e96..5e3ad0e62cf 100644 --- a/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java +++ b/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java @@ -1,5 +1,6 @@ package cn.nukkit.level.util; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.utils.BinaryStream; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -20,7 +21,7 @@ public PalettedBlockStorage() { public PalettedBlockStorage(BitArrayVersion version) { this.bitArray = version.createPalette(SIZE); this.palette = new IntArrayList(16); - this.palette.add(0); // Air is at the start of every palette. + this.palette.add(GlobalBlockPalette.getOrCreateRuntimeId(0)); // Air is at the start of every palette. } private PalettedBlockStorage(BitArray bitArray, IntList palette) { diff --git a/src/main/java/cn/nukkit/math/AxisAlignedBB.java b/src/main/java/cn/nukkit/math/AxisAlignedBB.java index 640c07a3991..792afbbd7ff 100644 --- a/src/main/java/cn/nukkit/math/AxisAlignedBB.java +++ b/src/main/java/cn/nukkit/math/AxisAlignedBB.java @@ -87,6 +87,10 @@ default AxisAlignedBB setBB(AxisAlignedBB bb) { return this; } + default AxisAlignedBB getOffsetBoundingBox(BlockFace face, double x, double y, double z) { + return getOffsetBoundingBox(face.getXOffset() * x, face.getYOffset() * y, face.getZOffset() * z); + } + default AxisAlignedBB getOffsetBoundingBox(double x, double y, double z) { return new SimpleAxisAlignedBB(this.getMinX() + x, this.getMinY() + y, this.getMinZ() + z, this.getMaxX() + x, this.getMaxY() + y, this.getMaxZ() + z); } diff --git a/src/main/java/cn/nukkit/metrics/Metrics.java b/src/main/java/cn/nukkit/metrics/Metrics.java new file mode 100644 index 00000000000..b7122e3baa3 --- /dev/null +++ b/src/main/java/cn/nukkit/metrics/Metrics.java @@ -0,0 +1,533 @@ +package cn.nukkit.metrics; + +import cn.nukkit.utils.MainLogger; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + * + * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + public static final int B_STATS_VERSION = 1; + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/server-implementation"; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + // The name of the server software + private final String name; + + // The uuid of the server + private final String serverUUID; + + // Should failed requests be logged? + private static boolean logFailedRequests = false; + + // The logger for the failed requests + private static MainLogger logger; + + public Metrics(String name, String serverUUID, boolean logFailedRequests, MainLogger logger) { + this.name = name; + this.serverUUID = serverUUID; + Metrics.logFailedRequests = logFailedRequests; + Metrics.logger = logger; + + // Start submitting the data + startSubmitting(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Runnable submitTask = this::submitData; + + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + /** + * Gets the plugin specific data. + * + * @return The plugin specific data. + */ + private JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + data.put("pluginName", name); // Append the name of the server software + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // OS specific data + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + data.put("serverUUID", serverUUID); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + pluginData.add(getPluginData()); + data.put("plugins", pluginData); + + try { + // We are still in the Thread of the timer, so nothing get blocked :) + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + logger.warning("Could not submit stats of " + name, e); + } + } + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + + HttpsURLConnection connection = (HttpsURLConnection) new java.net.URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/metrics/NukkitMetrics.java b/src/main/java/cn/nukkit/metrics/NukkitMetrics.java new file mode 100644 index 00000000000..0a1c9bde671 --- /dev/null +++ b/src/main/java/cn/nukkit/metrics/NukkitMetrics.java @@ -0,0 +1,170 @@ +package cn.nukkit.metrics; + +import cn.nukkit.Server; +import cn.nukkit.utils.Config; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NukkitMetrics { + private static boolean metricsStarted = false; + + private final Server server; + + private boolean enabled; + private String serverUUID; + private boolean logFailedRequests; + + public NukkitMetrics(Server server) { + this.server = server; + + if (metricsStarted) { + return; + } + + try { + this.loadConfig(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (enabled) { + Metrics metrics = new Metrics("Nukkit", serverUUID, logFailedRequests, server.getLogger()); + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> server.getOnlinePlayers().size())); + metrics.addCustomChart(new Metrics.SimplePie("codename", server::getCodename)); + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", server::getVersion)); + metrics.addCustomChart(new Metrics.SimplePie("nukkit_version", server::getNukkitVersion)); + metrics.addCustomChart(new Metrics.SimplePie("xbox_auth", () -> server.getPropertyBoolean("xbox-auth") ? "Required" : "Not required")); + + metrics.addCustomChart(new Metrics.AdvancedPie("player_platform", () -> { + Map valueMap = new HashMap<>(); + + server.getOnlinePlayers().forEach((uuid, player) -> { + String deviceOS = mapDeviceOSToString(player.getLoginChainData().getDeviceOS()); + if (!valueMap.containsKey(deviceOS)) { + valueMap.put(deviceOS, 1); + } else { + valueMap.put(deviceOS, valueMap.get(deviceOS) + 1); + } + }); + return valueMap; + })); + + metrics.addCustomChart(new Metrics.AdvancedPie("player_game_version", () -> { + Map valueMap = new HashMap<>(); + + server.getOnlinePlayers().forEach((uuid, player) -> { + String gameVersion = player.getLoginChainData().getGameVersion(); + if (!valueMap.containsKey(gameVersion)) { + valueMap.put(gameVersion, 1); + } else { + valueMap.put(gameVersion, valueMap.get(gameVersion) + 1); + } + }); + return valueMap; + })); + + // The following code can be attributed to the PaperMC project + // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614 + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + // http://openjdk.java.net/jeps/223 + // Java decided to change their versioning scheme and in doing so modified the java.version system + // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier + // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+ + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it + // valid strings for the major may potentially include values such as -ea to deannotate a pre release + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + return map; + })); + + metricsStarted = true; + } + } + + /** + * Loads the bStats configuration. + */ + private void loadConfig() throws IOException { + File bStatsFolder = new File(server.getPluginPath(), "bStats"); + + if (!bStatsFolder.exists() && !bStatsFolder.mkdirs()) { + server.getLogger().warning("Failed to create bStats metrics directory"); + return; + } + + File configFile = new File(bStatsFolder, "config.yml"); + if (!configFile.exists()) { + writeFile(configFile, + "# bStats collects some data for plugin authors like how many servers are using their plugins.", + "# To honor their work, you should not disable it.", + "# This has nearly no effect on the server performance!", + "# Check out https://bStats.org/ to learn more :)", + "enabled: true", + "serverUuid: \"" + UUID.randomUUID().toString() + "\"", + "logFailedRequests: false"); + } + + Config config = new Config(configFile, Config.YAML); + + // Load configuration + this.enabled = config.getBoolean("enabled", true); + this.serverUUID = config.getString("serverUuid"); + this.logFailedRequests = config.getBoolean("logFailedRequests", false); + } + + private void writeFile(File file, String... lines) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (String line : lines) { + writer.write(line); + writer.newLine(); + } + } + } + + private String mapDeviceOSToString(int os) { + switch (os) { + case 1: return "Android"; + case 2: return "iOS"; + case 3: return "macOS"; + case 4: return "FireOS"; + case 5: return "Gear VR"; + case 6: return "Hololens"; + case 7: return "Windows 10"; + case 8: return "Windows"; + case 9: return "Dedicated"; + case 10: return "PS4"; + case 11: return "Switch"; + case 12: return "Switch"; + case 13: return "Xbox One"; + case 14: return "Windows Phone"; + } + return "Unknown"; + } +} diff --git a/src/main/java/cn/nukkit/nbt/NBTIO.java b/src/main/java/cn/nukkit/nbt/NBTIO.java index 5ce1b2f3795..0cad0c7c183 100644 --- a/src/main/java/cn/nukkit/nbt/NBTIO.java +++ b/src/main/java/cn/nukkit/nbt/NBTIO.java @@ -28,7 +28,7 @@ public static CompoundTag putItemHelper(Item item) { } public static CompoundTag putItemHelper(Item item, Integer slot) { - CompoundTag tag = new CompoundTag(null) + CompoundTag tag = new CompoundTag((String) null) .putShort("id", item.getId()) .putByte("Count", item.getCount()) .putShort("Damage", item.getDamage()); diff --git a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java index 10def9433b0..617ee5afd8b 100644 --- a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java @@ -12,14 +12,23 @@ import java.util.StringJoiner; public class CompoundTag extends Tag implements Cloneable { - private final Map tags = new HashMap<>(); + private final Map tags; public CompoundTag() { - super(""); + this(""); } public CompoundTag(String name) { + this(name, new HashMap<>()); + } + + public CompoundTag(Map tags) { + this("", tags); + } + + public CompoundTag(String name, Map tags) { super(name); + this.tags = tags; } @Override diff --git a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java index e6465316d63..ab4c76d2db5 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java @@ -49,7 +49,7 @@ public byte getId() { @Override public String toString() { - return "" + data; + return "ShortTag " + this.getName() + "(data: " + data + ")"; } @Override diff --git a/src/main/java/cn/nukkit/nbt/tag/Tag.java b/src/main/java/cn/nukkit/nbt/tag/Tag.java index 3b66e063b2b..2f33f67ed71 100644 --- a/src/main/java/cn/nukkit/nbt/tag/Tag.java +++ b/src/main/java/cn/nukkit/nbt/tag/Tag.java @@ -60,7 +60,9 @@ public void print(String prefix, PrintStream out) { out.print("(\"" + name + "\")"); } out.print(": "); - out.println(toString()); + if (getId() != TAG_Compound && getId() != TAG_List) { + out.println(parseValue()); + } } public Tag setName(String name) { diff --git a/src/main/java/cn/nukkit/network/LittleEndianByteBufInputStream.java b/src/main/java/cn/nukkit/network/LittleEndianByteBufInputStream.java new file mode 100644 index 00000000000..f2194b676b3 --- /dev/null +++ b/src/main/java/cn/nukkit/network/LittleEndianByteBufInputStream.java @@ -0,0 +1,51 @@ +package cn.nukkit.network; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; + +import java.io.IOException; + +public class LittleEndianByteBufInputStream extends ByteBufInputStream { + + private final ByteBuf buffer; + + public LittleEndianByteBufInputStream(ByteBuf buffer) { + super(buffer); + this.buffer = buffer; + } + + @Override + public char readChar() throws IOException { + return Character.reverseBytes(buffer.readChar()); + } + + @Override + public double readDouble() throws IOException { + return buffer.readDoubleLE(); + } + + @Override + public float readFloat() throws IOException { + return buffer.readFloatLE(); + } + + @Override + public short readShort() throws IOException { + return buffer.readShortLE(); + } + + @Override + public int readUnsignedShort() throws IOException { + return buffer.readUnsignedShortLE(); + } + + @Override + public long readLong() throws IOException { + return buffer.readLongLE(); + } + + @Override + public int readInt() throws IOException { + return buffer.readIntLE(); + } +} diff --git a/src/main/java/cn/nukkit/network/LittleEndianByteBufOutputStream.java b/src/main/java/cn/nukkit/network/LittleEndianByteBufOutputStream.java new file mode 100644 index 00000000000..0e4c65d660a --- /dev/null +++ b/src/main/java/cn/nukkit/network/LittleEndianByteBufOutputStream.java @@ -0,0 +1,54 @@ +package cn.nukkit.network; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class LittleEndianByteBufOutputStream extends ByteBufOutputStream { + + private final ByteBuf buffer; + + public LittleEndianByteBufOutputStream(ByteBuf buffer) { + super(buffer); + this.buffer = buffer; + } + + @Override + public void writeChar(int v) throws IOException { + this.buffer.writeChar(Character.reverseBytes((char) v)); + } + + @Override + public void writeDouble(double v) throws IOException { + this.buffer.writeDoubleLE(v); + } + + @Override + public void writeFloat(float v) throws IOException { + this.buffer.writeFloatLE(v); + } + + @Override + public void writeShort(int val) throws IOException { + this.buffer.writeShortLE(val); + } + + @Override + public void writeLong(long val) throws IOException { + this.buffer.writeLongLE(val); + } + + @Override + public void writeInt(int val) throws IOException { + this.buffer.writeIntLE(val); + } + + @Override + public void writeUTF(String string) throws IOException { + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + this.writeShort(bytes.length); + this.write(bytes); + } +} diff --git a/src/main/java/cn/nukkit/network/Network.java b/src/main/java/cn/nukkit/network/Network.java index 8f039c62cb0..34664469c25 100644 --- a/src/main/java/cn/nukkit/network/Network.java +++ b/src/main/java/cn/nukkit/network/Network.java @@ -5,22 +5,23 @@ import cn.nukkit.Server; import cn.nukkit.nbt.stream.FastByteArrayOutputStream; import cn.nukkit.network.protocol.*; -import cn.nukkit.utils.Binary; import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.MainLogger; import cn.nukkit.utils.ThreadCache; import cn.nukkit.utils.Utils; import cn.nukkit.utils.VarInt; -import cn.nukkit.utils.Zlib; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.extern.log4j.Log4j2; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.ArrayList; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ProtocolException; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -71,62 +72,70 @@ public Network(Server server) { public static byte[] inflateRaw(byte[] data) throws IOException, DataFormatException { Inflater inflater = INFLATER_RAW.get(); - inflater.reset(); - inflater.setInput(data); - inflater.finished(); - - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buf = BUFFER.get(); - while (!inflater.finished()) { - int i = inflater.inflate(buf); - if (i == 0) { - throw new IOException("Could not decompress the data. Needs input: "+inflater.needsInput()+", Needs Dictionary: "+inflater.needsDictionary()); + try { + inflater.setInput(data); + inflater.finished(); + + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + byte[] buf = BUFFER.get(); + while (!inflater.finished()) { + int i = inflater.inflate(buf); + if (i == 0) { + throw new IOException("Could not decompress the data. Needs input: " + inflater.needsInput() + ", Needs Dictionary: " + inflater.needsDictionary()); + } + bos.write(buf, 0, i); } - bos.write(buf, 0, i); + return bos.toByteArray(); + } finally { + inflater.reset(); } - return bos.toByteArray(); } public static byte[] deflateRaw(byte[] data, int level) throws IOException { Deflater deflater = DEFLATER_RAW.get(); - deflater.reset(); - deflater.setLevel(level); - deflater.setInput(data); - deflater.finish(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buffer = BUFFER.get(); - while (!deflater.finished()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } + try { + deflater.setLevel(level); + deflater.setInput(data); + deflater.finish(); + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + byte[] buffer = BUFFER.get(); + while (!deflater.finished()) { + int i = deflater.deflate(buffer); + bos.write(buffer, 0, i); + } - return bos.toByteArray(); + return bos.toByteArray(); + } finally { + deflater.reset(); + } } public static byte[] deflateRaw(byte[][] datas, int level) throws IOException { Deflater deflater = DEFLATER_RAW.get(); - deflater.reset(); - deflater.setLevel(level); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buffer = BUFFER.get(); - - for (byte[] data : datas) { - deflater.setInput(data); - while (!deflater.needsInput()) { + try { + deflater.setLevel(level); + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + byte[] buffer = BUFFER.get(); + + for (byte[] data : datas) { + deflater.setInput(data); + while (!deflater.needsInput()) { + int i = deflater.deflate(buffer); + bos.write(buffer, 0, i); + } + } + deflater.finish(); + while (!deflater.finished()) { int i = deflater.deflate(buffer); bos.write(buffer, 0, i); } + return bos.toByteArray(); + } finally { + deflater.reset(); } - deflater.finish(); - while (!deflater.finished()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - //Deflater::end is called the time when the process exits. - return bos.toByteArray(); } public void addStatistics(double upload, double download) { @@ -215,52 +224,63 @@ public Server getServer() { } public void processBatch(BatchPacket packet, Player player) { + List packets = new ObjectArrayList<>(); + try { + processBatch(packet.payload, packets); + } catch (ProtocolException e) { + player.close("", e.getMessage()); + log.error("Unable to process player packets ", e); + } + } + + public void processBatch(byte[] payload, Collection packets) throws ProtocolException { byte[] data; try { - data = Network.inflateRaw(packet.payload); + data = Network.inflateRaw(payload); //data = Zlib.inflate(packet.payload, 2 * 1024 * 1024); // Max 2MB } catch (Exception e) { log.debug("Exception while inflating batch packet", e); return; } - int len = data.length; BinaryStream stream = new BinaryStream(data); try { - List packets = new ArrayList<>(); int count = 0; - while (stream.offset < len) { + while (!stream.feof()) { count++; if (count >= 1000) { - player.close("", "Illegal Batch Packet"); - return; + throw new ProtocolException("Illegal batch with " + count + " packets"); } byte[] buf = stream.getByteArray(); - DataPacket pk = this.getPacketFromBuffer(buf); + ByteArrayInputStream bais = new ByteArrayInputStream(buf); + int header = (int) VarInt.readUnsignedVarInt(bais); + + // | Client ID | Sender ID | Packet ID | + // | 2 bits | 2 bits | 10 bits | + int packetId = header & 0x3ff; + + DataPacket pk = this.getPacket(packetId); if (pk != null) { + pk.setBuffer(buf, buf.length - bais.available()); try { pk.decode(); } catch (Exception e) { - log.warn("Unable to decode {} from {}", pk.getClass().getSimpleName(), player.getName()); - log.throwing(e); if (log.isTraceEnabled()) { - log.trace("Dumping Packet\n{}", ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(packet.payload))); + log.trace("Dumping Packet\n{}", ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(buf))); } - throw e; + log.error("Unable to decode packet", e); + throw new IllegalStateException("Unable to decode " + pk.getClass().getSimpleName()); } packets.add(pk); + } else { + log.debug("Received unknown packet with ID: {}", Integer.toHexString(packetId)); } } - - processPackets(player, packets); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Error whilst decoding batch packet", e); - } + MainLogger.getLogger().error("Error whilst decoding batch packet", e); } } @@ -275,17 +295,8 @@ public void processPackets(Player player, List packets) { packets.forEach(player::handleDataPacket); } - private DataPacket getPacketFromBuffer(byte[] buffer) throws IOException { - ByteArrayInputStream stream = new ByteArrayInputStream(buffer); - DataPacket pk = this.getPacket((byte) VarInt.readUnsignedVarInt(stream)); - if (pk != null) { - pk.setBuffer(buffer, buffer.length - stream.available()); - } - return pk; - } - - public DataPacket getPacket(byte id) { - Class clazz = this.packetPool[id & 0xff]; + public DataPacket getPacket(int id) { + Class clazz = this.packetPool[id]; if (clazz != null) { try { return clazz.newInstance(); @@ -329,6 +340,7 @@ private void registerPackets() { this.registerPacket(ProtocolInfo.ADD_PLAYER_PACKET, AddPlayerPacket.class); this.registerPacket(ProtocolInfo.ADVENTURE_SETTINGS_PACKET, AdventureSettingsPacket.class); this.registerPacket(ProtocolInfo.ANIMATE_PACKET, AnimatePacket.class); + this.registerPacket(ProtocolInfo.ANVIL_DAMAGE_PACKET, AnvilDamagePacket.class); this.registerPacket(ProtocolInfo.AVAILABLE_COMMANDS_PACKET, AvailableCommandsPacket.class); this.registerPacket(ProtocolInfo.BATCH_PACKET, BatchPacket.class); this.registerPacket(ProtocolInfo.BLOCK_ENTITY_DATA_PACKET, BlockEntityDataPacket.class); @@ -430,5 +442,6 @@ private void registerPackets() { this.registerPacket(ProtocolInfo.PLAYER_ARMOR_DAMAGE_PACKET, PlayerArmorDamagePacket.class); this.registerPacket(ProtocolInfo.PLAYER_ENCHANT_OPTIONS_PACKET, PlayerEnchantOptionsPacket.class); this.registerPacket(ProtocolInfo.UPDATE_PLAYER_GAME_TYPE_PACKET, UpdatePlayerGameTypePacket.class); + this.registerPacket(ProtocolInfo.FILTER_TEXT_PACKET, FilterTextPacket.class); } } diff --git a/src/main/java/cn/nukkit/network/RakNetInterface.java b/src/main/java/cn/nukkit/network/RakNetInterface.java index 1bda4d466f8..238bea31618 100644 --- a/src/main/java/cn/nukkit/network/RakNetInterface.java +++ b/src/main/java/cn/nukkit/network/RakNetInterface.java @@ -7,7 +7,9 @@ import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.protocol.ProtocolInfo; +import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.Utils; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.nukkitx.network.raknet.*; import com.nukkitx.network.util.DisconnectReason; @@ -15,17 +17,23 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramPacket; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.FastThreadLocal; +import io.netty.util.concurrent.ScheduledFuture; +import io.netty.util.internal.PlatformDependent; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.apache.logging.log4j.message.FormattedMessage; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; /** @@ -41,7 +49,19 @@ public class RakNetInterface implements RakNetServerListener, AdvancedSourceInte private final RakNetServer raknet; - private Set sessionListeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map sessions = new HashMap<>(); + + private final Queue sessionCreationQueue = PlatformDependent.newMpscQueue(); + + + private final Set> tickFutures = new HashSet<>(); + + private final FastThreadLocal> sessionsToTick = new FastThreadLocal>() { + @Override + protected Set initialValue() { + return Collections.newSetFromMap(new IdentityHashMap<>()); + } + }; private byte[] advertisement; @@ -53,6 +73,14 @@ public RakNetInterface(Server server) { this.raknet = new RakNetServer(bindAddress, Runtime.getRuntime().availableProcessors()); this.raknet.bind().join(); this.raknet.setListener(this); + + for (EventExecutor executor : this.raknet.getBootstrap().config().group()) { + this.tickFutures.add(executor.scheduleAtFixedRate(() -> { + for (NukkitRakNetSession session : sessionsToTick.get()) { + session.sendOutbound(); + } + }, 0, 50, TimeUnit.MILLISECONDS)); + } } @Override @@ -62,18 +90,41 @@ public void setNetwork(Network network) { @Override public boolean process() { - Iterator iterator = this.sessionListeners.iterator(); + NukkitRakNetSession session; + while ((session = this.sessionCreationQueue.poll()) != null) { + InetSocketAddress address = session.raknet.getAddress(); + PlayerCreationEvent ev = new PlayerCreationEvent(this, Player.class, Player.class, null, address); + this.server.getPluginManager().callEvent(ev); + Class clazz = ev.getPlayerClass(); + + try { + Constructor constructor = clazz.getConstructor(SourceInterface.class, Long.class, InetSocketAddress.class); + Player player = constructor.newInstance(this, ev.getClientId(), ev.getSocketAddress()); + this.server.addPlayer(address, player); + session.player = player; + this.sessions.put(address, session); + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + Server.getInstance().getLogger().logException(e); + } + } + + Iterator iterator = this.sessions.values().iterator(); while (iterator.hasNext()) { - NukkitSessionListener listener = iterator.next(); - Player player = listener.player; - if (listener.disconnectReason != null) { - player.close(player.getLeaveMessage(), listener.disconnectReason, false); + NukkitRakNetSession nukkitSession = iterator.next(); + Player player = nukkitSession.player; + if (nukkitSession.disconnectReason != null) { + player.close(player.getLeaveMessage(), nukkitSession.disconnectReason, false); iterator.remove(); continue; } DataPacket packet; - while ((packet = listener.packets.poll()) != null) { - listener.player.handleDataPacket(packet); + while ((packet = nukkitSession.inbound.poll()) != null) { + try { + nukkitSession.player.handleDataPacket(packet); + } catch (Exception e) { + log.error(new FormattedMessage("An error occurred whilst handling {} for {}", + new Object[]{packet.getClass().getSimpleName(), nukkitSession.player.getName()}, e)); + } } } return true; @@ -100,11 +151,13 @@ public void close(Player player, String reason) { @Override public void shutdown() { + this.tickFutures.forEach(future -> future.cancel(false)); this.raknet.close(); } @Override public void emergencyShutdown() { + this.tickFutures.forEach(future -> future.cancel(true)); this.raknet.close(); } @@ -161,30 +214,13 @@ public Integer putPacket(Player player, DataPacket packet, boolean needACK) { @Override public Integer putPacket(Player player, DataPacket packet, boolean needACK, boolean immediate) { - RakNetServerSession session = this.raknet.getSession(player.getSocketAddress()); - if (session == null) { - return null; - } + NukkitRakNetSession session = this.sessions.get(player.getSocketAddress()); - byte[] buffer; - if (packet.pid() == ProtocolInfo.BATCH_PACKET) { - buffer = ((BatchPacket) packet).payload; - if (buffer == null) { - return null; - } - } else { - this.server.batchPackets(new Player[]{player}, new DataPacket[]{packet}, true); - return null; + if (session != null) { + packet.tryEncode(); + session.outbound.offer(packet); } - ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer(1 + buffer.length); - byteBuf.writeByte(0xfe); - byteBuf.writeBytes(buffer); - byteBuf.readerIndex(0); - - session.send(byteBuf, immediate ? RakNetPriority.IMMEDIATE : RakNetPriority.MEDIUM, packet.reliability, - packet.getChannel()); - return null; } @@ -200,20 +236,15 @@ public byte[] onQuery(InetSocketAddress inetSocketAddress) { @Override public void onSessionCreation(RakNetServerSession session) { - PlayerCreationEvent ev = new PlayerCreationEvent(this, Player.class, Player.class, null, session.getAddress()); - this.server.getPluginManager().callEvent(ev); - Class clazz = ev.getPlayerClass(); - - try { - Constructor constructor = clazz.getConstructor(SourceInterface.class, Long.class, InetSocketAddress.class); - Player player = (Player) constructor.newInstance(this, ev.getClientId(), ev.getSocketAddress()); - this.server.addPlayer(session.getAddress(), player); - NukkitSessionListener listener = new NukkitSessionListener(player); - this.sessionListeners.add(listener); - session.setListener(listener); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { - Server.getInstance().getLogger().logException(e); - } + NukkitRakNetSession nukkitSession = new NukkitRakNetSession(session); + session.setListener(nukkitSession); + this.sessionCreationQueue.offer(nukkitSession); + + // We need to make sure this gets put into the correct thread local hashmap + // for ticking or race conditions will occur. + session.getEventLoop().execute(() -> { + this.sessionsToTick.get().add(nukkitSession); + }); } @Override @@ -222,22 +253,23 @@ public void onUnhandledDatagram(ChannelHandlerContext ctx, DatagramPacket datagr } @RequiredArgsConstructor - private class NukkitSessionListener implements RakNetSessionListener { - private final Player player; - private final Queue packets = new ConcurrentLinkedQueue<>(); + private class NukkitRakNetSession implements RakNetSessionListener { + private final RakNetServerSession raknet; + private final Queue inbound = PlatformDependent.newSpscQueue(); + private final Queue outbound = PlatformDependent.newMpscQueue(); private String disconnectReason = null; + private Player player; @Override public void onSessionChangeState(RakNetState rakNetState) { - } @Override public void onDisconnect(DisconnectReason disconnectReason) { if (disconnectReason == DisconnectReason.TIMED_OUT) { - this.disconnectReason = "Timed out"; + this.disconnect("Timed out"); } else { - this.disconnectReason = "Disconnected from Server"; + this.disconnect("Disconnected from Server"); } } @@ -246,17 +278,15 @@ public void onEncapsulated(EncapsulatedPacket packet) { ByteBuf buffer = packet.getBuffer(); short packetId = buffer.readUnsignedByte(); if (packetId == 0xfe) { - DataPacket batchPacket = RakNetInterface.this.network.getPacket(ProtocolInfo.BATCH_PACKET); - if (batchPacket == null) { - return; - } - byte[] packetBuffer = new byte[buffer.readableBytes()]; buffer.readBytes(packetBuffer); - batchPacket.setBuffer(packetBuffer); - batchPacket.decode(); - packets.offer(batchPacket); + try { + RakNetInterface.this.network.processBatch(packetBuffer, this.inbound); + } catch (ProtocolException e) { + this.disconnect("Sent malformed packet"); + log.error("Unable to process batch packet", e); + } } } @@ -264,5 +294,55 @@ public void onEncapsulated(EncapsulatedPacket packet) { public void onDirect(ByteBuf byteBuf) { // We don't allow any direct packets so ignore. } + + private void disconnect(String message) { + this.disconnectReason = message; + RakNetInterface.this.sessionsToTick.get().remove(this); + } + + private void sendOutbound() { + List toBatch = new ObjectArrayList<>(); + DataPacket packet; + while ((packet = this.outbound.poll()) != null) { + if (packet.pid() == ProtocolInfo.BATCH_PACKET) { + if (!toBatch.isEmpty()) { + this.sendPackets(toBatch); + toBatch.clear(); + } + + this.sendPacket(((BatchPacket) packet).payload); + } else { + toBatch.add(packet); + } + } + + if (!toBatch.isEmpty()) { + this.sendPackets(toBatch); + } + } + + private void sendPackets(Collection packets) { + BinaryStream batched = new BinaryStream(); + for (DataPacket packet : packets) { + Preconditions.checkArgument(!(packet instanceof BatchPacket), "Cannot batch BatchPacket"); + Preconditions.checkState(packet.isEncoded, "Packet should have already been encoded"); + byte[] buf = packet.getBuffer(); + batched.putUnsignedVarInt(buf.length); + batched.put(buf); + } + + try { + this.sendPacket(Network.deflateRaw(batched.getBuffer(), network.getServer().networkCompressionLevel)); + } catch (IOException e) { + log.error("Unable to compress batched packets", e); + } + } + + private void sendPacket(byte[] payload) { + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer(1 + payload.length); + byteBuf.writeByte(0xfe); + byteBuf.writeBytes(payload); + this.raknet.send(byteBuf); + } } } diff --git a/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java b/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java new file mode 100644 index 00000000000..f03d8b886f0 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java @@ -0,0 +1,32 @@ +package cn.nukkit.network.protocol; + +import cn.nukkit.math.BlockVector3; +import lombok.ToString; + +@ToString +public class AnvilDamagePacket extends DataPacket { + + public int damage; + public int x; + public int y; + public int z; + + @Override + public byte pid() { + return ProtocolInfo.ANVIL_DAMAGE_PACKET; + } + + @Override + public void decode() { + this.damage = this.getByte(); + BlockVector3 vec = this.getBlockVector3(); + this.x = vec.x; + this.y = vec.y; + this.z = vec.z; + } + + @Override + public void encode() { + + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java b/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java index 3044535699a..8e59f8fa958 100644 --- a/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java @@ -26,23 +26,20 @@ public class AvailableCommandsPacket extends DataPacket { public static final int ARG_FLAG_SOFT_ENUM = 0x4000000; public static final int ARG_TYPE_INT = 1; - public static final int ARG_TYPE_FLOAT = 2; - public static final int ARG_TYPE_VALUE = 3; - public static final int ARG_TYPE_WILDCARD_INT = 4; - public static final int ARG_TYPE_OPERATOR = 5; - public static final int ARG_TYPE_TARGET = 6; - public static final int ARG_TYPE_WILDCARD_TARGET = 7; - - public static final int ARG_TYPE_FILE_PATH = 14; - - public static final int ARG_TYPE_STRING = 29; - public static final int ARG_TYPE_BLOCK_POSITION = 37; - public static final int ARG_TYPE_POSITION = 38; - - public static final int ARG_TYPE_MESSAGE = 41; - public static final int ARG_TYPE_RAWTEXT = 43; - public static final int ARG_TYPE_JSON = 47; - public static final int ARG_TYPE_COMMAND = 54; + public static final int ARG_TYPE_FLOAT = 3; + public static final int ARG_TYPE_VALUE = 4; + public static final int ARG_TYPE_WILDCARD_INT = 5; + public static final int ARG_TYPE_OPERATOR = 6; + public static final int ARG_TYPE_TARGET = 7; + public static final int ARG_TYPE_WILDCARD_TARGET = 8; + public static final int ARG_TYPE_FILE_PATH = 16; + public static final int ARG_TYPE_STRING = 32; + public static final int ARG_TYPE_BLOCK_POSITION = 40; + public static final int ARG_TYPE_POSITION = 41; + public static final int ARG_TYPE_MESSAGE = 44; + public static final int ARG_TYPE_RAWTEXT = 46; + public static final int ARG_TYPE_JSON = 50; + public static final int ARG_TYPE_COMMAND = 63; public Map commands; public final Map> softEnums = new HashMap<>(); diff --git a/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java b/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java index d168053c818..6d8d96c0a80 100644 --- a/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java @@ -16,15 +16,18 @@ public byte pid() { } public int windowId; + public boolean wasServerInitiated = true; @Override public void decode() { this.windowId = (byte) this.getByte(); + this.wasServerInitiated = this.getBoolean(); } @Override public void encode() { this.reset(); this.putByte((byte) this.windowId); + this.putBoolean(this.wasServerInitiated); } } diff --git a/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java b/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java index e06922caf9c..d54b04b7c5e 100644 --- a/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java @@ -25,8 +25,8 @@ public class CraftingDataPacket extends DataPacket { public static final String CRAFTING_TAG_SMOKER = "smoker"; private List entries = new ArrayList<>(); - private List brewingEntries = new ArrayList<>(); - private List containerEntries = new ArrayList<>(); + private final List brewingEntries = new ArrayList<>(); + private final List containerEntries = new ArrayList<>(); public boolean cleanRecipes; public void addShapelessRecipe(ShapelessRecipe... recipe) { @@ -41,6 +41,10 @@ public void addFurnaceRecipe(FurnaceRecipe... recipe) { Collections.addAll(entries, recipe); } + public void addMultiRecipe(MultiRecipe... recipe) { + Collections.addAll(entries, recipe); + } + public void addBrewingRecipe(BrewingRecipe... recipe) { Collections.addAll(brewingEntries, recipe); } @@ -65,6 +69,8 @@ public void encode() { this.reset(); this.putUnsignedVarInt(entries.size()); + int recipeNetworkId = 1; + for (Recipe recipe : entries) { this.putVarInt(recipe.getType().ordinal()); switch (recipe.getType()) { @@ -77,11 +83,11 @@ public void encode() { this.putRecipeIngredient(ingredient); } this.putUnsignedVarInt(1); - this.putSlot(shapeless.getResult()); + this.putSlot(shapeless.getResult(), true); this.putUUID(shapeless.getId()); this.putString(CRAFTING_TAG_CRAFTING_TABLE); this.putVarInt(shapeless.getPriority()); - this.putUnsignedVarInt(0); + this.putUnsignedVarInt(recipeNetworkId++); break; case SHAPED: ShapedRecipe shaped = (ShapedRecipe) recipe; @@ -99,12 +105,12 @@ public void encode() { outputs.addAll(shaped.getExtraResults()); this.putUnsignedVarInt(outputs.size()); for (Item output : outputs) { - this.putSlot(output); + this.putSlot(output, true); } this.putUUID(shaped.getId()); this.putString(CRAFTING_TAG_CRAFTING_TABLE); this.putVarInt(shaped.getPriority()); - this.putUnsignedVarInt(0); + this.putUnsignedVarInt(recipeNetworkId++); break; case FURNACE: case FURNACE_DATA: @@ -114,27 +120,31 @@ public void encode() { if (recipe.getType() == RecipeType.FURNACE_DATA) { this.putVarInt(input.getDamage()); } - this.putSlot(furnace.getResult()); + this.putSlot(furnace.getResult(), true); this.putString(CRAFTING_TAG_FURNACE); break; + case MULTI: + this.putUUID(((MultiRecipe) recipe).getId()); + this.putUnsignedVarInt(recipeNetworkId++); + break; } } this.putUnsignedVarInt(this.brewingEntries.size()); for (BrewingRecipe recipe : brewingEntries) { - this.putVarInt(recipe.getInput().getId()); + this.putVarInt(recipe.getInput().getNetworkId()); this.putVarInt(recipe.getInput().getDamage()); - this.putVarInt(recipe.getIngredient().getId()); + this.putVarInt(recipe.getIngredient().getNetworkId()); this.putVarInt(recipe.getIngredient().getDamage()); - this.putVarInt(recipe.getResult().getId()); + this.putVarInt(recipe.getResult().getNetworkId()); this.putVarInt(recipe.getResult().getDamage()); } this.putUnsignedVarInt(this.containerEntries.size()); for (ContainerRecipe recipe : containerEntries) { - this.putVarInt(recipe.getInput().getId()); - this.putVarInt(recipe.getIngredient().getId()); - this.putVarInt(recipe.getResult().getId()); + this.putVarInt(recipe.getInput().getNetworkId()); + this.putVarInt(recipe.getIngredient().getNetworkId()); + this.putVarInt(recipe.getResult().getNetworkId()); } this.putBoolean(cleanRecipes); diff --git a/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java b/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java index 4daedf84d16..efc9847b8a2 100644 --- a/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java @@ -22,11 +22,10 @@ public void decode() { @Override public void encode() { this.reset(); - this.putVarInt(entries.length); + this.putUnsignedVarInt(entries.length); for (int i = 0; i < entries.length; i++) { this.putUnsignedVarInt(i + 1); - this.putSlot(entries[i]); + this.putSlot(entries[i], true); } - } } diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index 3a24945a4a8..3969ca1cc19 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -12,7 +12,7 @@ */ public abstract class DataPacket extends BinaryStream implements Cloneable { - public boolean isEncoded = false; + public volatile boolean isEncoded = false; private int channel = 0; public RakNetReliability reliability = RakNetReliability.RELIABLE_ORDERED; @@ -23,6 +23,13 @@ public abstract class DataPacket extends BinaryStream implements Cloneable { public abstract void encode(); + public final void tryEncode() { + if (!this.isEncoded) { + this.isEncoded = true; + this.encode(); + } + } + @Override public DataPacket reset() { super.reset(); @@ -64,9 +71,8 @@ public BatchPacket compress(int level) { byte[] buf = getBuffer(); batchPayload[0] = Binary.writeUnsignedVarInt(buf.length); batchPayload[1] = buf; - byte[] data = Binary.appendBytes(batchPayload); try { - batch.payload = Network.deflateRaw(data, level); + batch.payload = Network.deflateRaw(batchPayload, level); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java b/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java index 40d938cb4e9..ba49ea4c280 100644 --- a/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java @@ -20,7 +20,7 @@ public byte pid() { @Override public void decode() { - this.runtimeId = this.getEntityUniqueId(); + this.runtimeId = this.getEntityRuntimeId(); int size = (int) this.getUnsignedVarInt(); for (int i = 0; i < size; i++) { UUID id = this.getUUID(); @@ -31,7 +31,7 @@ public void decode() { @Override public void encode() { this.reset(); - this.putEntityUniqueId(runtimeId); + this.putEntityRuntimeId(runtimeId); this.putUnsignedVarInt(pieceIds.size()); for (UUID id : pieceIds) { this.putUUID(id); diff --git a/src/main/java/cn/nukkit/network/protocol/EmotePacket.java b/src/main/java/cn/nukkit/network/protocol/EmotePacket.java index 6cbfbd49b29..13d9b674647 100644 --- a/src/main/java/cn/nukkit/network/protocol/EmotePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EmotePacket.java @@ -16,7 +16,7 @@ public byte pid() { @Override public void decode() { - this.runtimeId = this.getUnsignedVarLong(); + this.runtimeId = this.getEntityRuntimeId(); this.emoteID = this.getString(); this.flags = (byte) this.getByte(); } @@ -24,7 +24,7 @@ public void decode() { @Override public void encode() { this.reset(); - this.putUnsignedVarLong(this.runtimeId); + this.putEntityRuntimeId(this.runtimeId); this.putString(this.emoteID); this.putByte(flags); } diff --git a/src/main/java/cn/nukkit/network/protocol/FilterTextPacket.java b/src/main/java/cn/nukkit/network/protocol/FilterTextPacket.java new file mode 100644 index 00000000000..0d73c3b01e1 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/FilterTextPacket.java @@ -0,0 +1,30 @@ +package cn.nukkit.network.protocol; + +import lombok.ToString; + +@ToString +public class FilterTextPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.FILTER_TEXT_PACKET; + + public String text; + public boolean fromServer; + + @Override + public byte pid() { + return NETWORK_ID; + } + + @Override + public void decode() { + this.text = this.getString(); + this.fromServer = this.getBoolean(); + } + + @Override + public void encode() { + this.reset(); + this.putString(this.text); + this.putBoolean(this.fromServer); + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java index 198a6a45278..7947e92f3f6 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java @@ -43,7 +43,6 @@ public void encode() { this.putUnsignedVarInt(this.inventoryId); this.putUnsignedVarInt(this.slots.length); for (Item slot : this.slots) { - this.putVarInt(slot.getId()); this.putSlot(slot); } } diff --git a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java index 99b63119fa0..9fbde849b28 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java @@ -32,7 +32,6 @@ public void encode() { this.reset(); this.putUnsignedVarInt(this.inventoryId); this.putUnsignedVarInt(this.slot); - this.putVarInt(this.item.getId()); this.putSlot(this.item); } } diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java index b9ac3a30bd1..f95d63292cc 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java @@ -39,7 +39,6 @@ public class InventoryTransactionPacket extends DataPacket { public int transactionType; public NetworkInventoryAction[] actions; public TransactionData transactionData; - public boolean hasNetworkIds; public int legacyRequestId; /** @@ -48,6 +47,7 @@ public class InventoryTransactionPacket extends DataPacket { */ public boolean isCraftingPart = false; public boolean isEnchantingPart = false; + public boolean isRepairItemPart = false; @Override public byte pid() { @@ -60,7 +60,6 @@ public void encode() { this.putVarInt(this.legacyRequestId); //TODO legacySlot array this.putUnsignedVarInt(this.transactionType); - this.putBoolean(this.hasNetworkIds); this.putUnsignedVarInt(this.actions.length); for (NetworkInventoryAction action : this.actions) { action.write(this); @@ -120,8 +119,6 @@ public void decode() { this.transactionType = (int) this.getUnsignedVarInt(); - this.hasNetworkIds = this.getBoolean(); - int length = (int) this.getUnsignedVarInt(); Collection actions = new ArrayDeque<>(); for (int i = 0; i < length; i++) { diff --git a/src/main/java/cn/nukkit/network/protocol/LoginPacket.java b/src/main/java/cn/nukkit/network/protocol/LoginPacket.java index 61cad3ea87f..3312461a8ac 100644 --- a/src/main/java/cn/nukkit/network/protocol/LoginPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LoginPacket.java @@ -82,6 +82,9 @@ private void decodeSkinData() { if (skinToken.has("SkinId")) { skin.setSkinId(skinToken.get("SkinId").getAsString()); } + if (skinToken.has("PlayFabID")) { + skin.setPlayFabId(skinToken.get("PlayFabID").getAsString()); + } if (skinToken.has("CapeId")) { skin.setCapeId(skinToken.get("CapeId").getAsString()); } @@ -150,7 +153,8 @@ private static SkinAnimation getAnimation(JsonObject element) { byte[] data = Base64.getDecoder().decode(element.get("Image").getAsString()); int width = element.get("ImageWidth").getAsInt(); int height = element.get("ImageHeight").getAsInt(); - return new SkinAnimation(new SerializedImage(width, height, data), type, frames); + int expression = element.get("AnimationExpression").getAsInt(); + return new SkinAnimation(new SerializedImage(width, height, data), type, frames, expression); } private static SerializedImage getImage(JsonObject token, String name) { diff --git a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java index ea6a56482bd..1b97f5eae4e 100644 --- a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java @@ -14,9 +14,9 @@ public class MoveEntityDeltaPacket extends DataPacket { public static final int FLAG_HAS_PITCH = 0b100000; public int flags = 0; - public int xDelta = 0; - public int yDelta = 0; - public int zDelta = 0; + public float x = 0; + public float y = 0; + public float z = 0; public double yawDelta = 0; public double headYawDelta = 0; public double pitchDelta = 0; @@ -29,9 +29,9 @@ public byte pid() { @Override public void decode() { this.flags = this.getByte(); - this.xDelta = getCoordinate(FLAG_HAS_X); - this.yDelta = getCoordinate(FLAG_HAS_Y); - this.zDelta = getCoordinate(FLAG_HAS_Z); + this.x = getCoordinate(FLAG_HAS_X); + this.y = getCoordinate(FLAG_HAS_Y); + this.z = getCoordinate(FLAG_HAS_Z); this.yawDelta = getRotation(FLAG_HAS_YAW); this.headYawDelta = getRotation(FLAG_HAS_HEAD_YAW); this.pitchDelta = getRotation(FLAG_HAS_PITCH); @@ -40,17 +40,17 @@ public void decode() { @Override public void encode() { this.putByte((byte) flags); - putCoordinate(FLAG_HAS_X, this.xDelta); - putCoordinate(FLAG_HAS_Y, this.yDelta); - putCoordinate(FLAG_HAS_Z, this.zDelta); + putCoordinate(FLAG_HAS_X, this.x); + putCoordinate(FLAG_HAS_Y, this.y); + putCoordinate(FLAG_HAS_Z, this.z); putRotation(FLAG_HAS_YAW, this.yawDelta); putRotation(FLAG_HAS_HEAD_YAW, this.headYawDelta); putRotation(FLAG_HAS_PITCH, this.pitchDelta); } - private int getCoordinate(int flag) { + private float getCoordinate(int flag) { if ((flags & flag) != 0) { - return this.getVarInt(); + return this.getLFloat(); } return 0; } @@ -62,9 +62,9 @@ private double getRotation(int flag) { return 0d; } - private void putCoordinate(int flag, int value) { + private void putCoordinate(int flag, float value) { if ((flags & flag) != 0) { - this.putVarInt(value); + this.putLFloat(value); } } diff --git a/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java b/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java index bb09c316be9..e8d4a50c9fc 100644 --- a/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java @@ -28,6 +28,7 @@ public class MovePlayerPacket extends DataPacket { public long ridingEid; public int int1 = 0; public int int2 = 0; + public long frame; @Override public void decode() { @@ -46,6 +47,7 @@ public void decode() { this.int1 = this.getLInt(); this.int2 = this.getLInt(); } + this.frame = this.getUnsignedVarLong(); } @Override @@ -63,6 +65,7 @@ public void encode() { this.putLInt(this.int1); this.putLInt(this.int2); } + this.putUnsignedVarLong(this.frame); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java index 653810e6115..b2694be4fbf 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java @@ -44,8 +44,8 @@ public void encode() { } if (type == TYPE_ADD) { - for (Entry entry : this.entries) { // Biggest wtf - this.putBoolean(entry.skin.isTrusted()); + for (Entry entry : this.entries) { + this.putBoolean(true); } } } diff --git a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java index 3ac5dab33ed..72dd02dd2ea 100644 --- a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java +++ b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java @@ -14,12 +14,12 @@ public interface ProtocolInfo { * Actual Minecraft: PE protocol version */ @SuppressWarnings("UnnecessaryBoxing") - int CURRENT_PROTOCOL = Integer.valueOf("408"); // DO NOT REMOVE BOXING + int CURRENT_PROTOCOL = Integer.valueOf("431"); // DO NOT REMOVE BOXING List SUPPORTED_PROTOCOLS = Ints.asList(CURRENT_PROTOCOL); - String MINECRAFT_VERSION = "v1.16.20"; - String MINECRAFT_VERSION_NETWORK = "1.16.20"; + String MINECRAFT_VERSION = "v1.16.220"; + String MINECRAFT_VERSION_NETWORK = "1.16.220"; byte LOGIN_PACKET = 0x01; byte PLAY_STATUS_PACKET = 0x02; @@ -171,6 +171,14 @@ public interface ProtocolInfo { byte POS_TRACKING_CLIENT_REQUEST_PACKET = (byte) 0x9a; byte DEBUG_INFO_PACKET = (byte) 0x9b; byte PACKET_VIOLATION_WARNING_PACKET = (byte) 0x9c; + byte MOTION_PREDICTION_HINTS_PACKET = (byte) 0x9d; + byte ANIMATE_ENTITY_PACKET = (byte) 0x9e; + byte CAMERA_SHAKE_PACKET = (byte) 0x9f; + byte PLAYER_FOG_PACKET = (byte) 0xa0; + byte CORRECT_PLAYER_MOVE_PREDICTION_PACKET = (byte) 0xa1; + byte ITEM_COMPONENT_PACKET = (byte) 0xa2; + byte FILTER_TEXT_PACKET = (byte) 0xa3; + byte CLIENTBOUND_DEBUG_RENDERER_PACKET = (byte) 0xa4; byte BATCH_PACKET = (byte) 0xff; } diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java index 40a7b5b30bd..7a601abcd95 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java @@ -37,8 +37,10 @@ public void encode() { this.putString(entry.getPackVersion()); this.putString(""); //TODO: subpack name } - this.putBoolean(this.isExperimental); + this.putString(this.gameVersion); + this.putLInt(0); // Experiments length + this.putBoolean(false); // Were experiments previously toggled } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java index 9bce9daee48..fd868b369ce 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java @@ -24,8 +24,8 @@ public void encode() { this.putBoolean(this.mustAccept); this.putBoolean(this.scripting); - encodePacks(this.resourcePackEntries); encodePacks(this.behaviourPackEntries); + encodePacks(this.resourcePackEntries); } private void encodePacks(ResourcePack[] packs) { @@ -38,6 +38,7 @@ private void encodePacks(ResourcePack[] packs) { this.putString(""); // sub-pack name this.putString(""); // content identity this.putBoolean(false); // scripting + this.putBoolean(false); // raytracing capable } } diff --git a/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java b/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java index a04bc6ba07b..88a36def1d9 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java @@ -19,6 +19,7 @@ public byte pid() { public long eid; public EntityMetadata metadata; + public long frame; @Override public void decode() { @@ -30,5 +31,6 @@ public void encode() { this.reset(); this.putEntityRuntimeId(this.eid); this.put(Binary.writeMetadata(this.metadata)); + this.putUnsignedVarLong(this.frame); } } diff --git a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java index 345f0b118d7..a1bcb233445 100644 --- a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java @@ -1,26 +1,15 @@ package cn.nukkit.network.protocol; -import cn.nukkit.Server; +import cn.nukkit.item.RuntimeItems; import cn.nukkit.level.GameRules; -import cn.nukkit.level.GlobalBlockPalette; -import cn.nukkit.utils.BinaryStream; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; import lombok.ToString; import lombok.extern.log4j.Log4j2; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.Collection; - /** * Created on 15-10-13. */ @Log4j2 -@ToString(exclude = {"blockPalette"}) +@ToString public class StartGamePacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.START_GAME_PACKET; @@ -31,31 +20,6 @@ public class StartGamePacket extends DataPacket { public static final int GAME_PUBLISH_SETTING_FRIENDS_OF_FRIENDS = 3; public static final int GAME_PUBLISH_SETTING_PUBLIC = 4; - private static final byte[] ITEM_DATA_PALETTE; - - static { - InputStream stream = Server.class.getClassLoader().getResourceAsStream("runtime_item_ids.json"); - if (stream == null) { - throw new AssertionError("Unable to locate RuntimeID table"); - } - Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); - - Gson gson = new Gson(); - Type collectionType = new TypeToken>() { - }.getType(); - Collection entries = gson.fromJson(reader, collectionType); - BinaryStream paletteBuffer = new BinaryStream(); - - paletteBuffer.putUnsignedVarInt(entries.size()); - - for (ItemData data : entries) { - paletteBuffer.putString(data.name); - paletteBuffer.putLShort(data.id); - } - - ITEM_DATA_PALETTE = paletteBuffer.getBuffer(); - } - @Override public byte pid() { return NETWORK_ID; @@ -132,8 +96,8 @@ public void encode() { this.putLFloat(this.pitch); this.putVarInt(this.seed); - this.putLShort(0x00); // SpawnBiomeType - this.putString(""); // UserDefinedBiomeName + this.putLShort(0x00); // SpawnBiomeType - Default + this.putString("plains"); // UserDefinedBiomeName this.putVarInt(this.dimension); this.putVarInt(this.generator); this.putVarInt(this.worldGamemode); @@ -143,7 +107,7 @@ public void encode() { this.putVarInt(this.dayCycleStopTime); this.putVarInt(this.eduEditionOffer); this.putBoolean(this.hasEduFeaturesEnabled); - this.putString(""); // UnknownString0 + this.putString(""); // Education Edition Product ID this.putLFloat(this.rainLevel); this.putLFloat(this.lightningLevel); this.putBoolean(this.hasConfirmedPlatformLockedContent); @@ -154,6 +118,8 @@ public void encode() { this.putBoolean(this.commandsEnabled); this.putBoolean(this.isTexturePacksRequired); this.putGameRules(this.gameRules); + this.putLInt(0); // Experiment count + this.putBoolean(false); // Were experiments previously toggled this.putBoolean(this.bonusChest); this.putBoolean(this.hasStartWithMapEnabled); this.putVarInt(this.permissionLevel); @@ -166,26 +132,23 @@ public void encode() { this.putBoolean(this.isWorldTemplateOptionLocked); this.putBoolean(this.isOnlySpawningV1Villagers); this.putString(this.vanillaVersion); - this.putLInt(0); // UnknownInt0 - this.putLInt(0); // UnknownInt1 - this.putBoolean(false); - this.putBoolean(false); + this.putLInt(16); // Limited world width + this.putLInt(16); // Limited world height + this.putBoolean(false); // Nether type + this.putBoolean(false); // Experimental Gameplay this.putString(this.levelId); this.putString(this.worldName); this.putString(this.premiumWorldTemplateId); this.putBoolean(this.isTrial); - this.putBoolean(this.isMovementServerAuthoritative); + this.putUnsignedVarInt(this.isMovementServerAuthoritative ? 1 : 0); // 2 - rewind + this.putVarInt(0); // RewindHistorySize + this.putBoolean(false); // isServerAuthoritativeBlockBreaking this.putLLong(this.currentTick); this.putVarInt(this.enchantmentSeed); - this.put(GlobalBlockPalette.BLOCK_PALETTE); - this.put(ITEM_DATA_PALETTE); + this.putUnsignedVarInt(0); // Custom blocks + this.put(RuntimeItems.getRuntimeMapping().getItemDataPalette()); this.putString(this.multiplayerCorrelationId); this.putBoolean(this.isInventoryServerAuthoritative); } - - private static class ItemData { - private String name; - private int id; - } } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java index 07668b6f96a..88ed705b909 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java @@ -13,6 +13,7 @@ public class UpdateAttributesPacket extends DataPacket { public Attribute[] entries; public long entityId; + public long frame; @Override public byte pid() { @@ -40,6 +41,7 @@ public void encode() { this.putString(entry.getName()); } } + this.putUnsignedVarInt(this.frame); } } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java index ecac51e3fa2..b93bd97d6d0 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java @@ -35,8 +35,8 @@ public void encode() { this.putByte(windowType); this.putVarInt(unknownVarInt1); this.putVarInt(tradeTier); - this.putEntityUniqueId(player); this.putEntityUniqueId(trader); + this.putEntityUniqueId(player); this.putString(displayName); this.putBoolean(screen2); this.putBoolean(isWilling); diff --git a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java index b9bb4b7ee03..49943e718e4 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java @@ -5,6 +5,7 @@ import cn.nukkit.inventory.BeaconInventory; import cn.nukkit.inventory.EnchantInventory; import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.PlayerUIComponent; import cn.nukkit.inventory.transaction.action.*; import cn.nukkit.item.Item; import cn.nukkit.network.protocol.InventoryTransactionPacket; @@ -95,6 +96,11 @@ public NetworkInventoryAction read(InventoryTransactionPacket packet) { case SOURCE_TYPE_ENCHANT_MATERIAL: packet.isEnchantingPart = true; break; + case SOURCE_TYPE_ANVIL_INPUT: + case SOURCE_TYPE_ANVIL_MATERIAL: + case SOURCE_TYPE_ANVIL_RESULT: + packet.isRepairItemPart = true; + break; } break; } @@ -102,10 +108,6 @@ public NetworkInventoryAction read(InventoryTransactionPacket packet) { this.inventorySlot = (int) packet.getUnsignedVarInt(); this.oldItem = packet.getSlot(); this.newItem = packet.getSlot(); - - if (packet.hasNetworkIds) { - this.stackNetworkId = packet.getVarInt(); - } return this; } @@ -128,10 +130,6 @@ public void write(InventoryTransactionPacket packet) { packet.putUnsignedVarInt(this.inventorySlot); packet.putSlot(this.oldItem); packet.putSlot(this.newItem); - - if (packet.hasNetworkIds) { - packet.putVarInt(this.stackNetworkId); - } } public InventoryAction createInventoryAction(Player player) { @@ -144,21 +142,46 @@ public InventoryAction createInventoryAction(Player player) { } // ID 124 with slot 14/15 is enchant inventory if (this.windowId == ContainerIds.UI) { - if (this.inventorySlot == EnchantInventory.ENCHANT_INPUT_ITEM_UI_SLOT) { - if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); - return null; - } - this.windowId = Player.ENCHANT_WINDOW_ID; - this.inventorySlot = 0; - // TODO, check if unenchanted item and send EnchantOptionsPacket - } else if (this.inventorySlot == EnchantInventory.ENCHANT_REAGENT_UI_SLOT) { - if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); - return null; - } - this.windowId = Player.ENCHANT_WINDOW_ID; - this.inventorySlot = 1; + switch (this.inventorySlot) { + case PlayerUIComponent.CREATED_ITEM_OUTPUT_UI_SLOT: + if (player.getWindowById(Player.ANVIL_WINDOW_ID) != null) { + this.windowId = Player.ANVIL_WINDOW_ID; + this.inventorySlot = 2; + } + break; + case EnchantInventory.ENCHANT_INPUT_ITEM_UI_SLOT: + if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); + return null; + } + this.windowId = Player.ENCHANT_WINDOW_ID; + this.inventorySlot = 0; + // TODO, check if unenchanted item and send EnchantOptionsPacket + break; + case EnchantInventory.ENCHANT_REAGENT_UI_SLOT: + if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); + return null; + } + this.windowId = Player.ENCHANT_WINDOW_ID; + this.inventorySlot = 1; + break; + case AnvilInventory.ANVIL_INPUT_UI_SLOT: + if (player.getWindowById(Player.ANVIL_WINDOW_ID) == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have anvil window open"); + return null; + } + this.windowId = Player.ANVIL_WINDOW_ID; + this.inventorySlot = 0; + break; + case AnvilInventory.ANVIL_MATERIAL_UI_SLOT: + if (player.getWindowById(Player.ANVIL_WINDOW_ID) == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have anvil window open"); + return null; + } + this.windowId = Player.ANVIL_WINDOW_ID; + this.inventorySlot = 1; + break; } } @@ -223,28 +246,12 @@ public InventoryAction createInventoryAction(Player player) { switch (this.windowId) { case SOURCE_TYPE_ANVIL_INPUT: - //System.out.println("action input"); - this.inventorySlot = 0; - return new SlotChangeAction(anvil, this.inventorySlot, this.oldItem, this.newItem); case SOURCE_TYPE_ANVIL_MATERIAL: - //System.out.println("material"); - this.inventorySlot = 1; - return new SlotChangeAction(anvil, this.inventorySlot, this.oldItem, this.newItem); - case SOURCE_TYPE_ANVIL_OUTPUT: - //System.out.println("action output"); - break; case SOURCE_TYPE_ANVIL_RESULT: - this.inventorySlot = 2; - anvil.clear(0); - Item material = anvil.getItem(1); - if (!material.isNull()) { - material.setCount(material.getCount() - 1); - anvil.setItem(1, material); - } - anvil.setItem(2, this.oldItem); - //System.out.println("action result"); - return new SlotChangeAction(anvil, this.inventorySlot, this.oldItem, this.newItem); + return new RepairItemAction(this.oldItem, this.newItem, this.windowId); } + + return new SlotChangeAction(anvil, this.inventorySlot, this.oldItem, this.newItem); } if (this.windowId >= SOURCE_TYPE_ENCHANT_OUTPUT && this.windowId <= SOURCE_TYPE_ENCHANT_INPUT) { diff --git a/src/main/java/cn/nukkit/plugin/PluginManager.java b/src/main/java/cn/nukkit/plugin/PluginManager.java index c43f28ba2cf..81e3c6ee514 100644 --- a/src/main/java/cn/nukkit/plugin/PluginManager.java +++ b/src/main/java/cn/nukkit/plugin/PluginManager.java @@ -177,7 +177,7 @@ public Map loadPlugins(File dictionary, List newLoaders, try { //Check the format: majorVersion.minorVersion.patch - if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", version)) { + if (!Pattern.matches("^[0-9]+(\\.[0-9]+){0,2}$", version)) { throw new IllegalArgumentException(); } } catch (NullPointerException | IllegalArgumentException e) { @@ -244,7 +244,7 @@ public Map loadPlugins(File dictionary, List newLoaders, dependencies.get(name).remove(dependency); } else if (!plugins.containsKey(dependency)) { this.server.getLogger().critical(this.server.getLanguage().translateString("nukkit" + - ".plugin.loadError", new String[]{name, "%nukkit.plugin.unknownDependency"})); + ".plugin.loadError", name, "%nukkit.plugin.unknownDependency", dependency)); break; } } diff --git a/src/main/java/cn/nukkit/potion/Effect.java b/src/main/java/cn/nukkit/potion/Effect.java index e7c6a1a1e69..3412ba82234 100644 --- a/src/main/java/cn/nukkit/potion/Effect.java +++ b/src/main/java/cn/nukkit/potion/Effect.java @@ -22,12 +22,16 @@ public class Effect implements Cloneable { public static final int MINING_FATIGUE = 4; public static final int STRENGTH = 5; public static final int HEALING = 6; + public static final int INSTANT_HEALTH = 6; public static final int HARMING = 7; + public static final int INSTANT_DAMAGE = 7; public static final int JUMP = 8; + public static final int JUMP_BOOST = 8; public static final int NAUSEA = 9; public static final int CONFUSION = 9; public static final int REGENERATION = 10; public static final int DAMAGE_RESISTANCE = 11; + public static final int RESISTANCE = 11; public static final int FIRE_RESISTANCE = 12; public static final int WATER_BREATHING = 13; public static final int INVISIBILITY = 14; @@ -42,8 +46,10 @@ public class Effect implements Cloneable { public static final int SATURATION = 23; public static final int LEVITATION = 24; public static final int FATAL_POISON = 25; - public static final int COUNDIT_POWER = 26; + public static final int CONDUIT_POWER = 26; public static final int SLOW_FALLING = 27; + public static final int BAD_OMEN = 28; + public static final int VILLAGE_HERO = 29; protected static Effect[] effects; @@ -76,10 +82,12 @@ public static void init() { effects[Effect.ABSORPTION] = new Effect(Effect.ABSORPTION, "%potion.absorption", 36, 107, 251); effects[Effect.SATURATION] = new Effect(Effect.SATURATION, "%potion.saturation", 255, 0, 255); - effects[Effect.LEVITATION] = new Effect(Effect.LEVITATION, "%potion.levitation", 206, 255, 255); + effects[Effect.LEVITATION] = new Effect(Effect.LEVITATION, "%potion.levitation", 206, 255, 255, true); effects[Effect.FATAL_POISON] = new Effect(Effect.FATAL_POISON, "%potion.poison", 78, 147, 49, true); - effects[Effect.COUNDIT_POWER] = new Effect(Effect.COUNDIT_POWER, "%potion.conduitPower", 29, 194, 209); + effects[Effect.CONDUIT_POWER] = new Effect(Effect.CONDUIT_POWER, "%potion.conduitPower", 29, 194, 209); effects[Effect.SLOW_FALLING] = new Effect(Effect.SLOW_FALLING, "%potion.slowFalling", 206, 255, 255); + effects[Effect.BAD_OMEN] = new Effect(Effect.BAD_OMEN, "%effect.badOmen", 11, 97, 56, true); + effects[Effect.VILLAGE_HERO] = new Effect(Effect.VILLAGE_HERO, "%effect.villageHero", 68, 255, 68); } public static Effect getEffect(int id) { @@ -145,7 +153,7 @@ public int getDuration() { } public boolean isVisible() { - return show; + return show && this.id != VILLAGE_HERO; } public Effect setVisible(boolean visible) { @@ -179,6 +187,7 @@ public boolean canTick() { int interval; switch (this.id) { case Effect.POISON: //POISON + case Effect.FATAL_POISON: if ((interval = (25 >> this.amplifier)) > 0) { return (this.duration % interval) == 0; } @@ -200,7 +209,8 @@ public boolean canTick() { public void applyEffect(Entity entity) { switch (this.id) { case Effect.POISON: //POISON - if (entity.getHealth() > 1) { + case Effect.FATAL_POISON: + if (entity.getHealth() > 1 || this.id == FATAL_POISON) { entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, 1)); } break; diff --git a/src/main/java/cn/nukkit/potion/Potion.java b/src/main/java/cn/nukkit/potion/Potion.java index 9a8f6c21959..47dd48d9a21 100644 --- a/src/main/java/cn/nukkit/potion/Potion.java +++ b/src/main/java/cn/nukkit/potion/Potion.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityLiving; +import cn.nukkit.entity.EntitySmite; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.EntityRegainHealthEvent; @@ -201,11 +202,19 @@ public void applyPotion(Entity entity, double health) { switch (this.getId()) { case INSTANT_HEALTH: case INSTANT_HEALTH_II: - entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); + if (entity instanceof EntitySmite) { + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (double) (6 << (applyEffect.getAmplifier() + 1))))); + } else { + entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); + } break; case HARMING: case HARMING_II: - entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (double) (6 << (applyEffect.getAmplifier() + 1))))); + if (entity instanceof EntitySmite) { + entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); + } else { + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (double) (6 << (applyEffect.getAmplifier() + 1))))); + } break; default: int duration = (int) ((isSplash() ? health : 1) * (double) applyEffect.getDuration() + 0.5); diff --git a/src/main/java/cn/nukkit/raknet/RakNet.java b/src/main/java/cn/nukkit/raknet/RakNet.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/cn/nukkit/raknet/protocol/Packet.java b/src/main/java/cn/nukkit/raknet/protocol/Packet.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/cn/nukkit/raknet/server/SessionManager.java b/src/main/java/cn/nukkit/raknet/server/SessionManager.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java b/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java index bb1a26a24b6..150f3043251 100644 --- a/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java +++ b/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java @@ -48,11 +48,16 @@ private void perform(long tick) { lastTick = tick; Set updates = pendingUpdates = queuedUpdates.remove(tick); if (updates != null) { - for (BlockUpdateEntry entry : updates) { + Iterator updateIterator = updates.iterator(); + + while (updateIterator.hasNext()) { + BlockUpdateEntry entry = updateIterator.next(); + Vector3 pos = entry.pos; if (level.isChunkLoaded(NukkitMath.floorDouble(pos.x) >> 4, NukkitMath.floorDouble(pos.z) >> 4)) { Block block = level.getBlock(entry.pos); + updateIterator.remove(); if (Block.equals(block, entry.block, false)) { block.onUpdate(Level.BLOCK_UPDATE_SCHEDULED); } diff --git a/src/main/java/cn/nukkit/utils/Binary.java b/src/main/java/cn/nukkit/utils/Binary.java index 93bf08a6e4f..2d5bbef300a 100644 --- a/src/main/java/cn/nukkit/utils/Binary.java +++ b/src/main/java/cn/nukkit/utils/Binary.java @@ -472,11 +472,14 @@ public static byte[] appendBytes(byte[][] bytes) { for (byte[] b : bytes) { length += b.length; } - ByteBuffer buffer = ByteBuffer.allocate(length); + + byte[] appendedBytes = new byte[length]; + int index = 0; for (byte[] b : bytes) { - buffer.put(b); + System.arraycopy(b, 0, appendedBytes, index, b.length); + index += b.length; } - return buffer.array(); + return appendedBytes; } public static byte[] appendBytes(byte byte1, byte[]... bytes2) { @@ -497,12 +500,16 @@ public static byte[] appendBytes(byte[] bytes1, byte[]... bytes2) { for (byte[] bytes : bytes2) { length += bytes.length; } - ByteBuffer buffer = ByteBuffer.allocate(length); - buffer.put(bytes1); - for (byte[] bytes : bytes2) { - buffer.put(bytes); + + byte[] appendedBytes = new byte[length]; + System.arraycopy(bytes1, 0, appendedBytes, 0, bytes1.length); + int index = bytes1.length; + + for (byte[] b : bytes2) { + System.arraycopy(b, 0, appendedBytes, index, b.length); + index += b.length; } - return buffer.array(); + return appendedBytes; } diff --git a/src/main/java/cn/nukkit/utils/BinaryStream.java b/src/main/java/cn/nukkit/utils/BinaryStream.java index 90f3efbdb52..d118b4a629d 100644 --- a/src/main/java/cn/nukkit/utils/BinaryStream.java +++ b/src/main/java/cn/nukkit/utils/BinaryStream.java @@ -1,11 +1,12 @@ package cn.nukkit.utils; +import cn.nukkit.block.Block; import cn.nukkit.entity.Attribute; import cn.nukkit.entity.data.Skin; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDurable; +import cn.nukkit.item.*; import cn.nukkit.level.GameRule; import cn.nukkit.level.GameRules; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockVector3; import cn.nukkit.math.Vector3f; @@ -13,8 +14,12 @@ import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.network.LittleEndianByteBufInputStream; +import cn.nukkit.network.LittleEndianByteBufOutputStream; import cn.nukkit.network.protocol.types.EntityLink; -import it.unimi.dsi.fastutil.io.FastByteArrayInputStream; +import io.netty.buffer.AbstractByteBufAllocator; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import java.io.IOException; import java.lang.reflect.Array; @@ -260,6 +265,7 @@ public UUID getUUID() { public void putSkin(Skin skin) { this.putString(skin.getSkinId()); + this.putString(skin.getPlayFabId()); this.putString(skin.getSkinResourcePatch()); this.putImage(skin.getSkinData()); @@ -269,6 +275,7 @@ public void putSkin(Skin skin) { this.putImage(animation.image); this.putLInt(animation.type); this.putLFloat(animation.frames); + this.putLInt(animation.expression); } this.putImage(skin.getCapeData()); @@ -306,6 +313,7 @@ public void putSkin(Skin skin) { public Skin getSkin() { Skin skin = new Skin(); skin.setSkinId(this.getString()); + skin.setPlayFabId(this.getString()); skin.setSkinResourcePatch(this.getString()); skin.setSkinData(this.getImage()); @@ -314,7 +322,8 @@ public Skin getSkin() { SerializedImage image = this.getImage(); int type = this.getLInt(); float frames = this.getLFloat(); - skin.getAnimations().add(new SkinAnimation(image, type, frames)); + int expression = this.getLInt(); + skin.getAnimations().add(new SkinAnimation(image, type, frames, expression)); } skin.setCapeData(this.getImage()); @@ -365,162 +374,212 @@ public SerializedImage getImage() { } public Item getSlot() { - int id = this.getVarInt(); - + int id = getVarInt(); if (id == 0) { return Item.get(0, 0, 0); } - int auxValue = this.getVarInt(); - int data = auxValue >> 8; - if (data == Short.MAX_VALUE) { - data = -1; + + int count = getLShort(); + int damage = (int) getUnsignedVarInt(); + + int fullId = RuntimeItems.getRuntimeMapping().getLegacyFullId(id); + id = RuntimeItems.getId(fullId); + + /*boolean hasData = RuntimeItems.hasData(fullId); // Unnecessary when the damage is read from NBT + if (hasData) { + damage = RuntimeItems.getData(fullId); + }*/ + + if (getBoolean()) { // hasNetId + getVarInt(); // netId } - int cnt = auxValue & 0xff; - int nbtLen = this.getLShort(); + getVarInt(); // blockRuntimeId + + byte[] bytes = getByteArray(); + ByteBuf buf = AbstractByteBufAllocator.DEFAULT.ioBuffer(bytes.length); + buf.writeBytes(bytes); + byte[] nbt = new byte[0]; - if (nbtLen < Short.MAX_VALUE) { - nbt = this.get(nbtLen); - } else if (nbtLen == 65535) { - int nbtTagCount = (int) getUnsignedVarInt(); - int offset = getOffset(); - FastByteArrayInputStream stream = new FastByteArrayInputStream(get()); - for (int i = 0; i < nbtTagCount; i++) { - try { - // TODO: 05/02/2019 This hack is necessary because we keep the raw NBT tag. Try to remove it. - CompoundTag tag = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN, true); - // tool damage hack - if (tag.contains("Damage")) { - data = tag.getInt("Damage"); - tag.remove("Damage"); - } - if (tag.contains("__DamageConflict__")) { - tag.put("Damage", tag.removeAndGet("__DamageConflict__")); - } - if (tag.getAllTags().size() > 0) { - nbt = NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN, false); - } - } catch (IOException e) { - throw new RuntimeException(e); + String[] canPlace; + String[] canBreak; + + try (LittleEndianByteBufInputStream stream = new LittleEndianByteBufInputStream(buf)) { + int nbtSize = stream.readShort(); + + CompoundTag compoundTag = null; + if (nbtSize > 0) { + compoundTag = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN); + } else if (nbtSize == -1) { + int tagCount = stream.readUnsignedByte(); + if (tagCount != 1) throw new IllegalArgumentException("Expected 1 tag but got " + tagCount); + compoundTag = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN); + } + + if (compoundTag != null && compoundTag.getAllTags().size() > 0) { + if (compoundTag.contains("Damage")) { + damage = compoundTag.getInt("Damage"); + compoundTag.remove("Damage"); + } + if (compoundTag.contains("__DamageConflict__")) { + compoundTag.put("Damage", compoundTag.removeAndGet("__DamageConflict__")); + } + if (!compoundTag.isEmpty()) { + nbt = NBTIO.write(compoundTag, ByteOrder.LITTLE_ENDIAN); } } - setOffset(offset + (int) stream.position()); - } - String[] canPlaceOn = new String[this.getVarInt()]; - for (int i = 0; i < canPlaceOn.length; ++i) { - canPlaceOn[i] = this.getString(); - } + canPlace = new String[stream.readInt()]; + for (int i = 0; i < canPlace.length; i++) { + canPlace[i] = stream.readUTF(); + } + + canBreak = new String[stream.readInt()]; + for (int i = 0; i < canBreak.length; i++) { + canBreak[i] = stream.readUTF(); + } - String[] canDestroy = new String[this.getVarInt()]; - for (int i = 0; i < canDestroy.length; ++i) { - canDestroy[i] = this.getString(); + if (id == ItemID.SHIELD) { + stream.readLong(); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to read item user data", e); + } finally { + buf.release(); } - Item item = Item.get( - id, data, cnt, nbt - ); + Item item = Item.get(id, damage, count, nbt); - if (canDestroy.length > 0 || canPlaceOn.length > 0) { + if (canBreak.length > 0 || canPlace.length > 0) { CompoundTag namedTag = item.getNamedTag(); if (namedTag == null) { namedTag = new CompoundTag(); } - if (canDestroy.length > 0) { + if (canBreak.length > 0) { ListTag listTag = new ListTag<>("CanDestroy"); - for (String blockName : canDestroy) { + for (String blockName : canBreak) { listTag.add(new StringTag("", blockName)); } namedTag.put("CanDestroy", listTag); } - if (canPlaceOn.length > 0) { + if (canPlace.length > 0) { ListTag listTag = new ListTag<>("CanPlaceOn"); - for (String blockName : canPlaceOn) { + for (String blockName : canPlace) { listTag.add(new StringTag("", blockName)); } namedTag.put("CanPlaceOn", listTag); } - item.setNamedTag(namedTag); - } - if (item.getId() == 513) { // TODO: Shields - this.getVarLong(); + item.setNamedTag(namedTag); } return item; } public void putSlot(Item item) { + this.putSlot(item, false); + } + + public void putSlot(Item item, boolean instanceItem) { if (item == null || item.getId() == 0) { - this.putVarInt(0); + putByte((byte) 0); return; } - boolean isDurable = item instanceof ItemDurable; + int networkFullId = RuntimeItems.getRuntimeMapping().getNetworkFullId(item); + int networkId = RuntimeItems.getNetworkId(networkFullId); + + putVarInt(networkId); + putLShort(item.getCount()); - this.putVarInt(item.getId()); + boolean useLegacyData = false; + if (item.getId() > 256) { // Not a block + if (item instanceof ItemDurable || !RuntimeItems.hasData(networkFullId)) { + useLegacyData = true; + } + } + putUnsignedVarInt(useLegacyData ? item.getDamage() : 0); - int auxValue = item.getCount(); - if (!isDurable) { - auxValue |= (((item.hasMeta() ? item.getDamage() : -1) & 0x7fff) << 8); + if (!instanceItem) { + putBoolean(true); + putVarInt(0); //TODO } - this.putVarInt(auxValue); - if (item.hasCompoundTag() || isDurable) { - try { - // hack for tool damage + Block block = item.getBlockUnsafe(); + int runtimeId = block == null ? 0 : GlobalBlockPalette.getOrCreateRuntimeId(block.getId(), block.getDamage()); + putVarInt(runtimeId); + + ByteBuf userDataBuf = ByteBufAllocator.DEFAULT.ioBuffer(); + try (LittleEndianByteBufOutputStream stream = new LittleEndianByteBufOutputStream(userDataBuf)) { + if (item.getDamage() != 0) { byte[] nbt = item.getCompoundTag(); CompoundTag tag; if (nbt == null || nbt.length == 0) { tag = new CompoundTag(); } else { - tag = NBTIO.read(nbt, ByteOrder.LITTLE_ENDIAN, false); + tag = NBTIO.read(nbt, ByteOrder.LITTLE_ENDIAN); } if (tag.contains("Damage")) { tag.put("__DamageConflict__", tag.removeAndGet("Damage")); } - if (isDurable) { - tag.putInt("Damage", item.getDamage()); - } + tag.putInt("Damage", item.getDamage()); + stream.writeShort(-1); + stream.writeByte(1); // Hardcoded in current version + stream.write(NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN)); + } else if (item.hasCompoundTag()) { + stream.writeShort(-1); + stream.writeByte(1); // Hardcoded in current version + stream.write(item.getCompoundTag()); + } else { + userDataBuf.writeShortLE(0); + } - this.putLShort(0xffff); - this.putByte((byte) 1); - this.put(NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN, true)); - } catch (IOException e) { - throw new RuntimeException(e); + List canPlaceOn = extractStringList(item, "CanPlaceOn"); + stream.writeInt(canPlaceOn.size()); + for (String string : canPlaceOn) { + stream.writeUTF(string); } - } else { - this.putLShort(0); - } - List canPlaceOn = extractStringList(item, "CanPlaceOn"); - List canDestroy = extractStringList(item, "CanDestroy"); - this.putVarInt(canPlaceOn.size()); - for (String block : canPlaceOn) { - this.putString(block); - } - this.putVarInt(canDestroy.size()); - for (String block : canDestroy) { - this.putString(block); - } - if (item.getId() == 513) { // TODO: Shields - this.putVarLong(0); + List canDestroy = extractStringList(item, "CanDestroy"); + stream.writeInt(canDestroy.size()); + for (String string : canDestroy) { + stream.writeUTF(string); + } + + if (item.getId() == ItemID.SHIELD) { + stream.writeLong(0); + } + + byte[] bytes = new byte[userDataBuf.readableBytes()]; + userDataBuf.readBytes(bytes); + putByteArray(bytes); + } catch (IOException e) { + throw new IllegalStateException("Unable to write item user data", e); + } finally { + userDataBuf.release(); } } public Item getRecipeIngredient() { - int id = this.getVarInt(); - - if (id == 0) { + int networkId = this.getVarInt(); + if (networkId == 0) { return Item.get(0, 0, 0); } + int legacyFullId = RuntimeItems.getRuntimeMapping().getLegacyFullId(networkId); + int id = RuntimeItems.getId(legacyFullId); + boolean hasData = RuntimeItems.hasData(legacyFullId); + int damage = this.getVarInt(); - if (damage == 0x7fff) damage = -1; - int count = this.getVarInt(); + if (hasData) { + damage = RuntimeItems.getData(legacyFullId); + } else if (damage == 0x7fff) { + damage = -1; + } + int count = this.getVarInt(); return Item.get(id, damage, count); } @@ -529,13 +588,15 @@ public void putRecipeIngredient(Item ingredient) { this.putVarInt(0); return; } - this.putVarInt(ingredient.getId()); - int damage; - if (ingredient.hasMeta()) { - damage = ingredient.getDamage(); - } else { - damage = 0x7fff; + + int networkFullId = RuntimeItems.getRuntimeMapping().getNetworkFullId(ingredient); + int networkId = RuntimeItems.getNetworkId(networkFullId); + int damage = ingredient.hasMeta() ? ingredient.getDamage() : 0x7fff; + if (RuntimeItems.hasData(networkFullId)) { + damage = 0; } + + this.putVarInt(networkId); this.putVarInt(damage); this.putVarInt(ingredient.getCount()); } diff --git a/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java b/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java index 6ecbd7ecce1..8c9cf530e67 100644 --- a/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java +++ b/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java @@ -2,19 +2,25 @@ import cn.nukkit.Server; import com.google.common.base.Preconditions; -import lombok.RequiredArgsConstructor; import java.io.*; import java.util.Optional; import java.util.UUID; -@RequiredArgsConstructor public class DefaultPlayerDataSerializer implements PlayerDataSerializer { - private final Server server; + private String dataPath; + + public DefaultPlayerDataSerializer(Server server) { + this(server.getDataPath()); + } + + public DefaultPlayerDataSerializer(String dataPath) { + this.dataPath = dataPath; + } @Override public Optional read(String name, UUID uuid) throws IOException { - String path = server.getDataPath() + "players/" + name + ".dat"; + String path = dataPath + "players/" + name + ".dat"; File file = new File(path); if (!file.exists()) { return Optional.empty(); @@ -26,7 +32,7 @@ public Optional read(String name, UUID uuid) throws IOException { @Override public OutputStream write(String name, UUID uuid) throws IOException { Preconditions.checkNotNull(name, "name"); - String path = server.getDataPath() + "players/" + name + ".dat"; + String path = dataPath + "players/" + name + ".dat"; File file = new File(path); return new FileOutputStream(file); } diff --git a/src/main/java/cn/nukkit/utils/SkinAnimation.java b/src/main/java/cn/nukkit/utils/SkinAnimation.java index 99db3288b38..236261d21ae 100644 --- a/src/main/java/cn/nukkit/utils/SkinAnimation.java +++ b/src/main/java/cn/nukkit/utils/SkinAnimation.java @@ -7,10 +7,12 @@ public class SkinAnimation { public final SerializedImage image; public final int type; public final float frames; + public final int expression; - public SkinAnimation(SerializedImage image, int type, float frames) { + public SkinAnimation(SerializedImage image, int type, float frames, int expression) { this.image = image; this.type = type; this.frames = frames; + this.expression = expression; } } diff --git a/src/main/java/cn/nukkit/utils/TerracottaColor.java b/src/main/java/cn/nukkit/utils/TerracottaColor.java index 55670d3c089..03800a599cd 100644 --- a/src/main/java/cn/nukkit/utils/TerracottaColor.java +++ b/src/main/java/cn/nukkit/utils/TerracottaColor.java @@ -76,6 +76,8 @@ public static TerracottaColor getByDyeData(int dyeColorMeta) { return BY_DYE_DATA[dyeColorMeta & 0x0f]; } - public static TerracottaColor getByTerracottaData(int terracottaColorMeta) { return BY_TERRACOTA_DATA[terracottaColorMeta & 0x0f]; } + public static TerracottaColor getByTerracottaData(int terracottaColorMeta) { + return BY_TERRACOTA_DATA[terracottaColorMeta & 0x0f]; + } } diff --git a/src/main/resources/biome_definitions.dat b/src/main/resources/biome_definitions.dat index 6d72cc92491..68705c5d4f9 100644 Binary files a/src/main/resources/biome_definitions.dat and b/src/main/resources/biome_definitions.dat differ diff --git a/src/main/resources/creativeitems.json b/src/main/resources/creativeitems.json index c427b77bc1a..66333799077 100644 --- a/src/main/resources/creativeitems.json +++ b/src/main/resources/creativeitems.json @@ -1411,8 +1411,7 @@ "damage": 5 }, { - "id": -218, - "damage": 3 + "id": -218 }, { "id": 295 @@ -2068,6 +2067,10 @@ "id": 383, "damage": 126 }, + { + "id": 383, + "damage": 127 + }, { "id": 383, "damage": 41 @@ -2116,10 +2119,6 @@ "id": 383, "damage": 59 }, - { - "id": 383, - "damage": 260 - }, { "id": 49 }, @@ -3250,8 +3249,7 @@ "id": -202 }, { - "id": -219, - "damage": 3 + "id": -219 }, { "id": 720 diff --git a/src/main/resources/entity_identifiers.dat b/src/main/resources/entity_identifiers.dat index c9477ba85dd..24e14e8ffcc 100644 Binary files a/src/main/resources/entity_identifiers.dat and b/src/main/resources/entity_identifiers.dat differ diff --git a/src/main/resources/lang b/src/main/resources/lang index aa22d15587b..e39cdad344e 160000 --- a/src/main/resources/lang +++ b/src/main/resources/lang @@ -1 +1 @@ -Subproject commit aa22d15587b11647d2466ec3739947387ff98296 +Subproject commit e39cdad344ee2919e619ef352236489f32fb3cde diff --git a/src/main/resources/recipes.json b/src/main/resources/recipes.json index b02a4320036..1761f87b200 100644 --- a/src/main/resources/recipes.json +++ b/src/main/resources/recipes.json @@ -1384,8 +1384,7 @@ "type": 0, "input": [ { - "id": 155, - "damage": -1 + "id": 155 } ], "output": [ @@ -11612,11 +11611,11 @@ "type": 1, "input": { "A": { - "id": -264, + "id": 280, "damage": -1 }, "B": { - "id": 280, + "id": -264, "damage": -1 } }, @@ -11638,11 +11637,11 @@ "type": 1, "input": { "A": { - "id": -265, + "id": 280, "damage": -1 }, "B": { - "id": 280, + "id": -265, "damage": -1 } }, @@ -11726,8 +11725,7 @@ }, "output" : [ { - "id": -219, - "damage": 3 + "id": -219 } ], "shape": [ @@ -11753,8 +11751,7 @@ }, "output": [ { - "id": -219, - "damage": 3 + "id": -219 } ], "shape": [ @@ -11780,8 +11777,7 @@ }, "output": [ { - "id": -219, - "damage": 3 + "id": -219 } ], "shape": [ @@ -45528,8 +45524,7 @@ { "type" : 3, "input" : { - "id" : 155, - "damage" : -1 + "id" : 155 }, "output" : { "id" : 155, diff --git a/src/main/resources/runtime_block_states.dat b/src/main/resources/runtime_block_states.dat index cb7852bec9a..5d14c13ad53 100644 Binary files a/src/main/resources/runtime_block_states.dat and b/src/main/resources/runtime_block_states.dat differ diff --git a/src/main/resources/runtime_item_ids.json b/src/main/resources/runtime_item_ids.json index 82d33640624..ccacb0ffa37 100644 --- a/src/main/resources/runtime_item_ids.json +++ b/src/main/resources/runtime_item_ids.json @@ -1,3230 +1 @@ -[ - { - "name": "minecraft:purpur_block", - "id": 201 - }, - { - "name": "minecraft:bow", - "id": 261 - }, - { - "name": "minecraft:end_bricks", - "id": 206 - }, - { - "name": "minecraft:air", - "id": -158 - }, - { - "name": "minecraft:element_94", - "id": -105 - }, - { - "name": "minecraft:rabbit", - "id": 411 - }, - { - "name": "minecraft:element_25", - "id": -36 - }, - { - "name": "minecraft:mushroom_stew", - "id": 282 - }, - { - "name": "minecraft:polished_blackstone_brick_slab", - "id": -284 - }, - { - "name": "minecraft:cooked_porkchop", - "id": 320 - }, - { - "name": "minecraft:record_ward", - "id": 509 - }, - { - "name": "minecraft:appleenchanted", - "id": 466 - }, - { - "name": "minecraft:pumpkin", - "id": 86 - }, - { - "name": "minecraft:slime", - "id": 165 - }, - { - "name": "minecraft:apple", - "id": 260 - }, - { - "name": "minecraft:element_50", - "id": -61 - }, - { - "name": "minecraft:stripped_oak_log", - "id": -10 - }, - { - "name": "minecraft:golden_apple", - "id": 322 - }, - { - "name": "minecraft:fish", - "id": 349 - }, - { - "name": "minecraft:item.dark_oak_door", - "id": 197 - }, - { - "name": "minecraft:light_block", - "id": -215 - }, - { - "name": "minecraft:yellow_glazed_terracotta", - "id": 224 - }, - { - "name": "minecraft:stone_brick_stairs", - "id": 109 - }, - { - "name": "minecraft:portal", - "id": 90 - }, - { - "name": "minecraft:gold_ingot", - "id": 266 - }, - { - "name": "minecraft:iron_ingot", - "id": 265 - }, - { - "name": "minecraft:cookie", - "id": 357 - }, - { - "name": "minecraft:porkchop", - "id": 319 - }, - { - "name": "minecraft:bread", - "id": 297 - }, - { - "name": "minecraft:element_7", - "id": -18 - }, - { - "name": "minecraft:diamond_block", - "id": 57 - }, - { - "name": "minecraft:iron_pickaxe", - "id": 257 - }, - { - "name": "minecraft:element_27", - "id": -38 - }, - { - "name": "minecraft:beef", - "id": 363 - }, - { - "name" : "minecraft:salmon", - "id" : 460 - }, - { - "name": "minecraft:melon", - "id": 360 - }, - { - "name": "minecraft:clownfish", - "id": 461 - }, - { - "name": "minecraft:element_16", - "id": -27 - }, - { - "name": "minecraft:tripwire", - "id": 132 - }, - { - "name": "minecraft:stone_axe", - "id": 275 - }, - { - "name": "minecraft:stained_glass_pane", - "id": 160 - }, - { - "name": "minecraft:trapped_chest", - "id": 146 - }, - { - "name": "minecraft:pufferfish", - "id": 462 - }, - { - "name": "minecraft:bucket", - "id": 325 - }, - { - "name": "minecraft:ancient_debris", - "id": -271 - }, - { - "name": "minecraft:anvil", - "id": 145 - }, - { - "name": "minecraft:stick", - "id": 280 - }, - { - "name": "minecraft:cooked_fish", - "id": 350 - }, - { - "name": "minecraft:cooked_salmon", - "id": 463 - }, - { - "name": "minecraft:element_61", - "id": -72 - }, - { - "name": "minecraft:sparkler", - "id": 442 - }, - { - "name": "minecraft:warped_door", - "id": 756 - }, - { - "name" : "minecraft:dried_kelp", - "id" : 464 - }, - { - "name": "minecraft:hay_block", - "id": 170 - }, - { - "name": "minecraft:wooden_shovel", - "id": 269 - }, - { - "name": "minecraft:nautilus_shell", - "id": 465 - }, - { - "name": "minecraft:element_1", - "id": -12 - }, - { - "name": "minecraft:stonecutter_block", - "id": -197 - }, - { - "name": "minecraft:cooked_beef", - "id": 364 - }, - { - "name": "minecraft:comparator", - "id": 404 - }, - { - "name": "minecraft:carrot", - "id": 391 - }, - { - "name": "minecraft:command_block", - "id": 137 - }, - { - "name": "minecraft:chicken", - "id": 365 - }, - { - "name": "minecraft:potion", - "id": 373 - }, - { - "name": "minecraft:rotten_flesh", - "id": 367 - }, - { - "name": "minecraft:dirt", - "id": 3 - }, - { - "name": "minecraft:element_62", - "id": -73 - }, - { - "name": "minecraft:daylight_detector", - "id": 151 - }, - { - "name": "minecraft:snow_layer", - "id": 78 - }, - { - "name": "minecraft:rabbit_foot", - "id": 414 - }, - { - "name": "minecraft:lingering_potion", - "id": 441 - }, - { - "name": "minecraft:campfire", - "id": 720 - }, - { - "name": "minecraft:smoker", - "id": -198 - }, - { - "name": "minecraft:warped_fence", - "id": -257 - }, - { - "name": "minecraft:cooked_chicken", - "id": 366 - }, - { - "name": "minecraft:light_blue_glazed_terracotta", - "id": 223 - }, - { - "name": "minecraft:stone_sword", - "id": 272 - }, - { - "name": "minecraft:record_far", - "id": 504 - }, - { - "name": "minecraft:spider_eye", - "id": 375 - }, - { - "name": "minecraft:smooth_quartz_stairs", - "id": -185 - }, - { - "name": "minecraft:potato", - "id": 392 - }, - { - "name": "minecraft:baked_potato", - "id": 393 - }, - { - "name": "minecraft:element_88", - "id": -99 - }, - { - "name": "minecraft:golden_carrot", - "id": 396 - }, - { - "name": "minecraft:spruce_stairs", - "id": 134 - }, - { - "name": "minecraft:poisonous_potato", - "id": 394 - }, - { - "name": "minecraft:element_13", - "id": -24 - }, - { - "name": "minecraft:obsidian", - "id": 49 - }, - { - "name": "minecraft:pumpkin_pie", - "id": 400 - }, - { - "name": "minecraft:diamond_pickaxe", - "id": 278 - }, - { - "name": "minecraft:lantern", - "id": -208 - }, - { - "name": "minecraft:iron_sword", - "id": 267 - }, - { - "name": "minecraft:smooth_stone", - "id": -183 - }, - { - "name": "minecraft:beetroot", - "id": 457 - }, - { - "name": "minecraft:element_43", - "id": -54 - }, - { - "name": "minecraft:beetroot_soup", - "id": 459 - }, - { - "name": "minecraft:red_mushroom", - "id": 40 - }, - { - "name": "minecraft:wooden_pickaxe", - "id": 270 - }, - { - "name": "minecraft:invisiblebedrock", - "id": 95 - }, - { - "name": "minecraft:sweet_berries", - "id": 477 - }, - { - "name": "minecraft:prismarine_bricks_stairs", - "id": -4 - }, - { - "name": "minecraft:cooked_rabbit", - "id": 412 - }, - { - "name": "minecraft:rabbit_stew", - "id": 413 - }, - { - "name": "minecraft:birch_fence_gate", - "id": 184 - }, - { - "name": "minecraft:wheat_seeds", - "id": 295 - }, - { - "name": "minecraft:chest", - "id": 54 - }, - { - "name": "minecraft:pumpkin_seeds", - "id": 361 - }, - { - "name": "minecraft:element_2", - "id": -13 - }, - { - "name": "minecraft:item.crimson_door", - "id": -244 - }, - { - "name": "minecraft:command_block_minecart", - "id": 443 - }, - { - "name": "minecraft:melon_seeds", - "id": 362 - }, - { - "name": "minecraft:iron_axe", - "id": 258 - }, - { - "name": "minecraft:spawn_egg", - "id": 383 - }, - { - "name": "minecraft:element_93", - "id": -104 - }, - { - "name": "minecraft:nether_wart", - "id": 372 - }, - { - "name": "minecraft:beetroot_seeds", - "id": 458 - }, - { - "name": "minecraft:element_35", - "id": -46 - }, - { - "name" : "minecraft:iron_shovel", - "id" : 256 - }, - { - "name": "minecraft:element_104", - "id": -115 - }, - { - "name": "minecraft:granite_stairs", - "id": -169 - }, - { - "name": "minecraft:flint_and_steel", - "id": 259 - }, - { - "name": "minecraft:stone_shovel", - "id": 273 - }, - { - "name": "minecraft:horsearmorleather", - "id": 416 - }, - { - "name": "minecraft:item.cauldron", - "id": 118 - }, - { - "name": "minecraft:melon_block", - "id": 103 - }, - { - "name": "minecraft:arrow", - "id": 262 - }, - { - "name": "minecraft:coal", - "id": 263 - }, - { - "name": "minecraft:real_double_stone_slab2", - "id": 181 - }, - { - "name": "minecraft:chorus_plant", - "id": 240 - }, - { - "name": "minecraft:gold_block", - "id": 41 - }, - { - "name": "minecraft:carrots", - "id": 141 - }, - { - "name": "minecraft:diamond", - "id": 264 - }, - { - "name": "minecraft:wooden_sword", - "id": 268 - }, - { - "name": "minecraft:record_strad", - "id": 508 - }, - { - "name": "minecraft:netherite_boots", - "id": 751 - }, - { - "name": "minecraft:dark_oak_stairs", - "id": 164 - }, - { - "name": "minecraft:farmland", - "id": 60 - }, - { - "name": "minecraft:wooden_axe", - "id": 271 - }, - { - "name": "minecraft:stone_pickaxe", - "id": 274 - }, - { - "name": "minecraft:planks", - "id": 5 - }, - { - "name": "minecraft:chainmail_helmet", - "id": 302 - }, - { - "name": "minecraft:diamond_shovel", - "id": 277 - }, - { - "name": "minecraft:diamond_sword", - "id": 276 - }, - { - "name": "minecraft:smithing_table", - "id": -202 - }, - { - "name": "minecraft:diamond_axe", - "id": 279 - }, - { - "name": "minecraft:bowl", - "id": 281 - }, - { - "name": "minecraft:flowing_water", - "id": 8 - }, - { - "name": "minecraft:golden_sword", - "id": 283 - }, - { - "name": "minecraft:honey_block", - "id": -220 - }, - { - "name": "minecraft:golden_shovel", - "id": 284 - }, - { - "name": "minecraft:golden_pickaxe", - "id": 285 - }, - { - "name": "minecraft:lit_redstone_lamp", - "id": 124 - }, - { - "name": "minecraft:elytra", - "id": 444 - }, - { - "name": "minecraft:golden_axe", - "id": 286 - }, - { - "name": "minecraft:element_52", - "id": -63 - }, - { - "name": "minecraft:string", - "id": 287 - }, - { - "name": "minecraft:real_double_stone_slab4", - "id": -168 - }, - { - "name": "minecraft:feather", - "id": 288 - }, - { - "name": "minecraft:gunpowder", - "id": 289 - }, - { - "name": "minecraft:acacia_stairs", - "id": 163 - }, - { - "name" : "minecraft:wooden_hoe", - "id" : 290 - }, - { - "name": "minecraft:stone_hoe", - "id": 291 - }, - { - "name": "minecraft:iron_hoe", - "id": 292 - }, - { - "name": "minecraft:diamond_hoe", - "id": 293 - }, - { - "name": "minecraft:element_86", - "id": -97 - }, - { - "name": "minecraft:golden_hoe", - "id": 294 - }, - { - "name": "minecraft:wheat", - "id": 296 - }, - { - "name": "minecraft:leather_helmet", - "id": 298 - }, - { - "name": "minecraft:leather_chestplate", - "id": 299 - }, - { - "name": "minecraft:leather_leggings", - "id": 300 - }, - { - "name": "minecraft:lodestone", - "id": -222 - }, - { - "name": "minecraft:brown_mushroom", - "id": 39 - }, - { - "name": "minecraft:leather_boots", - "id": 301 - }, - { - "name": "minecraft:chainmail_chestplate", - "id": 303 - }, - { - "name": "minecraft:end_gateway", - "id": 209 - }, - { - "name": "minecraft:item.beetroot", - "id": 244 - }, - { - "name" : "minecraft:chainmail_leggings", - "id" : 304 - }, - { - "name": "minecraft:element_101", - "id": -112 - }, - { - "name": "minecraft:chainmail_boots", - "id": 305 - }, - { - "name": "minecraft:soul_sand", - "id": 88 - }, - { - "name": "minecraft:iron_helmet", - "id": 306 - }, - { - "name": "minecraft:snowball", - "id": 332 - }, - { - "name": "minecraft:element_49", - "id": -60 - }, - { - "name": "minecraft:record_mellohi", - "id": 506 - }, - { - "name": "minecraft:iron_chestplate", - "id": 307 - }, - { - "name": "minecraft:barrel", - "id": -203 - }, - { - "name": "minecraft:iron_leggings", - "id": 308 - }, - { - "name": "minecraft:crimson_double_slab", - "id": -266 - }, - { - "name": "minecraft:iron_boots", - "id": 309 - }, - { - "name": "minecraft:real_double_stone_slab3", - "id": -167 - }, - { - "name": "minecraft:ender_eye", - "id": 381 - }, - { - "name": "minecraft:stickypistonarmcollision", - "id": -217 - }, - { - "name": "minecraft:iron_trapdoor", - "id": 167 - }, - { - "name": "minecraft:diamond_helmet", - "id": 310 - }, - { - "name": "minecraft:stone_pressure_plate", - "id": 70 - }, - { - "name": "minecraft:diamond_chestplate", - "id": 311 - }, - { - "name": "minecraft:sand", - "id": 12 - }, - { - "name": "minecraft:light_weighted_pressure_plate", - "id": 147 - }, - { - "name": "minecraft:piston", - "id": 33 - }, - { - "name": "minecraft:diamond_leggings", - "id": 312 - }, - { - "name": "minecraft:element_30", - "id": -41 - }, - { - "name": "minecraft:diamond_boots", - "id": 313 - }, - { - "name": "minecraft:golden_helmet", - "id": 314 - }, - { - "name": "minecraft:element_51", - "id": -62 - }, - { - "name": "minecraft:double_wooden_slab", - "id": 157 - }, - { - "name": "minecraft:hard_stained_glass", - "id": 254 - }, - { - "name": "minecraft:element_84", - "id": -95 - }, - { - "name": "minecraft:golden_chestplate", - "id": 315 - }, - { - "name": "minecraft:sealantern", - "id": 169 - }, - { - "name": "minecraft:bedrock", - "id": 7 - }, - { - "name": "minecraft:glowstone", - "id": 89 - }, - { - "name": "minecraft:golden_leggings", - "id": 316 - }, - { - "name": "minecraft:golden_boots", - "id": 317 - }, - { - "name": "minecraft:shield", - "id": 513 - }, - { - "name": "minecraft:jungle_fence_gate", - "id": 185 - }, - { - "name": "minecraft:carpet", - "id": 171 - }, - { - "name": "minecraft:flowing_lava", - "id": 10 - }, - { - "name": "minecraft:flint", - "id": 318 - }, - { - "name": "minecraft:painting", - "id": 321 - }, - { - "name": "minecraft:heart_of_the_sea", - "id": 467 - }, - { - "name": "minecraft:sign", - "id": 323 - }, - { - "name": "minecraft:muttonraw", - "id": 423 - }, - { - "name": "minecraft:element_55", - "id": -66 - }, - { - "name": "minecraft:wooden_door", - "id": 324 - }, - { - "name": "minecraft:concrete_powder", - "id": 237 - }, - { - "name": "minecraft:minecart", - "id": 328 - }, - { - "name": "minecraft:saddle", - "id": 329 - }, - { - "name": "minecraft:nether_wart_block", - "id": 214 - }, - { - "name": "minecraft:crimson_roots", - "id": -223 - }, - { - "name": "minecraft:element_116", - "id": -127 - }, - { - "name": "minecraft:iron_door", - "id": 330 - }, - { - "name": "minecraft:redstone", - "id": 331 - }, - { - "name": "minecraft:boat", - "id": 333 - }, - { - "name": "minecraft:written_book", - "id": 387 - }, - { - "name": "minecraft:iron_ore", - "id": 15 - }, - { - "name": "minecraft:leather", - "id": 334 - }, - { - "name": "minecraft:kelp", - "id": 335 - }, - { - "name": "minecraft:gold_nugget", - "id": 371 - }, - { - "name": "minecraft:brick", - "id": 336 - }, - { - "name": "minecraft:element_68", - "id": -79 - }, - { - "name": "minecraft:clay_ball", - "id": 337 - }, - { - "name": "minecraft:carrotonastick", - "id": 398 - }, - { - "name": "minecraft:reeds", - "id": 338 - }, - { - "name": "minecraft:paper", - "id": 339 - }, - { - "name": "minecraft:element_23", - "id": -34 - }, - { - "name": "minecraft:coral", - "id": -131 - }, - { - "name": "minecraft:book", - "id": 340 - }, - { - "name": "minecraft:end_portal", - "id": 119 - }, - { - "name": "minecraft:trident", - "id": 455 - }, - { - "name": "minecraft:slime_ball", - "id": 341 - }, - { - "name": "minecraft:chest_minecart", - "id": 342 - }, - { - "name": "minecraft:element_71", - "id": -82 - }, - { - "name": "minecraft:egg", - "id": 344 - }, - { - "name": "minecraft:netherite_sword", - "id": 743 - }, - { - "name": "minecraft:item.reeds", - "id": 83 - }, - { - "name" : "minecraft:compass", - "id" : 345 - }, - { - "name": "minecraft:crimson_stairs", - "id": -254 - }, - { - "name": "minecraft:fishing_rod", - "id": 346 - }, - { - "name": "minecraft:andesite_stairs", - "id": -171 - }, - { - "name": "minecraft:reserved6", - "id": 255 - }, - { - "name": "minecraft:clock", - "id": 347 - }, - { - "name": "minecraft:red_sandstone", - "id": 179 - }, - { - "name": "minecraft:spruce_button", - "id": -144 - }, - { - "name": "minecraft:glowstone_dust", - "id": 348 - }, - { - "name": "minecraft:blaze_rod", - "id": 369 - }, - { - "name": "minecraft:dye", - "id": 351 - }, - { - "name": "minecraft:element_74", - "id": -85 - }, - { - "name" : "minecraft:bone", - "id" : 352 - }, - { - "name": "minecraft:map", - "id": 358 - }, - { - "name": "minecraft:sugar", - "id": 353 - }, - { - "name": "minecraft:name_tag", - "id": 421 - }, - { - "name": "minecraft:cake", - "id": 354 - }, - { - "name": "minecraft:bed", - "id": 355 - }, - { - "name": "minecraft:stained_glass", - "id": 241 - }, - { - "name": "minecraft:repeater", - "id": 356 - }, - { - "name": "minecraft:beacon", - "id": 138 - }, - { - "name": "minecraft:netherite_chestplate", - "id": 749 - }, - { - "name": "minecraft:unpowered_comparator", - "id": 149 - }, - { - "name": "minecraft:shears", - "id": 359 - }, - { - "name": "minecraft:element_31", - "id": -42 - }, - { - "name": "minecraft:ender_pearl", - "id": 368 - }, - { - "name": "minecraft:red_sandstone_stairs", - "id": 180 - }, - { - "name": "minecraft:carved_pumpkin", - "id": -155 - }, - { - "name": "minecraft:ghast_tear", - "id": 370 - }, - { - "name": "minecraft:glass_bottle", - "id": 374 - }, - { - "name": "minecraft:element_44", - "id": -55 - }, - { - "name": "minecraft:lava", - "id": 11 - }, - { - "name": "minecraft:polished_blackstone_brick_stairs", - "id": -275 - }, - { - "name": "minecraft:jungle_pressure_plate", - "id": -153 - }, - { - "name": "minecraft:fermented_spider_eye", - "id": 376 - }, - { - "name": "minecraft:honeycomb_block", - "id": -221 - }, - { - "name": "minecraft:blaze_powder", - "id": 377 - }, - { - "name": "minecraft:magma_cream", - "id": 378 - }, - { - "name": "minecraft:jigsaw", - "id": -211 - }, - { - "name": "minecraft:brewing_stand", - "id": 379 - }, - { - "name": "minecraft:cauldron", - "id": 380 - }, - { - "name": "minecraft:element_111", - "id": -122 - }, - { - "name": "minecraft:rapid_fertilizer", - "id": 449 - }, - { - "name": "minecraft:clay", - "id": 82 - }, - { - "name": "minecraft:speckled_melon", - "id": 382 - }, - { - "name": "minecraft:experience_bottle", - "id": 384 - }, - { - "name": "minecraft:element_48", - "id": -59 - }, - { - "name": "minecraft:coal_block", - "id": 173 - }, - { - "name": "minecraft:fireball", - "id": 385 - }, - { - "name": "minecraft:writable_book", - "id": 386 - }, - { - "name": "minecraft:element_69", - "id": -80 - }, - { - "name": "minecraft:emerald", - "id": 388 - }, - { - "name": "minecraft:record_pigstep", - "id": 759 - }, - { - "name": "minecraft:element_66", - "id": -77 - }, - { - "name": "minecraft:frame", - "id": 389 - }, - { - "name": "minecraft:brewingstandblock", - "id": 117 - }, - { - "name": "minecraft:flower_pot", - "id": 390 - }, - { - "name": "minecraft:emptymap", - "id": 395 - }, - { - "name": "minecraft:element_110", - "id": -121 - }, - { - "name": "minecraft:element_75", - "id": -86 - }, - { - "name": "minecraft:skull", - "id": 397 - }, - { - "name": "minecraft:crimson_door", - "id": 755 - }, - { - "name": "minecraft:sponge", - "id": 19 - }, - { - "name": "minecraft:netherstar", - "id": 399 - }, - { - "name": "minecraft:fireworks", - "id": 401 - }, - { - "name": "minecraft:hopper_minecart", - "id": 408 - }, - { - "name" : "minecraft:fireworkscharge", - "id" : 402 - }, - { - "name" : "minecraft:enchanted_book", - "id" : 403 - }, - { - "name" : "minecraft:netherbrick", - "id" : 405 - }, - { - "name": "minecraft:cobblestone_wall", - "id": 139 - }, - { - "name" : "minecraft:quartz", - "id" : 406 - }, - { - "name" : "minecraft:tnt_minecart", - "id" : 407 - }, - { - "name": "minecraft:element_63", - "id": -74 - }, - { - "name": "minecraft:hopper", - "id": 410 - }, - { - "name": "minecraft:cobblestone", - "id": 4 - }, - { - "name": "minecraft:dragon_breath", - "id": 437 - }, - { - "name": "minecraft:rabbit_hide", - "id": 415 - }, - { - "name": "minecraft:horsearmoriron", - "id": 417 - }, - { - "name": "minecraft:horsearmorgold", - "id": 418 - }, - { - "name": "minecraft:colored_torch_bp", - "id": 204 - }, - { - "name": "minecraft:element_102", - "id": -113 - }, - { - "name": "minecraft:quartz_ore", - "id": 153 - }, - { - "name": "minecraft:netherite_shovel", - "id": 744 - }, - { - "name": "minecraft:horsearmordiamond", - "id": 419 - }, - { - "name": "minecraft:record_13", - "id": 500 - }, - { - "name": "minecraft:record_cat", - "id": 501 - }, - { - "name": "minecraft:element_3", - "id": -14 - }, - { - "name": "minecraft:polished_diorite_stairs", - "id": -173 - }, - { - "name": "minecraft:monster_egg", - "id": 97 - }, - { - "name": "minecraft:record_blocks", - "id": 502 - }, - { - "name": "minecraft:crimson_standing_sign", - "id": -250 - }, - { - "name": "minecraft:record_chirp", - "id": 503 - }, - { - "name": "minecraft:record_mall", - "id": 505 - }, - { - "name": "minecraft:respawn_anchor", - "id": -272 - }, - { - "name": "minecraft:record_stal", - "id": 507 - }, - { - "name": "minecraft:record_11", - "id": 510 - }, - { - "name": "minecraft:record_wait", - "id": 511 - }, - { - "name": "minecraft:info_update2", - "id": 249 - }, - { - "name": "minecraft:lead", - "id": 420 - }, - { - "name": "minecraft:prismarine_crystals", - "id": 422 - }, - { - "name": "minecraft:acacia_sign", - "id": 475 - }, - { - "name": "minecraft:muttoncooked", - "id": 424 - }, - { - "name": "minecraft:armor_stand", - "id": 425 - }, - { - "name": "minecraft:coal_ore", - "id": 16 - }, - { - "name": "minecraft:element_32", - "id": -43 - }, - { - "name": "minecraft:spruce_door", - "id": 427 - }, - { - "name": "minecraft:phantom_membrane", - "id": 470 - }, - { - "name": "minecraft:birch_door", - "id": 428 - }, - { - "name": "minecraft:element_85", - "id": -96 - }, - { - "name": "minecraft:polished_blackstone_wall", - "id": -297 - }, - { - "name": "minecraft:jungle_door", - "id": 429 - }, - { - "name": "minecraft:acacia_door", - "id": 430 - }, - { - "name": "minecraft:element_42", - "id": -53 - }, - { - "name": "minecraft:dark_oak_door", - "id": 431 - }, - { - "name": "minecraft:netherite_leggings", - "id": 750 - }, - { - "name": "minecraft:stripped_crimson_stem", - "id": -240 - }, - { - "name": "minecraft:chorus_fruit", - "id": 432 - }, - { - "name": "minecraft:camera", - "id": 498 - }, - { - "name": "minecraft:suspicious_stew", - "id": 734 - }, - { - "name": "minecraft:chorus_fruit_popped", - "id": 433 - }, - { - "name": "minecraft:element_98", - "id": -109 - }, - { - "name" : "minecraft:splash_potion", - "id" : 438 - }, - { - "name": "minecraft:element_73", - "id": -84 - }, - { - "name": "minecraft:prismarine_shard", - "id": 409 - }, - { - "name": "minecraft:seagrass", - "id": -130 - }, - { - "name": "minecraft:dark_oak_pressure_plate", - "id": -152 - }, - { - "name": "minecraft:shulker_shell", - "id": 445 - }, - { - "name": "minecraft:redstone_block", - "id": 152 - }, - { - "name": "minecraft:banner", - "id": 446 - }, - { - "name": "minecraft:totem", - "id": 450 - }, - { - "name": "minecraft:blackstone_slab", - "id": -282 - }, - { - "name": "minecraft:element_118", - "id": -129 - }, - { - "name": "minecraft:iron_nugget", - "id": 452 - }, - { - "name": "minecraft:netherite_pickaxe", - "id": 745 - }, - { - "name": "minecraft:jukebox", - "id": 84 - }, - { - "name": "minecraft:turtle_shell_piece", - "id": 468 - }, - { - "name": "minecraft:turtle_helmet", - "id": 469 - }, - { - "name": "minecraft:crossbow", - "id": 471 - }, - { - "name": "minecraft:glowingobsidian", - "id": 246 - }, - { - "name": "minecraft:leaves2", - "id": 161 - }, - { - "name": "minecraft:spruce_sign", - "id": 472 - }, - { - "name": "minecraft:element_38", - "id": -49 - }, - { - "name": "minecraft:coral_fan_hang2", - "id": -136 - }, - { - "name": "minecraft:birch_sign", - "id": 473 - }, - { - "name": "minecraft:coral_fan_dead", - "id": -134 - }, - { - "name": "minecraft:balloon", - "id": 448 - }, - { - "name": "minecraft:jungle_sign", - "id": 474 - }, - { - "name": "minecraft:darkoak_sign", - "id": 476 - }, - { - "name": "minecraft:element_24", - "id": -35 - }, - { - "name": "minecraft:banner_pattern", - "id": 434 - }, - { - "name": "minecraft:honeycomb", - "id": 736 - }, - { - "name": "minecraft:element_78", - "id": -89 - }, - { - "name": "minecraft:red_nether_brick", - "id": 215 - }, - { - "name": "minecraft:honey_bottle", - "id": 737 - }, - { - "name": "minecraft:compound", - "id": 499 - }, - { - "name": "minecraft:ice_bomb", - "id": 453 - }, - { - "name": "minecraft:brick_block", - "id": 45 - }, - { - "name": "minecraft:bleach", - "id": 451 - }, - { - "name": "minecraft:colored_torch_rg", - "id": 202 - }, - { - "name": "minecraft:medicine", - "id": 447 - }, - { - "name": "minecraft:warped_fungus", - "id": -229 - }, - { - "name": "minecraft:end_portal_frame", - "id": 120 - }, - { - "name": "minecraft:element_92", - "id": -103 - }, - { - "name": "minecraft:glow_stick", - "id": 166 - }, - { - "name": "minecraft:lodestonecompass", - "id": 741 - }, - { - "name": "minecraft:element_17", - "id": -28 - }, - { - "name": "minecraft:lit_pumpkin", - "id": 91 - }, - { - "name": "minecraft:netherite_ingot", - "id": 742 - }, - { - "name": "minecraft:chain_command_block", - "id": 189 - }, - { - "name": "minecraft:loom", - "id": -204 - }, - { - "name": "minecraft:item.warped_door", - "id": -245 - }, - { - "name": "minecraft:netherite_axe", - "id": 746 - }, - { - "name": "minecraft:netherite_hoe", - "id": 747 - }, - { - "name": "minecraft:dark_oak_fence_gate", - "id": 186 - }, - { - "name": "minecraft:element_115", - "id": -126 - }, - { - "name": "minecraft:netherite_helmet", - "id": 748 - }, - { - "name": "minecraft:element_117", - "id": -128 - }, - { - "name": "minecraft:netherite_scrap", - "id": 752 - }, - { - "name": "minecraft:crimson_sign", - "id": 753 - }, - { - "name": "minecraft:concrete", - "id": 236 - }, - { - "name": "minecraft:chiseled_nether_bricks", - "id": -302 - }, - { - "name": "minecraft:mob_spawner", - "id": 52 - }, - { - "name": "minecraft:warped_sign", - "id": 754 - }, - { - "name": "minecraft:chain", - "id": 758 - }, - { - "name": "minecraft:warped_fungus_on_a_stick", - "id": 757 - }, - { - "name": "minecraft:nether_sprouts", - "id": 760 - }, - { - "name": "minecraft:cartography_table", - "id": -200 - }, - { - "name": "minecraft:polished_blackstone_slab", - "id": -293 - }, - { - "name": "minecraft:soul_campfire", - "id": 801 - }, - { - "name": "minecraft:stone", - "id": 1 - }, - { - "name": "minecraft:wool", - "id": 35 - }, - { - "name": "minecraft:yellow_flower", - "id": 37 - }, - { - "name": "minecraft:stained_hardened_clay", - "id": 159 - }, - { - "name": "minecraft:log", - "id": 17 - }, - { - "name": "minecraft:fence", - "id": 85 - }, - { - "name": "minecraft:element_53", - "id": -64 - }, - { - "name": "minecraft:stonebrick", - "id": 98 - }, - { - "name": "minecraft:lit_blast_furnace", - "id": -214 - }, - { - "name": "minecraft:coral_block", - "id": -132 - }, - { - "name": "minecraft:polished_blackstone_bricks", - "id": -274 - }, - { - "name": "minecraft:double_stone_slab", - "id": 44 - }, - { - "name": "minecraft:element_100", - "id": -111 - }, - { - "name": "minecraft:double_stone_slab2", - "id": 182 - }, - { - "name": "minecraft:fence_gate", - "id": 107 - }, - { - "name": "minecraft:double_stone_slab3", - "id": -162 - }, - { - "name": "minecraft:rail", - "id": 66 - }, - { - "name": "minecraft:double_stone_slab4", - "id": -166 - }, - { - "name": "minecraft:stripped_acacia_log", - "id": -8 - }, - { - "name": "minecraft:real_double_stone_slab", - "id": 43 - }, - { - "name": "minecraft:coral_fan", - "id": -133 - }, - { - "name": "minecraft:sea_pickle", - "id": -156 - }, - { - "name": "minecraft:polished_blackstone_button", - "id": -296 - }, - { - "name": "minecraft:element_90", - "id": -101 - }, - { - "name": "minecraft:polished_blackstone_double_slab", - "id": -294 - }, - { - "name": "minecraft:sapling", - "id": 6 - }, - { - "name": "minecraft:leaves", - "id": 18 - }, - { - "name": "minecraft:sandstone", - "id": 24 - }, - { - "name": "minecraft:silver_glazed_terracotta", - "id": 228 - }, - { - "name": "minecraft:wooden_slab", - "id": 158 - }, - { - "name": "minecraft:warped_roots", - "id": -224 - }, - { - "name": "minecraft:element_11", - "id": -22 - }, - { - "name": "minecraft:red_flower", - "id": 38 - }, - { - "name": "minecraft:element_59", - "id": -70 - }, - { - "name": "minecraft:double_plant", - "id": 175 - }, - { - "name": "minecraft:waterlily", - "id": 111 - }, - { - "name": "minecraft:quartz_block", - "id": 155 - }, - { - "name": "minecraft:element_95", - "id": -106 - }, - { - "name": "minecraft:soul_soil", - "id": -236 - }, - { - "name": "minecraft:acacia_pressure_plate", - "id": -150 - }, - { - "name": "minecraft:tallgrass", - "id": 31 - }, - { - "name": "minecraft:brown_mushroom_block", - "id": 99 - }, - { - "name": "minecraft:element_103", - "id": -114 - }, - { - "name": "minecraft:crimson_fungus", - "id": -228 - }, - { - "name": "minecraft:item.frame", - "id": 199 - }, - { - "name": "minecraft:red_mushroom_block", - "id": 100 - }, - { - "name": "minecraft:log2", - "id": 162 - }, - { - "name": "minecraft:conduit", - "id": -157 - }, - { - "name": "minecraft:prismarine", - "id": 168 - }, - { - "name": "minecraft:magma", - "id": 213 - }, - { - "name": "minecraft:element_22", - "id": -33 - }, - { - "name" : "minecraft:undyed_shulker_box", - "id" : 205 - }, - { - "name": "minecraft:shulker_box", - "id": 218 - }, - { - "name": "minecraft:spruce_standing_sign", - "id": -181 - }, - { - "name": "minecraft:sticky_piston", - "id": 29 - }, - { - "name": "minecraft:element_10", - "id": -21 - }, - { - "name": "minecraft:turtle_egg", - "id": -159 - }, - { - "name": "minecraft:bamboo", - "id": -163 - }, - { - "name": "minecraft:observer", - "id": 251 - }, - { - "name" : "minecraft:scaffolding", - "id" : -165 - }, - { - "name" : "minecraft:blast_furnace", - "id" : -196 - }, - { - "name": "minecraft:grindstone", - "id": -195 - }, - { - "name" : "minecraft:bell", - "id" : -206 - }, - { - "name": "minecraft:end_rod", - "id": 208 - }, - { - "name": "minecraft:fletching_table", - "id": -201 - }, - { - "name": "minecraft:item.hopper", - "id": 154 - }, - { - "name": "minecraft:wood", - "id": -212 - }, - { - "name": "minecraft:chemistry_table", - "id": 238 - }, - { - "name": "minecraft:tnt", - "id": 46 - }, - { - "name": "minecraft:hard_stained_glass_pane", - "id": 191 - }, - { - "name": "minecraft:crimson_slab", - "id": -264 - }, - { - "name": "minecraft:element_87", - "id": -98 - }, - { - "name": "minecraft:warped_slab", - "id": -265 - }, - { - "name": "minecraft:element_0", - "id": 36 - }, - { - "name": "minecraft:element_4", - "id": -15 - }, - { - "name": "minecraft:ender_chest", - "id": 130 - }, - { - "name": "minecraft:element_5", - "id": -16 - }, - { - "name" : "minecraft:element_6", - "id" : -17 - }, - { - "name" : "minecraft:element_8", - "id" : -19 - }, - { - "name" : "minecraft:element_9", - "id" : -20 - }, - { - "name" : "minecraft:element_12", - "id" : -23 - }, - { - "name" : "minecraft:element_14", - "id" : -25 - }, - { - "name": "minecraft:element_15", - "id": -26 - }, - { - "name": "minecraft:element_18", - "id": -29 - }, - { - "name": "minecraft:element_19", - "id": -30 - }, - { - "name": "minecraft:element_20", - "id": -31 - }, - { - "name": "minecraft:element_21", - "id": -32 - }, - { - "name": "minecraft:element_26", - "id": -37 - }, - { - "name": "minecraft:element_28", - "id": -39 - }, - { - "name": "minecraft:element_29", - "id": -40 - }, - { - "name": "minecraft:element_33", - "id": -44 - }, - { - "name": "minecraft:element_34", - "id": -45 - }, - { - "name": "minecraft:element_36", - "id": -47 - }, - { - "name": "minecraft:ice", - "id": 79 - }, - { - "name": "minecraft:element_37", - "id": -48 - }, - { - "name": "minecraft:element_39", - "id": -50 - }, - { - "name": "minecraft:element_40", - "id": -51 - }, - { - "name": "minecraft:element_41", - "id": -52 - }, - { - "name": "minecraft:element_45", - "id": -56 - }, - { - "name": "minecraft:element_46", - "id": -57 - }, - { - "name": "minecraft:netherite_block", - "id": -270 - }, - { - "name": "minecraft:element_47", - "id": -58 - }, - { - "name": "minecraft:element_54", - "id": -65 - }, - { - "name": "minecraft:element_56", - "id": -67 - }, - { - "name": "minecraft:black_glazed_terracotta", - "id": 235 - }, - { - "name": "minecraft:lit_redstone_ore", - "id": 74 - }, - { - "name": "minecraft:crafting_table", - "id": 58 - }, - { - "name": "minecraft:element_57", - "id": -68 - }, - { - "name": "minecraft:element_58", - "id": -69 - }, - { - "name": "minecraft:element_60", - "id": -71 - }, - { - "name" : "minecraft:element_64", - "id" : -75 - }, - { - "name" : "minecraft:element_65", - "id" : -76 - }, - { - "name": "minecraft:element_67", - "id": -78 - }, - { - "name": "minecraft:element_70", - "id": -81 - }, - { - "name": "minecraft:element_72", - "id": -83 - }, - { - "name": "minecraft:element_76", - "id": -87 - }, - { - "name": "minecraft:dark_oak_button", - "id": -142 - }, - { - "name": "minecraft:element_77", - "id": -88 - }, - { - "name": "minecraft:diorite_stairs", - "id": -170 - }, - { - "name": "minecraft:redstone_torch", - "id": 76 - }, - { - "name": "minecraft:element_79", - "id": -90 - }, - { - "name": "minecraft:iron_bars", - "id": 101 - }, - { - "name": "minecraft:element_80", - "id": -91 - }, - { - "name": "minecraft:element_81", - "id": -92 - }, - { - "name": "minecraft:element_82", - "id": -93 - }, - { - "name": "minecraft:underwater_torch", - "id": 239 - }, - { - "name": "minecraft:blue_ice", - "id": -11 - }, - { - "name": "minecraft:element_83", - "id": -94 - }, - { - "name": "minecraft:element_89", - "id": -100 - }, - { - "name": "minecraft:element_91", - "id": -102 - }, - { - "name": "minecraft:element_96", - "id": -107 - }, - { - "name": "minecraft:element_97", - "id": -108 - }, - { - "name": "minecraft:cactus", - "id": 81 - }, - { - "name" : "minecraft:element_99", - "id" : -110 - }, - { - "name": "minecraft:element_105", - "id": -116 - }, - { - "name": "minecraft:element_106", - "id": -117 - }, - { - "name": "minecraft:cyan_glazed_terracotta", - "id": 229 - }, - { - "name": "minecraft:element_107", - "id": -118 - }, - { - "name": "minecraft:element_108", - "id": -119 - }, - { - "name": "minecraft:element_109", - "id": -120 - }, - { - "name": "minecraft:element_112", - "id": -123 - }, - { - "name": "minecraft:warped_button", - "id": -261 - }, - { - "name": "minecraft:element_113", - "id": -124 - }, - { - "name": "minecraft:birch_stairs", - "id": 135 - }, - { - "name": "minecraft:element_114", - "id": -125 - }, - { - "name": "minecraft:composter", - "id": -213 - }, - { - "name": "minecraft:crying_obsidian", - "id": -289 - }, - { - "name": "minecraft:end_crystal", - "id": 426 - }, - { - "name": "minecraft:tripwire_hook", - "id": 131 - }, - { - "name": "minecraft:blue_glazed_terracotta", - "id": 231 - }, - { - "name": "minecraft:daylight_detector_inverted", - "id": 178 - }, - { - "name": "minecraft:warped_trapdoor", - "id": -247 - }, - { - "name": "minecraft:twisting_vines", - "id": -287 - }, - { - "name": "minecraft:noteblock", - "id": 25 - }, - { - "name": "minecraft:gravel", - "id": 13 - }, - { - "name": "minecraft:golden_rail", - "id": 27 - }, - { - "name": "minecraft:warped_wall_sign", - "id": -253 - }, - { - "name": "minecraft:oak_stairs", - "id": 53 - }, - { - "name": "minecraft:grass", - "id": 2 - }, - { - "name": "minecraft:acacia_button", - "id": -140 - }, - { - "name": "minecraft:snow", - "id": 80 - }, - { - "name": "minecraft:detector_rail", - "id": 28 - }, - { - "name": "minecraft:dark_oak_trapdoor", - "id": -147 - }, - { - "name": "minecraft:spruce_pressure_plate", - "id": -154 - }, - { - "name": "minecraft:water", - "id": 9 - }, - { - "name": "minecraft:furnace", - "id": 61 - }, - { - "name": "minecraft:item.wooden_door", - "id": 64 - }, - { - "name": "minecraft:gold_ore", - "id": 14 - }, - { - "name": "minecraft:web", - "id": 30 - }, - { - "name": "minecraft:unlit_redstone_torch", - "id": 75 - }, - { - "name": "minecraft:ladder", - "id": 65 - }, - { - "name": "minecraft:sweet_berry_bush", - "id": -207 - }, - { - "name": "minecraft:standing_sign", - "id": 63 - }, - { - "name": "minecraft:glass", - "id": 20 - }, - { - "name": "minecraft:lapis_ore", - "id": 21 - }, - { - "name": "minecraft:bookshelf", - "id": 47 - }, - { - "name": "minecraft:item.bed", - "id": 26 - }, - { - "name": "minecraft:stripped_warped_hyphae", - "id": -301 - }, - { - "name": "minecraft:wither_rose", - "id": -216 - }, - { - "name": "minecraft:wooden_pressure_plate", - "id": 72 - }, - { - "name": "minecraft:powered_comparator", - "id": 150 - }, - { - "name": "minecraft:lapis_block", - "id": 22 - }, - { - "name": "minecraft:dispenser", - "id": 23 - }, - { - "name": "minecraft:item.wheat", - "id": 59 - }, - { - "name": "minecraft:item.spruce_door", - "id": 193 - }, - { - "name": "minecraft:diamond_ore", - "id": 56 - }, - { - "name": "minecraft:deadbush", - "id": 32 - }, - { - "name": "minecraft:pistonarmcollision", - "id": 34 - }, - { - "name": "minecraft:blackstone_stairs", - "id": -276 - }, - { - "name": "minecraft:dried_kelp_block", - "id": -139 - }, - { - "name": "minecraft:item.soul_campfire", - "id": -290 - }, - { - "name": "minecraft:green_glazed_terracotta", - "id": 233 - }, - { - "name": "minecraft:crimson_pressure_plate", - "id": -262 - }, - { - "name": "minecraft:spruce_fence_gate", - "id": 183 - }, - { - "name": "minecraft:iron_block", - "id": 42 - }, - { - "name": "minecraft:lever", - "id": 69 - }, - { - "name": "minecraft:mossy_cobblestone", - "id": 48 - }, - { - "name": "minecraft:torch", - "id": 50 - }, - { - "name": "minecraft:acacia_fence_gate", - "id": 187 - }, - { - "name": "minecraft:quartz_stairs", - "id": 156 - }, - { - "name": "minecraft:dragon_egg", - "id": 122 - }, - { - "name": "minecraft:lava_cauldron", - "id": -210 - }, - { - "name": "minecraft:jungle_standing_sign", - "id": -188 - }, - { - "name": "minecraft:redstone_wire", - "id": 55 - }, - { - "name": "minecraft:jungle_wall_sign", - "id": -189 - }, - { - "name": "minecraft:lit_furnace", - "id": 62 - }, - { - "name": "minecraft:beehive", - "id": -219 - }, - { - "name": "minecraft:crimson_wall_sign", - "id": -252 - }, - { - "name": "minecraft:stone_stairs", - "id": 67 - }, - { - "name": "minecraft:orange_glazed_terracotta", - "id": 221 - }, - { - "name": "minecraft:brick_stairs", - "id": 108 - }, - { - "name": "minecraft:wall_sign", - "id": 68 - }, - { - "name": "minecraft:warped_nylium", - "id": -233 - }, - { - "name": "minecraft:quartz_bricks", - "id": -304 - }, - { - "name" : "minecraft:item.iron_door", - "id" : 71 - }, - { - "name": "minecraft:redstone_ore", - "id": 73 - }, - { - "name": "minecraft:lectern", - "id": -194 - }, - { - "name": "minecraft:gilded_blackstone", - "id": -281 - }, - { - "name" : "minecraft:red_nether_brick_stairs", - "id" : -184 - }, - { - "name": "minecraft:basalt", - "id": -234 - }, - { - "name": "minecraft:stone_button", - "id": 77 - }, - { - "name" : "minecraft:netherrack", - "id" : 87 - }, - { - "name": "minecraft:nether_brick_stairs", - "id": 114 - }, - { - "name": "minecraft:item.acacia_door", - "id": 196 - }, - { - "name": "minecraft:item.cake", - "id": 92 - }, - { - "name" : "minecraft:unpowered_repeater", - "id" : 93 - }, - { - "name": "minecraft:powered_repeater", - "id": 94 - }, - { - "name": "minecraft:trapdoor", - "id": 96 - }, - { - "name": "minecraft:coral_fan_hang3", - "id": -137 - }, - { - "name": "minecraft:item.jungle_door", - "id": 195 - }, - { - "name": "minecraft:glass_pane", - "id": 102 - }, - { - "name": "minecraft:emerald_ore", - "id": 129 - }, - { - "name": "minecraft:crimson_planks", - "id": -242 - }, - { - "name": "minecraft:crimson_stem", - "id": -225 - }, - { - "name": "minecraft:weeping_vines", - "id": -231 - }, - { - "name": "minecraft:pumpkin_stem", - "id": 104 - }, - { - "name": "minecraft:emerald_block", - "id": 133 - }, - { - "name": "minecraft:melon_stem", - "id": 105 - }, - { - "name": "minecraft:chemical_heat", - "id": 192 - }, - { - "name": "minecraft:warped_wart_block", - "id": -227 - }, - { - "name": "minecraft:vine", - "id": 106 - }, - { - "name": "minecraft:bamboo_sapling", - "id": -164 - }, - { - "name": "minecraft:standing_banner", - "id": 176 - }, - { - "name": "minecraft:mycelium", - "id": 110 - }, - { - "name": "minecraft:nether_gold_ore", - "id": -288 - }, - { - "name": "minecraft:nether_brick", - "id": 112 - }, - { - "name": "minecraft:warped_double_slab", - "id": -267 - }, - { - "name": "minecraft:nether_brick_fence", - "id": 113 - }, - { - "name": "minecraft:sandstone_stairs", - "id": 128 - }, - { - "name": "minecraft:item.nether_wart", - "id": 115 - }, - { - "name": "minecraft:enchanting_table", - "id": 116 - }, - { - "name": "minecraft:end_stone", - "id": 121 - }, - { - "name": "minecraft:redstone_lamp", - "id": 123 - }, - { - "name": "minecraft:jungle_stairs", - "id": 136 - }, - { - "name": "minecraft:dropper", - "id": 125 - }, - { - "name" : "minecraft:activator_rail", - "id" : 126 - }, - { - "name" : "minecraft:cocoa", - "id" : 127 - }, - { - "name": "minecraft:soul_torch", - "id": -268 - }, - { - "name" : "minecraft:info_update", - "id" : 248 - }, - { - "name": "minecraft:packed_ice", - "id": 174 - }, - { - "name" : "minecraft:coral_fan_hang", - "id" : -135 - }, - { - "name" : "minecraft:item.flower_pot", - "id" : 140 - }, - { - "name" : "minecraft:potatoes", - "id" : 142 - }, - { - "name" : "minecraft:wooden_button", - "id" : 143 - }, - { - "name" : "minecraft:item.skull", - "id" : 144 - }, - { - "name" : "minecraft:heavy_weighted_pressure_plate", - "id" : 148 - }, - { - "name": "minecraft:purple_glazed_terracotta", - "id": 219 - }, - { - "name": "minecraft:stripped_jungle_log", - "id": -7 - }, - { - "name": "minecraft:hardened_clay", - "id": 172 - }, - { - "name": "minecraft:warped_planks", - "id": -243 - }, - { - "name": "minecraft:acacia_wall_sign", - "id": -191 - }, - { - "name": "minecraft:purpur_stairs", - "id": 203 - }, - { - "name": "minecraft:wall_banner", - "id": 177 - }, - { - "name": "minecraft:spruce_trapdoor", - "id": -149 - }, - { - "name": "minecraft:repeating_command_block", - "id": 188 - }, - { - "name": "minecraft:item.chain", - "id": -286 - }, - { - "name": "minecraft:item.birch_door", - "id": 194 - }, - { - "name": "minecraft:grass_path", - "id": 198 - }, - { - "name": "minecraft:blackstone", - "id": -273 - }, - { - "name": "minecraft:chorus_flower", - "id": 200 - }, - { - "name": "minecraft:normal_stone_stairs", - "id": -180 - }, - { - "name": "minecraft:barrier", - "id": -161 - }, - { - "name": "minecraft:frosted_ice", - "id": 207 - }, - { - "name": "minecraft:structure_block", - "id": 252 - }, - { - "name": "minecraft:allow", - "id": 210 - }, - { - "name": "minecraft:pink_glazed_terracotta", - "id": 226 - }, - { - "name": "minecraft:deny", - "id": 211 - }, - { - "name": "minecraft:border_block", - "id": 212 - }, - { - "name": "minecraft:movingblock", - "id": 250 - }, - { - "name": "minecraft:bone_block", - "id": 216 - }, - { - "name": "minecraft:structure_void", - "id": 217 - }, - { - "name" : "minecraft:white_glazed_terracotta", - "id" : 220 - }, - { - "name" : "minecraft:magenta_glazed_terracotta", - "id" : 222 - }, - { - "name": "minecraft:lime_glazed_terracotta", - "id": 225 - }, - { - "name": "minecraft:gray_glazed_terracotta", - "id": 227 - }, - { - "name": "minecraft:brown_glazed_terracotta", - "id": 232 - }, - { - "name": "minecraft:red_glazed_terracotta", - "id": 234 - }, - { - "name": "minecraft:crimson_nylium", - "id": -232 - }, - { - "name": "minecraft:acacia_trapdoor", - "id": -145 - }, - { - "name": "minecraft:smooth_sandstone_stairs", - "id": -177 - }, - { - "name": "minecraft:item.camera", - "id": 242 - }, - { - "name": "minecraft:podzol", - "id": 243 - }, - { - "name": "minecraft:stonecutter", - "id": 245 - }, - { - "name": "minecraft:netherreactor", - "id": 247 - }, - { - "name": "minecraft:prismarine_stairs", - "id": -2 - }, - { - "name": "minecraft:dark_prismarine_stairs", - "id": -3 - }, - { - "name": "minecraft:stripped_spruce_log", - "id": -5 - }, - { - "name": "minecraft:stripped_birch_log", - "id": -6 - }, - { - "name": "minecraft:stripped_dark_oak_log", - "id": -9 - }, - { - "name": "minecraft:fire", - "id": 51 - }, - { - "name": "minecraft:hard_glass", - "id": 253 - }, - { - "name": "minecraft:acacia_standing_sign", - "id": -190 - }, - { - "name": "minecraft:hard_glass_pane", - "id": 190 - }, - { - "name": "minecraft:mossy_cobblestone_stairs", - "id": -179 - }, - { - "name": "minecraft:crimson_fence_gate", - "id": -258 - }, - { - "name": "minecraft:mossy_stone_brick_stairs", - "id": -175 - }, - { - "name": "minecraft:item.nether_sprouts", - "id": -238 - }, - { - "name": "minecraft:polished_blackstone_brick_double_slab", - "id": -285 - }, - { - "name": "minecraft:cracked_polished_blackstone_bricks", - "id": -280 - }, - { - "name": "minecraft:smooth_red_sandstone_stairs", - "id": -176 - }, - { - "name": "minecraft:shroomlight", - "id": -230 - }, - { - "name": "minecraft:stripped_crimson_hyphae", - "id": -300 - }, - { - "name": "minecraft:crimson_button", - "id": -260 - }, - { - "name": "minecraft:soul_fire", - "id": -237 - }, - { - "name": "minecraft:polished_basalt", - "id": -235 - }, - { - "name": "minecraft:jungle_button", - "id": -143 - }, - { - "name": "minecraft:birch_pressure_plate", - "id": -151 - }, - { - "name": "minecraft:stripped_warped_stem", - "id": -241 - }, - { - "name": "minecraft:birch_wall_sign", - "id": -187 - }, - { - "name": "minecraft:jungle_trapdoor", - "id": -148 - }, - { - "name": "minecraft:item.kelp", - "id": -138 - }, - { - "name": "minecraft:birch_button", - "id": -141 - }, - { - "name": "minecraft:birch_trapdoor", - "id": -146 - }, - { - "name": "minecraft:bubble_column", - "id": -160 - }, - { - "name": "minecraft:polished_granite_stairs", - "id": -172 - }, - { - "name": "minecraft:polished_andesite_stairs", - "id": -174 - }, - { - "name": "minecraft:end_brick_stairs", - "id": -178 - }, - { - "name": "minecraft:spruce_wall_sign", - "id": -182 - }, - { - "name": "minecraft:chiseled_polished_blackstone", - "id": -279 - }, - { - "name": "minecraft:birch_standing_sign", - "id": -186 - }, - { - "name": "minecraft:darkoak_standing_sign", - "id": -192 - }, - { - "name": "minecraft:darkoak_wall_sign", - "id": -193 - }, - { - "name": "minecraft:lit_smoker", - "id": -199 - }, - { - "name": "minecraft:item.campfire", - "id": -209 - }, - { - "name": "minecraft:bee_nest", - "id": -218 - }, - { - "name": "minecraft:warped_fence_gate", - "id": -259 - }, - { - "name": "minecraft:warped_stem", - "id": -226 - }, - { - "name": "minecraft:blackstone_double_slab", - "id": -283 - }, - { - "name": "minecraft:target", - "id": -239 - }, - { - "name": "minecraft:crimson_trapdoor", - "id": -246 - }, - { - "name": "minecraft:polished_blackstone_brick_wall", - "id": -278 - }, - { - "name": "minecraft:warped_standing_sign", - "id": -251 - }, - { - "name": "minecraft:warped_stairs", - "id": -255 - }, - { - "name": "minecraft:crimson_fence", - "id": -256 - }, - { - "name": "minecraft:warped_pressure_plate", - "id": -263 - }, - { - "name": "minecraft:soul_lantern", - "id": -269 - }, - { - "name": "minecraft:blackstone_wall", - "id": -277 - }, - { - "name": "minecraft:polished_blackstone", - "id": -291 - }, - { - "name": "minecraft:polished_blackstone_stairs", - "id": -292 - }, - { - "name": "minecraft:polished_blackstone_pressure_plate", - "id": -295 - }, - { - "name": "minecraft:warped_hyphae", - "id": -298 - }, - { - "name": "minecraft:crimson_hyphae", - "id": -299 - }, - { - "name": "minecraft:cracked_nether_bricks", - "id": -303 - } -] \ No newline at end of file +[{"oldData":4,"name":"minecraft:acacia_boat","id":377,"oldId":333},{"name":"minecraft:acacia_button","id":-140,"oldId":-140},{"name":"minecraft:acacia_door","id":546,"oldId":430},{"name":"minecraft:acacia_fence_gate","id":187,"oldId":187},{"name":"minecraft:acacia_pressure_plate","id":-150,"oldId":-150},{"name":"minecraft:acacia_sign","id":569,"oldId":475},{"name":"minecraft:acacia_stairs","id":163,"oldId":163},{"name":"minecraft:acacia_standing_sign","id":-190,"oldId":-190},{"name":"minecraft:acacia_trapdoor","id":-145,"oldId":-145},{"name":"minecraft:acacia_wall_sign","id":-191,"oldId":-191},{"name":"minecraft:activator_rail","id":126,"oldId":126},{"name":"minecraft:agent_spawn_egg","id":485,"oldId":383,"oldData":56},{"name":"minecraft:air","id":-158,"oldId":-158},{"name":"minecraft:allow","id":210,"oldId":210},{"name":"minecraft:ancient_debris","id":-271,"oldId":-271},{"name":"minecraft:andesite_stairs","id":-171,"oldId":-171},{"name":"minecraft:anvil","id":145,"oldId":145},{"name":"minecraft:apple","id":257,"oldId":260},{"name":"minecraft:armor_stand","id":542,"oldId":425},{"name":"minecraft:arrow","id":301,"oldId":262},{"name":"minecraft:baked_potato","id":281,"oldId":393},{"name":"minecraft:balloon","id":588,"oldId":448},{"name":"minecraft:bamboo","id":-163,"oldId":-163},{"name":"minecraft:bamboo_sapling","id":-164,"oldId":-164},{"name":"minecraft:banner","id":557,"oldId":446},{"name":"minecraft:banner_pattern","id":614,"oldId":434},{"name":"minecraft:barrel","id":-203,"oldId":-203},{"name":"minecraft:barrier","id":-161,"oldId":-161},{"name":"minecraft:basalt","id":-234,"oldId":-234},{"name":"minecraft:bat_spawn_egg","id":451,"oldId":383,"oldData":19},{"name":"minecraft:beacon","id":138,"oldId":138},{"name":"minecraft:bed","id":416,"oldId":355},{"name":"minecraft:bedrock","id":7,"oldId":7},{"name":"minecraft:bee_nest","id":-218,"oldId":-218},{"name":"minecraft:bee_spawn_egg","id":492,"oldId":383,"oldData":122},{"name":"minecraft:beef","id":273,"oldId":363},{"name":"minecraft:beehive","id":-219,"oldId":-219},{"name":"minecraft:beetroot","id":285,"oldId":457},{"name":"minecraft:beetroot_seeds","id":295,"oldId":458},{"name":"minecraft:beetroot_soup","id":286,"oldId":459},{"name":"minecraft:bell","id":-206,"oldId":-206},{"oldData":2,"name":"minecraft:birch_boat","id":374,"oldId":333},{"name":"minecraft:birch_button","id":-141,"oldId":-141},{"name":"minecraft:birch_door","id":544,"oldId":428},{"name":"minecraft:birch_fence_gate","id":184,"oldId":184},{"name":"minecraft:birch_pressure_plate","id":-151,"oldId":-151},{"name":"minecraft:birch_sign","id":567,"oldId":473},{"name":"minecraft:birch_stairs","id":135,"oldId":135},{"name":"minecraft:birch_standing_sign","id":-186,"oldId":-186},{"name":"minecraft:birch_trapdoor","id":-146,"oldId":-146},{"name":"minecraft:birch_wall_sign","id":-187,"oldId":-187},{"oldData":16,"name":"minecraft:black_dye","id":393,"oldId":351},{"name":"minecraft:black_glazed_terracotta","id":235,"oldId":235},{"name":"minecraft:blackstone","id":-273,"oldId":-273},{"name":"minecraft:blackstone_double_slab","id":-283,"oldId":-283},{"name":"minecraft:blackstone_slab","id":-282,"oldId":-282},{"name":"minecraft:blackstone_stairs","id":-276,"oldId":-276},{"name":"minecraft:blackstone_wall","id":-277,"oldId":-277},{"name":"minecraft:blast_furnace","id":-196,"oldId":-196},{"name":"minecraft:blaze_powder","id":427,"oldId":377},{"name":"minecraft:blaze_rod","id":421,"oldId":369},{"name":"minecraft:blaze_spawn_egg","id":454,"oldId":383,"oldData":43},{"name":"minecraft:bleach","id":586,"oldId":451},{"oldData":18,"name":"minecraft:blue_dye","id":397,"oldId":351},{"name":"minecraft:blue_glazed_terracotta","id":231,"oldId":231},{"name":"minecraft:blue_ice","id":-11,"oldId":-11},{"name":"minecraft:boat","id":612,"oldId":333},{"name":"minecraft:bone","id":413,"oldId":352},{"name":"minecraft:bone_block","id":216,"oldId":216},{"oldData":15,"name":"minecraft:bone_meal","id":409,"oldId":351},{"name":"minecraft:book","id":385,"oldId":340},{"name":"minecraft:bookshelf","id":47,"oldId":47},{"name":"minecraft:border_block","id":212,"oldId":212},{"oldData":5,"name":"minecraft:bordure_indented_banner_pattern","id":576,"oldId":434},{"name":"minecraft:bow","id":300,"oldId":261},{"name":"minecraft:bowl","id":321,"oldId":281},{"name":"minecraft:bread","id":261,"oldId":297},{"name":"minecraft:brewing_stand","id":429,"oldId":379},{"name":"minecraft:brewingstandblock","id":117,"oldId":117},{"name":"minecraft:brick","id":381,"oldId":336},{"name":"minecraft:brick_block","id":45,"oldId":45},{"name":"minecraft:brick_stairs","id":108,"oldId":108},{"oldData":17,"name":"minecraft:brown_dye","id":396,"oldId":351},{"name":"minecraft:brown_glazed_terracotta","id":232,"oldId":232},{"name":"minecraft:brown_mushroom","id":39,"oldId":39},{"name":"minecraft:brown_mushroom_block","id":99,"oldId":99},{"name":"minecraft:bubble_column","id":-160,"oldId":-160},{"oldData":0,"name":"minecraft:bucket","id":360,"oldId":325},{"name":"minecraft:cactus","id":81,"oldId":81},{"name":"minecraft:cake","id":415,"oldId":354},{"name":"minecraft:camera","id":583,"oldId":498},{"name":"minecraft:campfire","id":578,"oldId":720},{"name":"minecraft:carpet","id":171,"oldId":171},{"name":"minecraft:carrot","id":279,"oldId":391},{"name":"minecraft:carrot_on_a_stick","id":507,"oldId":398},{"name":"minecraft:carrots","id":141,"oldId":141},{"name":"minecraft:cartography_table","id":-200,"oldId":-200},{"name":"minecraft:carved_pumpkin","id":-155,"oldId":-155},{"name":"minecraft:cat_spawn_egg","id":486,"oldId":383,"oldData":75},{"name":"minecraft:cauldron","id":430,"oldId":380},{"name":"minecraft:cave_spider_spawn_egg","id":455,"oldId":383,"oldData":40},{"name":"minecraft:chain","id":608,"oldId":758},{"name":"minecraft:chain_command_block","id":189,"oldId":189},{"name":"minecraft:chainmail_boots","id":342,"oldId":305},{"name":"minecraft:chainmail_chestplate","id":340,"oldId":303},{"name":"minecraft:chainmail_helmet","id":339,"oldId":302},{"name":"minecraft:chainmail_leggings","id":341,"oldId":304},{"oldData":1,"name":"minecraft:charcoal","id":303,"oldId":263},{"name":"minecraft:chemical_heat","id":192,"oldId":192},{"name":"minecraft:chemistry_table","id":238,"oldId":238},{"name":"minecraft:chest","id":54,"oldId":54},{"name":"minecraft:chest_minecart","id":387,"oldId":342},{"name":"minecraft:chicken","id":275,"oldId":365},{"name":"minecraft:chicken_spawn_egg","id":433,"oldId":383,"oldData":10},{"name":"minecraft:chiseled_nether_bricks","id":-302,"oldId":-302},{"name":"minecraft:chiseled_polished_blackstone","id":-279,"oldId":-279},{"name":"minecraft:chorus_flower","id":200,"oldId":200},{"name":"minecraft:chorus_fruit","id":548,"oldId":432},{"name":"minecraft:chorus_plant","id":240,"oldId":240},{"name":"minecraft:clay","id":82,"oldId":82},{"name":"minecraft:clay_ball","id":382,"oldId":337},{"name":"minecraft:clock","id":391,"oldId":347},{"oldData":0,"name":"minecraft:coal","id":302,"oldId":263},{"name":"minecraft:coal_block","id":173,"oldId":173},{"name":"minecraft:coal_ore","id":16,"oldId":16},{"name":"minecraft:cobblestone","id":4,"oldId":4},{"name":"minecraft:cobblestone_wall","id":139,"oldId":139},{"name":"minecraft:cocoa","id":127,"oldId":127},{"oldData":3,"name":"minecraft:cocoa_beans","id":410,"oldId":351},{"name":"minecraft:cod","id":264,"oldId":349},{"oldData":2,"name":"minecraft:cod_bucket","id":364,"oldId":325},{"name":"minecraft:cod_spawn_egg","id":478,"oldId":383,"oldData":112},{"name":"minecraft:colored_torch_bp","id":204,"oldId":204},{"name":"minecraft:colored_torch_rg","id":202,"oldId":202},{"name":"minecraft:command_block","id":137,"oldId":137},{"name":"minecraft:command_block_minecart","id":553,"oldId":443},{"name":"minecraft:comparator","id":512,"oldId":404},{"name":"minecraft:compass","id":389,"oldId":345},{"name":"minecraft:composter","id":-213,"oldId":-213},{"name":"minecraft:compound","id":584,"oldId":499},{"name":"minecraft:concrete","id":236,"oldId":236},{"name":"minecraft:concrete_powder","id":237,"oldId":237},{"name":"minecraft:conduit","id":-157,"oldId":-157},{"name":"minecraft:cooked_beef","id":274,"oldId":364},{"name":"minecraft:cooked_chicken","id":276,"oldId":366},{"name":"minecraft:cooked_cod","id":268,"oldId":350},{"name":"minecraft:cooked_mutton","id":541,"oldId":424},{"name":"minecraft:cooked_porkchop","id":263,"oldId":320},{"name":"minecraft:cooked_rabbit","id":289,"oldId":412},{"name":"minecraft:cooked_salmon","id":269,"oldId":463},{"name":"minecraft:cookie","id":271,"oldId":357},{"name":"minecraft:coral","id":-131,"oldId":-131},{"name":"minecraft:coral_block","id":-132,"oldId":-132},{"name":"minecraft:coral_fan","id":-133,"oldId":-133},{"name":"minecraft:coral_fan_dead","id":-134,"oldId":-134},{"name":"minecraft:coral_fan_hang","id":-135,"oldId":-135},{"name":"minecraft:coral_fan_hang2","id":-136,"oldId":-136},{"name":"minecraft:coral_fan_hang3","id":-137,"oldId":-137},{"name":"minecraft:cow_spawn_egg","id":434,"oldId":383,"oldData":11},{"name":"minecraft:cracked_nether_bricks","id":-303,"oldId":-303},{"name":"minecraft:cracked_polished_blackstone_bricks","id":-280,"oldId":-280},{"name":"minecraft:crafting_table","id":58,"oldId":58},{"oldData":0,"name":"minecraft:creeper_banner_pattern","id":572,"oldId":434},{"name":"minecraft:creeper_spawn_egg","id":439,"oldId":383,"oldData":33},{"name":"minecraft:crimson_button","id":-260,"oldId":-260},{"name":"minecraft:crimson_door","id":605,"oldId":755},{"name":"minecraft:crimson_double_slab","id":-266,"oldId":-266},{"name":"minecraft:crimson_fence","id":-256,"oldId":-256},{"name":"minecraft:crimson_fence_gate","id":-258,"oldId":-258},{"name":"minecraft:crimson_fungus","id":-228,"oldId":-228},{"name":"minecraft:crimson_hyphae","id":-299,"oldId":-299},{"name":"minecraft:crimson_nylium","id":-232,"oldId":-232},{"name":"minecraft:crimson_planks","id":-242,"oldId":-242},{"name":"minecraft:crimson_pressure_plate","id":-262,"oldId":-262},{"name":"minecraft:crimson_roots","id":-223,"oldId":-223},{"name":"minecraft:crimson_sign","id":603,"oldId":753},{"name":"minecraft:crimson_slab","id":-264,"oldId":-264},{"name":"minecraft:crimson_stairs","id":-254,"oldId":-254},{"name":"minecraft:crimson_standing_sign","id":-250,"oldId":-250},{"name":"minecraft:crimson_stem","id":-225,"oldId":-225},{"name":"minecraft:crimson_trapdoor","id":-246,"oldId":-246},{"name":"minecraft:crimson_wall_sign","id":-252,"oldId":-252},{"name":"minecraft:crossbow","id":565,"oldId":471},{"name":"minecraft:crying_obsidian","id":-289,"oldId":-289},{"oldData":6,"name":"minecraft:cyan_dye","id":399,"oldId":351},{"name":"minecraft:cyan_glazed_terracotta","id":229,"oldId":229},{"oldData":5,"name":"minecraft:dark_oak_boat","id":378,"oldId":333},{"name":"minecraft:dark_oak_button","id":-142,"oldId":-142},{"name":"minecraft:dark_oak_door","id":547,"oldId":431},{"name":"minecraft:dark_oak_fence_gate","id":186,"oldId":186},{"name":"minecraft:dark_oak_pressure_plate","id":-152,"oldId":-152},{"name":"minecraft:dark_oak_sign","id":570,"oldId":476},{"name":"minecraft:dark_oak_stairs","id":164,"oldId":164},{"name":"minecraft:dark_oak_trapdoor","id":-147,"oldId":-147},{"name":"minecraft:dark_prismarine_stairs","id":-3,"oldId":-3},{"name":"minecraft:darkoak_standing_sign","id":-192,"oldId":-192},{"name":"minecraft:darkoak_wall_sign","id":-193,"oldId":-193},{"name":"minecraft:daylight_detector","id":151,"oldId":151},{"name":"minecraft:daylight_detector_inverted","id":178,"oldId":178},{"name":"minecraft:deadbush","id":32,"oldId":32},{"name":"minecraft:debug_stick","id":580},{"name":"minecraft:deny","id":211,"oldId":211},{"name":"minecraft:detector_rail","id":28,"oldId":28},{"name":"minecraft:diamond","id":304,"oldId":264},{"name":"minecraft:diamond_axe","id":319,"oldId":279},{"name":"minecraft:diamond_block","id":57,"oldId":57},{"name":"minecraft:diamond_boots","id":350,"oldId":313},{"name":"minecraft:diamond_chestplate","id":348,"oldId":311},{"name":"minecraft:diamond_helmet","id":347,"oldId":310},{"name":"minecraft:diamond_hoe","id":332,"oldId":293},{"name":"minecraft:diamond_horse_armor","id":523,"oldId":419},{"name":"minecraft:diamond_leggings","id":349,"oldId":312},{"name":"minecraft:diamond_ore","id":56,"oldId":56},{"name":"minecraft:diamond_pickaxe","id":318,"oldId":278},{"name":"minecraft:diamond_shovel","id":317,"oldId":277},{"name":"minecraft:diamond_sword","id":316,"oldId":276},{"name":"minecraft:diorite_stairs","id":-170,"oldId":-170},{"name":"minecraft:dirt","id":3,"oldId":3},{"name":"minecraft:dispenser","id":23,"oldId":23},{"name":"minecraft:dolphin_spawn_egg","id":482,"oldId":383,"oldData":31},{"name":"minecraft:donkey_spawn_egg","id":463,"oldId":383,"oldData":24},{"name":"minecraft:double_plant","id":175,"oldId":175},{"name":"minecraft:double_stone_slab","id":44,"oldId":44},{"name":"minecraft:double_stone_slab2","id":182,"oldId":182},{"name":"minecraft:double_stone_slab3","id":-162,"oldId":-162},{"name":"minecraft:double_stone_slab4","id":-166,"oldId":-166},{"name":"minecraft:double_wooden_slab","id":157,"oldId":157},{"name":"minecraft:dragon_breath","id":550,"oldId":437},{"name":"minecraft:dragon_egg","id":122,"oldId":122},{"name":"minecraft:dried_kelp","id":270,"oldId":464},{"name":"minecraft:dried_kelp_block","id":-139,"oldId":-139},{"name":"minecraft:dropper","id":125,"oldId":125},{"name":"minecraft:drowned_spawn_egg","id":481,"oldId":383,"oldData":110},{"name":"minecraft:dye","id":613,"oldId":351},{"name":"minecraft:egg","id":388,"oldId":344},{"name":"minecraft:elder_guardian_spawn_egg","id":469,"oldId":383,"oldData":50},{"name":"minecraft:element_0","id":36,"oldId":36},{"name":"minecraft:element_1","id":-12,"oldId":-12},{"name":"minecraft:element_10","id":-21,"oldId":-21},{"name":"minecraft:element_100","id":-111,"oldId":-111},{"name":"minecraft:element_101","id":-112,"oldId":-112},{"name":"minecraft:element_102","id":-113,"oldId":-113},{"name":"minecraft:element_103","id":-114,"oldId":-114},{"name":"minecraft:element_104","id":-115,"oldId":-115},{"name":"minecraft:element_105","id":-116,"oldId":-116},{"name":"minecraft:element_106","id":-117,"oldId":-117},{"name":"minecraft:element_107","id":-118,"oldId":-118},{"name":"minecraft:element_108","id":-119,"oldId":-119},{"name":"minecraft:element_109","id":-120,"oldId":-120},{"name":"minecraft:element_11","id":-22,"oldId":-22},{"name":"minecraft:element_110","id":-121,"oldId":-121},{"name":"minecraft:element_111","id":-122,"oldId":-122},{"name":"minecraft:element_112","id":-123,"oldId":-123},{"name":"minecraft:element_113","id":-124,"oldId":-124},{"name":"minecraft:element_114","id":-125,"oldId":-125},{"name":"minecraft:element_115","id":-126,"oldId":-126},{"name":"minecraft:element_116","id":-127,"oldId":-127},{"name":"minecraft:element_117","id":-128,"oldId":-128},{"name":"minecraft:element_118","id":-129,"oldId":-129},{"name":"minecraft:element_12","id":-23,"oldId":-23},{"name":"minecraft:element_13","id":-24,"oldId":-24},{"name":"minecraft:element_14","id":-25,"oldId":-25},{"name":"minecraft:element_15","id":-26,"oldId":-26},{"name":"minecraft:element_16","id":-27,"oldId":-27},{"name":"minecraft:element_17","id":-28,"oldId":-28},{"name":"minecraft:element_18","id":-29,"oldId":-29},{"name":"minecraft:element_19","id":-30,"oldId":-30},{"name":"minecraft:element_2","id":-13,"oldId":-13},{"name":"minecraft:element_20","id":-31,"oldId":-31},{"name":"minecraft:element_21","id":-32,"oldId":-32},{"name":"minecraft:element_22","id":-33,"oldId":-33},{"name":"minecraft:element_23","id":-34,"oldId":-34},{"name":"minecraft:element_24","id":-35,"oldId":-35},{"name":"minecraft:element_25","id":-36,"oldId":-36},{"name":"minecraft:element_26","id":-37,"oldId":-37},{"name":"minecraft:element_27","id":-38,"oldId":-38},{"name":"minecraft:element_28","id":-39,"oldId":-39},{"name":"minecraft:element_29","id":-40,"oldId":-40},{"name":"minecraft:element_3","id":-14,"oldId":-14},{"name":"minecraft:element_30","id":-41,"oldId":-41},{"name":"minecraft:element_31","id":-42,"oldId":-42},{"name":"minecraft:element_32","id":-43,"oldId":-43},{"name":"minecraft:element_33","id":-44,"oldId":-44},{"name":"minecraft:element_34","id":-45,"oldId":-45},{"name":"minecraft:element_35","id":-46,"oldId":-46},{"name":"minecraft:element_36","id":-47,"oldId":-47},{"name":"minecraft:element_37","id":-48,"oldId":-48},{"name":"minecraft:element_38","id":-49,"oldId":-49},{"name":"minecraft:element_39","id":-50,"oldId":-50},{"name":"minecraft:element_4","id":-15,"oldId":-15},{"name":"minecraft:element_40","id":-51,"oldId":-51},{"name":"minecraft:element_41","id":-52,"oldId":-52},{"name":"minecraft:element_42","id":-53,"oldId":-53},{"name":"minecraft:element_43","id":-54,"oldId":-54},{"name":"minecraft:element_44","id":-55,"oldId":-55},{"name":"minecraft:element_45","id":-56,"oldId":-56},{"name":"minecraft:element_46","id":-57,"oldId":-57},{"name":"minecraft:element_47","id":-58,"oldId":-58},{"name":"minecraft:element_48","id":-59,"oldId":-59},{"name":"minecraft:element_49","id":-60,"oldId":-60},{"name":"minecraft:element_5","id":-16,"oldId":-16},{"name":"minecraft:element_50","id":-61,"oldId":-61},{"name":"minecraft:element_51","id":-62,"oldId":-62},{"name":"minecraft:element_52","id":-63,"oldId":-63},{"name":"minecraft:element_53","id":-64,"oldId":-64},{"name":"minecraft:element_54","id":-65,"oldId":-65},{"name":"minecraft:element_55","id":-66,"oldId":-66},{"name":"minecraft:element_56","id":-67,"oldId":-67},{"name":"minecraft:element_57","id":-68,"oldId":-68},{"name":"minecraft:element_58","id":-69,"oldId":-69},{"name":"minecraft:element_59","id":-70,"oldId":-70},{"name":"minecraft:element_6","id":-17,"oldId":-17},{"name":"minecraft:element_60","id":-71,"oldId":-71},{"name":"minecraft:element_61","id":-72,"oldId":-72},{"name":"minecraft:element_62","id":-73,"oldId":-73},{"name":"minecraft:element_63","id":-74,"oldId":-74},{"name":"minecraft:element_64","id":-75,"oldId":-75},{"name":"minecraft:element_65","id":-76,"oldId":-76},{"name":"minecraft:element_66","id":-77,"oldId":-77},{"name":"minecraft:element_67","id":-78,"oldId":-78},{"name":"minecraft:element_68","id":-79,"oldId":-79},{"name":"minecraft:element_69","id":-80,"oldId":-80},{"name":"minecraft:element_7","id":-18,"oldId":-18},{"name":"minecraft:element_70","id":-81,"oldId":-81},{"name":"minecraft:element_71","id":-82,"oldId":-82},{"name":"minecraft:element_72","id":-83,"oldId":-83},{"name":"minecraft:element_73","id":-84,"oldId":-84},{"name":"minecraft:element_74","id":-85,"oldId":-85},{"name":"minecraft:element_75","id":-86,"oldId":-86},{"name":"minecraft:element_76","id":-87,"oldId":-87},{"name":"minecraft:element_77","id":-88,"oldId":-88},{"name":"minecraft:element_78","id":-89,"oldId":-89},{"name":"minecraft:element_79","id":-90,"oldId":-90},{"name":"minecraft:element_8","id":-19,"oldId":-19},{"name":"minecraft:element_80","id":-91,"oldId":-91},{"name":"minecraft:element_81","id":-92,"oldId":-92},{"name":"minecraft:element_82","id":-93,"oldId":-93},{"name":"minecraft:element_83","id":-94,"oldId":-94},{"name":"minecraft:element_84","id":-95,"oldId":-95},{"name":"minecraft:element_85","id":-96,"oldId":-96},{"name":"minecraft:element_86","id":-97,"oldId":-97},{"name":"minecraft:element_87","id":-98,"oldId":-98},{"name":"minecraft:element_88","id":-99,"oldId":-99},{"name":"minecraft:element_89","id":-100,"oldId":-100},{"name":"minecraft:element_9","id":-20,"oldId":-20},{"name":"minecraft:element_90","id":-101,"oldId":-101},{"name":"minecraft:element_91","id":-102,"oldId":-102},{"name":"minecraft:element_92","id":-103,"oldId":-103},{"name":"minecraft:element_93","id":-104,"oldId":-104},{"name":"minecraft:element_94","id":-105,"oldId":-105},{"name":"minecraft:element_95","id":-106,"oldId":-106},{"name":"minecraft:element_96","id":-107,"oldId":-107},{"name":"minecraft:element_97","id":-108,"oldId":-108},{"name":"minecraft:element_98","id":-109,"oldId":-109},{"name":"minecraft:element_99","id":-110,"oldId":-110},{"name":"minecraft:elytra","id":554,"oldId":444},{"name":"minecraft:emerald","id":502,"oldId":388},{"name":"minecraft:emerald_block","id":133,"oldId":133},{"name":"minecraft:emerald_ore","id":129,"oldId":129},{"name":"minecraft:empty_map","id":505,"oldId":395},{"name":"minecraft:enchanted_book","id":511,"oldId":403},{"name":"minecraft:enchanted_golden_apple","id":259,"oldId":466},{"name":"minecraft:enchanting_table","id":116,"oldId":116},{"name":"minecraft:end_brick_stairs","id":-178,"oldId":-178},{"name":"minecraft:end_bricks","id":206,"oldId":206},{"name":"minecraft:end_crystal","id":616,"oldId":426},{"name":"minecraft:end_gateway","id":209,"oldId":209},{"name":"minecraft:end_portal","id":119,"oldId":119},{"name":"minecraft:end_portal_frame","id":120,"oldId":120},{"name":"minecraft:end_rod","id":208,"oldId":208},{"name":"minecraft:end_stone","id":121,"oldId":121},{"name":"minecraft:ender_chest","id":130,"oldId":130},{"name":"minecraft:ender_eye","id":431,"oldId":381},{"name":"minecraft:ender_pearl","id":420,"oldId":368},{"name":"minecraft:enderman_spawn_egg","id":440,"oldId":383,"oldData":38},{"name":"minecraft:endermite_spawn_egg","id":458,"oldId":383,"oldData":55},{"name":"minecraft:evoker_spawn_egg","id":473,"oldId":383,"oldData":104},{"name":"minecraft:experience_bottle","id":498,"oldId":384},{"name":"minecraft:farmland","id":60,"oldId":60},{"name":"minecraft:feather","id":327,"oldId":288},{"name":"minecraft:fence","id":85,"oldId":85},{"name":"minecraft:fence_gate","id":107,"oldId":107},{"name":"minecraft:fermented_spider_eye","id":426,"oldId":376},{"oldData":4,"name":"minecraft:field_masoned_banner_pattern","id":575,"oldId":434},{"name":"minecraft:filled_map","id":418,"oldId":358},{"name":"minecraft:fire","id":51,"oldId":51},{"name":"minecraft:fire_charge","id":499,"oldId":385},{"name":"minecraft:firework_rocket","id":509,"oldId":401},{"name":"minecraft:firework_star","id":510,"oldId":402},{"name":"minecraft:fishing_rod","id":390,"oldId":346},{"name":"minecraft:fletching_table","id":-201,"oldId":-201},{"name":"minecraft:flint","id":356,"oldId":318},{"name":"minecraft:flint_and_steel","id":299,"oldId":259},{"oldData":2,"name":"minecraft:flower_banner_pattern","id":571,"oldId":434},{"name":"minecraft:flower_pot","id":504,"oldId":390},{"name":"minecraft:flowing_lava","id":10,"oldId":10},{"name":"minecraft:flowing_water","id":8,"oldId":8},{"name":"minecraft:fox_spawn_egg","id":488,"oldId":383,"oldData":121},{"name":"minecraft:frame","id":503,"oldId":389},{"name":"minecraft:frosted_ice","id":207,"oldId":207},{"name":"minecraft:furnace","id":61,"oldId":61},{"name":"minecraft:ghast_spawn_egg","id":452,"oldId":383,"oldData":41},{"name":"minecraft:ghast_tear","id":422,"oldId":370},{"name":"minecraft:gilded_blackstone","id":-281,"oldId":-281},{"name":"minecraft:glass","id":20,"oldId":20},{"name":"minecraft:glass_bottle","id":425,"oldId":374},{"name":"minecraft:glass_pane","id":102,"oldId":102},{"name":"minecraft:glistering_melon_slice","id":432,"oldId":382},{"name":"minecraft:glow_stick","id":166,"oldId":166},{"name":"minecraft:glowingobsidian","id":246,"oldId":246},{"name":"minecraft:glowstone","id":89,"oldId":89},{"name":"minecraft:glowstone_dust","id":392,"oldId":348},{"name":"minecraft:gold_block","id":41,"oldId":41},{"name":"minecraft:gold_ingot","id":306,"oldId":266},{"name":"minecraft:gold_nugget","id":423,"oldId":371},{"name":"minecraft:gold_ore","id":14,"oldId":14},{"name":"minecraft:golden_apple","id":258,"oldId":322},{"name":"minecraft:golden_axe","id":325,"oldId":286},{"name":"minecraft:golden_boots","id":354,"oldId":317},{"name":"minecraft:golden_carrot","id":283,"oldId":396},{"name":"minecraft:golden_chestplate","id":352,"oldId":315},{"name":"minecraft:golden_helmet","id":351,"oldId":314},{"name":"minecraft:golden_hoe","id":333,"oldId":294},{"name":"minecraft:golden_horse_armor","id":522,"oldId":418},{"name":"minecraft:golden_leggings","id":353,"oldId":316},{"name":"minecraft:golden_pickaxe","id":324,"oldId":285},{"name":"minecraft:golden_rail","id":27,"oldId":27},{"name":"minecraft:golden_shovel","id":323,"oldId":284},{"name":"minecraft:golden_sword","id":322,"oldId":283},{"name":"minecraft:granite_stairs","id":-169,"oldId":-169},{"name":"minecraft:grass","id":2,"oldId":2},{"name":"minecraft:grass_path","id":198,"oldId":198},{"name":"minecraft:gravel","id":13,"oldId":13},{"oldData":8,"name":"minecraft:gray_dye","id":401,"oldId":351},{"name":"minecraft:gray_glazed_terracotta","id":227,"oldId":227},{"oldData":2,"name":"minecraft:green_dye","id":395,"oldId":351},{"name":"minecraft:green_glazed_terracotta","id":233,"oldId":233},{"name":"minecraft:grindstone","id":-195,"oldId":-195},{"name":"minecraft:guardian_spawn_egg","id":459,"oldId":383,"oldData":49},{"name":"minecraft:gunpowder","id":328,"oldId":289},{"name":"minecraft:hard_glass","id":253,"oldId":253},{"name":"minecraft:hard_glass_pane","id":190,"oldId":190},{"name":"minecraft:hard_stained_glass","id":254,"oldId":254},{"name":"minecraft:hard_stained_glass_pane","id":191,"oldId":191},{"name":"minecraft:hardened_clay","id":172,"oldId":172},{"name":"minecraft:hay_block","id":170,"oldId":170},{"name":"minecraft:heart_of_the_sea","id":561,"oldId":467},{"name":"minecraft:heavy_weighted_pressure_plate","id":148,"oldId":148},{"name":"minecraft:hoglin_spawn_egg","id":494,"oldId":383,"oldData":124},{"name":"minecraft:honey_block","id":-220,"oldId":-220},{"name":"minecraft:honey_bottle","id":582,"oldId":737},{"name":"minecraft:honeycomb","id":581,"oldId":736},{"name":"minecraft:honeycomb_block","id":-221,"oldId":-221},{"name":"minecraft:hopper","id":517,"oldId":410},{"name":"minecraft:hopper_minecart","id":516,"oldId":408},{"name":"minecraft:horse_spawn_egg","id":456,"oldId":383,"oldData":23},{"name":"minecraft:husk_spawn_egg","id":461,"oldId":383,"oldData":47},{"name":"minecraft:ice","id":79,"oldId":79},{"name":"minecraft:ice_bomb","id":585,"oldId":453},{"name":"minecraft:info_update","id":248,"oldId":248},{"name":"minecraft:info_update2","id":249,"oldId":249},{"oldData":0,"name":"minecraft:ink_sac","id":411,"oldId":351},{"name":"minecraft:invisiblebedrock","id":95,"oldId":95},{"name":"minecraft:iron_axe","id":298,"oldId":258},{"name":"minecraft:iron_bars","id":101,"oldId":101},{"name":"minecraft:iron_block","id":42,"oldId":42},{"name":"minecraft:iron_boots","id":346,"oldId":309},{"name":"minecraft:iron_chestplate","id":344,"oldId":307},{"name":"minecraft:iron_door","id":370,"oldId":330},{"name":"minecraft:iron_helmet","id":343,"oldId":306},{"name":"minecraft:iron_hoe","id":331,"oldId":292},{"name":"minecraft:iron_horse_armor","id":521,"oldId":417},{"name":"minecraft:iron_ingot","id":305,"oldId":265},{"name":"minecraft:iron_leggings","id":345,"oldId":308},{"name":"minecraft:iron_nugget","id":559,"oldId":452},{"name":"minecraft:iron_ore","id":15,"oldId":15},{"name":"minecraft:iron_pickaxe","id":297,"oldId":257},{"name":"minecraft:iron_shovel","id":296,"oldId":256},{"name":"minecraft:iron_sword","id":307,"oldId":267},{"name":"minecraft:iron_trapdoor","id":167,"oldId":167},{"name":"minecraft:item.acacia_door","id":196,"oldId":196},{"name":"minecraft:item.bed","id":26,"oldId":26},{"name":"minecraft:item.beetroot","id":244,"oldId":244},{"name":"minecraft:item.birch_door","id":194,"oldId":194},{"name":"minecraft:item.cake","id":92,"oldId":92},{"name":"minecraft:item.camera","id":242,"oldId":242},{"name":"minecraft:item.campfire","id":-209,"oldId":-209},{"name":"minecraft:item.cauldron","id":118,"oldId":118},{"name":"minecraft:item.chain","id":-286,"oldId":-286},{"name":"minecraft:item.crimson_door","id":-244,"oldId":-244},{"name":"minecraft:item.dark_oak_door","id":197,"oldId":197},{"name":"minecraft:item.flower_pot","id":140,"oldId":140},{"name":"minecraft:item.frame","id":199,"oldId":199},{"name":"minecraft:item.hopper","id":154,"oldId":154},{"name":"minecraft:item.iron_door","id":71,"oldId":71},{"name":"minecraft:item.jungle_door","id":195,"oldId":195},{"name":"minecraft:item.kelp","id":-138,"oldId":-138},{"name":"minecraft:item.nether_sprouts","id":-238,"oldId":-238},{"name":"minecraft:item.nether_wart","id":115,"oldId":115},{"name":"minecraft:item.reeds","id":83,"oldId":83},{"name":"minecraft:item.skull","id":144,"oldId":144},{"name":"minecraft:item.soul_campfire","id":-290,"oldId":-290},{"name":"minecraft:item.spruce_door","id":193,"oldId":193},{"name":"minecraft:item.warped_door","id":-245,"oldId":-245},{"name":"minecraft:item.wheat","id":59,"oldId":59},{"name":"minecraft:item.wooden_door","id":64,"oldId":64},{"name":"minecraft:jigsaw","id":-211,"oldId":-211},{"name":"minecraft:jukebox","id":84,"oldId":84},{"oldData":3,"name":"minecraft:jungle_boat","id":375,"oldId":333},{"name":"minecraft:jungle_button","id":-143,"oldId":-143},{"name":"minecraft:jungle_door","id":545,"oldId":429},{"name":"minecraft:jungle_fence_gate","id":185,"oldId":185},{"name":"minecraft:jungle_pressure_plate","id":-153,"oldId":-153},{"name":"minecraft:jungle_sign","id":568,"oldId":474},{"name":"minecraft:jungle_stairs","id":136,"oldId":136},{"name":"minecraft:jungle_standing_sign","id":-188,"oldId":-188},{"name":"minecraft:jungle_trapdoor","id":-148,"oldId":-148},{"name":"minecraft:jungle_wall_sign","id":-189,"oldId":-189},{"name":"minecraft:kelp","id":380,"oldId":335},{"name":"minecraft:ladder","id":65,"oldId":65},{"name":"minecraft:lantern","id":-208,"oldId":-208},{"name":"minecraft:lapis_block","id":22,"oldId":22},{"oldData":4,"name":"minecraft:lapis_lazuli","id":412,"oldId":351},{"name":"minecraft:lapis_ore","id":21,"oldId":21},{"name":"minecraft:lava","id":11,"oldId":11},{"oldData":10,"name":"minecraft:lava_bucket","id":363,"oldId":325},{"name":"minecraft:lava_cauldron","id":-210,"oldId":-210},{"name":"minecraft:lead","id":537,"oldId":420},{"name":"minecraft:leather","id":379,"oldId":334},{"name":"minecraft:leather_boots","id":338,"oldId":301},{"name":"minecraft:leather_chestplate","id":336,"oldId":299},{"name":"minecraft:leather_helmet","id":335,"oldId":298},{"name":"minecraft:leather_horse_armor","id":520,"oldId":416},{"name":"minecraft:leather_leggings","id":337,"oldId":300},{"name":"minecraft:leaves","id":18,"oldId":18},{"name":"minecraft:leaves2","id":161,"oldId":161},{"name":"minecraft:lectern","id":-194,"oldId":-194},{"name":"minecraft:lever","id":69,"oldId":69},{"name":"minecraft:light_block","id":-215,"oldId":-215},{"oldData":12,"name":"minecraft:light_blue_dye","id":405,"oldId":351},{"name":"minecraft:light_blue_glazed_terracotta","id":223,"oldId":223},{"oldData":7,"name":"minecraft:light_gray_dye","id":400,"oldId":351},{"name":"minecraft:light_weighted_pressure_plate","id":147,"oldId":147},{"oldData":10,"name":"minecraft:lime_dye","id":403,"oldId":351},{"name":"minecraft:lime_glazed_terracotta","id":225,"oldId":225},{"name":"minecraft:lingering_potion","id":552,"oldId":441},{"name":"minecraft:lit_blast_furnace","id":-214,"oldId":-214},{"name":"minecraft:lit_furnace","id":62,"oldId":62},{"name":"minecraft:lit_pumpkin","id":91,"oldId":91},{"name":"minecraft:lit_redstone_lamp","id":124,"oldId":124},{"name":"minecraft:lit_redstone_ore","id":74,"oldId":74},{"name":"minecraft:lit_smoker","id":-199,"oldId":-199},{"name":"minecraft:llama_spawn_egg","id":471,"oldId":383,"oldData":29},{"name":"minecraft:lodestone","id":-222,"oldId":-222},{"name":"minecraft:lodestone_compass","id":591,"oldId":741},{"name":"minecraft:log","id":17,"oldId":17},{"name":"minecraft:log2","id":162,"oldId":162},{"name":"minecraft:loom","id":-204,"oldId":-204},{"oldData":13,"name":"minecraft:magenta_dye","id":406,"oldId":351},{"name":"minecraft:magenta_glazed_terracotta","id":222,"oldId":222},{"name":"minecraft:magma","id":213,"oldId":213},{"name":"minecraft:magma_cream","id":428,"oldId":378},{"name":"minecraft:magma_cube_spawn_egg","id":453,"oldId":383,"oldData":42},{"name":"minecraft:medicine","id":589,"oldId":447},{"name":"minecraft:melon_block","id":103,"oldId":103},{"name":"minecraft:melon_seeds","id":293,"oldId":362},{"name":"minecraft:melon_slice","id":272,"oldId":360},{"name":"minecraft:melon_stem","id":105,"oldId":105},{"oldData":1,"name":"minecraft:milk_bucket","id":361,"oldId":325},{"name":"minecraft:minecart","id":368,"oldId":328},{"name":"minecraft:mob_spawner","id":52,"oldId":52},{"oldData":3,"name":"minecraft:mojang_banner_pattern","id":574,"oldId":434},{"name":"minecraft:monster_egg","id":97,"oldId":97},{"name":"minecraft:mooshroom_spawn_egg","id":438,"oldId":383,"oldData":16},{"name":"minecraft:mossy_cobblestone","id":48,"oldId":48},{"name":"minecraft:mossy_cobblestone_stairs","id":-179,"oldId":-179},{"name":"minecraft:mossy_stone_brick_stairs","id":-175,"oldId":-175},{"name":"minecraft:movingblock","id":250,"oldId":250},{"name":"minecraft:mule_spawn_egg","id":464,"oldId":383,"oldData":25},{"name":"minecraft:mushroom_stew","id":260,"oldId":282},{"name":"minecraft:music_disc_11","id":534,"oldId":510},{"name":"minecraft:music_disc_13","id":524,"oldId":500},{"name":"minecraft:music_disc_blocks","id":526,"oldId":502},{"name":"minecraft:music_disc_cat","id":525,"oldId":501},{"name":"minecraft:music_disc_chirp","id":527,"oldId":503},{"name":"minecraft:music_disc_far","id":528,"oldId":504},{"name":"minecraft:music_disc_mall","id":529,"oldId":505},{"name":"minecraft:music_disc_mellohi","id":530,"oldId":506},{"name":"minecraft:music_disc_pigstep","id":609,"oldId":759},{"name":"minecraft:music_disc_stal","id":531,"oldId":507},{"name":"minecraft:music_disc_strad","id":532,"oldId":508},{"name":"minecraft:music_disc_wait","id":535,"oldId":511},{"name":"minecraft:music_disc_ward","id":533,"oldId":509},{"name":"minecraft:mutton","id":540,"oldId":423},{"name":"minecraft:mycelium","id":110,"oldId":110},{"name":"minecraft:name_tag","id":538,"oldId":421},{"name":"minecraft:nautilus_shell","id":560,"oldId":465},{"name":"minecraft:nether_brick","id":112,"oldId":112},{"name":"minecraft:nether_brick_fence","id":113,"oldId":113},{"name":"minecraft:nether_brick_stairs","id":114,"oldId":114},{"name":"minecraft:nether_gold_ore","id":-288,"oldId":-288},{"name":"minecraft:nether_sprouts","id":610,"oldId":760},{"name":"minecraft:nether_star","id":508,"oldId":399},{"name":"minecraft:nether_wart","id":294,"oldId":372},{"name":"minecraft:nether_wart_block","id":214,"oldId":214},{"name":"minecraft:netherbrick","id":513,"oldId":405},{"name":"minecraft:netherite_axe","id":596,"oldId":746},{"name":"minecraft:netherite_block","id":-270,"oldId":-270},{"name":"minecraft:netherite_boots","id":601,"oldId":751},{"name":"minecraft:netherite_chestplate","id":599,"oldId":749},{"name":"minecraft:netherite_helmet","id":598,"oldId":748},{"name":"minecraft:netherite_hoe","id":597,"oldId":747},{"name":"minecraft:netherite_ingot","id":592,"oldId":742},{"name":"minecraft:netherite_leggings","id":600,"oldId":750},{"name":"minecraft:netherite_pickaxe","id":595,"oldId":745},{"name":"minecraft:netherite_scrap","id":602,"oldId":752},{"name":"minecraft:netherite_shovel","id":594,"oldId":744},{"name":"minecraft:netherite_sword","id":593,"oldId":743},{"name":"minecraft:netherrack","id":87,"oldId":87},{"name":"minecraft:netherreactor","id":247,"oldId":247},{"name":"minecraft:normal_stone_stairs","id":-180,"oldId":-180},{"name":"minecraft:noteblock","id":25,"oldId":25},{"name":"minecraft:npc_spawn_egg","id":468,"oldId":383,"oldData":51},{"oldData":0,"name":"minecraft:oak_boat","id":373,"oldId":333},{"name":"minecraft:oak_sign","id":358,"oldId":323},{"name":"minecraft:oak_stairs","id":53,"oldId":53},{"name":"minecraft:observer","id":251,"oldId":251},{"name":"minecraft:obsidian","id":49,"oldId":49},{"name":"minecraft:ocelot_spawn_egg","id":449,"oldId":383,"oldData":22},{"oldData":14,"name":"minecraft:orange_dye","id":407,"oldId":351},{"name":"minecraft:orange_glazed_terracotta","id":221,"oldId":221},{"name":"minecraft:packed_ice","id":174,"oldId":174},{"name":"minecraft:painting","id":357,"oldId":321},{"name":"minecraft:panda_spawn_egg","id":487,"oldId":383,"oldData":113},{"name":"minecraft:paper","id":384,"oldId":339},{"name":"minecraft:parrot_spawn_egg","id":476,"oldId":383,"oldData":30},{"name":"minecraft:phantom_membrane","id":564,"oldId":470},{"name":"minecraft:phantom_spawn_egg","id":484,"oldId":383,"oldData":58},{"name":"minecraft:pig_spawn_egg","id":435,"oldId":383,"oldData":12},{"oldData":6,"name":"minecraft:piglin_banner_pattern","id":577,"oldId":434},{"name":"minecraft:piglin_brute_spawn_egg","id":497,"oldId":383,"oldData":127},{"name":"minecraft:piglin_spawn_egg","id":495,"oldId":383,"oldData":123},{"name":"minecraft:pillager_spawn_egg","id":489,"oldId":383,"oldData":114},{"oldData":9,"name":"minecraft:pink_dye","id":402,"oldId":351},{"name":"minecraft:pink_glazed_terracotta","id":226,"oldId":226},{"name":"minecraft:piston","id":33,"oldId":33},{"name":"minecraft:pistonarmcollision","id":34,"oldId":34},{"name":"minecraft:planks","id":5,"oldId":5},{"name":"minecraft:podzol","id":243,"oldId":243},{"name":"minecraft:poisonous_potato","id":282,"oldId":394},{"name":"minecraft:polar_bear_spawn_egg","id":470,"oldId":383,"oldData":28},{"name":"minecraft:polished_andesite_stairs","id":-174,"oldId":-174},{"name":"minecraft:polished_basalt","id":-235,"oldId":-235},{"name":"minecraft:polished_blackstone","id":-291,"oldId":-291},{"name":"minecraft:polished_blackstone_brick_double_slab","id":-285,"oldId":-285},{"name":"minecraft:polished_blackstone_brick_slab","id":-284,"oldId":-284},{"name":"minecraft:polished_blackstone_brick_stairs","id":-275,"oldId":-275},{"name":"minecraft:polished_blackstone_brick_wall","id":-278,"oldId":-278},{"name":"minecraft:polished_blackstone_bricks","id":-274,"oldId":-274},{"name":"minecraft:polished_blackstone_button","id":-296,"oldId":-296},{"name":"minecraft:polished_blackstone_double_slab","id":-294,"oldId":-294},{"name":"minecraft:polished_blackstone_pressure_plate","id":-295,"oldId":-295},{"name":"minecraft:polished_blackstone_slab","id":-293,"oldId":-293},{"name":"minecraft:polished_blackstone_stairs","id":-292,"oldId":-292},{"name":"minecraft:polished_blackstone_wall","id":-297,"oldId":-297},{"name":"minecraft:polished_diorite_stairs","id":-173,"oldId":-173},{"name":"minecraft:polished_granite_stairs","id":-172,"oldId":-172},{"name":"minecraft:popped_chorus_fruit","id":549,"oldId":433},{"name":"minecraft:porkchop","id":262,"oldId":319},{"name":"minecraft:portal","id":90,"oldId":90},{"name":"minecraft:potato","id":280,"oldId":392},{"name":"minecraft:potatoes","id":142,"oldId":142},{"name":"minecraft:potion","id":424,"oldId":373},{"name":"minecraft:powered_comparator","id":150,"oldId":150},{"name":"minecraft:powered_repeater","id":94,"oldId":94},{"name":"minecraft:prismarine","id":168,"oldId":168},{"name":"minecraft:prismarine_bricks_stairs","id":-4,"oldId":-4},{"name":"minecraft:prismarine_crystals","id":539,"oldId":422},{"name":"minecraft:prismarine_shard","id":555,"oldId":409},{"name":"minecraft:prismarine_stairs","id":-2,"oldId":-2},{"name":"minecraft:pufferfish","id":267,"oldId":462},{"oldData":5,"name":"minecraft:pufferfish_bucket","id":367,"oldId":325},{"name":"minecraft:pufferfish_spawn_egg","id":479,"oldId":383,"oldData":108},{"name":"minecraft:pumpkin","id":86,"oldId":86},{"name":"minecraft:pumpkin_pie","id":284,"oldId":400},{"name":"minecraft:pumpkin_seeds","id":292,"oldId":361},{"name":"minecraft:pumpkin_stem","id":104,"oldId":104},{"oldData":5,"name":"minecraft:purple_dye","id":398,"oldId":351},{"name":"minecraft:purple_glazed_terracotta","id":219,"oldId":219},{"name":"minecraft:purpur_block","id":201,"oldId":201},{"name":"minecraft:purpur_stairs","id":203,"oldId":203},{"name":"minecraft:quartz","id":514,"oldId":406},{"name":"minecraft:quartz_block","id":155,"oldId":155},{"name":"minecraft:quartz_bricks","id":-304,"oldId":-304},{"name":"minecraft:quartz_ore","id":153,"oldId":153},{"name":"minecraft:quartz_stairs","id":156,"oldId":156},{"name":"minecraft:rabbit","id":288,"oldId":411},{"name":"minecraft:rabbit_foot","id":518,"oldId":414},{"name":"minecraft:rabbit_hide","id":519,"oldId":415},{"name":"minecraft:rabbit_spawn_egg","id":457,"oldId":383,"oldData":18},{"name":"minecraft:rabbit_stew","id":290,"oldId":413},{"name":"minecraft:rail","id":66,"oldId":66},{"name":"minecraft:rapid_fertilizer","id":587,"oldId":449},{"name":"minecraft:ravager_spawn_egg","id":491,"oldId":383,"oldData":59},{"name":"minecraft:real_double_stone_slab","id":43,"oldId":43},{"name":"minecraft:real_double_stone_slab2","id":181,"oldId":181},{"name":"minecraft:real_double_stone_slab3","id":-167,"oldId":-167},{"name":"minecraft:real_double_stone_slab4","id":-168,"oldId":-168},{"oldData":1,"name":"minecraft:red_dye","id":394,"oldId":351},{"name":"minecraft:red_flower","id":38,"oldId":38},{"name":"minecraft:red_glazed_terracotta","id":234,"oldId":234},{"name":"minecraft:red_mushroom","id":40,"oldId":40},{"name":"minecraft:red_mushroom_block","id":100,"oldId":100},{"name":"minecraft:red_nether_brick","id":215,"oldId":215},{"name":"minecraft:red_nether_brick_stairs","id":-184,"oldId":-184},{"name":"minecraft:red_sandstone","id":179,"oldId":179},{"name":"minecraft:red_sandstone_stairs","id":180,"oldId":180},{"name":"minecraft:redstone","id":371,"oldId":331},{"name":"minecraft:redstone_block","id":152,"oldId":152},{"name":"minecraft:redstone_lamp","id":123,"oldId":123},{"name":"minecraft:redstone_ore","id":73,"oldId":73},{"name":"minecraft:redstone_torch","id":76,"oldId":76},{"name":"minecraft:redstone_wire","id":55,"oldId":55},{"name":"minecraft:repeater","id":417,"oldId":356},{"name":"minecraft:repeating_command_block","id":188,"oldId":188},{"name":"minecraft:reserved6","id":255,"oldId":255},{"name":"minecraft:respawn_anchor","id":-272,"oldId":-272},{"name":"minecraft:rotten_flesh","id":277,"oldId":367},{"name":"minecraft:saddle","id":369,"oldId":329},{"name":"minecraft:salmon","id":265,"oldId":460},{"oldData":3,"name":"minecraft:salmon_bucket","id":365,"oldId":325},{"name":"minecraft:salmon_spawn_egg","id":480,"oldId":383,"oldData":109},{"name":"minecraft:sand","id":12,"oldId":12},{"name":"minecraft:sandstone","id":24,"oldId":24},{"name":"minecraft:sandstone_stairs","id":128,"oldId":128},{"name":"minecraft:sapling","id":6,"oldId":6},{"name":"minecraft:scaffolding","id":-165,"oldId":-165},{"name":"minecraft:scute","id":562,"oldId":468},{"name":"minecraft:sea_pickle","id":-156,"oldId":-156},{"name":"minecraft:seagrass","id":-130,"oldId":-130},{"name":"minecraft:sealantern","id":169,"oldId":169},{"name":"minecraft:shears","id":419,"oldId":359},{"name":"minecraft:sheep_spawn_egg","id":436,"oldId":383,"oldData":13},{"name":"minecraft:shield","id":355,"oldId":513},{"name":"minecraft:shroomlight","id":-230,"oldId":-230},{"name":"minecraft:shulker_box","id":218,"oldId":218},{"name":"minecraft:shulker_shell","id":556,"oldId":445},{"name":"minecraft:shulker_spawn_egg","id":467,"oldId":383,"oldData":54},{"name":"minecraft:silver_glazed_terracotta","id":228,"oldId":228},{"name":"minecraft:silverfish_spawn_egg","id":441,"oldId":383,"oldData":39},{"name":"minecraft:skeleton_horse_spawn_egg","id":465,"oldId":383,"oldData":26},{"name":"minecraft:skeleton_spawn_egg","id":442,"oldId":383,"oldData":34},{"name":"minecraft:skull","id":506,"oldId":397},{"oldData":1,"name":"minecraft:skull_banner_pattern","id":573,"oldId":434},{"name":"minecraft:slime","id":165,"oldId":165},{"name":"minecraft:slime_ball","id":386,"oldId":341},{"name":"minecraft:slime_spawn_egg","id":443,"oldId":383,"oldData":37},{"name":"minecraft:smithing_table","id":-202,"oldId":-202},{"name":"minecraft:smoker","id":-198,"oldId":-198},{"name":"minecraft:smooth_quartz_stairs","id":-185,"oldId":-185},{"name":"minecraft:smooth_red_sandstone_stairs","id":-176,"oldId":-176},{"name":"minecraft:smooth_sandstone_stairs","id":-177,"oldId":-177},{"name":"minecraft:smooth_stone","id":-183,"oldId":-183},{"name":"minecraft:snow","id":80,"oldId":80},{"name":"minecraft:snow_layer","id":78,"oldId":78},{"name":"minecraft:snowball","id":372,"oldId":332},{"name":"minecraft:soul_campfire","id":611,"oldId":801},{"name":"minecraft:soul_fire","id":-237,"oldId":-237},{"name":"minecraft:soul_lantern","id":-269,"oldId":-269},{"name":"minecraft:soul_sand","id":88,"oldId":88},{"name":"minecraft:soul_soil","id":-236,"oldId":-236},{"name":"minecraft:soul_torch","id":-268,"oldId":-268},{"name":"minecraft:sparkler","id":590,"oldId":442},{"name":"minecraft:spawn_egg","id":615,"oldId":383},{"name":"minecraft:spider_eye","id":278,"oldId":375},{"name":"minecraft:spider_spawn_egg","id":444,"oldId":383,"oldData":35},{"name":"minecraft:splash_potion","id":551,"oldId":438},{"name":"minecraft:sponge","id":19,"oldId":19},{"oldData":1,"name":"minecraft:spruce_boat","id":376,"oldId":333},{"name":"minecraft:spruce_button","id":-144,"oldId":-144},{"name":"minecraft:spruce_door","id":543,"oldId":427},{"name":"minecraft:spruce_fence_gate","id":183,"oldId":183},{"name":"minecraft:spruce_pressure_plate","id":-154,"oldId":-154},{"name":"minecraft:spruce_sign","id":566,"oldId":472},{"name":"minecraft:spruce_stairs","id":134,"oldId":134},{"name":"minecraft:spruce_standing_sign","id":-181,"oldId":-181},{"name":"minecraft:spruce_trapdoor","id":-149,"oldId":-149},{"name":"minecraft:spruce_wall_sign","id":-182,"oldId":-182},{"name":"minecraft:squid_spawn_egg","id":448,"oldId":383,"oldData":17},{"name":"minecraft:stained_glass","id":241,"oldId":241},{"name":"minecraft:stained_glass_pane","id":160,"oldId":160},{"name":"minecraft:stained_hardened_clay","id":159,"oldId":159},{"name":"minecraft:standing_banner","id":176,"oldId":176},{"name":"minecraft:standing_sign","id":63,"oldId":63},{"name":"minecraft:stick","id":320,"oldId":280},{"name":"minecraft:sticky_piston","id":29,"oldId":29},{"name":"minecraft:stickypistonarmcollision","id":-217,"oldId":-217},{"name":"minecraft:stone","id":1,"oldId":1},{"name":"minecraft:stone_axe","id":315,"oldId":275},{"name":"minecraft:stone_brick_stairs","id":109,"oldId":109},{"name":"minecraft:stone_button","id":77,"oldId":77},{"name":"minecraft:stone_hoe","id":330,"oldId":291},{"name":"minecraft:stone_pickaxe","id":314,"oldId":274},{"name":"minecraft:stone_pressure_plate","id":70,"oldId":70},{"name":"minecraft:stone_shovel","id":313,"oldId":273},{"name":"minecraft:stone_stairs","id":67,"oldId":67},{"name":"minecraft:stone_sword","id":312,"oldId":272},{"name":"minecraft:stonebrick","id":98,"oldId":98},{"name":"minecraft:stonecutter","id":245,"oldId":245},{"name":"minecraft:stonecutter_block","id":-197,"oldId":-197},{"name":"minecraft:stray_spawn_egg","id":460,"oldId":383,"oldData":46},{"name":"minecraft:strider_spawn_egg","id":493,"oldId":383,"oldData":125},{"name":"minecraft:string","id":326,"oldId":287},{"name":"minecraft:stripped_acacia_log","id":-8,"oldId":-8},{"name":"minecraft:stripped_birch_log","id":-6,"oldId":-6},{"name":"minecraft:stripped_crimson_hyphae","id":-300,"oldId":-300},{"name":"minecraft:stripped_crimson_stem","id":-240,"oldId":-240},{"name":"minecraft:stripped_dark_oak_log","id":-9,"oldId":-9},{"name":"minecraft:stripped_jungle_log","id":-7,"oldId":-7},{"name":"minecraft:stripped_oak_log","id":-10,"oldId":-10},{"name":"minecraft:stripped_spruce_log","id":-5,"oldId":-5},{"name":"minecraft:stripped_warped_hyphae","id":-301,"oldId":-301},{"name":"minecraft:stripped_warped_stem","id":-241,"oldId":-241},{"name":"minecraft:structure_block","id":252,"oldId":252},{"name":"minecraft:structure_void","id":217,"oldId":217},{"name":"minecraft:sugar","id":414,"oldId":353},{"name":"minecraft:sugar_cane","id":383,"oldId":338},{"name":"minecraft:suspicious_stew","id":579,"oldId":734},{"name":"minecraft:sweet_berries","id":287,"oldId":477},{"name":"minecraft:sweet_berry_bush","id":-207,"oldId":-207},{"name":"minecraft:tallgrass","id":31,"oldId":31},{"name":"minecraft:target","id":-239,"oldId":-239},{"name":"minecraft:tnt","id":46,"oldId":46},{"name":"minecraft:tnt_minecart","id":515,"oldId":407},{"name":"minecraft:torch","id":50,"oldId":50},{"name":"minecraft:totem_of_undying","id":558,"oldId":450},{"name":"minecraft:trapdoor","id":96,"oldId":96},{"name":"minecraft:trapped_chest","id":146,"oldId":146},{"name":"minecraft:trident","id":536,"oldId":455},{"name":"minecraft:tripwire","id":132,"oldId":132},{"name":"minecraft:tripwire_hook","id":131,"oldId":131},{"name":"minecraft:tropical_fish","id":266,"oldId":461},{"oldData":4,"name":"minecraft:tropical_fish_bucket","id":366,"oldId":325},{"name":"minecraft:tropical_fish_spawn_egg","id":477,"oldId":383,"oldData":111},{"name":"minecraft:turtle_egg","id":-159,"oldId":-159},{"name":"minecraft:turtle_helmet","id":563,"oldId":469},{"name":"minecraft:turtle_spawn_egg","id":483,"oldId":383,"oldData":74},{"name":"minecraft:twisting_vines","id":-287,"oldId":-287},{"name":"minecraft:underwater_torch","id":239,"oldId":239},{"name":"minecraft:undyed_shulker_box","id":205,"oldId":205},{"name":"minecraft:unknown","id":-305},{"name":"minecraft:unlit_redstone_torch","id":75,"oldId":75},{"name":"minecraft:unpowered_comparator","id":149,"oldId":149},{"name":"minecraft:unpowered_repeater","id":93,"oldId":93},{"name":"minecraft:vex_spawn_egg","id":474,"oldId":383,"oldData":105},{"name":"minecraft:villager_spawn_egg","id":447,"oldId":383,"oldData":15},{"name":"minecraft:vindicator_spawn_egg","id":472,"oldId":383,"oldData":57},{"name":"minecraft:vine","id":106,"oldId":106},{"name":"minecraft:wall_banner","id":177,"oldId":177},{"name":"minecraft:wall_sign","id":68,"oldId":68},{"name":"minecraft:wandering_trader_spawn_egg","id":490,"oldId":383,"oldData":118},{"name":"minecraft:warped_button","id":-261,"oldId":-261},{"name":"minecraft:warped_door","id":606,"oldId":756},{"name":"minecraft:warped_double_slab","id":-267,"oldId":-267},{"name":"minecraft:warped_fence","id":-257,"oldId":-257},{"name":"minecraft:warped_fence_gate","id":-259,"oldId":-259},{"name":"minecraft:warped_fungus","id":-229,"oldId":-229},{"name":"minecraft:warped_fungus_on_a_stick","id":607,"oldId":757},{"name":"minecraft:warped_hyphae","id":-298,"oldId":-298},{"name":"minecraft:warped_nylium","id":-233,"oldId":-233},{"name":"minecraft:warped_planks","id":-243,"oldId":-243},{"name":"minecraft:warped_pressure_plate","id":-263,"oldId":-263},{"name":"minecraft:warped_roots","id":-224,"oldId":-224},{"name":"minecraft:warped_sign","id":604,"oldId":754},{"name":"minecraft:warped_slab","id":-265,"oldId":-265},{"name":"minecraft:warped_stairs","id":-255,"oldId":-255},{"name":"minecraft:warped_standing_sign","id":-251,"oldId":-251},{"name":"minecraft:warped_stem","id":-226,"oldId":-226},{"name":"minecraft:warped_trapdoor","id":-247,"oldId":-247},{"name":"minecraft:warped_wall_sign","id":-253,"oldId":-253},{"name":"minecraft:warped_wart_block","id":-227,"oldId":-227},{"name":"minecraft:water","id":9,"oldId":9},{"oldData":8,"name":"minecraft:water_bucket","id":362,"oldId":325},{"name":"minecraft:waterlily","id":111,"oldId":111},{"name":"minecraft:web","id":30,"oldId":30},{"name":"minecraft:weeping_vines","id":-231,"oldId":-231},{"name":"minecraft:wheat","id":334,"oldId":296},{"name":"minecraft:wheat_seeds","id":291,"oldId":295},{"oldData":19,"name":"minecraft:white_dye","id":408,"oldId":351},{"name":"minecraft:white_glazed_terracotta","id":220,"oldId":220},{"name":"minecraft:witch_spawn_egg","id":450,"oldId":383,"oldData":45},{"name":"minecraft:wither_rose","id":-216,"oldId":-216},{"name":"minecraft:wither_skeleton_spawn_egg","id":462,"oldId":383,"oldData":48},{"name":"minecraft:wolf_spawn_egg","id":437,"oldId":383,"oldData":14},{"name":"minecraft:wood","id":-212,"oldId":-212},{"name":"minecraft:wooden_axe","id":311,"oldId":271},{"name":"minecraft:wooden_button","id":143,"oldId":143},{"name":"minecraft:wooden_door","id":359,"oldId":324},{"name":"minecraft:wooden_hoe","id":329,"oldId":290},{"name":"minecraft:wooden_pickaxe","id":310,"oldId":270},{"name":"minecraft:wooden_pressure_plate","id":72,"oldId":72},{"name":"minecraft:wooden_shovel","id":309,"oldId":269},{"name":"minecraft:wooden_slab","id":158,"oldId":158},{"name":"minecraft:wooden_sword","id":308,"oldId":268},{"name":"minecraft:wool","id":35,"oldId":35},{"name":"minecraft:writable_book","id":500,"oldId":386},{"name":"minecraft:written_book","id":501,"oldId":387},{"oldData":11,"name":"minecraft:yellow_dye","id":404,"oldId":351},{"name":"minecraft:yellow_flower","id":37,"oldId":37},{"name":"minecraft:yellow_glazed_terracotta","id":224,"oldId":224},{"name":"minecraft:zoglin_spawn_egg","id":496,"oldId":383,"oldData":126},{"name":"minecraft:zombie_horse_spawn_egg","id":466,"oldId":383,"oldData":27},{"name":"minecraft:zombie_pigman_spawn_egg","id":446,"oldId":383,"oldData":36},{"name":"minecraft:zombie_spawn_egg","id":445,"oldId":383,"oldData":32},{"name":"minecraft:zombie_villager_spawn_egg","id":475,"oldId":383,"oldData":44}]