Spigot codding, inventory - java

I made a simple plugin on actions. I have one issue.
When player press shift and quickly close inventory, he can get item without pay. I need to fix this, what do I need to do?
Code Below:
#EventHandler public void inventoryHandler(InventoryClickEvent e) throws Exception {
for (AuctionInventory[] inventories : inventoryMap.values()){
for (AuctionInventory inventory : inventories){
if (inventory.getInventoryType().equals(AuctionInventoryType.MAIN)) {
if (e.getClickedInventory().equals(inventory.getSource())) {
e.setResult(Event.Result.DENY);
e.setCancelled(true);
if (e.getCurrentItem().getType() != Material.AIR) {
Player p = (Player) e.getWhoClicked();
for (Button button : buttonList){
if (button.getItem().equals(e.getCurrentItem())){
button.doLogic(inventory.getSource(), p);
e.setResult(Event.Result.DENY);
}
}
IProduct product = InventoryUtil.getProductByItem(getProducts().values(), e.getCurrentItem());
if ((product != null)) {
buyProduct(p, product);
}
}
}
}
}
}
}
#Override #Deprecated #SuppressWarnings("all")
public boolean buyProduct(Player p, IProduct product) throws Exception {
if (!product.getSeller().equals(p.getUniqueId())) {
if (Economy.getMoney(p.getName()) < product.getPrice()) {
return false;
}
p.getInventory().addItem(product.getItem());
removeProduct(product);
Economy.setMoney(p.getName(), Economy.getMoney(p.getName()) - product.getPrice()); Economy.setMoney(Bukkit.getOfflinePlayer(product.getSeller()).getName(), Economy.getMoney(Bukkit.getOfflinePlayer(product.getSeller()).getName()) + product.getPrice());
return true;
}
return false;
}

It is hard to say, if your logic related to checking inventory is fine I would say that you missed InventoryDragEvent. You need to implement that event too, and block any interactions related to your inventories.
Also in your ClickEvent you should block interaction if your inventory is open - including clicks inside player inventory, as there are interactions that can move items from opened inventory by clicking inside own inventory.
Also getClickedInventory() can return null.
So you should just check event.getView().getTopInventory() check if it is not a null, and if it is your inventory.
Also your for loop with buttons does not break/return, so even if player will hit a button you will still try to find item to sell, that might cause some issues too.
Also I noticed few others problems here, you should not use .setMoney function if you are using Vault API as this might break compatibility with other plugins, same with checking money by getMoney.
There is special public boolean has(OfflinePlayer player, double amount); method to check if player have enough money, as this function will work well with plugins that allows for negative amounts, or paying in different way.
Then you should take money from player by: public EconomyResponse withdrawPlayer(OfflinePlayer player, double amount); also you should then check response to ensure that it was successful.
p.getInventory().addItem(product.getItem()); what if player does not have enough space in inventory? Note that this method returns Map<Integer, ItemStack> where key is index of item from method argument (as it is varargs, in your case key can be only a 0, as you are only passing single argument) and item that didn't fit into inventory. (note that one part of stack might be added, like 12 from 43 items in stack)

Related

Checking "rules" in Java without lots of if statements

I'm creating a springboot banking API and in order to create a transaction a bunch of "rules" have to be checked.
e.g:
Current logged in user can't withdraw money from another user's savings account
Amount can't be higher/lower than certain number
etc.
This causes my createTransaction method to contain a lot of if statements (12!). This is what my code looks like in pseudo:
public ResponseEntity<String> createTransaction(Transaction body) {
if (check rule 1) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("...");
}
if (check rule 2) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("...");
}
// etc...
// Transaction complies to set rules
return ResponseEntity.status(HttpStatus.CREATED).body("Transaction successful!");
}
I can post my actual code if necessary but I think this paints the picture without having anyone to read 100 lines of code.
Because I have around 12 if statements checking these rules, my function is quite lengthy and difficult to read/maintain.
Googling for a solution didn't bring up results I was looking for. I've tried implementing exceptions but this didn't remove the amount of if statements. Maybe a switch could improve a bit, but I'm wondering if there's a clean OOP solution.
My question is: How can I clean this code up (OOP style)?
Thanks in advance.
You should create a TransactionRule interface that allows you to implement specific transaction rules, and then use a stream to get the final result:
public interface TransactionRule {
public boolean isAllowed(Transaction someTransaction);
}
Example implementation 1:
public class SufficientBudgetTransactionRule implements TransactionRule {
public boolean isAllowed(Transaction someTransaction) {
// Custom logic e.g.
return someTransaction.wallet.value >= someTransaction.transaction.value;
}
}
Example implementation 2:
public class NotInFutureTransactionRule implements TransactionRule {
public boolean isAllowed(Transaction someTransaction) {
// Custom logic e.g.
return someTransaction.transaction.datetime.isBefore(OffsetDateTime.now());
}
}
Then, you can store all the TransactionRules in a List and check whether they all validate like so:
private final List<TransactionRule> transactionRules; // Fill these of course
public boolean allTransactionRulesMatch(Transaction someTransaction) {
return transactionRules.stream()
.map(transactionRule -> transactionRule.isAllowed(someTransaction))
.allMatch(result => result);
}

Teleport to next player

I am working on a Spigot 1.8.9 plugin and am trying to add a feature when a staff right-clicks an item it teleports them to the next player that isn't in vanish and not themselves and if there aren't any it should return null.
On click I attempted to add all possible users to a list using
public static List<User> getPossibleUsers(User user){
List<User> result = new ArrayList<>();
for(User target : users)
if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
result.add(target);
return result;
}
The staff is also assigned an int called nextPlayer which is set to 0 when they login. Then when they click I add one to the int so next time they click it can get the next user.
private User getNextPlayer(User user) {
int next = user.nextPlayer;
List<User> users = getPossibleUsers(user);
if(users.size() == 0)
return null;
int current = 0;
for(User target : users) {
if(current == next){
return target;
}
current++;
}
user.nextPlayer = next;
}
The problem is I don't know how to make the getNextPlayer method correctly and make it efficient. I also would like to also to make it so once it hits the last player it loops back to the first player.
I'd suggest thinking about your problem entirely differently if you want it to be efficient, but efficiency really isn't a concern in this situation, so I'm opting to not pre-maturely optimize and instead work with the code you already have.
public static List<User> getPossibleUsers(User user){
List<User> result = new ArrayList<>();
for(User target : users)
if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
result.add(target);
return result;
}
This currently returns the Users in the same order, as they are defined on users.
This better have a natural sort order, otherwise you are going to have issues when people join / leave the server, as it will cause people to change their ordering in the list.
Now let's get back to first principals.
int next = user.nextPlayer;
Looks like you are storing the index of the player in the list you have already been in on the 'user'.
Once you have this, you can access that index directly from the list.
https://docs.oracle.com/javase/8/docs/api/java/util/List.html#get-int-
E get(int index)
So, doing users.get(next++); is all you need to do to 'fix' the code you have above. it increments next, and gets the user at that position (assuming the ordering is consistent, and hasn't changed) However, it may throw an exception if it's out of range of the list, so we wrap it in
if(next <= users.length) {
users.get(next++);
} else return null;
This will change it to returning null, if it would otherwise throw an exception.
BUT all of this still has a fatal flaw, that if the list is mutated between calls, that you could be potentially skipping or changing the order around.
A far better solution to this, is to instead cache the visited users, as well as the last visited user.
If the users are ordered, and you store the last visited user, instead of the index, you are storing data that is much more resilient to change, and more closely matches the behavior you want.
To more closely match your needs, you are asking that.
Generate a predictable, ordered list of users that don't include the admin, or anyone else that is vanished, to aid the admin in predicting where they are going.
Rotate through this list, by right clicking with a tool, (Note this is async, so all the state needs to be saved)
Ensure that all visited users are visited before repeating the sequence.
public class TeleportTooldata {
private ListIterator<UUID> cursor;
private List<UUID> cachedOrder;
public TeleportTooldata(List<UUID> applicableUsers) {
cachedOrder = applicableUsers;
}
#Nullable
public UUID next() {
if (!cursor.hasNext()) return null;
UUID next = cursor.next();
if (!cachedOrder.contains(next)) {
cachedOrder.add(next);
}
return next;
}
public void Update(List<UUID> applicableUsers) {
applicableUsers.removeAll(cachedOrder);
cachedOrder.addAll(applicableUsers);
}
}
public class TeleportToolUtil {
YourPluginUserRepo repo;
Map<User, TeleportTooldata> storage; //This could be a cache, make sure to remove if they log out, or maybe timed as well.
public List<UUID> getApplicableUsers() {
return repo.getOnlineUsers().stream()
.filter(User::isVanish)
.sorted(Comparator.comparing(User::getId)) // You can change the sort order
.map(User::getId)
.collect(Collectors.toList());
}
public void onToolUse(User user) {
TeleportTooldata data = storage.computeIfAbsent(user, x -> new TeleportTooldata(getApplicableUsers()));
UUID next = data.next();
if (next == null) {
data.Update(getApplicableUsers());
next = data.next();
if(next == null) {
storage.put(user, new TeleportTooldata(getApplicableUsers()));
next = data.next();
}
}
user.teleportTo(next);
}
}
A few changes.
We are now caching the ordering, so that you could conceptually also let the user go backwards through the list.
We are using ListIterator. ListIterator is an object that loops through lists, and stores the current position for you! Much like you were doing before, but without indexes.
We now have the possibility to update the data, in case a player joins late, or someone unvanishes they will be put at the back of the list if they are not already inside it.
when we run out of users, we attempt an update, if we are really out, we start again with a brand new list. (note this won't guarantee the same order every time (people will be 'properly' sorted when it updates if they were previously appended, but it's close enough for this usecase)
However! We still need to be mindful of memory leaks. using UUID's rather then players or users, means this class is very light weight, we should be pretty safe from memory leaks in the list of UUID AS LONG as the TeleportTooldata doesn't live too long.
You can replace the Map of TeleportTooldata with a cache (maybe from Guava?) to remove the data some time after the admin leaves the game.
If TeleportTooldata was expected to be long-lived, we would want to seriously consider removing UUID's from the history.
Also, not handled in my example, is the possibility of the users going offline after the order is cached.
To handle this, before teleporting the player, check if the uuid is online, otherwise go to the 'next' and follow all the same logic again.

Java convention in practice - return mutliple values from method

I have two questions about Java Convention. I try to make use od Robert C. Martin's "Clean Code".
Following case:
public void startProgressIfAllowed() {
try {
tryStartProgressIfAllowed();
} catch (Exception exception) {
// log error
}
}
private void tryStartProgressIfAllowed() {
if (isStartProgressAllowed()) {
stopProgressOnCurrentlyStartedTask();
startProgressOnThisTask();
}
}
private boolean isStartProgressAllowed() {
// Calls JOptionPane.showConfirmDialog with JOptionPane.YES_NO_OPTION.
// Created dialog contains checkbox indicating that saving currently started task is required.
// returns boolean depending on JOptionPane.YES_NO_OPTION clicked button
}
private void stopProgressOnCurrentlyStartedTask() {
// Saves currently started task depending on checkbox selecion property and stops currently started.
// What is the correct way to get checkbox selecion property?
}
Proposed solution:
public void tryStartProgressIfAllowed() {
if (tryToStopProgressOnStartedTaskIfNecessary()) {
startProgressOnThisTask();
}
}
private boolean tryToStopProgressOnStartedTaskIfNecessary() {
// Calls JOptionPane.showConfirmDialog with JOptionPane.YES_NO_OPTION.
// Created dialog contains checkbox indicating that saving currently started task is required.
// Depending on checkbox selecion property saves task.
// returns boolean depending on JOptionPane.YES_NO_OPTION clicked button
}
But this approach doesn't meet the "Command Query Separation" principle, because tryToStopProgressOnStartedTaskIfNecessary(...) method performs some logic and returns success/failure value.
I think this approach also doesn't meet the "One level of abstraction per function" principle, because I suppose "check" and "save" operations are on different levels of abstraction.
Is the method name correct to avoid disinformation? Maybe better name would be tryToStopProgressAndSaveStartedTaskIfNecessary(...)?
Is there any better solution for above problem?
What about the following:
public void tryStartProgressOnThisTaskIfAllowed() {
tryStopTaskInProgressIfAllowed()
if (!isTaskInProgress()) {
tryStartProgressOnThisTask();
}
}
private void tryStopTaskInProgressIfAllowed() {
if (!isTaskInProgress()) {
return;
}
TaskInProgressResult result = whatToDoWithTaskInProgress();
if (result == Result.KEEP) {
return;
} else if (result == Result.DROP)
tryDropTaskInProgress();
} else if (result == Result.SAVE) {
trySaveTaskInProgress();
}
}
About your points:
You now have two separate methods for C and Q
I think the two things whatToDoWithTaskInProgress and tryDropTaskInProgress are the same level of abstraction. If you'd inline the code of one or the other you were absolutely right of course.
I changed some of the method names according to my taste :) The only thing I still don't like is the part "OnThisTask" because this task is somewhat meaningless. Maybe it's only because the rest of the code is unknown maybe OnNextTask or OnNewTask are better.
The problem we were having is that we were thinking in UI terms YES/NO + checkbox value. But it is much better to think in business terms here. I identified three different outcomes that are of interest: KEEP, SAVE, DROP How the answer is obtained should not matter to the calling method.
This seems something to ask on CodeReview, see the drop down at the top left of the page.
An example of how such stateliness is realized in Java SE: the regex Matcher class.
String s = ...
Pattern pattern = Pattern.compile("...");
Matcher m = pattern.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, ... m.group(1) ...);
}
m.appendTail(sb);
with m.matches() and m.lookingAt as alternative circuits too.
In short state is held in a processing class on the actual data (String here).

LWJGL Keyboard loop

I've created a static Input class, that basicly have a method that I can call, which is this:
public static boolean GetKeyDown(int keyCode) {
while(Keyboard.next()) {
Keyboard.enableRepeatEvents(false);
if (Keyboard.getEventKeyState()) {
if (Keyboard.getEventKey() == keyCode) {
return true;
} else {
return false;
}
}
}
return false;
}
And in my game update loop, I've wanted to use this, instead of having to make a single while-loop:
if(Input.GetKeyDown(KeyCode.S)) {
//Something happens
}
if(Input.GetKeyDown(KeyCode.R)) {
//Something happens
}
//etc..
But it seems that only the first one loaded, will work. In this case 'S'. Is there a way for me to do be able to use the others too?
That is because in your GetKeyDown() method, you call Keyboard.next(), when you call that method it removes the Event of the current key from Keyboard, the only gets refilled with Events, when you call Display.update();
NOTE: This method does not query the operating system for new events. To do that, Display.processMessages() (or Display.update()) must be called first.
Source: LWJGL Docs
You Could
Instead you can use the Keyboard.isKeyDown(int key) method, to achieve what you're trying to do.
Though it returns true/false depending on the following.
Returns: true if the key is down according to the last poll()
But that still doesn't quite fix the problem because it relies on the poll() method.
Fixing The Problem
You can fix the problem by creating some custom methods to use with the Keyboard class, as you already did, though as said the Keyboard Events only gets updated when you call the Display.update(); method.
You already got the right idea about which function to create, though you need to split them into, two different methods. You need a secondary method which you call once each time you want to update your keyboard.
public class MyKeyboard {
/*
* Remember that the index where we store the value,
* is the index of the key. Thereby one key might have
* an index of 400, then your array need to have at least
* the same size, to be able to store it.
*/
public static boolean[] keys = new boolean[100]; // 100 is the amount of keys to remember!
public static void update() {
while(Keyboard.next()) {
if (Keyboard.getEventKey() < keys.length) {
keys[Keyboard.getEventKey()] = Keyboard.getEventKeyState();
}
}
}
public static boolean isKeyDown(int key) {
if ((key > 0) && (key < keys.length)) {
return keys[key];
}
return false;
}
}
Remember to only call the MyKeyboard.update() method once per Display.update() I also renamed your GetKeyDown() method to isKeyDown(), because I think that sounds and describes it better, but you can rename it again in your project if you want to.
The above code was made within this answer, without the use of an IDE, etc. So if there's anything wrong with it I apologize, but just comment and I will fix it.
One problem that arises with this method is the lack of rechecking. Since Keyboard.next() only checks the inputs that have occurred in the current frame. A button which was once pressed will remain "pressed" until it is pressed again. I ran into this problem while trying to implement this solution. The answer to this new problem is here:
public static void update() {
for(int i = 0; i < keys.length; i++) {
keys[i] = false;
}
while(Keyboard.next()) {
keys[Keyboard.getEventKey()] = Keyboard.getEventKeyState();
}
}
You must clear the keypresses of the previous frame by setting everything to false.

MVC pattern in java, more than one update method in observer

I'm writing a chess game with gui in mvc design.
Step 1: a main menu pops and you choose a game mod.
Step 2: once you chose, the main menu closes and a new window opens with the board and pieces, then you play with the mouse.
for step 1; I use actionEvent and check the string of the button you clicked on.
for example, you have the button Standard Game, and then model sets up the dataBoard and notifies observer(=view).
for step 2; I use mouseEvent and check relative coordinates x/y, the model does what it does and decides if you can move the piece.
I want to have two update methods in view, one for step 1, the other for step 2.
currently it always goes to the first update.
// this is in the model, initializing once you chose a game mod,
// this is for the first step.
public void initializeGame(String givenString){
abstractGame = factoryGame.createGame(givenString);
abstractGame.startPlaying(boardTest);
setChanged();
notifyObservers(5);
}
// this is in the model, doing stuff, this is for the second step.
public boolean checkGivenCoordinates(int sourceRow, int sourceColumn, int destinationRow, int destinationColumn) throws IncorrectCoordinatesException, IncorrectColorException, InvalidMoveException
{
if(abstractGame.checkCorrectCoordinates(sourceRow, sourceColumn, destinationRow, destinationColumn) == true)
{
abstractGame.checkMove(sourceRow, sourceColumn, destinationRow, destinationColumn);
int [] changeView = {sourceRow, sourceColumn, destinationRow, destinationColumn};
System.out.println("Model : in the move ");
setChanged();
notifyObservers(changeView);
return true;
}
else
throw new IncorrectCoordinatesException();
}
// Called from the Model
public void update(Observable obs, Object obj) { // this is the one it always goes to now.
//who called us and what did they send?
System.out.println ("First update View : Observable is " + obs.getClass() + ", object passed is " + obj.getClass());
} //update()
// Called from the Model
/* this is for step 2, but is not invoked.
The array I send holds 4 ints, source row/column and destination row/column.
this is what I do in model just prior to notifying,
supposed to go to step 2's update method,
but as stated, it doesnt.
*/
public void update(Observable obs, int[] obj) {
//who called us and what did they send?
System.out.println ("Second update View : Observable is " + obs.getClass() + ", object passed is " + obj.getClass());
graphBoardView.setMove(obj[0], obj[1], obj[2], obj[3]);
} //update()
so,
1) can I have more than one update in a single class?
2) if so, how can I direct notifying to the correct update method?
3) if q1 isn't possible, how can I bypass this?
4) could it be that Object obj causes it to always go to the first one?
thanks in advance,
ariel.
You can indeed have more than one method of the same name, but with different parameters, in the same class.
You can see what you're seeing when the compiler cant determine which method to use if there is ambiguity - remember that that an array is an Object too.
With a simple test case it should work:
new Test().update(new Observable(), new int[]{1, 2});
new Test().update(new Observable(), new Object());
The correct methods get invoked.
How are you calling your methods?

Categories

Resources