1
mirror of https://github.com/StarWishsama/Slimefun4.git synced 2024-09-20 11:45:51 +00:00

Merge branch 'master' of https://github.com/Slimefun/Slimefun4 into master

This commit is contained in:
Moritz Kempe 2020-10-30 13:25:44 +01:00
commit 8324f5d830
31 changed files with 676 additions and 156 deletions

View File

@ -6,10 +6,13 @@ yaml-files:
rules:
## A warning is sufficient here
line-length:
max: 180
level: warning
## Don't warn for line lengths
line-length: disable
truthy:
allowed-values: ['true', 'false']
## We don't want it to trigger for the 'on' in our workflows
check-keys: false
## We don't need indentation warnings
indentation: disable

View File

@ -6,7 +6,7 @@ on:
- master
jobs:
build:
check:
name: URL Checker
runs-on: ubuntu-latest
@ -17,7 +17,7 @@ jobs:
with:
git_path: https://github.com/Slimefun/Slimefun4
file_types: .md,.java,.yml
print_all: False
print_all: false
retry_count: 2
## These URLs will always be correct, even if their services may be offline right now
white_listed_patterns: http://textures.minecraft.net/texture/,https://pastebin.com/,https://www.spigotmc.org/threads/spigot-bungeecord-1-16-1.447405/#post-3852349,https://gitlocalize.com/repo/3841

View File

@ -18,6 +18,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v2
- name: YAML Linter
uses: ibiqlik/action-yamllint@v1.0.0
uses: ibiqlik/action-yamllint@v2.0.0
with:
config_file: '.github/configs/yaml-linter.yml'

View File

@ -29,6 +29,12 @@
#### Changes
* Removed 1.13 support
* Cooling Units can no longer be placed down
* Heating Coils can no longer be placed down
* Electric Motors can no longer be placed down
* Cargo Motors can no longer be placed down
* Magnets can no longer be placed down
* Electromagnets can no longer be placed down
#### Fixes
* Fixed #2448
@ -36,6 +42,11 @@
* Fixed #2478
* Fixed #2493
* Fixed a missing slot in the contributors menu
* Fixed color codes in script downloading screen
* Fixed #2505
* Fixed contributors not showing correctly
* Fixed #2469
* Fixed #2509
## Release Candidate 17 (17 Oct 2020)

View File

@ -329,7 +329,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.5.15</version>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
@ -381,7 +381,7 @@
<dependency>
<groupId>com.gmail.nossr50.mcMMO</groupId>
<artifactId>mcMMO</artifactId>
<version>2.1.149</version>
<version>2.1.150</version>
<scope>provided</scope>
<exclusions>
<exclusion>

View File

@ -2,6 +2,9 @@ package io.github.thebusybiscuit.slimefun4.core.services;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bukkit.Keyed;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@ -11,6 +14,9 @@ import org.bukkit.block.TileState;
import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.plugin.Plugin;
import io.papermc.lib.PaperLib;
import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult;
/**
* The {@link BlockDataService} is similar to the {@link CustomItemDataService},
* it is responsible for storing NBT data inside a {@link TileState}.
@ -24,7 +30,17 @@ public class BlockDataService implements PersistentDataService, Keyed {
private final NamespacedKey namespacedKey;
public BlockDataService(Plugin plugin, String key) {
/**
* This creates a new {@link BlockDataService} for the given {@link Plugin}.
* The {@link Plugin} and key will together form a {@link NamespacedKey} used to store
* data on a {@link TileState}.
*
* @param plugin
* The {@link Plugin} responsible for this service
* @param key
* The key under which to store data
*/
public BlockDataService(@Nonnull Plugin plugin, @Nonnull String key) {
namespacedKey = new NamespacedKey(plugin, key);
}
@ -41,12 +57,16 @@ public class BlockDataService implements PersistentDataService, Keyed {
* @param value
* The value to store
*/
public void setBlockData(Block b, String value) {
BlockState state = b.getState();
public void setBlockData(@Nonnull Block b, @Nonnull String value) {
BlockStateSnapshotResult result = PaperLib.getBlockState(b, false);
BlockState state = result.getState();
if (state instanceof TileState) {
setString((TileState) state, namespacedKey, value);
state.update();
if (result.isSnapshot()) {
state.update();
}
}
}
@ -57,8 +77,8 @@ public class BlockDataService implements PersistentDataService, Keyed {
* The {@link Block} to retrieve data from
* @return The stored value
*/
public Optional<String> getBlockData(Block b) {
BlockState state = b.getState();
public Optional<String> getBlockData(@Nonnull Block b) {
BlockState state = PaperLib.getBlockState(b, false).getState();
if (state instanceof TileState) {
return getString((TileState) state, namespacedKey);
@ -77,9 +97,10 @@ public class BlockDataService implements PersistentDataService, Keyed {
*
* @param type
* The {@link Material} to check for
*
* @return Whether the given {@link Material} is considered a Tile Entity
*/
public boolean isTileEntity(Material type) {
public boolean isTileEntity(@Nullable Material type) {
if (type == null || type.isAir()) {
// Cannot store data on air
return false;
@ -104,8 +125,10 @@ public class BlockDataService implements PersistentDataService, Keyed {
case BARREL:
case SPAWNER:
case BEACON:
// All of the above Materials are Tile Entities
return true;
default:
// Otherwise we assume they're not Tile Entities
return false;
}
}

View File

@ -2,6 +2,10 @@ package io.github.thebusybiscuit.slimefun4.core.services;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang.Validate;
import org.bukkit.Keyed;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@ -35,7 +39,7 @@ public class CustomItemDataService implements PersistentDataService, Keyed {
* @param key
* The key under which to store data
*/
public CustomItemDataService(Plugin plugin, String key) {
public CustomItemDataService(@Nonnull Plugin plugin, @Nonnull String key) {
// Null-Validation is performed in the NamespacedKey constructor
namespacedKey = new NamespacedKey(plugin, key);
}
@ -45,13 +49,37 @@ public class CustomItemDataService implements PersistentDataService, Keyed {
return namespacedKey;
}
public void setItemData(ItemStack item, String id) {
/**
* This method stores the given id on the provided {@link ItemStack} via
* persistent data.
*
* @param item
* The {@link ItemStack} to store data on
* @param id
* The id to store on the {@link ItemStack}
*/
public void setItemData(@Nonnull ItemStack item, @Nonnull String id) {
Validate.notNull(item, "The Item cannot be null!");
Validate.notNull(id, "Cannot store null on an Item!");
ItemMeta im = item.getItemMeta();
setItemData(im, id);
item.setItemMeta(im);
}
public void setItemData(ItemMeta im, String id) {
/**
* This method stores the given id on the provided {@link ItemMeta} via
* persistent data.
*
* @param im
* The {@link ItemMeta} to store data on
* @param id
* The id to store on the {@link ItemMeta}
*/
public void setItemData(@Nonnull ItemMeta im, @Nonnull String id) {
Validate.notNull(im, "The ItemMeta cannot be null!");
Validate.notNull(id, "Cannot store null on an ItemMeta!");
setString(im, namespacedKey, id);
}
@ -65,7 +93,8 @@ public class CustomItemDataService implements PersistentDataService, Keyed {
*
* @return An {@link Optional} describing the result
*/
public Optional<String> getItemData(ItemStack item) {
@Nonnull
public Optional<String> getItemData(@Nullable ItemStack item) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return Optional.empty();
}
@ -82,7 +111,10 @@ public class CustomItemDataService implements PersistentDataService, Keyed {
*
* @return An {@link Optional} describing the result
*/
public Optional<String> getItemData(ItemMeta meta) {
@Nonnull
public Optional<String> getItemData(@Nonnull ItemMeta meta) {
Validate.notNull(meta, "Cannot read data from null!");
return getString(meta, namespacedKey);
}

View File

@ -28,6 +28,12 @@ public class CustomTextureService {
private String version = null;
private boolean modified = false;
/**
* This creates a new {@link CustomTextureService} for the provided {@link Config}
*
* @param config
* The {@link Config} to read custom model data from
*/
public CustomTextureService(@Nonnull Config config) {
this.config = config;
config.getConfiguration().options().header("This file is used to assign items from Slimefun or any of its addons\n" + "the 'CustomModelData' NBT tag. This can be used in conjunction with a custom resource pack\n" + "to give items custom textures.\n0 means there is no data assigned to that item.\n\n" + "There is no official Slimefun resource pack at the moment.");
@ -49,6 +55,8 @@ public class CustomTextureService {
config.setDefaultValue("SLIMEFUN_GUIDE", 0);
config.setDefaultValue("_UI_BACKGROUND", 0);
config.setDefaultValue("_UI_NO_PERMISSION", 0);
config.setDefaultValue("_UI_NOT_RESEARCHED", 0);
config.setDefaultValue("_UI_INPUT_SLOT", 0);
config.setDefaultValue("_UI_OUTPUT_SLOT", 0);
config.setDefaultValue("_UI_BACK", 0);
@ -82,22 +90,60 @@ public class CustomTextureService {
return version;
}
/**
* This returns true if any custom model data was configured.
* If every item id has no configured custom model data, it will return false.
*
* @return Whether any custom model data was configured
*/
public boolean isActive() {
return modified;
}
/**
* This returns the configured custom model data for a given id.
*
* @param id
* The id to get the data for
*
* @return The configured custom model data
*/
public int getModelData(@Nonnull String id) {
Validate.notNull(id, "Cannot get the ModelData for 'null'");
return config.getInt(id);
}
/**
* This method sets the custom model data for this {@link ItemStack}
* to the value configured for the provided item id.
*
* @param item
* The {@link ItemStack} to set the custom model data for
* @param id
* The id for which to get the configured model data
*/
public void setTexture(@Nonnull ItemStack item, @Nonnull String id) {
Validate.notNull(item, "The Item cannot be null!");
Validate.notNull(id, "Cannot store null on an Item!");
ItemMeta im = item.getItemMeta();
setTexture(im, id);
item.setItemMeta(im);
}
/**
* This method sets the custom model data for this {@link ItemMeta}
* to the value configured for the provided item id.
*
* @param im
* The {@link ItemMeta} to set the custom model data for
* @param id
* The id for which to get the configured model data
*/
public void setTexture(@Nonnull ItemMeta im, @Nonnull String id) {
Validate.notNull(im, "The ItemMeta cannot be null!");
Validate.notNull(id, "Cannot store null on an ItemMeta!");
int data = getModelData(id);
im.setCustomModelData(data == 0 ? null : data);
}

View File

@ -34,9 +34,26 @@ import kong.unirest.UnirestException;
*/
public class MetricsService {
/**
* The URL pointing towards the GitHub API.
*/
private static final String API_URL = "https://api.github.com/";
/**
* The Name of our repository
*/
private static final String REPO_NAME = "MetricsModule";
/**
* The URL pointing towards the /releases/ endpoint of our
* Metrics repository
*/
private static final String RELEASES_URL = API_URL + "repos/Slimefun/" + REPO_NAME + "/releases/latest";
/**
* The URL pointing towards the download location for a
* GitHub release of our Metrics repository
*/
private static final String DOWNLOAD_URL = "https://github.com/Slimefun/" + REPO_NAME + "/releases/download";
private final SlimefunPlugin plugin;
@ -48,9 +65,22 @@ public class MetricsService {
private boolean hasDownloadedUpdate = false;
static {
Unirest.config().concurrency(2, 1).setDefaultHeader("User-Agent", "MetricsModule Auto-Updater").setDefaultHeader("Accept", "application/vnd.github.v3+json").enableCookieManagement(false).cookieSpec("ignoreCookies");
// @formatter:off (We want this to stay this nicely aligned :D )
Unirest.config()
.concurrency(2, 1)
.setDefaultHeader("User-Agent", "MetricsModule Auto-Updater")
.setDefaultHeader("Accept", "application/vnd.github.v3+json")
.enableCookieManagement(false)
.cookieSpec("ignoreCookies");
// @formatter:on
}
/**
* This constructs a new instance of our {@link MetricsService}.
*
* @param plugin
* Our {@link SlimefunPlugin} instance
*/
public MetricsService(@Nonnull SlimefunPlugin plugin) {
this.plugin = plugin;
this.parentFolder = new File(plugin.getDataFolder(), "cache" + File.separatorChar + "modules");

View File

@ -16,11 +16,28 @@ import me.mrCookieSlime.Slimefun.api.Slimefun;
class ContributionsConnector extends GitHubConnector {
// GitHub Bots that do not count as Contributors
// (includes "invalid-email-address" because it is an invalid contributor)
private static final List<String> blacklist = Arrays.asList("invalid-email-address", "renovate-bot", "TheBusyBot", "ImgBotApp", "imgbot", "imgbot[bot]", "github-actions[bot]", "gitlocalize-app", "gitlocalize-app[bot]", "mt-gitlocalize");
/*
* @formatter:off
* GitHub Bots that do not count as Contributors
* (includes "invalid-email-address" because it is an invalid contributor)
*/
private static final List<String> blacklist = Arrays.asList(
"invalid-email-address",
"renovate-bot",
"TheBusyBot",
"ImgBotApp",
"imgbot",
"imgbot[bot]",
"github-actions[bot]",
"gitlocalize-app",
"gitlocalize-app[bot]",
"mt-gitlocalize"
);
// Matches a GitHub name with a Minecraft name.
/*
* @formatter:on
* Matches a GitHub name with a Minecraft name.
*/
private static final Map<String, String> aliases = new HashMap<>();
// Should probably be switched to UUIDs at some point...
@ -82,8 +99,16 @@ class ContributionsConnector extends GitHubConnector {
}
@Override
public String getURLSuffix() {
return "/contributors?per_page=100&page=" + page;
public String getEndpoint() {
return "/contributors";
}
@Override
public Map<String, Object> getParameters() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("per_page", 100);
parameters.put("page", page);
return parameters;
}
private void computeContributors(@Nonnull JSONArray array) {

View File

@ -1,6 +1,8 @@
package io.github.thebusybiscuit.slimefun4.core.services.github;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
@ -35,8 +37,13 @@ class GitHubActivityConnector extends GitHubConnector {
}
@Override
public String getURLSuffix() {
public String getEndpoint() {
return "";
}
@Override
public Map<String, Object> getParameters() {
return new HashMap<>();
}
}

View File

@ -6,12 +6,13 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
@ -31,22 +32,49 @@ import me.mrCookieSlime.Slimefun.api.Slimefun;
abstract class GitHubConnector {
private static final String API_URL = "https://api.github.com/";
private static final String USER_AGENT = "Slimefun4 (https://github.com/Slimefun)";
protected File file;
protected String repository;
protected final GitHubService github;
private final String url;
private File file;
@ParametersAreNonnullByDefault
public GitHubConnector(GitHubService github, String repository) {
/**
* This creates a new {@link GitHubConnector} for the given repository.
*
* @param github
* Our instance of {@link GitHubService}
* @param repository
* The repository we want to connect to
*/
GitHubConnector(@Nonnull GitHubService github, @Nonnull String repository) {
this.github = github;
this.repository = repository;
this.url = API_URL + "repos/" + repository + getEndpoint();
}
/**
* This returns the name of our cache {@link File}.
*
* @return The cache {@link File} name
*/
@Nonnull
public abstract String getFileName();
/**
* This is our {@link URL} endpoint.
* It is the suffix of the {@link URL} we want to connect to.
*
* @return Our endpoint
*/
@Nonnull
public abstract String getURLSuffix();
public abstract String getEndpoint();
/**
* This {@link Map} contains the query parameters for our {@link URL}.
*
* @return A {@link Map} with our query parameters
*/
@Nonnull
public abstract Map<String, Object> getParameters();
/**
* This method is called when the connection finished successfully.
@ -63,7 +91,12 @@ abstract class GitHubConnector {
// Don't do anything by default
}
public void pullFile() {
/**
* This method will connect to GitHub and store the received data inside a local
* cache {@link File}.
* Make sure to call this method asynchronously!
*/
void download() {
file = new File("plugins/Slimefun/cache/github/" + getFileName() + ".json");
if (github.isLoggingEnabled()) {
@ -71,16 +104,19 @@ abstract class GitHubConnector {
}
try {
HttpResponse<JsonNode> resp = Unirest.get(API_URL + "repos/" + repository + getURLSuffix())
.header("User-Agent", "Slimefun4 (https://github.com/Slimefun)")
// @formatter:off
HttpResponse<JsonNode> response = Unirest.get(url)
.queryString(getParameters())
.header("User-Agent", USER_AGENT)
.asJson();
// @formatter:on
if (resp.isSuccess()) {
onSuccess(resp.getBody());
writeCacheFile(resp.getBody());
if (response.isSuccess()) {
onSuccess(response.getBody());
writeCacheFile(response.getBody());
} else {
if (github.isLoggingEnabled()) {
Slimefun.getLogger().log(Level.WARNING, "Failed to fetch {0}: {1} - {2}", new Object[] { repository + getURLSuffix(), resp.getStatus(), resp.getBody() });
Slimefun.getLogger().log(Level.WARNING, "Failed to fetch {0}: {1} - {2}", new Object[] { url, response.getStatus(), response.getBody() });
}
// It has the cached file, let's just read that then

View File

@ -1,5 +1,7 @@
package io.github.thebusybiscuit.slimefun4.core.services.github;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
@ -50,8 +52,15 @@ class GitHubIssuesConnector extends GitHubConnector {
}
@Override
public String getURLSuffix() {
return "/issues?per_page=100";
public String getEndpoint() {
return "/issues";
}
@Override
public Map<String, Object> getParameters() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("per_page", 100);
return parameters;
}
}

View File

@ -57,7 +57,8 @@ public class GitHubService {
}
public void start(@Nonnull SlimefunPlugin plugin) {
plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new GitHubTask(this), 80L, 60 * 60 * 20L);
GitHubTask task = new GitHubTask(this);
plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, task, 80L, 60 * 60 * 20L);
}
/**

View File

@ -1,5 +1,6 @@
package io.github.thebusybiscuit.slimefun4.core.services.github;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ -38,10 +39,14 @@ class GitHubTask implements Runnable {
@Override
public void run() {
gitHubService.getConnectors().forEach(GitHubConnector::pullFile);
gitHubService.getConnectors().forEach(GitHubConnector::download);
grabTextures();
}
/**
* This method will pull the skin textures for every {@link Contributor} and store
* the {@link UUID} and received skin inside a local cache {@link File}.
*/
private void grabTextures() {
// Store all queried usernames to prevent 429 responses for pinging the
// same URL twice in one run.

View File

@ -271,10 +271,10 @@ public class ChestSlimefunGuide implements SlimefunGuideImplementation {
if (isSurvivalMode() && !Slimefun.hasPermission(p, sfitem, false)) {
List<String> message = SlimefunPlugin.getPermissionsService().getLore(sfitem);
menu.addItem(index, new CustomItem(Material.BARRIER, sfitem.getItemName(), message.toArray(new String[0])));
menu.addItem(index, new CustomItem(ChestMenuUtils.getNoPermissionItem(), sfitem.getItemName(), message.toArray(new String[0])));
menu.addMenuClickHandler(index, ChestMenuUtils.getEmptyClickHandler());
} else if (isSurvivalMode() && research != null && !profile.hasUnlocked(research)) {
menu.addItem(index, new CustomItem(Material.BARRIER, ChatColor.WHITE + ItemUtils.getItemName(sfitem.getItem()), "&4&l" + SlimefunPlugin.getLocalization().getMessage(p, "guide.locked"), "", "&a> Click to unlock", "", "&7Cost: &b" + research.getCost() + " Level(s)"));
menu.addItem(index, new CustomItem(ChestMenuUtils.getNotResearchedItem(), ChatColor.WHITE + ItemUtils.getItemName(sfitem.getItem()), "&4&l" + SlimefunPlugin.getLocalization().getMessage(p, "guide.locked"), "", "&a> Click to unlock", "", "&7Cost: &b" + research.getCost() + " Level(s)"));
menu.addMenuClickHandler(index, (pl, slot, item, action) -> {
if (!SlimefunPlugin.getRegistry().getCurrentlyResearchingPlayers().contains(pl.getUniqueId())) {
if (research.canUnlock(pl)) {

View File

@ -1,5 +1,7 @@
package io.github.thebusybiscuit.slimefun4.implementation.items.androids;
import javax.annotation.Nonnull;
/**
* This enum holds all the different types a {@link ProgrammableAndroid} can represent.
*
@ -55,7 +57,7 @@ public enum AndroidType {
*/
NON_FIGHTER;
boolean isType(AndroidType type) {
boolean isType(@Nonnull AndroidType type) {
return type == NONE || type == this || (type == NON_FIGHTER && this != FIGHTER);
}

View File

@ -8,6 +8,7 @@ import java.util.Map;
import java.util.function.Predicate;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Ageable;
@ -20,126 +21,223 @@ import io.github.thebusybiscuit.slimefun4.utils.HeadTexture;
import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils;
import me.mrCookieSlime.Slimefun.api.inventory.BlockMenu;
enum Instruction {
/**
* This enum holds every {@link Instruction} for the {@link ProgrammableAndroid}
* added by Slimefun itself.
*
* @author TheBusyBiscuit
*
*/
public enum Instruction {
// Start and End Parts
/**
* This {@link Instruction} is the starting point of a {@link Script}.
*/
START(AndroidType.NONE, HeadTexture.SCRIPT_START),
/**
* This {@link Instruction} is the end token of a {@link Script}.
* Once this {@link Instruction} is reached, the {@link Script} will start again.
*/
REPEAT(AndroidType.NONE, HeadTexture.SCRIPT_REPEAT),
/**
* This {@link Instruction} will make the {@link ProgrammableAndroid} wait
* for one Slimefun tick.
*/
WAIT(AndroidType.NONE, HeadTexture.SCRIPT_WAIT),
// Movement
/**
* This will make the {@link ProgrammableAndroid} go forward.
*/
GO_FORWARD(AndroidType.NON_FIGHTER, HeadTexture.SCRIPT_FORWARD, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.move(b, face, target);
}),
/**
* This will make the {@link ProgrammableAndroid} go up.
*/
GO_UP(AndroidType.NON_FIGHTER, HeadTexture.SCRIPT_UP, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.UP);
android.move(b, face, target);
}),
/**
* This will make the {@link ProgrammableAndroid} go down.
*/
GO_DOWN(AndroidType.NON_FIGHTER, HeadTexture.SCRIPT_DOWN, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.DOWN);
android.move(b, face, target);
}),
// Directions
/**
* This will make the {@link ProgrammableAndroid} rotate to the left side.
*/
TURN_LEFT(AndroidType.NONE, HeadTexture.SCRIPT_LEFT, (android, b, inv, face) -> {
int mod = -1;
android.rotate(b, face, mod);
}),
/**
* This will make the {@link ProgrammableAndroid} rotate to the right side.
*/
TURN_RIGHT(AndroidType.NONE, HeadTexture.SCRIPT_RIGHT, (android, b, inv, face) -> {
int mod = 1;
android.rotate(b, face, mod);
}),
// Action - Pickaxe
/**
* This will make a {@link MinerAndroid} dig the {@link Block} above.
*/
DIG_UP(AndroidType.MINER, HeadTexture.SCRIPT_DIG_UP, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.UP);
android.dig(b, inv, target);
}),
/**
* This will make a {@link MinerAndroid} dig the {@link Block} ahead.
*/
DIG_FORWARD(AndroidType.MINER, HeadTexture.SCRIPT_DIG_FORWARD, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.dig(b, inv, target);
}),
/**
* This will make a {@link MinerAndroid} dig the {@link Block} below.
*/
DIG_DOWN(AndroidType.MINER, HeadTexture.SCRIPT_DIG_DOWN, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.DOWN);
android.dig(b, inv, target);
}),
/**
* This will make a {@link MinerAndroid} dig the {@link Block} above
* and then move itself to that new {@link Location}.
*/
MOVE_AND_DIG_UP(AndroidType.MINER, HeadTexture.SCRIPT_DIG_UP, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.UP);
android.moveAndDig(b, inv, face, target);
}),
/**
* This will make a {@link MinerAndroid} dig the {@link Block} ahead
* and then move itself to that new {@link Location}.
*/
MOVE_AND_DIG_FORWARD(AndroidType.MINER, HeadTexture.SCRIPT_DIG_FORWARD, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.moveAndDig(b, inv, face, target);
}),
/**
* This will make a {@link MinerAndroid} dig the {@link Block} below
* and then move itself to that new {@link Location}.
*/
MOVE_AND_DIG_DOWN(AndroidType.MINER, HeadTexture.SCRIPT_DIG_DOWN, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.DOWN);
android.moveAndDig(b, inv, face, target);
}),
// Action - Sword
/**
* This will make a {@link ButcherAndroid} attack any {@link LivingEntity}
* ahead of them.
*/
ATTACK_MOBS_ANIMALS(AndroidType.FIGHTER, HeadTexture.SCRIPT_ATTACK, (android, b, inv, face) -> {
Predicate<LivingEntity> predicate = e -> true;
android.attack(b, face, predicate);
}),
/**
* This will make a {@link ButcherAndroid} attack any {@link Monster}
* ahead of them.
*/
ATTACK_MOBS(AndroidType.FIGHTER, HeadTexture.SCRIPT_ATTACK, (android, b, inv, face) -> {
Predicate<LivingEntity> predicate = e -> e instanceof Monster;
android.attack(b, face, predicate);
}),
/**
* This will make a {@link ButcherAndroid} attack any {@link Animals Animal}
* ahead of them.
*/
ATTACK_ANIMALS(AndroidType.FIGHTER, HeadTexture.SCRIPT_ATTACK, (android, b, inv, face) -> {
Predicate<LivingEntity> predicate = e -> e instanceof Animals;
android.attack(b, face, predicate);
}),
/**
* This will make a {@link ButcherAndroid} attack any <strong>adult</strong>
* {@link Animals Animal} ahead of them.
*/
ATTACK_ANIMALS_ADULT(AndroidType.FIGHTER, HeadTexture.SCRIPT_ATTACK, (android, b, inv, face) -> {
Predicate<LivingEntity> predicate = e -> e instanceof Animals && e instanceof Ageable && ((Ageable) e).isAdult();
Predicate<LivingEntity> predicate = e -> e instanceof Animals && ((Ageable) e).isAdult();
android.attack(b, face, predicate);
}),
// Action - Axe
/**
* This will make a {@link WoodcutterAndroid} chop down the tree in front of them.
*/
CHOP_TREE(AndroidType.WOODCUTTER, HeadTexture.SCRIPT_CHOP_TREE),
// Action - Fishing Rod
/**
* This {@link Instruction} makes a {@link FisherAndroid} try to catch fish from
* the water below.
*/
CATCH_FISH(AndroidType.FISHERMAN, HeadTexture.SCRIPT_FISH, (android, b, inv, face) -> android.fish(b, inv)),
// Action - Hoe
/**
* This {@link Instruction} will make a {@link FarmerAndroid} try to harvest
* the {@link Block} in front of them.
*/
FARM_FORWARD(AndroidType.FARMER, HeadTexture.SCRIPT_FARM_FORWARD, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.farm(inv, target);
}),
/**
* This {@link Instruction} will make a {@link FarmerAndroid} try to harvest
* the {@link Block} below.
*/
FARM_DOWN(AndroidType.FARMER, HeadTexture.SCRIPT_FARM_DOWN, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.DOWN);
android.farm(inv, target);
}),
// Action - ExoticGarden
/**
* This {@link Instruction} will make a {@link FarmerAndroid} try to harvest
* the {@link Block} in front of them.
*
* <strong>This includes plants from ExoticGarden.</strong>
*/
FARM_EXOTIC_FORWARD(AndroidType.ADVANCED_FARMER, HeadTexture.SCRIPT_FARM_FORWARD, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.exoticFarm(inv, target);
}),
/**
* This {@link Instruction} will make a {@link FarmerAndroid} try to harvest
* the {@link Block} below.
*
* <strong>This includes plants from ExoticGarden.</strong>
*/
FARM_EXOTIC_DOWN(AndroidType.ADVANCED_FARMER, HeadTexture.SCRIPT_FARM_DOWN, (android, b, inv, face) -> {
Block target = b.getRelative(BlockFace.DOWN);
android.exoticFarm(inv, target);
}),
// Action - Interface
/**
* This {@link Instruction} will force the {@link ProgrammableAndroid} to push their
* items into an {@link AndroidInterface} ahead of them.
*/
INTERFACE_ITEMS(AndroidType.NONE, HeadTexture.SCRIPT_PUSH_ITEMS, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.depositItems(inv, target);
}),
/**
* This {@link Instruction} will force the {@link ProgrammableAndroid} to pull
* fuel from an {@link AndroidInterface} ahead of them.
*/
INTERFACE_FUEL(AndroidType.NONE, HeadTexture.SCRIPT_PULL_FUEL, (android, b, inv, face) -> {
Block target = b.getRelative(face);
android.refuel(inv, target);

View File

@ -58,6 +58,7 @@ import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.interfaces.InventoryBlock;
import me.mrCookieSlime.Slimefun.Objects.handlers.BlockTicker;
import me.mrCookieSlime.Slimefun.Objects.handlers.ItemHandler;
import me.mrCookieSlime.Slimefun.api.BlockStorage;
import me.mrCookieSlime.Slimefun.api.Slimefun;
import me.mrCookieSlime.Slimefun.api.SlimefunItemStack;
import me.mrCookieSlime.Slimefun.api.inventory.BlockMenu;
import me.mrCookieSlime.Slimefun.api.inventory.BlockMenuPreset;
@ -69,6 +70,7 @@ public class ProgrammableAndroid extends SlimefunItem implements InventoryBlock,
private static final int[] BORDER = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 18, 24, 25, 26, 27, 33, 35, 36, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53 };
private static final int[] OUTPUT_BORDER = { 10, 11, 12, 13, 14, 19, 23, 28, 32, 37, 38, 39, 40, 41 };
private static final String DEFAULT_SCRIPT = "START-TURN_LEFT-REPEAT";
private static final int MAX_SCRIPT_LENGTH = 54;
protected final List<MachineFuel> fuelTypes = new ArrayList<>();
protected final String texture;
@ -407,19 +409,23 @@ public class ProgrammableAndroid extends SlimefunItem implements InventoryBlock,
} else {
Script script = scripts.get(target);
menu.addItem(index, script.getAsItemStack(this, p), (player, slot, stack, action) -> {
if (action.isShiftClicked()) {
if (script.isAuthor(player)) {
SlimefunPlugin.getLocalization().sendMessage(player, "android.scripts.rating.own", true);
} else if (script.canRate(player)) {
script.rate(player, !action.isRightClicked());
openScriptDownloader(player, b, page);
} else {
SlimefunPlugin.getLocalization().sendMessage(player, "android.scripts.rating.already", true);
try {
if (action.isShiftClicked()) {
if (script.isAuthor(player)) {
SlimefunPlugin.getLocalization().sendMessage(player, "android.scripts.rating.own", true);
} else if (script.canRate(player)) {
script.rate(player, !action.isRightClicked());
openScriptDownloader(player, b, page);
} else {
SlimefunPlugin.getLocalization().sendMessage(player, "android.scripts.rating.already", true);
}
} else if (!action.isRightClicked()) {
script.download();
setScript(b.getLocation(), script.getSourceCode());
openScriptEditor(player, b);
}
} else if (!action.isRightClicked()) {
script.download();
setScript(b.getLocation(), script.getSourceCode());
openScriptEditor(player, b);
} catch (Exception x) {
Slimefun.getLogger().log(Level.SEVERE, "An Exception was thrown when a User tried to download a Script!", x);
}
return false;
@ -534,12 +540,19 @@ public class ProgrammableAndroid extends SlimefunItem implements InventoryBlock,
}
@Nonnull
protected String getScript(@Nonnull Location l) {
public String getScript(@Nonnull Location l) {
Validate.notNull(l, "Location for android not specified");
String script = BlockStorage.getLocationInfo(l, "script");
return script != null ? script : DEFAULT_SCRIPT;
}
protected void setScript(@Nonnull Location l, @Nonnull String script) {
public void setScript(@Nonnull Location l, @Nonnull String script) {
Validate.notNull(l, "Location for android not specified");
Validate.notNull(script, "No script given");
Validate.isTrue(script.startsWith(Instruction.START.name() + '-'), "A script must begin with a 'START' token.");
Validate.isTrue(script.endsWith('-' + Instruction.REPEAT.name()), "A script must end with a 'REPEAT' token.");
Validate.isTrue(PatternUtils.DASH.split(script).length <= MAX_SCRIPT_LENGTH, "Scripts may not have more than " + MAX_SCRIPT_LENGTH + " segments");
BlockStorage.addBlockInfo(l, "script", script);
}

View File

@ -130,18 +130,19 @@ public final class Script {
@Nonnull
ItemStack getAsItemStack(@Nonnull ProgrammableAndroid android, @Nonnull Player p) {
List<String> lore = new LinkedList<>();
lore.add("&7by &r" + getAuthor());
lore.add("&7by &f" + getAuthor());
lore.add("");
lore.add("&7Downloads: &r" + getDownloads());
lore.add("&7Downloads: &f" + getDownloads());
lore.add("&7Rating: " + getScriptRatingPercentage());
lore.add("&a" + getUpvotes() + " \u263A &7| &4\u2639 " + getDownvotes());
lore.add("");
lore.add("&eLeft Click &rto download this Script");
lore.add("&eLeft Click &fto download this Script");
lore.add("&4(This will override your current Script)");
if (canRate(p)) {
lore.add("&eShift + Left Click &rto leave a positive Rating");
lore.add("&eShift + Right Click &rto leave a negative Rating");
lore.add("");
lore.add("&eShift + Left Click &fto leave a positive Rating");
lore.add("&eShift + Right Click &fto leave a negative Rating");
}
return new CustomItem(android.getItem(), "&b" + getName(), lore.toArray(new String[0]));

View File

@ -2,6 +2,8 @@ package io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks;
import java.util.List;
import javax.annotation.ParametersAreNonnullByDefault;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
@ -14,7 +16,6 @@ import org.bukkit.inventory.ItemStack;
import io.github.thebusybiscuit.cscorelib2.inventory.ItemUtils;
import io.github.thebusybiscuit.cscorelib2.item.CustomItem;
import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine;
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils;
import io.papermc.lib.PaperLib;
@ -23,7 +24,7 @@ import me.mrCookieSlime.Slimefun.Objects.Category;
import me.mrCookieSlime.Slimefun.api.Slimefun;
import me.mrCookieSlime.Slimefun.api.SlimefunItemStack;
public class ArmorForge extends MultiBlockMachine {
public class ArmorForge extends BackpackCrafter {
public ArmorForge(Category category, SlimefunItemStack item) {
super(category, item, new ItemStack[] { null, null, null, null, new ItemStack(Material.ANVIL), null, null, new CustomItem(Material.DISPENSER, "Dispenser (Facing up)"), null }, BlockFace.SELF);
@ -31,8 +32,8 @@ public class ArmorForge extends MultiBlockMachine {
@Override
public void onInteract(Player p, Block b) {
Block dispBlock = b.getRelative(BlockFace.DOWN);
BlockState state = PaperLib.getBlockState(dispBlock, false).getState();
Block dispenser = b.getRelative(BlockFace.DOWN);
BlockState state = PaperLib.getBlockState(dispenser, false).getState();
if (state instanceof Dispenser) {
Dispenser disp = (Dispenser) state;
@ -44,13 +45,7 @@ public class ArmorForge extends MultiBlockMachine {
ItemStack output = RecipeType.getRecipeOutputList(this, inputs.get(i)).clone();
if (Slimefun.hasUnlocked(p, output, true)) {
Inventory outputInv = findOutputInventory(output, dispBlock, inv);
if (outputInv != null) {
craft(p, output, inv, outputInv);
} else {
SlimefunPlugin.getLocalization().sendMessage(p, "machines.full-inventory", true);
}
craft(p, output, inv, dispenser);
}
return;
@ -71,26 +66,35 @@ public class ArmorForge extends MultiBlockMachine {
return true;
}
private void craft(Player p, ItemStack output, Inventory inv, Inventory outputInv) {
for (int j = 0; j < 9; j++) {
ItemStack item = inv.getContents()[j];
@ParametersAreNonnullByDefault
private void craft(Player p, ItemStack output, Inventory inv, Block dispenser) {
Inventory fakeInv = createVirtualInventory(inv);
Inventory outputInv = findOutputInventory(output, dispenser, inv, fakeInv);
if (item != null && item.getType() != Material.AIR) {
ItemUtils.consumeItem(item, true);
}
}
if (outputInv != null) {
for (int j = 0; j < 9; j++) {
ItemStack item = inv.getContents()[j];
for (int j = 0; j < 4; j++) {
int current = j;
SlimefunPlugin.runSync(() -> {
if (current < 3) {
p.getWorld().playSound(p.getLocation(), Sound.BLOCK_ANVIL_USE, 1F, 2F);
} else {
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1F, 1F);
outputInv.addItem(output);
if (item != null && item.getType() != Material.AIR) {
ItemUtils.consumeItem(item, true);
}
}, j * 20L);
}
for (int j = 0; j < 4; j++) {
int current = j;
SlimefunPlugin.runSync(() -> {
if (current < 3) {
p.getWorld().playSound(p.getLocation(), Sound.BLOCK_ANVIL_USE, 1F, 2F);
} else {
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1F, 1F);
outputInv.addItem(output);
}
}, j * 20L);
}
} else {
SlimefunPlugin.getLocalization().sendMessage(p, "machines.full-inventory", true);
}
}

View File

@ -32,6 +32,7 @@ import me.mrCookieSlime.Slimefun.api.SlimefunItemStack;
*
* @see EnhancedCraftingTable
* @see MagicWorkbench
* @see ArmorForge
*
*/
abstract class BackpackCrafter extends MultiBlockMachine {

View File

@ -40,7 +40,7 @@ public class SlimefunBootsListener implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player && e.getCause() == DamageCause.FALL) {
onFallDamage(e);
onFallDamage(e);
}
}

View File

@ -219,6 +219,7 @@ public final class SlimefunItemSetup {
registeredItems = true;
DefaultCategories categories = new DefaultCategories();
// @formatter:off (We will need to refactor this one day)
new SlimefunItem(categories.weapons, SlimefunItems.GRANDMAS_WALKING_STICK, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {null, new ItemStack(Material.OAK_LOG), null, null, new ItemStack(Material.OAK_LOG), null, null, new ItemStack(Material.OAK_LOG), null})
.register(plugin);
@ -1092,7 +1093,7 @@ public final class SlimefunItemSetup {
new RestoredBackpack(categories.usefulItems).register(plugin);
new SlimefunItem(categories.technicalComponents, SlimefunItems.MAGNET, RecipeType.SMELTERY,
new UnplaceableBlock(categories.technicalComponents, SlimefunItems.MAGNET, RecipeType.SMELTERY,
new ItemStack[] {SlimefunItems.NICKEL_INGOT, SlimefunItems.ALUMINUM_DUST, SlimefunItems.IRON_DUST, SlimefunItems.COBALT_INGOT, null, null, null, null, null})
.register(plugin);
@ -1303,15 +1304,15 @@ public final class SlimefunItemSetup {
new ItemStack[] {SlimefunItems.CARBONADO, SlimefunItems.BASIC_CIRCUIT_BOARD, SlimefunItems.CARBONADO, SlimefunItems.HEATING_COIL, SlimefunItems.REINFORCED_FURNACE, SlimefunItems.HEATING_COIL, SlimefunItems.CARBONADO, SlimefunItems.ELECTRIC_MOTOR, SlimefunItems.CARBONADO})
.register(plugin);
new SlimefunItem(categories.technicalComponents, SlimefunItems.ELECTRO_MAGNET, RecipeType.ENHANCED_CRAFTING_TABLE,
new UnplaceableBlock(categories.technicalComponents, SlimefunItems.ELECTRO_MAGNET, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {SlimefunItems.NICKEL_INGOT, SlimefunItems.MAGNET, SlimefunItems.COBALT_INGOT, null, SlimefunItems.BATTERY, null, null, null, null})
.register(plugin);
new SlimefunItem(categories.technicalComponents, SlimefunItems.ELECTRIC_MOTOR, RecipeType.ENHANCED_CRAFTING_TABLE,
new UnplaceableBlock(categories.technicalComponents, SlimefunItems.ELECTRIC_MOTOR, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, null, SlimefunItems.ELECTRO_MAGNET, null, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE})
.register(plugin);
new SlimefunItem(categories.technicalComponents, SlimefunItems.HEATING_COIL, RecipeType.ENHANCED_CRAFTING_TABLE,
new UnplaceableBlock(categories.technicalComponents, SlimefunItems.HEATING_COIL, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.ELECTRIC_MOTOR, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE, SlimefunItems.COPPER_WIRE})
.register(plugin);
@ -1455,7 +1456,7 @@ public final class SlimefunItemSetup {
new SlimefunItemStack(SlimefunItems.HARDENED_GLASS, 16))
.register(plugin);
new SlimefunItem(categories.technicalComponents, SlimefunItems.COOLING_UNIT, RecipeType.ENHANCED_CRAFTING_TABLE,
new UnplaceableBlock(categories.technicalComponents, SlimefunItems.COOLING_UNIT, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {new ItemStack(Material.ICE), new ItemStack(Material.ICE), new ItemStack(Material.ICE), SlimefunItems.ALUMINUM_INGOT, SlimefunItems.ELECTRIC_MOTOR, SlimefunItems.ALUMINUM_INGOT, new ItemStack(Material.ICE), new ItemStack(Material.ICE), new ItemStack(Material.ICE)})
.register(plugin);
@ -2867,7 +2868,7 @@ public final class SlimefunItemSetup {
}.register(plugin);
new SlimefunItem(categories.cargo, SlimefunItems.CARGO_MOTOR, RecipeType.ENHANCED_CRAFTING_TABLE,
new UnplaceableBlock(categories.cargo, SlimefunItems.CARGO_MOTOR, RecipeType.ENHANCED_CRAFTING_TABLE,
new ItemStack[] {SlimefunItems.HARDENED_GLASS, SlimefunItems.ELECTRO_MAGNET, SlimefunItems.HARDENED_GLASS, SlimefunItems.SILVER_INGOT, SlimefunItems.ELECTRIC_MOTOR, SlimefunItems.SILVER_INGOT, SlimefunItems.HARDENED_GLASS, SlimefunItems.ELECTRO_MAGNET, SlimefunItems.HARDENED_GLASS},
new SlimefunItemStack(SlimefunItems.CARGO_MOTOR, 4))
.register(plugin);
@ -3048,6 +3049,8 @@ public final class SlimefunItemSetup {
new ElytraCap(categories.magicalArmor, SlimefunItems.ELYTRA_CAP, RecipeType.ARMOR_FORGE,
new ItemStack[]{new ItemStack(Material.SLIME_BALL), new ItemStack(Material.SLIME_BALL), new ItemStack(Material.SLIME_BALL), SlimefunItems.ELYTRA_SCALE, SlimefunItems.ELYTRA_SCALE, SlimefunItems.ELYTRA_SCALE, new ItemStack(Material.SLIME_BALL), new ItemStack(Material.LEATHER_HELMET), new ItemStack(Material.SLIME_BALL)})
.register(plugin);
// @formatter:on
}
private static void registerArmorSet(Category category, ItemStack baseComponent, ItemStack[] items, String idSyntax, boolean vanilla, PotionEffect[][] effects, SlimefunAddon addon) {

View File

@ -29,6 +29,9 @@ public final class ChestMenuUtils {
private static final ItemStack INPUT_SLOT = new SlimefunItemStack("_UI_INPUT_SLOT", Material.CYAN_STAINED_GLASS_PANE, " ");
private static final ItemStack OUTPUT_SLOT = new SlimefunItemStack("_UI_OUTPUT_SLOT", Material.ORANGE_STAINED_GLASS_PANE, " ");
private static final ItemStack NO_PERMISSION = new SlimefunItemStack("_UI_NO_PERMISSION", Material.BARRIER, "No Permission");
private static final ItemStack NOT_RESEARCHED = new SlimefunItemStack("_UI_NOT_RESEARCHED", Material.BARRIER, "Not researched");
private static final ItemStack BACK_BUTTON = new SlimefunItemStack("_UI_BACK", Material.ENCHANTED_BOOK, "&7\u21E6 Back", meta -> meta.addItemFlags(ItemFlag.HIDE_ENCHANTS));
private static final ItemStack MENU_BUTTON = new SlimefunItemStack("_UI_MENU", Material.COMPARATOR, "&eSettings / Info", "", "&7\u21E8 Click to see more");
private static final ItemStack SEARCH_BUTTON = new SlimefunItemStack("_UI_SEARCH", Material.NAME_TAG, "&bSearch");
@ -46,6 +49,16 @@ public final class ChestMenuUtils {
return UI_BACKGROUND;
}
@Nonnull
public static ItemStack getNoPermissionItem() {
return NO_PERMISSION;
}
@Nonnull
public static ItemStack getNotResearchedItem() {
return NOT_RESEARCHED;
}
@Nonnull
public static ItemStack getInputSlotTexture() {
return INPUT_SLOT;

View File

@ -4,6 +4,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import io.github.thebusybiscuit.slimefun4.utils.tags.SlimefunTag;
@ -25,10 +28,12 @@ public final class ColoredMaterials {
*/
private ColoredMaterials() {}
// @formatter:off (We want this to stay formatted like this)
/**
* This {@link List} contains all wool colors ordered by their appearance ingame.
*/
public static final List<Material> WOOL = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> WOOL = asList(new Material[] {
Material.WHITE_WOOL,
Material.ORANGE_WOOL,
Material.MAGENTA_WOOL,
@ -45,12 +50,12 @@ public final class ColoredMaterials {
Material.GREEN_WOOL,
Material.RED_WOOL,
Material.BLACK_WOOL
));
});
/**
* This {@link List} contains all stained glass colors ordered by their appearance ingame.
*/
public static final List<Material> STAINED_GLASS = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> STAINED_GLASS = asList(new Material[] {
Material.WHITE_STAINED_GLASS,
Material.ORANGE_STAINED_GLASS,
Material.MAGENTA_STAINED_GLASS,
@ -67,12 +72,12 @@ public final class ColoredMaterials {
Material.GREEN_STAINED_GLASS,
Material.RED_STAINED_GLASS,
Material.BLACK_STAINED_GLASS
));
});
/**
* This {@link List} contains all stained glass pane colors ordered by their appearance ingame.
*/
public static final List<Material> STAINED_GLASS_PANE = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> STAINED_GLASS_PANE = asList(new Material[] {
Material.WHITE_STAINED_GLASS_PANE,
Material.ORANGE_STAINED_GLASS_PANE,
Material.MAGENTA_STAINED_GLASS_PANE,
@ -89,12 +94,12 @@ public final class ColoredMaterials {
Material.GREEN_STAINED_GLASS_PANE,
Material.RED_STAINED_GLASS_PANE,
Material.BLACK_STAINED_GLASS_PANE
));
});
/**
* This {@link List} contains all terracotta colors ordered by their appearance ingame.
*/
public static final List<Material> TERRACOTTA = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> TERRACOTTA = asList(new Material[] {
Material.WHITE_TERRACOTTA,
Material.ORANGE_TERRACOTTA,
Material.MAGENTA_TERRACOTTA,
@ -111,12 +116,12 @@ public final class ColoredMaterials {
Material.GREEN_TERRACOTTA,
Material.RED_TERRACOTTA,
Material.BLACK_TERRACOTTA
));
});
/**
* This {@link List} contains all glazed terracotta colors ordered by their appearance ingame.
*/
public static final List<Material> GLAZED_TERRACOTTA = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> GLAZED_TERRACOTTA = asList(new Material[] {
Material.WHITE_GLAZED_TERRACOTTA,
Material.ORANGE_GLAZED_TERRACOTTA,
Material.MAGENTA_GLAZED_TERRACOTTA,
@ -133,12 +138,12 @@ public final class ColoredMaterials {
Material.GREEN_GLAZED_TERRACOTTA,
Material.RED_GLAZED_TERRACOTTA,
Material.BLACK_GLAZED_TERRACOTTA
));
});
/**
* This {@link List} contains all concrete colors ordered by their appearance ingame.
*/
public static final List<Material> CONCRETE = Collections.unmodifiableList(Arrays.asList(
public static final List<Material> CONCRETE = asList(new Material[] {
Material.WHITE_CONCRETE,
Material.ORANGE_CONCRETE,
Material.MAGENTA_CONCRETE,
@ -155,6 +160,16 @@ public final class ColoredMaterials {
Material.GREEN_CONCRETE,
Material.RED_CONCRETE,
Material.BLACK_CONCRETE
));
});
// @formatter:on
@Nonnull
private static List<Material> asList(@Nonnull Material[] materials) {
Validate.noNullElements(materials, "The List cannot contain any null elements");
Validate.isTrue(materials.length == 16, "Expected 16, received: " + materials.length + ". Did you miss a color?");
return Collections.unmodifiableList(Arrays.asList(materials));
}
}

View File

@ -1,6 +1,7 @@
package io.github.thebusybiscuit.slimefun4.utils;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.LocalDateTime;
@ -24,7 +25,7 @@ public final class NumberUtils {
/**
* This is our {@link DecimalFormat} for decimal values.
*/
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##", DecimalFormatSymbols.getInstance(Locale.ROOT));
/**
* We do not want any instance of this to be created.
@ -105,8 +106,31 @@ public final class NumberUtils {
*/
@Nonnull
public static String getElapsedTime(@Nonnull LocalDateTime date) {
Validate.notNull(date, "Provided date was null");
long hours = Duration.between(date, LocalDateTime.now()).toHours();
return getElapsedTime(LocalDateTime.now(), date);
}
/**
* This returns the elapsed time between the two given {@link LocalDateTime LocalDateTimes}.
* The output will be nicely formatted based on the elapsed hours or days between the
* given {@link LocalDateTime LocalDateTime}.
*
* If a {@link LocalDateTime} from today and yesterday (exactly 24h apart) was passed it
* will return {@code "1d"}.
* One hour later it will read {@code "1d 1h"}. For values smaller than an hour {@code "< 1h"}
* will be returned instead.
*
* @param start
* The starting {@link LocalDateTime}.
* @param end
* The ending {@link LocalDateTime}.
*
* @return The elapsed time as a {@link String}
*/
@Nonnull
public static String getElapsedTime(@Nonnull LocalDateTime start, @Nonnull LocalDateTime end) {
Validate.notNull(start, "Provided start was null");
Validate.notNull(end, "Provided end was null");
long hours = Duration.between(start, end).toHours();
if (hours == 0) {
return "< 1h";
@ -133,12 +157,24 @@ public final class NumberUtils {
return timeleft + seconds + "s";
}
/**
* This method parses a {@link String} into an {@link Integer}.
* If the {@link String} could not be parsed correctly, the provided
* default value will be returned instead.
*
* @param str
* The {@link String} to parse
* @param defaultValue
* The default value for when the {@link String} could not be parsed
*
* @return The resulting {@link Integer}
*/
public static int getInt(@Nonnull String str, int defaultValue) {
if (PatternUtils.NUMERIC.matcher(str).matches()) {
return Integer.parseInt(str);
} else {
return defaultValue;
}
return defaultValue;
}
@Nonnull
@ -183,6 +219,8 @@ public final class NumberUtils {
* The value to clamp
* @param max
* The maximum value
*
* @return The clamped value
*/
public static int clamp(int min, int value, int max) {
if (value < min) {

View File

@ -5,9 +5,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
@ -238,31 +241,30 @@ public abstract class AContainer extends SlimefunItem implements InventoryBlock,
BlockMenu inv = BlockStorage.getInventory(b);
if (isProcessing(b)) {
int timeleft = progress.get(b);
if (timeleft > 0) {
ChestMenuUtils.updateProgressbar(inv, 22, timeleft, processing.get(b).getTicks(), getProgressBar());
if (takeCharge(b.getLocation())) {
if (isChargeable()) {
if (getCharge(b.getLocation()) < getEnergyConsumption()) {
return;
int timeleft = progress.get(b);
if (timeleft > 0) {
ChestMenuUtils.updateProgressbar(inv, 22, timeleft, processing.get(b).getTicks(), getProgressBar());
progress.put(b, timeleft - 1);
} else {
inv.replaceExistingItem(22, new CustomItem(Material.BLACK_STAINED_GLASS_PANE, " "));
for (ItemStack output : processing.get(b).getOutput()) {
inv.pushItem(output.clone(), getOutputSlots());
}
removeCharge(b.getLocation(), getEnergyConsumption());
Bukkit.getPluginManager().callEvent(new AsyncMachineProcessCompleteEvent(b.getLocation(), AContainer.this, getProcessing(b)));
progress.remove(b);
processing.remove(b);
}
progress.put(b, timeleft - 1);
} else {
inv.replaceExistingItem(22, new CustomItem(Material.BLACK_STAINED_GLASS_PANE, " "));
for (ItemStack output : processing.get(b).getOutput()) {
inv.pushItem(output.clone(), getOutputSlots());
}
Bukkit.getPluginManager().callEvent(new AsyncMachineProcessCompleteEvent(b.getLocation(), AContainer.this, getProcessing(b)));
progress.remove(b);
processing.remove(b);
}
} else {
MachineRecipe next = findNextRecipe(inv);
@ -273,6 +275,26 @@ public abstract class AContainer extends SlimefunItem implements InventoryBlock,
}
}
/**
* This method will remove charge from a location if it is chargeable.
*
* @param l
* location to try to remove charge from
* @return Whether charge was taken if its chargeable
*/
protected boolean takeCharge(@Nonnull Location l) {
Validate.notNull(l, "Can't attempt to take charge from a null location!");
if (isChargeable()) {
if (getCharge(l) < getEnergyConsumption()) {
return false;
}
removeCharge(l, getEnergyConsumption());
}
return true;
}
protected MachineRecipe findNextRecipe(BlockMenu inv) {
Map<Integer, ItemStack> inventory = new HashMap<>();

View File

@ -244,6 +244,7 @@ machines:
finished: Az Industrial Miner-ed kész! Összesen %ores% ércet szerzett!
anvil:
not-working: "&4Nem használhatsz Slimefun tárgyakat az üllőben!"
mcmmo-salvaging: "&4Nem hasznosíthatsz újra Slimefun tárgyakat!"
backpack:
already-open: "&cSajnáljuk, ez a hátizsák valahol máshol már nyitva van!"
no-stack: "&cNem halmozhatsz hátizsákokat"
@ -306,7 +307,7 @@ android:
rating:
own: "&4Nem értékelheted a saját szkriptedet!"
already: "&4Ezt a szkriptet már értékelted!"
editor: Szkript Szerkesztő
editor: Szkript szerkesztő
languages:
default: Szerver-alapértelmezett
en: Angol
@ -328,7 +329,6 @@ languages:
zh-CN: Kínai (Kína)
el: Görög
he: Héber
pt-BR: Portugál (Brazília)
ar: Arab
af: Afrikaans
da: Dán
@ -341,6 +341,7 @@ languages:
th: Thai
ro: Román
pt: Portugál (Portugália)
pt-BR: Portugál (Brazília)
bg: Bolgár
ko: Koreai
tr: Török
@ -355,5 +356,7 @@ villagers:
no-trading: "&4Nem cserélhetsz Slimefun tárgyakat falusiakkal!"
cartography_table:
not-working: "&4Nem használhatsz Slimefun tárgyakat térképasztalban."
cauldron:
no-discoloring: "&4Nem színteleníthetsz Slimefun páncélt!"
miner:
no-ores: "&eSajnálom, nem találtam semmilyen ércet a közelben!"

View File

@ -246,3 +246,4 @@ slimefun:
caveman_talisman: Az Ősember talizmánja
even_higher_tier_capacitors: 3. szintű kondenzátorok
elytra_cap: Ütközésvédelem
energy_connectors: Vezetékes csatlakozás

View File

@ -0,0 +1,78 @@
package io.github.thebusybiscuit.slimefun4.testing.tests.utils;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
class TestNumberUtils {
@Test
@DisplayName("Test NumberUtils.clamp(...)")
void testHumanize() {
// Below minimum
Assertions.assertEquals(2, NumberUtils.clamp(2, 0, 5));
// Normal
Assertions.assertEquals(3, NumberUtils.clamp(0, 3, 5));
// Above maximum
Assertions.assertEquals(20, NumberUtils.clamp(1, 100, 20));
}
@Test
@DisplayName("Test elapsed time string")
void testElapsedTime() {
LocalDateTime start = LocalDateTime.now();
LocalDateTime a = start.plusDays(1);
Assertions.assertEquals("1d", NumberUtils.getElapsedTime(start, a));
LocalDateTime b = start.plusHours(25);
Assertions.assertEquals("1d 1h", NumberUtils.getElapsedTime(start, b));
LocalDateTime c = start.plusHours(1);
Assertions.assertEquals("1h", NumberUtils.getElapsedTime(start, c));
LocalDateTime d = start.plusMinutes(12);
Assertions.assertEquals("< 1h", NumberUtils.getElapsedTime(start, d));
}
@Test
@DisplayName("Test Integer parsing")
void testIntegerParsing() {
Assertions.assertEquals(6, NumberUtils.getInt("6", 0));
Assertions.assertEquals(12, NumberUtils.getInt("I am a String", 12));
}
@Test
@DisplayName("Test nullable Long")
void testNullableLong() {
Assertions.assertEquals(10, NumberUtils.getLong(10L, 20L));
Assertions.assertEquals(20, NumberUtils.getLong(null, 20L));
}
@Test
@DisplayName("Test nullable Int")
void testNullableInt() {
Assertions.assertEquals(10, NumberUtils.getInt(10, 20));
Assertions.assertEquals(20, NumberUtils.getInt((Integer) null, 20));
}
@Test
@DisplayName("Test nullable Float")
void testNullableFloat() {
Assertions.assertEquals(10, NumberUtils.getFloat(10F, 20F));
Assertions.assertEquals(20, NumberUtils.getFloat(null, 20F));
}
@Test
@DisplayName("Test decimal rounding")
void testRounding() {
Assertions.assertEquals("5.25", NumberUtils.roundDecimalNumber(5.249999999999));
}
}