All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mmb.content.modular.ModularBlock Maven / Gradle / Ivy

Go to download

Dependency for the MultiMachineBuilder, a voxel game about building an industrial empire in a finite world. THIS RELEASE IS NOT PLAYABLE. To play the game, donwload from >ITCH.IO LINK HERE< or >GH releases link here<

The newest version!
/**
 * 
 */
package mmb.content.modular;

import java.awt.Graphics;

import org.checkerframework.common.returnsreceiver.qual.This;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import mmb.NN;
import mmb.Nil;
import mmb.cgui.BlockActivateListener;
import mmb.content.electric.Electricity;
import mmb.content.modular.chest.BlockModuleUniversal;
import mmb.content.modular.gui.ModularChestGUI;
import mmb.content.modular.part.PartEntry;
import mmb.content.modular.part.PartType;
import mmb.engine.block.SensitiveBlock;
import mmb.engine.chance.Chance;
import mmb.engine.inv.Inventory;
import mmb.engine.inv.ItemRecord;
import mmb.engine.inv.NoSuchInventory;
import mmb.engine.inv.io.InventoryReader;
import mmb.engine.inv.io.InventoryWriter;
import mmb.engine.item.ItemEntry;
import mmb.engine.recipe.RecipeOutput;
import mmb.engine.rotate.ChiralRotation;
import mmb.engine.rotate.Rotation;
import mmb.engine.rotate.Side;
import mmb.engine.texture.BlockDrawer;
import mmb.engine.worlds.world.World;
import mmb.menu.world.window.WorldWindow;

/**
 * A modular block.
 * @author oskar
 * @param  type of the block
 * @param  type of modules (usually {@link BlockModuleUniversal})
 * @param  type of core
 * @param  type of settings
 * @implSpec The type of the settings must be mutable to be loaded
 */
public interface ModularBlock<
	Tblock extends ModularBlock,
	Tmodule extends BlockModule,
	Tcore extends BlockCore,
	Tsettings> extends BlockActivateListener, SensitiveBlock{
	
	//Death handler
	/** Run when block is demolished */
	default void killModular() {
		//Kill the core
		Slot slotCore = slotC();
		if(slotCore != null) slotCore.set(null);
		
		//Kill modules
		killModule(slotInternal(Side.U));
		killModule(slotInternal(Side.D));
		killModule(slotInternal(Side.L));
		killModule(slotInternal(Side.R));
	}
	/**
	 * An iternal helper to kill a module
	 * @param slot
	 */
	default void killModule(@Nil Slot slot) {
		if(slot != null) slot.set(null);
	}
	
	//Click handler
	@Override
	default void click(int blockX, int blockY, World map, @Nil WorldWindow window, double partX, double partY) {
		if(window == null) return;
		boolean result = replaceHelper(window.selectedItem(), window.getPlayer().inv, partX, partY);
		if(!result) {
			ModularChestGUI tab = createGUI(window);
			boolean open =  openTab(tab);
			if(open) {
				window.openAndShowWindow(tab, type().title());
			}else {
				tab.close(window);
			}
		}
	}
	/**
	 * Internal method to handle clicks
	 * @param selected selected item record
	 * @param inv inventory
	 * @param partX X position of the click on this block
	 * @param partY Y position of the click on this block
	 * @return did the player click the slot?
	 */
	default boolean replaceHelper(@Nil ItemRecord selected, Inventory inv, double partX, double partY) {
		final double left = 5.0/16;
		final double right = 11.0/16;
		final double depth = 3.0/16;
		final double antidepth = 13.0/16;
		boolean inX = partX < right && partX > left;
		boolean inY = partY < right && partY > left;
		if(inX) {
			if(inY){
				replace(slotC(), selected, inv); //Center
				return true;
			}else if(partY > antidepth){
				replace(slot(Side.D), selected, inv); //Down
				return true;
			}else if(partY < depth){
				replace(slot(Side.U), selected, inv); //Up
				return true;
			}
		}else if(inY) {
			if(partX > antidepth){
				replace(slot(Side.R), selected, inv); //Right
				return true;
			}else if(partX < depth){
				replace(slot(Side.L), selected, inv); //Left
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Replaces the module with one requested by the player
	 * @apiNote A helper method used in default implementation of the click handler.
	 * @param slot module slot
	 * @param selected item record
	 * @param inv player inventory
	 */
	default > void replace(@Nil Slot slot, @Nil ItemRecord selected, Inventory inv) {
		//Null check
		if(slot == null) return;
		
		//Removal
		Tmc already = slot.get();
		if(already != null) {
			RecipeOutput toinsert = already.returnToPlayer();
			boolean insert = inv.bulkInsert(toinsert, 1) == 1;
			if(insert) slot.set(null);
			Chance drop = already.dropItems();
			drop.drop(null, owner(), posX(), posY());
		}
		
		//Addition
		if(selected != null) {
			if(!selected.canExtract()) return;
			if(selected.amount() == 0) return;
			ItemEntry selectedItem = selected.item();
			if(selectedItem instanceof PartType) {
				PartType pt = (PartType) selectedItem;
				PartEntry part = pt.createPart();
				boolean replace = slot.setto(part);
				if(replace) selected.extract(1);
			}
		}
	}
	
	//GUI
	/**
	 * Invoked when the tab is closed
	 * @param gui GUI to be closed
	 */
	public void closeTab(ModularChestGUI gui);
	/**
	 * Creates additional GUI for this block
	 * @param window world window to use
	 * @return an additional GUI
	 */
	public default ModularChestGUI createGUI(WorldWindow window) {
		return new ModularChestGUI(window, this);
	}
	/**
	 * Invoked when the tab is opened
	 * @param tab tab to open
	 * @return is the tab already open?
	 */
	public boolean openTab(ModularChestGUI tab);
	
	//Provision of internal access points to modules
	/**
	 * Provides an inventory for purposes of block modules
	 * @param s side to access from
	 * @return an inventory
	 */	
	@NN default Inventory i_inv(Side s) {
		return NoSuchInventory.INSTANCE;
	}
	/**
	 * Provides an inventory writer for purposes of block modules
	 * @param s side to access from
	 * @return an inventory writer
	 */
	default @NN InventoryWriter i_in(Side s) {
		return i_inv(s).createWriter();
	}
	/**
	 * Provides an inventory reader for purposes of block modules
	 * @param s side to access from
	 * @return an inventory reader
	 */
	default @NN InventoryReader i_out(Side s) {
		return i_inv(s).createReader();
	}
	/**
	 * Provides a boolean signal for purposes of block modules
	 * @param s side to access from
	 * @return a boolean signal
	 */
	default boolean i_signal(Side s) {
		return false;
	}
	/**
	 * Provides an electrical connection for purposes of block modules
	 * @param s side to access from
	 * @return an electrical connection
	 */
	@Nil default Electricity i_elec(Side s) {
		return null;
	}
		
	//Provision of access points for outside blocks
	@Override
	default boolean provideSignal(Side s) {
		@Nil Tmodule module = module(s);
		if(module == null) return i_signal(s);
		return module.provideSignal(that(), s);
	}
	@Override
	default Inventory getInventory(Side s) {
		@Nil Tmodule module = module(s);
		if(module == null) return i_inv(s);
		return module.provideInventory(that(), s);
	}
	@Override
	default InventoryReader getOutput(Side s) {
		@Nil Tmodule module = module(s);
		if(module == null) return i_out(s);
		return module.provideOutput(that(), s);
	}
	@Override
	default InventoryWriter getInput(Side s) {
		@Nil Tmodule module = module(s);
		if(module == null) return i_in(s);
		return module.provideInput(that(), s);
	}
	@Override
	default Electricity getElectricalConnection(Side s) {
		@Nil Tmodule module = module(s);
		if(module == null) return i_elec(s);
		return module.provideElectricity(that(), s);
	}
	
	//Access to external blocks from outside
	/**
	 * Find the input signal for the block
	 * @param w world in which block is located
	 * @param x X coordinate of the block
	 * @param y Y coordinate of the block
	 * @return a signal
	 */
	public default boolean findSignals(World w, int x, int y) {
		return findSignalsSub(w, x, y, Side.U)
			|| findSignalsSub(w, x, y, Side.D)
			|| findSignalsSub(w, x, y, Side.L)
			|| findSignalsSub(w, x, y, Side.R);
	}
	/**
	 * A helper for the {@link #findSignals(World, int, int)}
	 * @param w world in which block is located
	 * @param x X coordinate of the block
	 * @param y Y coordinate of the block
	 * @param s side, where the module is located
	 * @return a signal
	 */
	public default boolean findSignalsSub(World w, int x, int y, Side s) {
		@Nil Tmodule module = module(s);
		boolean sig = w.getAtSide(s, x, y).provideSignal(s.negate());
		if(module != null) sig = module.decorateExternalSignal(sig);
		return sig;
	}
	
	//Core
	/** @return core slot */
	@Nil public Slot<@Nil Tcore> slotC();
	/** @return the current core, or null if not found */
	@Nil public default Tcore core() {
		Slot<@Nil Tcore> slot = slotC();
		if(slot == null) return null;
		return slot.get();
	}
	/**
	 * Sets a core with types already checked (faster)
	 * @param core new core
	 * @return did the core chenge?
	 */
	public default boolean setCore(@Nil Tcore core) {
		Slot<@Nil Tcore> slot = slotC();
		if(slot == null) return false;
		slot.set(core);
		return true;
	}
	/**
	 * Sets a core withot types checked (slower)
	 * @param core new core
	 * @return did the core chenge?
	 */
	public default boolean settoCore(Object core) {
		Slot<@Nil Tcore> slot = slotC();
		if(slot == null) return false;
		return slot.setto(core);
	}
	
	//Modules
	/** @return is the block modular?*/
	public boolean isModular();
	/**
	 * Gets a module slot for a logical side
	 * @param s side to access from
	 * @return a module slot
	 */
	@Nil public Slot<@Nil Tmodule> slotInternal(Side s);
	/**
	 * Gets a module slot for a side
	 * @implSpec This method should not be overridden
	 * @param s side to access from
	 * @return a module slot
	 */
	@Nil public default Slot<@Nil Tmodule> slot(Side s){
		return slotInternal(getChirotation().apply(s));
	}
	/**
	 * Gets a module
	 * @implSpec This method should not be overridden
	 * @param s side to access from
	 * @return a module at given side
	 */
	@Nil public default Tmodule module(Side s) {
		@Nil Slot<@Nil Tmodule> slot = slot(s);
		if(slot == null) return null;
		return slot.get();
	}
	/**
	 * Gets a module using a logical side
	 * @implSpec This method should not be overridden
	 * @param s side to access from
	 * @return a module at given side
	 */
	@Nil public default Tmodule moduleInternal(Side s) {
		@Nil Slot<@Nil Tmodule> slot = slotInternal(s);
		if(slot == null) return null;
		return slot.get();
	}
	/**
	 * Sets a module
	 * @param module replacement module
	 * @param s logical side of the module
	 * @return did the module change?
	 */
	public default boolean setModule(@Nil Object module, Side s) {
		Slot<@Nil Tmodule> slot = slotInternal(s);
		if(slot == null) return false;
		return slot.setto(module);
	}
	
	//Settings
	/**
	 * Loads settings from a JSON node
	 * @param node node to load from
	 * @param settings settings to load
	 */
	public void loadSettings(@Nil JsonNode node, Tsettings settings);
	/**
	 * Saves settings
	 * @param node settings to save
	 * @return saved settings
	 */
	public JsonNode saveSettings(@Nil Tsettings node);
	/**
	 * Gets the current settings
	 * @return current settings, or null if unsupported
	 */
	public @Nil Tsettings getSettings();
	/**
	 * Replaces settings with the ones stored in the settings object
	 * @param settings settings to use
	 */
	public void setSettings(Tsettings settings);
	
	//Serialization
	/**
	 * Save core, modules and settings
	 * @param node object node to save to
	 */
	public default void saveModularHelper(ObjectNode node) {
		//Save modules
		if(isModular()) {
			node.set("moduleU", PartEntry.savePart(moduleInternal(Side.U)));
			node.set("moduleD", PartEntry.savePart(moduleInternal(Side.D)));
			node.set("moduleL", PartEntry.savePart(moduleInternal(Side.L)));
			node.set("moduleR", PartEntry.savePart(moduleInternal(Side.R)));
		}
		
		//Save core
		@Nil Slot<@Nil Tcore> coreSlot = slotC();
		if(coreSlot != null) {
			node.set("core", PartEntry.savePart(coreSlot.get()));
		}
		
		//Save settings
		node.set("settings", saveSettings(getSettings()));
	}
	/**
	 * Load core, module and settings
	 * @param node node to load from
	 */
	public default void loadModularHelper(@Nil JsonNode node) {
		if(node == null) return;
		
		//Load core
		@Nil Slot<@Nil Tcore> slotC = slotC();
		if(slotC != null) slotC.setto(PartEntry.loadFromJsonExpectType(node.get("core"), slotC.type));
		
		//Load modules
		loadModuleHelper(node.get("moduleU"), Side.U);
		loadModuleHelper(node.get("moduleD"), Side.D);
		loadModuleHelper(node.get("moduleL"), Side.L);
		loadModuleHelper(node.get("moduleR"), Side.R);
		
		//Load settings
		@Nil Tsettings settings = getSettings();
		if(settings != null) loadSettings(node.get("settings"), settings);
	}
	/**
	 * Loads a single module
	 * @param sub node to load from
	 * @param side side of the slot
	 */
	public default void loadModuleHelper(@Nil JsonNode sub, Side side) {
		Slot slot = slotInternal(side);
		if(slot != null) slot.setto(PartEntry.loadFromJsonExpectType(sub, slot.type));
	}
	
	//Ticker
	/**
	 * A tick helper
	 * @param w world in which block is located
	 * @param x X coordinate of the block
	 * @param y Y coordinate of the block
	 */
	public default void runAllModules(World w, int x, int y) {
		//Run the core
		Tcore core = core();
		if(core != null) core.runCore(that());
		
		ChiralRotation chirot = getChirotation();
		runModule(chirot, Side.U);
		runModule(chirot, Side.D);
		runModule(chirot, Side.L);
		runModule(chirot, Side.R);
	}
	/**
	 * @param chirot chirotation of the block
	 * @param s side, where the module is located
	 */
	public default void runModule(ChiralRotation chirot, Side s) {
		Tmodule module = moduleInternal(s);
		if(module != null) module.run(that(), s, chirot.apply(s));
	}
	
	/** @return this */
	@SuppressWarnings("unchecked")
	@This
	@NN public default Tblock that() {
		return (Tblock) this;
	}
	
	//Rendering
	/**
	 * Renders the background
	 * @param x left X coordinate on the screen
	 * @param y upper Y coordinate on the screen
	 * @param g graphics context
	 * @param s size
	 */
	public default void renderBG(Graphics g, int x, int y, int s) {
		SensitiveBlock.super.render(x, y, g, s);
	}
	@Override
	default void render(int x, int y, Graphics g, int side) {
		//Render background
		renderBG(g, x, y, side);
		
		//Render modules
		renderModule(x, y, g, side, Rotation.N);
		renderModule(x, y, g, side, Rotation.E);
		renderModule(x, y, g, side, Rotation.S);
		renderModule(x, y, g, side, Rotation.W);
		
		//Render core
		Tcore core = core();
		if(core != null) core.render(x, y, g, side);
	}
	/**
	 * Renders a module
	 * @param x left X coordinate on the screen
	 * @param y upper Y coordinate on the screen
	 * @param g graphics context
	 * @param size size
	 * @param r rotation of the module
	 */
	default void renderModule(int x, int y, Graphics g, int size, Rotation r) {
		Side s = r.U();
		Tmodule module = module(s);
		if(module == null) return;
		int offsetX = (13*size*s.blockOffsetX)/32;
		int offsetY = (13*size*s.blockOffsetY)/32;
		int px = x+offsetX;
		int py = y+offsetY;
		BlockDrawer rotated = module.rig().get(r);
		rotated.draw(this, px, py, g, size);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy