Skip to content

Instantly share code, notes, and snippets.

@wallabra
Last active October 6, 2017 19:29
Show Gist options
  • Save wallabra/b21a7f6640c3cfb116efbb0d00a39a04 to your computer and use it in GitHub Desktop.
Save wallabra/b21a7f6640c3cfb116efbb0d00a39a04 to your computer and use it in GitHub Desktop.
Leaf Sickle plugin prototype for Sponge API
package usr.gustavo6046.sponge.sickle;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.Map;
import java.util.Optional;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.BlockTypes;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.data.persistence.DataFormats;
import org.spongepowered.api.data.type.HandTypes;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.EntityTypes;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.block.TargetBlockEvent;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.EventContext;
import org.spongepowered.api.event.cause.EventContextKeys;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.event.item.inventory.ClickInventoryEvent;
import org.spongepowered.api.item.ItemTypes;
import org.spongepowered.api.item.inventory.Inventory;
import org.spongepowered.api.item.inventory.ItemStack;
import org.spongepowered.api.item.inventory.ItemStackSnapshot;
import org.spongepowered.api.item.inventory.crafting.CraftingOutput;
import org.spongepowered.api.item.recipe.crafting.Ingredient;
import org.spongepowered.api.item.recipe.crafting.ShapedCraftingRecipe;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.util.Direction;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import org.spongepowered.api.world.extent.Extent;
import usr.gustavo6046.sponge.lib.Capsule;
import usr.gustavo6046.sponge.lib.SpongeLinkedList;
/**
* The official Leaf Sickle plugin for Sponge API.
*
* @author Gustavo6046
*/
@Plugin(id = "leaf_sickle", name = "Leaf Sickle")
public class LeafSicklePlugin
{
static class SickleBreakEvent implements TargetBlockEvent
{
static class Builder
{
Player rootPlayer;
BlockSnapshot sourceBlock;
LinkedList<Location<World>> destinationBlocks;
protected Builder(Player breaker, BlockSnapshot sourceBlock)
{
rootPlayer = breaker;
this.sourceBlock = sourceBlock;
}
public Builder addAffectedBlock(Location<World> loc)
{
destinationBlocks.push(loc);
return this;
}
public Builder addAllAffectedblocks(LinkedList<Location<World>> locs)
{
destinationBlocks.addAll(locs);
return this;
}
public SickleBreakEvent build(boolean breakDestinationBlocks)
{
if ( breakDestinationBlocks )
for ( Location<World> loc : destinationBlocks )
loc.setBlock(BlockState.builder()
.blockType(BlockTypes.AIR)
.build()
);
return new SickleBreakEvent((Cause.builder()
.append(rootPlayer)
.append(sourceBlock)
.build(EventContext.builder()
.add(EventContextKeys.BLOCK_HIT, sourceBlock)
.add(EventContextKeys.PLAYER, rootPlayer)
.build()
)
), sourceBlock, destinationBlocks);
}
}
LinkedList<Location<World>> destinationBlocks;
BlockSnapshot sourceBlock;
Cause reason;
protected SickleBreakEvent(Cause builderReason, BlockSnapshot source, LinkedList<Location<World>> dest)
{
sourceBlock = source;
destinationBlocks = dest;
reason = builderReason;
}
@Override
public Cause getCause() {
return reason;
}
@Override
public BlockSnapshot getTargetBlock() {
return sourceBlock;
}
}
public static SickleBreakEvent.Builder eventBuilder(Player breaker, BlockSnapshot sourceBlock)
{
return new SickleBreakEvent.Builder(breaker, sourceBlock);
}
/**
* List of sickles. To keep track of which
* Golden Hoes are Sickles ;)
*/
public SpongeLinkedList< Capsule<ItemStackSnapshot> > sickles;
private ShapedCraftingRecipe sickleRec;
private int lastSickleId;
protected Map<Player, Inventory> lastCGrids;
/**
* All the Directions that neighbors a block
* orthogonally (i.e. by its 6 faces).
*/
private static Direction[] neighDirs = new Direction[] {
Direction.DOWN,
Direction.UP,
Direction.NORTH,
Direction.SOUTH,
Direction.EAST,
Direction.WEST
};
/**
* Checks if an ItemStack is a
* Leaf Sickle™.
*
* @param other The stack to check for sickles.
* @return Whether this stack is a Sickle™ or not.
* If it is then also files a takedown
* request via DMCA for copyright
* infringement :P
*/
public boolean isLeafSickle(ItemStack other)
{
if ( sickles == null )
{
defaultSickles();
return false;
}
for ( Capsule<ItemStackSnapshot> capsule : sickles )
if ( capsule.getValue().equals(other.createSnapshot()) )
return true;
return false;
}
/**
* Iterates on a block and breaks all blocks around it.
*
* @param loc The location on which to iterate.
* @param depth The current depth of iteration (usually one when starting).
* @param maxDepth The max depth of iteration.
* @param player The causing player.
* @param breaked The LinkedList to which append broken blocks.
*/
public void iterateBlockBreak(
Location<World> loc,
int depth,
int maxDepth,
Player player,
LinkedList<Location<World>> breaked
)
{
if ( !loc.getBlock().getType().equals(BlockTypes.LEAVES) )
return;
ItemStack item = ItemStack.builder().fromBlockState(loc.getBlock()).build();
Extent extent = loc.getExtent();
Entity itemEntity = extent.createEntity(EntityTypes.ITEM, loc.getPosition());
if ( itemEntity == null )
return;
itemEntity.offer(Keys.REPRESENTED_ITEM, item.createSnapshot());
extent.spawnEntity(itemEntity);
breaked.push(loc);
loc.setBlock(BlockState.builder()
.blockType(BlockTypes.AIR)
.build()
);
// ...and now the fun part!
if ( depth <= maxDepth )
for ( Direction dir : neighDirs )
iterateBlockBreak(loc.getBlockRelative(dir), depth + 1, maxDepth, player, breaked);
}
/**
* Checks for crafted Special Items and
* replaces them by a functioning version.
*
* Explanation: recipes usually generate
* a copy of the items, but this plugin
* requires these items to be stored in
* a LinkedList using a function.
*
* Of course, usually this function
* is not called with Recipes, so we try
* and catch when the items are crafted
* so as to check if they are Sickles and
* add them to the linked list.
*
* On block break, the item that broke blocks
* is compared with the list; if it is in the
* list, then the special power of Leaf Sickles™
* activates :)
*
*
* @param event The inventory event to trigger
* the crafting.
*/
@Listener
public void onCraft(ClickInventoryEvent event)
{
Optional<Player> _player = event.getCause().first(Player.class);
if ( !_player.isPresent() )
return;
Player player = _player.get();
Inventory inventory = event.getTargetInventory();
Inventory output = inventory.query(CraftingOutput.class);
if ( nonEmptyInv(output) && output.peek().isPresent() && output.peek().get().equalTo(generateExampleSickle()) )
{
ItemStack sickle = generateSickle(null);
output.offer(sickle);
System.out.println("Made Leaf Sickle "+sickle+" for player "+player.getIdentifier());
}
}
private boolean nonEmptyInv(Inventory output) {
return output.capacity() > 0;
}
/**
* Listens on block breaking events.
*
* @param event The event on which to listen.
*/
@Listener
public void onBlockBreak(ChangeBlockEvent.Break event)
{
Optional<Player> _player = event.getCause().first(Player.class);
if ( !_player.isPresent() )
return;
Player player = _player.get();
Optional<ItemStack> _stack;
if ( !(_stack = player.getItemInHand(HandTypes.MAIN_HAND)).isPresent() || !isLeafSickle(_stack.get()))
{
if ( _stack.isPresent() )
System.out.println("tool="+_stack.get());
else
System.out.println("tool=BARE_HANDS");
return;
}
event.getTransactions().stream().forEach((trans) ->
{
Location<World> block;
if ( ( block = trans.getOriginal().getLocation().get() ).getBlock().getType().equals(BlockTypes.LEAVES) )
{
LinkedList<Location<World>> breaked = new LinkedList<Location<World>>();
System.out.println("Breaking all blocks in a radius around: " + block.getPosition());
iterateBlockBreak(block, 1, 7, player, breaked);
Sponge.getEventManager().post(eventBuilder(player, trans.getOriginal())
.addAllAffectedblocks(breaked)
.build(false)
);
}
});
}
private void defaultSickles()
{
sickles = new SpongeLinkedList< Capsule<ItemStackSnapshot> >();
saveSickles();
}
public void saveSickles()
{
File f;
(f = new File("config/LeafSickle/data/sickles.json")).getParentFile().mkdirs();
try
{
Files.write(f.toPath(),
Charset
.forName("UTF-8")
.encode(DataFormats.JSON.write(sickles.toContainer()))
.array()
);
}
catch ( IOException e1 )
{
e1.printStackTrace();
return;
}
}
/**
* Ran when the plugin is called (equivalent to
* PreBeginPlay in Unreal).
*
* @param evt The event on which to listen
* @throws IOException
*/
@SuppressWarnings("unchecked")
@Listener
public void onPreInit(GamePreInitializationEvent evt) throws IOException
{
// =============
// Configuration Stuff
String json;
try
{
json = new String(
Files.readAllBytes(Paths.get("config/leafsickle/data/sickles.json")),
"utf-8"
);
}
catch ( NoSuchFileException err )
{
defaultSickles();
json = "";
}
Optional<SpongeLinkedList<Capsule<ItemStackSnapshot>>> _sickles;
if ( json.equals("") )
_sickles = Optional.empty();
else
_sickles = Sponge.getDataManager().deserialize(( Class< SpongeLinkedList< Capsule<ItemStackSnapshot> > > )(Object)SpongeLinkedList.class, DataFormats.JSON.read(json));
if ( _sickles.isPresent() )
sickles = _sickles.get();
else
defaultSickles();
// =============
Ingredient stick = Ingredient.of(ItemTypes.STICK);
Ingredient goldBar = Ingredient.of(ItemTypes.GOLD_INGOT);
sickleRec = ShapedCraftingRecipe.builder()
.aisle(
"gg ",
"s g",
"s "
)
.where('s', stick)
.where('g', goldBar)
.aisle(
" gg",
"g s",
" s"
)
.where('s', stick)
.where('g', goldBar)
.result(generateExampleSickle())
.build("leaf-sickle", this);
Sponge
.getRegistry()
.getCraftingRecipeRegistry()
.register(sickleRec);
}
/**
* Generates an example (item data only)
* Leaf Sickle™ and returns it.
*
* @return Duh.
*/
public ItemStack generateExampleSickle()
{
ItemStack sickle = ItemStack.builder()
.itemType(ItemTypes.GOLDEN_HOE)
.build();
sickle.offer(Keys.DISPLAY_NAME, Text.of(TextColors.LIGHT_PURPLE, "Leaf Sickle"));
return sickle;
}
/**
* This function generates a Leaf Sickle item
* and registers it.
*
* @param id A Capsule that holds an Integer object,
* which is set to the SickleID of the new
* Sickle.
* @return The item.
*/
public ItemStack generateSickle(Capsule<Integer> id)
{
ItemStack sickle = generateExampleSickle();
if ( id != null )
id.set(lastSickleId);
lastSickleId++;
Capsule<ItemStackSnapshot> resSnap = new Capsule<ItemStackSnapshot>(sickle.createSnapshot());
sickles.push(resSnap);
saveSickles();
return sickle;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment