Minecraft Server

Last year I decided to dive into hosting a Minecraft server with a friend.

During 6 months, I actively developed within the Paper environment, all in all it was a fun endevour – though it ran dry once we lost our passion to manage the server and implement new features.


What I learnt from it

Keeping users engaged in a unique minecraft server, where the aim of the server is both a Factions Server, and a Survival Server (with no resets, or few resets) is hard, we also made the mistake of adding a PvE aspect to the server where the user would engage in a donator perk where they could go through mini-dungeons not dissimilar to WoW ones, and gain rewards.

So combine these into a single package – we have players that want to:

  1. Prosper and grow their base.
  2. Players who want to destroy peoples bases and grief (because its factions).
  3. It has PvE aspects, that players love – but is both hard to maintain and make.

These don’t really have the same goal – and as such, a lot of people would get upset and quit.

Exposure is not always a good thing. We had a few different “Streamers” come to our server, and with them came griefing and toxicity. The viewers from the Streamer don’t want to play on the server for long, and typically are just there for the Streamer. And as such, we hated dealing with them.

Despite Mojangs stance on purchases in the game to only affect cosmetic items – users seemed to always attempt to barter with us to give them P2W items for real money, of course we declined but those players soon found themselves scrolling for another server to play on.

Features aren’t always good. I made quite a few different features that weren’t used in other plugins, and a lot of users never bothered to learn them. For example we had different TNT types, for use against bases, different size, different behaviours, etc. These were implemented for Raiding landmasses that seemed impenetrable – such as an obsidian base, or a base that was covered in water; people never used them and they hated the fact that they existed in the most case. Similarly to a custom system that allowed users to recruit “NPC Guards”, think of Conan Exile’s Thralls – these would follow the player around or guard a spot. The feature was largely unused, despite it being quite useful.


Custom PvE Events

Using a combination of plugins, and forcing interactivity – we were able to allow donators to invoke a timed event, where they would rush to complete a multi-staged dungeon and gain rewards on success.


Mercs

Extending the Sentinel & Citizens 2 plugins, we were able to allow the users to buy their own Mercs, and outfit them with Armor and Weapons for both defending territory and also assisting in raids.

I hooked this up to the Factions plugin we were using, and allowed independent ownership and upkeep costs. The NPC would respond to agressors and enemies of the owning faction.

See: Link1 & Link2


Custom TNT

The videos above, you can see custom TNT data, this is serialized in this case with an “unsafe” method of just using an enchanment level, and checking on the event if it contains the enchantment, this method is non-advisable due to potential exploits to modify the enchantment level of items.

When a TNT block is placed, we check the block of the player, and confirm whether it is an enchanted TNT, and then pass a FixedMetaDataValue to the block (and it is added to a table for saving/loading, afterwards).
Material block = e.getBlock().getType();
if (block == Material.TNT) 
{
    int level = e.getItemInHand().getEnchantmentLevel(Enchantment.DAMAGE_ALL);
    if (level != 0) {
        e.getBlockPlaced().setMetadata("TNTType", new FixedMetadataValue(Riverland._Instance, "Explosive" + level));
        Riverland._Instance.tntPositions.put(e.getBlockPlaced().getLocation(), test);
    }
}
And when the specific block is exploding, we need to pass that information to the exploding entity that spawns.
e.getEntity().setMetadata("TNTType", new FixedMetadataValue(Riverland._Instance, "Explosive" + Riverland._Instance.tntPositions.get(loc).intValue()));
Finally, when the block is about to explode, we override the logic.

if (event.getEntity().hasMetadata("TNTType")) 
{
    String compare = event.getEntity().getMetadata("TNTType").get(0).asString();
    if (compare.contains("Explosive2")) 
    {
        tnt.getLocation().getWorld().createExplosion(tnt.getLocation(), (float) Riverland._Instance.tntRadiusDefault);
        event.setCancelled(true);
    }
}

Raid Eggs

The video above demonstrates a method of spawning an object, through a unique egg type. When thrown, the projectile is added to a list of unique objects that contain the type data, and spawns any specified object on destruction, in this case – a sponge (the sponge was expanded to also effect lava blocks).

Example of deserializing the custom egg

  boolean isSponge = false;
  NamespacedKey key = new NamespacedKey(Riverland._Instance, "spongekey");
  ItemMeta itemMeta = event.getItem().getItemMeta();
   if(itemMeta.getCustomTagContainer().hasCustomTag(key , ItemTagType.INTEGER)) {
      int foundValue = itemMeta.getCustomTagContainer().getCustomTag(key, ItemTagType.INTEGER);
          if (foundValue == 1)
              isSponge = true;
   }

Example of spawning custom egg, and passing the data.

Player player = event.getPlayer();
Location loc = player.getEyeLocation().toVector().add(player.getLocation().getDirection().multiply(2)).toLocation(player.getWorld(), player.getLocation().getYaw(), player.getLocation().getPitch());
Egg egg = player.getWorld().spawn(loc, Egg.class);
if (isSponge) 
{
    egg.setMetadata("Sponge", new FixedMetadataValue(Riverland._Instance, true));
}

egg.setMetadata("Faction", new FixedMetadataValue(Riverland._Instance, factionPlayer.getFactionId() ));
egg.setShooter(player);
egg.setVelocity(player.getEyeLocation().getDirection().multiply(2));
Here is an example of how to spawn the sponge based on the metadata key.

boolean isSpongeEgg = event.getEntity().hasMetadata("Sponge");

if (isSpongeEgg) 
{

    ArrayList lavaBlocks = GetLavaBlocks(4, event.getHitBlock().getLocation(), null);
    for (Block lavaBlock : lavaBlocks) 
    {

        if (lavaBlock != null) {
            lavaBlock.setType(Material.AIR);
        }
    }
    event.getHitBlock().setType(Material.SPONGE);
}

You can check out the source on Github here: https://github.com/remusjones/Riverland_Dev

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s