[Beta] GiveLife - Give your life points to another player!

GiveLife

Givelife is a fairly simple plugin that allows one to give their some of their life points to another player.
This is a port from an old 1.6.4 Bukkit plugin I did back in 2013.
I did it back then to have a better understanding of how the Bukkit API and Java works and I’m redoing it now to learn more about the SpongeAPI.

GiveLife at GitHub: https://github.com/AniLeo/GiveLife
Build Status

Latest version: 0.04-Sponge (Beta)
Download GiveLife 0.04 (GitHub)
Download GiveLife 0.04 (GitHub - Direct Link)

Feel free to add in features or code suggestions, I’d appreciate it a lot.

Commands and Permissions

/lcheck                    (Permission is GiveLife.LCheck.Self)
/lcheck <player>           (Permission is GiveLife.LCheck.Others)
/lgive <player> <amount>   (Permission is GiveLife.LGive)

Statistics

MCStats

3 Likes

Is this intended to just copy the metricS.java to the project instead of using a jar?

Yes, that’s how it used to work in Bukkit, at least (example from Essentials).
Also, the latest PluginMetrics from Hidendra is for SpongeAPI 3.0.0 and using it directly won’t work. I had to modify line 339 in order for it to compile.
Seems like Hidendra has been inactive this latest month as some pull requests on their repository about it were already made and weren’t reviewed/accepted yet.

1 Like

You could use Statslite by @Minecrell to send data to PluginMetrics. I use it my own plugins and it works perfectly.

2 Likes

If there is any sort of error during command processing, such as a Player not being online, the intended action is, rather than send a message and return CommandResult.empty(), to throw a new CommandException with the Text message as the argument.

Also, what if players didn’t give health points, but rather direct health? As in, if I have 10 hearts and so do you, I can give you one, and you will have 11 and I’ll have 9.

1 Like

Thanks, I’ll look into correctly throwing the CommandException instead returning an empty result along with a message.

If I understood correctly, Player A has 10 hearts as maxHealth and so does Player B, Player A gives one heart to Player B and then Player A has 11 hearts as maxHealth and Player B has 9. That’s an interesting idea, I’ll look into it.
I’ll also need to limit the max value for the maxHealth a player can have, probably will be a variable on the config file, and apply forms of prevention for people not to exploit that function ingame in PvP scenarios or similar.
And most likely make that a temporary effect, with a variable defining for how long that lasts in the config file or as an argument on the command itself.

That sounds really good! Maybe max health would be a separate command from filled health.

Correctly throwing the CommandException now as of https://github.com/AniLeo/GiveLife/commit/008564db9039749da21c339268b644beca49f7a7

Also, I forgot to ask, what’s the Sponge’s equivalent to Vault in Bukkit? I wanted to integrate the commands with a currency system (optional, defined in the config, e.g: pay x currency to give life points / maxHealth)

Optional<EconomyService> service_ = game.getServiceManager().provide(EconomyService.class);
if (service_.isPresent()) {
    EconomyService service = service_.get();
    UniqueAccount account = service.getOrCreateAccount(p.getUniqueId()).get();
    TransactionResult result = account.withdraw(service.getDefaultCurrency(), new BigDecimal(amount), Cause.of(NamedCause.source(p), NamedCause.notifier(plugin)));
    if (result.getResult().equals(ResultType.ACCOUNT_NO_FUNDS)) {
        throw new CommandException(Text.of("You do not have enough money!"));
    } else if (result.getResult().equals(ResultType.FAILED)) {
        throw new CommandException(Text.of("Transaction failed!"));
    }
    // do health stuff
} else {
    // economy plugin not installed
}

So you would use the EconomyService from the ServiceManager for that. Sponge does not provide a default EconomyService implementation, however; you will need an economy plugin for this. It’s much better than Vault, as it has support for things like multiple currencies.
Also, take note of what I did up there. You shouldn’t check if they have enough money and then make the transaction, because different economy plugins have different implementations, and may support negative values, for instance. You should always perform the transaction blindly, and check the result after.
For more information, check out this.
It would probably be wise to check whether or not an economy plugin is installed during GamePostInitializationEvent, by attempting to provide it, and disabling if the Optional is not present. I personally think that @Dependency should have support for a general implementation of a service, or an equivalent, but it doesn’t yet.

1 Like

Also, I read your github commit. You can automatically throw a proper CommandException for permissions by calling args_c.checkPermission(src).

I’m using GiveLife.LCheck.Self and GiveLife.LCheck.Others for each scenario in LCheck (if the optional argument player is present - others; or if it isn’t - self) and only defining GiveLife.LCheck as a command requirement in the builder.
Can I define a permission for each scenario on the builder instead of checking them “manually” inside the command?

Edit: Nvm, I checked and I understood what you said. I thought it was checkPermission(src) instead of checkPermission(src, String), so I got confused for a bit. I’m currently src.hasPermission(String) instead of args_c.checkPermission(src, String), will change to checkPermission
Edit2: https://github.com/AniLeo/GiveLife/commit/c1cfafc5792baa0e256c69392031ec239e4547b8

Thanks for the Economy explanation. I’ll do it as soon as I can.
https://github.com/AniLeo/GiveLife/commit/9eda039812f41ad9e300e2e57c84e7555a30f2f5

Edit: Done
https://github.com/AniLeo/GiveLife/commit/cac51254bd2a965219ee60ea98ecb59802ececf2

A couple of more things to point out:

  • You can @Inject a Game, rather than using Sponge.getGame().
  • You can also @Inject a PluginContainer.
  • LCheck is only using getOne() on an unmodified GenericArguments.player(), you may want to use getAll() or wrap it in a GenericArguments.onlyOne().
  • When you get around to adding config support, you may want to retrieve Text objects directly from the config file rather than Strings which you pass to Text.of(), simply for maximum configurability; to emulate your replace() methods on that, you would load TextTemplates from the config file instead, and apply the arguments.
  • Commands should be registered in GameInitializationEvent or GamePostInitializationEvent, because while registering is actually handled during GameServerStartingEvent, the server can stop and start several times over the course of a singleplayer session using SpongeForge, and registering a command multiple times during a singleplayer session will result in bad things.
  • You do not need to log enable and disable messages, these are handled for you.
  • Rather than using GenericArguments.remainingJoinedStrings(), you can use GenericArguments.integer(), which will properly parse, throw parsing exceptions, etc. instead of having to do it yourself. This also results in not returning anomalous results like -1.
  • For LCheck, rather than returning a simple CommandResult.success(), you could return CommandResult.builder().queryResult(thp).build(), which will output a query result to things like comparators off command blocks. This is the purpose of returning an object instead of a simple boolean. You don’t have to, it’s just a best practice sort of thing, like performing economy transactions without checking if the balance is enough.
  • You can go ahead and junk all your isOnline() code. A Player is always online, and represents both the User and the Entity. In fact the isOnline() method is specified by User, which represents the player’s offline presence, similar to OfflinePlayer in Bukkit. If you wanted a User which could be either online or offline, you could use GenericArguments.user(), but GenericArguments.player() will always return online players. Users are also DataHolders, so you could actually get/set health, max health, etc. without them ever being online.
  • Looked at the comments too. if(t.getName().equals(p.getName())) may not work, but if(t.getUniqueId().equals(p.getUniqueId())) definitely would.

Sorry for long post.

2 Likes

Thanks for the detailed write-up. I’ve done most of the stuff.
Still working on it.

The Inject for the PluginContainer didn’t work, I had to roll that back

Also, I can’t get the config to work

I’m getting

[Server thread/ERROR] [Sponge]: Could not pass FMLPreInitializationEvent to org.spongepowered.mod.plugin.SpongeModPluginContainer@75aec1e5 java.lang.NullPointerException
at io.github.anileo.givelife.Main.onGamePreInitialization(Main.java:69)
~[Main.class:?]
(Which points to the part I call the config() function inside the Config class)

Been reading SpongeDocs and some Code Examples and still couldn’t find what’s missing

Edit: Fixed. I wasn’t defining the path correctly

0.04 (Beta) released

  • Added economy integration with LGive
  • Config (w/ Lang) implemented
  • Several code fixes, some code refactoring

LGive is untested as I don’t currently have access to my second minecraft account.