Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement client behaviour packs #1879

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
17 changes: 15 additions & 2 deletions src/main/java/cn/nukkit/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import cn.nukkit.plugin.Plugin;
import cn.nukkit.potion.Effect;
import cn.nukkit.resourcepacks.ResourcePack;
import cn.nukkit.resourcepacks.ResourcePackManager;
import cn.nukkit.scheduler.AsyncTask;
import cn.nukkit.utils.*;
import co.aikar.timings.Timing;
Expand Down Expand Up @@ -1950,8 +1951,11 @@ protected void processLogin() {
}
this.timeSinceRest = this.namedTag.getInt("TimeSinceRest");

ResourcePackManager resourcePackManager = this.server.getResourcePackManager();
ResourcePacksInfoPacket infoPacket = new ResourcePacksInfoPacket();
infoPacket.resourcePackEntries = this.server.getResourcePackManager().getResourceStack();
infoPacket.resourcePackEntries = resourcePackManager.getResourcePacks();
infoPacket.behaviorPackEntries = resourcePackManager.getBehaviorPacks();
infoPacket.scripting = Arrays.stream(infoPacket.behaviorPackEntries).anyMatch(ResourcePack::requiresScripting);
infoPacket.mustAccept = this.server.getForceResources();
this.dataPacket(infoPacket);
}
Expand Down Expand Up @@ -2199,6 +2203,14 @@ public void onCompletion(Server server) {
}

ResourcePackDataInfoPacket dataInfoPacket = new ResourcePackDataInfoPacket();
switch (resourcePack.getType()) {
case RESOURCE_PACK:
dataInfoPacket.type = ResourcePackDataInfoPacket.TYPE_RESOURCE;
break;
case BEHAVIOR_PACK:
dataInfoPacket.type = ResourcePackDataInfoPacket.TYPE_BEHAVIOR;
break;
}
dataInfoPacket.packId = resourcePack.getPackId();
dataInfoPacket.maxChunkSize = 1048576; //megabyte
dataInfoPacket.chunkCount = resourcePack.getPackSize() / dataInfoPacket.maxChunkSize;
Expand All @@ -2210,7 +2222,8 @@ public void onCompletion(Server server) {
case ResourcePackClientResponsePacket.STATUS_HAVE_ALL_PACKS:
ResourcePackStackPacket stackPacket = new ResourcePackStackPacket();
stackPacket.mustAccept = this.server.getForceResources();
stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack();
stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourcePacks();
stackPacket.behaviorPackStack = this.server.getResourcePackManager().getBehaviorPacks();
this.dataPacket(stackPacket);
break;
case ResourcePackClientResponsePacket.STATUS_COMPLETED:
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cn/nukkit/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ public Level remove(Object key) {
convertLegacyPlayerData();

this.craftingManager = new CraftingManager();
this.resourcePackManager = new ResourcePackManager(new File(Nukkit.DATA_PATH, "resource_packs"));
this.resourcePackManager = new ResourcePackManager(new File(Nukkit.DATA_PATH, "resource_packs"),new File(Nukkit.DATA_PATH, "client_behavior_packs"));

this.pluginManager = new PluginManager(this, this.commandMap);
this.pluginManager.subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this.consoleSender);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ResourcePackDataInfoPacket extends DataPacket {
public long compressedPackSize;
public byte[] sha256;
public boolean premium;
public int type = TYPE_RESOURCE;
public int type;

@Override
public void decode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ResourcePackStackPacket extends DataPacket {
public static final byte NETWORK_ID = ProtocolInfo.RESOURCE_PACK_STACK_PACKET;

public boolean mustAccept = false;
public ResourcePack[] behaviourPackStack = new ResourcePack[0];
public ResourcePack[] behaviorPackStack = new ResourcePack[0];
public ResourcePack[] resourcePackStack = new ResourcePack[0];
public boolean isExperimental = false;
public String gameVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK;
Expand All @@ -24,8 +24,8 @@ public void encode() {
this.reset();
this.putBoolean(this.mustAccept);

this.putUnsignedVarInt(this.behaviourPackStack.length);
for (ResourcePack entry : this.behaviourPackStack) {
this.putUnsignedVarInt(this.behaviorPackStack.length);
for (ResourcePack entry : this.behaviorPackStack) {
this.putString(entry.getPackId().toString());
this.putString(entry.getPackVersion());
this.putString(""); //TODO: subpack name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ResourcePacksInfoPacket extends DataPacket {
public boolean mustAccept;
public boolean scripting;
public boolean forceServerPacks;
public ResourcePack[] behaviourPackEntries = new ResourcePack[0];
public ResourcePack[] behaviorPackEntries = new ResourcePack[0];
public ResourcePack[] resourcePackEntries = new ResourcePack[0];

@Override
Expand All @@ -25,7 +25,7 @@ public void encode() {
this.putBoolean(this.mustAccept);
this.putBoolean(this.scripting);
this.putBoolean(this.forceServerPacks);
this.encodePacks(this.behaviourPackEntries);
this.encodePacks(this.behaviorPackEntries);
this.encodePacks(this.resourcePackEntries);
}

Expand All @@ -38,8 +38,11 @@ private void encodePacks(ResourcePack[] packs) {
this.putString(""); // encryption key
this.putString(""); // sub-pack name
this.putString(""); // content identity
this.putBoolean(false); // scripting
this.putBoolean(false); // raytracing capable
this.putBoolean(entry.requiresScripting()); // scripting

if (entry.getType().equals(ResourcePack.Type.RESOURCE_PACK)) {
this.putBoolean(false); // raytracing capable
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions src/main/java/cn/nukkit/resourcepacks/AbstractResourcePack.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package cn.nukkit.resourcepacks;

import cn.nukkit.Server;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import java.util.Arrays;
import java.util.UUID;

public abstract class AbstractResourcePack implements ResourcePack {
protected JsonObject manifest;
private UUID id = null;
private ResourcePack.Type type = null;

protected boolean verifyManifest() {
if (this.manifest.has("format_version") && this.manifest.has("header") && this.manifest.has("modules")) {
Expand Down Expand Up @@ -45,4 +48,34 @@ public String getPackVersion() {
version.get(1).getAsString(),
version.get(2).getAsString());
}

@Override
public Type getType() {
if (type == null) {
JsonArray modules = this.manifest.getAsJsonArray("modules").getAsJsonArray();

for (int i = 0; i < modules.size(); i++) {
JsonObject element = modules.get(i).getAsJsonObject();
String moduleType = element.get("type").getAsString();

ResourcePack.Type type = null;
for (ResourcePack.Type possibleType : ResourcePack.Type.values()) {
if (Arrays.asList(possibleType.getAllowedModuleTypes()).contains(moduleType)) {
type = possibleType;
break;
}
}

if (type == null) {
throw new IllegalArgumentException(Server.getInstance().getLanguage()
.translateString("nukkit.resources.unsupported-module-type", this.getPackName()));
}

this.type = type;
break;
}
}

return type;
}
}
25 changes: 24 additions & 1 deletion src/main/java/cn/nukkit/resourcepacks/ResourcePack.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.UUID;

/**
* Can either be a "Resource Pack" or a "Behavior Pack"
*/
public interface ResourcePack {
String getPackName();

Expand All @@ -14,4 +17,24 @@ public interface ResourcePack {
byte[] getSha256();

byte[] getPackChunk(int off, int len);
}

Type getType();

boolean requiresScripting();

enum Type {
// Module types may not overlap.
RESOURCE_PACK(new String[]{"resources"}),
BEHAVIOR_PACK(new String[]{"client_data"}); // only client scripts are supported

private final String[] allowedModuleTypes;

Type(String[] allowedModuleTypes) {
this.allowedModuleTypes = allowedModuleTypes;
}

public String[] getAllowedModuleTypes() {
return this.allowedModuleTypes;
}
}
}
88 changes: 76 additions & 12 deletions src/main/java/cn/nukkit/resourcepacks/ResourcePackManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@
import java.util.*;

public class ResourcePackManager {
private final Map<UUID, ResourcePack> resourcePacksById = new HashMap<>();
private ResourcePack[] resourcePacks;
private final Map<UUID, ResourcePack> packsById = new HashMap<>();
private final ResourcePack[] resourcePacks;
private final ResourcePack[] behaviorPacks;

public ResourcePackManager(File path) {
if (!path.exists()) {
path.mkdirs();
} else if (!path.isDirectory()) {
public ResourcePackManager(File resourcePacksPath, File behaviorPacksPath) {
if (!resourcePacksPath.exists()) {
resourcePacksPath.mkdirs();
} else if (!resourcePacksPath.isDirectory()) {
throw new IllegalArgumentException(Server.getInstance().getLanguage()
.translateString("nukkit.resources.invalid-path", path.getName()));
.translateString("nukkit.resources.invalid-path", resourcePacksPath.getName()));
}

if (!behaviorPacksPath.exists()) {
behaviorPacksPath.mkdirs();
} else if (!behaviorPacksPath.isDirectory()) {
throw new IllegalArgumentException(Server.getInstance().getLanguage()
.translateString("nukkit.resources.invalid-path", behaviorPacksPath.getName()));
}

List<ResourcePack> loadedResourcePacks = new ArrayList<>();
for (File pack : path.listFiles()) {
List<ResourcePack> loadedBehaviorPacks = new ArrayList<>();
for (File pack : resourcePacksPath.listFiles()) {
try {
ResourcePack resourcePack = null;

Expand All @@ -37,8 +46,57 @@ public ResourcePackManager(File path) {
}

if (resourcePack != null) {
if (!resourcePack.getType().equals(ResourcePack.Type.RESOURCE_PACK)) {
Server.getInstance().getLogger().warning(Server.getInstance().getLanguage()
.translateString("nukkit.resources.invalid-type-resource", pack.getName(), resourcePack.getPackName()));

continue;
}

loadedResourcePacks.add(resourcePack);
this.resourcePacksById.put(resourcePack.getPackId(), resourcePack);
this.packsById.put(resourcePack.getPackId(), resourcePack);

Server.getInstance().getLogger().info(Server.getInstance().getLanguage()
.translateString("nukkit.resources.loaded-resource-pack", resourcePack.getPackName()));
}
} catch (IllegalArgumentException e) {
Server.getInstance().getLogger().warning(Server.getInstance().getLanguage()
.translateString("nukkit.resources.fail", pack.getName(), e.getMessage()));

e.printStackTrace();
}
}

for (File pack : behaviorPacksPath.listFiles()) {
try {
ResourcePack behaviorPack = null;

if (!pack.isDirectory()) { //directory resource packs temporarily unsupported
switch (Files.getFileExtension(pack.getName())) {
case "zip":
case "mcpack":
behaviorPack = new ZippedResourcePack(pack);
break;
default:
Server.getInstance().getLogger().warning(Server.getInstance().getLanguage()
.translateString("nukkit.resources.unknown-format", pack.getName()));
break;
}
}

if (behaviorPack != null) {
if (!behaviorPack.getType().equals(ResourcePack.Type.BEHAVIOR_PACK)) {
Server.getInstance().getLogger().warning(Server.getInstance().getLanguage()
.translateString("nukkit.resources.invalid-type-behavior", pack.getName(), behaviorPack.getPackName()));

continue;
}

loadedBehaviorPacks.add(behaviorPack);
this.packsById.put(behaviorPack.getPackId(), behaviorPack);

Server.getInstance().getLogger().info(Server.getInstance().getLanguage()
.translateString("nukkit.resources.loaded-behavior-pack", behaviorPack.getPackName()));
}
} catch (IllegalArgumentException e) {
Server.getInstance().getLogger().warning(Server.getInstance().getLanguage()
Expand All @@ -47,15 +105,21 @@ public ResourcePackManager(File path) {
}

this.resourcePacks = loadedResourcePacks.toArray(new ResourcePack[0]);
this.behaviorPacks = loadedBehaviorPacks.toArray(new ResourcePack[0]);

Server.getInstance().getLogger().info(Server.getInstance().getLanguage()
.translateString("nukkit.resources.success", String.valueOf(this.resourcePacks.length)));
.translateString("nukkit.resources.success", String.valueOf(this.resourcePacks.length + this.behaviorPacks.length)));
}

public ResourcePack[] getResourceStack() {
public ResourcePack[] getResourcePacks() {
return this.resourcePacks;
}

public ResourcePack[] getBehaviorPacks() {
return this.behaviorPacks;
}

public ResourcePack getPackById(UUID id) {
return this.resourcePacksById.get(id);
return this.packsById.get(id);
}
}
17 changes: 17 additions & 0 deletions src/main/java/cn/nukkit/resourcepacks/ZippedResourcePack.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class ZippedResourcePack extends AbstractResourcePack {
private File file;
private byte[] sha256 = null;
private boolean requiresScripting = false;

public ZippedResourcePack(File file) {
if (!file.exists()) {
Expand All @@ -35,6 +37,16 @@ public ZippedResourcePack(File file) {
.parse(new InputStreamReader(zip.getInputStream(entry), StandardCharsets.UTF_8))
.getAsJsonObject();
}

Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();

if (zipEntry.getName().endsWith(".js")) {
// once a JavaScript file is found in the zip, we assume it is a behavior script
this.requiresScripting = true;
}
}
} catch (IOException e) {
Server.getInstance().getLogger().logException(e);
}
Expand Down Expand Up @@ -81,4 +93,9 @@ public byte[] getPackChunk(int off, int len) {

return chunk;
}

@Override
public boolean requiresScripting() {
return this.getType().equals(Type.BEHAVIOR_PACK) ? this.requiresScripting : false;
}
}