solution for number 12
added water splash for misfires added explosion, debris and fire for hit
This commit is contained in:
		@@ -9,7 +9,6 @@ implementation project(":jme-common")
 | 
			
		||||
    implementation project(":battleship:model")
 | 
			
		||||
 | 
			
		||||
    implementation libs.jme3.desktop
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.awt.dialogs
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
    runtimeOnly libs.jme3.jogg
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#
 | 
			
		||||
# Specifies the map used by the opponent in single mode.
 | 
			
		||||
# Single mode is activated if this property is set.
 | 
			
		||||
#map.opponent=maps/map2.json
 | 
			
		||||
map.opponent=maps/map2.json
 | 
			
		||||
#
 | 
			
		||||
# Specifies the map used by the player in single mode.
 | 
			
		||||
# The player must define their own map if this property is not set.
 | 
			
		||||
 
 | 
			
		||||
@@ -53,11 +53,12 @@ class NetworkDialog extends SimpleDialog {
 | 
			
		||||
        host.setPreferredWidth(400f);
 | 
			
		||||
        port.setSingleLine(true);
 | 
			
		||||
 | 
			
		||||
        final BattleshipApp app = network.getApp();
 | 
			
		||||
        final Container input = new Container(new SpringGridLayout());
 | 
			
		||||
        Checkbox hostServer = new Checkbox(lookup("start.own.server"));
 | 
			
		||||
        hostServer.setChecked(false);
 | 
			
		||||
        hostServer.addClickCommands(s->toggleOwnServer());
 | 
			
		||||
 | 
			
		||||
        final BattleshipApp app = network.getApp();
 | 
			
		||||
        final Container input = new Container(new SpringGridLayout());
 | 
			
		||||
        input.addChild(new Label(lookup("host.name") + ":  "));
 | 
			
		||||
        input.addChild(host, 1);
 | 
			
		||||
        input.addChild(new Label(lookup("port.number") + ":  "));
 | 
			
		||||
@@ -182,7 +183,7 @@ private void connect() {
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try{
 | 
			
		||||
                BattleshipServer battleshipServer = new BattleshipServer();
 | 
			
		||||
                BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText()));
 | 
			
		||||
                battleshipServer.run();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR,e);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,181 @@
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.Application;
 | 
			
		||||
import com.jme3.asset.AssetManager;
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh.Type;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public class HitEffectHandler {
 | 
			
		||||
    private final AssetManager assetManager;
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(HitEffectHandler.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for the HitEffectHandler class
 | 
			
		||||
     * @param app the main application
 | 
			
		||||
     */
 | 
			
		||||
    public HitEffectHandler(Application app){
 | 
			
		||||
        assetManager = app.getAssetManager();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an explosion effect when ship gets hit
 | 
			
		||||
     * @param battleshipNode the node of the ship that gets hit and the effect should be attached to
 | 
			
		||||
     * @param shot The shot taken on a field
 | 
			
		||||
     */
 | 
			
		||||
    public void hitEffect(Node battleshipNode, Shot shot){
 | 
			
		||||
        //Explosion
 | 
			
		||||
        ParticleEmitter explosion = new ParticleEmitter("Explosion", Type.Triangle, 30);
 | 
			
		||||
        explosion.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"));
 | 
			
		||||
        explosion.setImagesX(2);
 | 
			
		||||
        explosion.setImagesY(2);
 | 
			
		||||
        explosion.setStartColor(new ColorRGBA(0.96f, 0.82f, 0.6f, 1f));
 | 
			
		||||
        explosion.setEndColor(new ColorRGBA(0.88f, 0.32f, 0.025f, 1f));
 | 
			
		||||
        explosion.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
 | 
			
		||||
        explosion.setStartSize(0.45f);
 | 
			
		||||
        explosion.setEndSize(0.1f);
 | 
			
		||||
        explosion.setGravity(0, -0.5f, 0);
 | 
			
		||||
        explosion.setLowLife(1f);
 | 
			
		||||
        explosion.setHighLife(3.5f);
 | 
			
		||||
        explosion.setParticlesPerSec(0);
 | 
			
		||||
        explosion.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
 | 
			
		||||
        explosion.emitAllParticles();
 | 
			
		||||
 | 
			
		||||
         //Debris
 | 
			
		||||
        ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 6);
 | 
			
		||||
        Material debrisMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        debrisMaterial.setTexture("Texture", assetManager.loadTexture("Textures/Debris/debris.png"));
 | 
			
		||||
        debris.setMaterial(debrisMaterial);
 | 
			
		||||
        debris.setImagesX(2);
 | 
			
		||||
        debris.setImagesY(2);
 | 
			
		||||
        debris.setStartColor(ColorRGBA.DarkGray);
 | 
			
		||||
        debris.setEndColor(ColorRGBA.Gray);
 | 
			
		||||
        debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.25f, 2f, 0.25f));
 | 
			
		||||
        debris.setStartSize(1.2f);
 | 
			
		||||
        debris.setEndSize(0.8f);
 | 
			
		||||
        debris.setGravity(0, 1.5f, 0);
 | 
			
		||||
        debris.getParticleInfluencer().setVelocityVariation(0.3f);
 | 
			
		||||
        debris.setLowLife(1f);
 | 
			
		||||
        debris.setHighLife(3.5f);
 | 
			
		||||
        debris.setParticlesPerSec(0);
 | 
			
		||||
        debris.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
 | 
			
		||||
        debris.emitAllParticles();
 | 
			
		||||
 | 
			
		||||
        //Fire //TODO Fix creation of fire on the edge of the map
 | 
			
		||||
        ParticleEmitter fire = new ParticleEmitter("Fire", Type.Triangle, 30);
 | 
			
		||||
        Material fireMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        fireMaterial.setTexture("Texture", assetManager.loadTexture("Textures/Fire/fire.png"));
 | 
			
		||||
        fire.setMaterial(fireMaterial);
 | 
			
		||||
        fire.setImagesX(2);
 | 
			
		||||
        fire.setImagesY(2);
 | 
			
		||||
        fire.setStartColor(ColorRGBA.Orange);
 | 
			
		||||
        fire.setEndColor(ColorRGBA.Red);
 | 
			
		||||
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0));
 | 
			
		||||
        fire.setStartSize(0.2f);
 | 
			
		||||
        fire.setEndSize(0.05f);
 | 
			
		||||
        fire.setLowLife(1f);
 | 
			
		||||
        fire.setHighLife(2f);
 | 
			
		||||
        fire.getParticleInfluencer().setVelocityVariation(0.2f);
 | 
			
		||||
        fire.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
			
		||||
        fire.getLocalTranslation().subtractLocal(battleshipNode.getLocalTranslation());
 | 
			
		||||
        fire.emitAllParticles();
 | 
			
		||||
 | 
			
		||||
//        LOGGER.log(Level.DEBUG, "Created HitEffect at {0}", explosion.getLocalTranslation().toString());
 | 
			
		||||
//        LOGGER.log(Level.DEBUG, "Created HitEffect at {0}", debris.getLocalTranslation().toString());
 | 
			
		||||
        LOGGER.log(Level.INFO, "Created HitEffect at {0}", fire.getLocalTranslation().toString());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        battleshipNode.attachChild(explosion);
 | 
			
		||||
        explosion.addControl(new EffectControl(explosion, battleshipNode));
 | 
			
		||||
        battleshipNode.attachChild(fire);
 | 
			
		||||
        fire.addControl(new EffectControl(fire, battleshipNode));
 | 
			
		||||
        battleshipNode.attachChild(debris);
 | 
			
		||||
        debris.addControl(new EffectControl(debris, battleshipNode));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a splash effect if shot hits the water
 | 
			
		||||
     * @param shot The shot taken on a field
 | 
			
		||||
     */
 | 
			
		||||
    public ParticleEmitter missEffect(Shot shot){
 | 
			
		||||
        ParticleEmitter missEffect = new ParticleEmitter("HitEffect", Type.Triangle, 45);
 | 
			
		||||
        missEffect.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"));
 | 
			
		||||
        missEffect.setImagesX(2);
 | 
			
		||||
        missEffect.setImagesY(2);
 | 
			
		||||
        missEffect.setStartColor(new ColorRGBA(0.067f, 0.06f, 0.37f, 0.87f));
 | 
			
		||||
        missEffect.setEndColor(new ColorRGBA(0.32f, 0.55f, 0.87f, 0.79f));
 | 
			
		||||
        missEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
 | 
			
		||||
        missEffect.setStartSize(0.1f);
 | 
			
		||||
        missEffect.setEndSize(0.08f);
 | 
			
		||||
        missEffect.setGravity(0, 0.36f, 0);
 | 
			
		||||
        missEffect.setLowLife(0.7f);
 | 
			
		||||
        missEffect.setHighLife(1.8f);
 | 
			
		||||
        missEffect.setParticlesPerSec(0);
 | 
			
		||||
        missEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
 | 
			
		||||
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "Created MissEffect at {0}", missEffect.getLocalTranslation().toString());
 | 
			
		||||
 | 
			
		||||
        missEffect.emitAllParticles();
 | 
			
		||||
        missEffect.addControl(new EffectControl(missEffect));
 | 
			
		||||
        return missEffect;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Inner class to control effects
 | 
			
		||||
     */
 | 
			
		||||
    private static class EffectControl extends AbstractControl {
 | 
			
		||||
        private final ParticleEmitter emitter;
 | 
			
		||||
        private final Node parentNode;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Constructor used to attach effect to a node
 | 
			
		||||
         *
 | 
			
		||||
         * @param emitter the particle emitter to be controlled
 | 
			
		||||
         * @param parentNode the node to be attached
 | 
			
		||||
         */
 | 
			
		||||
        public EffectControl(ParticleEmitter emitter, Node parentNode) {
 | 
			
		||||
            this.emitter = emitter;
 | 
			
		||||
            this.parentNode = parentNode;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Constructor used if the effect shouldn't be attached to a node
 | 
			
		||||
         *
 | 
			
		||||
         * @param emitter the particle emitter to be controlled
 | 
			
		||||
         */
 | 
			
		||||
        public EffectControl(ParticleEmitter emitter){
 | 
			
		||||
            this.emitter = emitter;
 | 
			
		||||
            this.parentNode = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Checks if the Effect is not rendered anymore so it can be removed
 | 
			
		||||
         *
 | 
			
		||||
         * @param tpf time per frame (in seconds)
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void controlUpdate(float tpf) {
 | 
			
		||||
            if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) {
 | 
			
		||||
                if (parentNode != null)
 | 
			
		||||
                    parentNode.detachChild(emitter);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @param rm the RenderManager rendering the controlled Spatial (not null)
 | 
			
		||||
         * @param vp the ViewPort being rendered (not null)
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -44,6 +44,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
 | 
			
		||||
    private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
 | 
			
		||||
    private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
 | 
			
		||||
    private HitEffectHandler hitEffectHandler;
 | 
			
		||||
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
@@ -59,6 +60,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
 | 
			
		||||
        super(app.getGameLogic().getOwnMap(), root);
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.map = map;
 | 
			
		||||
        hitEffectHandler = new HitEffectHandler(app);
 | 
			
		||||
        addExisting();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +74,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shot shot) {
 | 
			
		||||
        return shot.isHit() ? handleHit(shot) : createCylinder(shot);
 | 
			
		||||
        return shot.isHit() ? handleHit(shot) : hitEffectHandler.missEffect(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -87,9 +89,7 @@ private Spatial handleHit(Shot shot) {
 | 
			
		||||
        final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
 | 
			
		||||
        final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
 | 
			
		||||
 | 
			
		||||
        final Geometry representation = createCylinder(shot);
 | 
			
		||||
        representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
 | 
			
		||||
        shipNode.attachChild(representation);
 | 
			
		||||
        hitEffectHandler.hitEffect(shipNode, shot);
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,9 @@
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
import static pp.util.FloatMath.DEG_TO_RAD;
 | 
			
		||||
import static pp.util.FloatMath.TWO_PI;
 | 
			
		||||
import static pp.util.FloatMath.sin;
 | 
			
		||||
@@ -47,6 +50,12 @@ class ShipControl extends AbstractControl {
 | 
			
		||||
     * The current time within the oscillation cycle, used to calculate the ship's pitch angle.
 | 
			
		||||
     */
 | 
			
		||||
    private float time;
 | 
			
		||||
    /**
 | 
			
		||||
     * Ship to be controlled
 | 
			
		||||
     */
 | 
			
		||||
    private final Battleship battleship;
 | 
			
		||||
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ShipControl.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ShipControl instance for the specified Battleship.
 | 
			
		||||
@@ -56,6 +65,7 @@ class ShipControl extends AbstractControl {
 | 
			
		||||
     * @param ship the Battleship object to control
 | 
			
		||||
     */
 | 
			
		||||
    public ShipControl(Battleship ship) {
 | 
			
		||||
        this.battleship = ship;
 | 
			
		||||
        // Determine the axis of rotation based on the ship's orientation
 | 
			
		||||
        axis = switch (ship.getRot()) {
 | 
			
		||||
            case LEFT, RIGHT -> Vector3f.UNIT_X;
 | 
			
		||||
@@ -63,7 +73,7 @@ public ShipControl(Battleship ship) {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Set the cycle duration and amplitude based on the ship's length
 | 
			
		||||
        cycle = ship.getLength() * 2f;
 | 
			
		||||
        cycle = battleship.getLength() * 2f;
 | 
			
		||||
        amplitude = 5f * DEG_TO_RAD / ship.getLength();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -78,14 +88,23 @@ protected void controlUpdate(float tpf) {
 | 
			
		||||
        // If spatial is null, do nothing
 | 
			
		||||
        if (spatial == null) return;
 | 
			
		||||
 | 
			
		||||
        // Update the time within the oscillation cycle
 | 
			
		||||
        time = (time + tpf) % cycle;
 | 
			
		||||
        if(battleship.isDestroyed() && spatial.getLocalTranslation().getY() < -0.6f){
 | 
			
		||||
            LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName());
 | 
			
		||||
            spatial.getParent().detachChild(spatial);
 | 
			
		||||
        }
 | 
			
		||||
        else if (battleship.isDestroyed()) {
 | 
			
		||||
            spatial.move(0,-0.2f*tpf,0);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // Update the time within the oscillation cycle
 | 
			
		||||
            time = (time + tpf) % cycle;
 | 
			
		||||
 | 
			
		||||
        // Calculate the current angle of the oscillation
 | 
			
		||||
        final float angle = amplitude * sin(time * TWO_PI / cycle);
 | 
			
		||||
            // Calculate the current angle of the oscillation
 | 
			
		||||
            final float angle = amplitude * sin(time * TWO_PI / cycle);
 | 
			
		||||
 | 
			
		||||
        // Update the pitch Quaternion with the new angle
 | 
			
		||||
        pitch.fromAngleAxis(angle, axis);
 | 
			
		||||
            // Update the pitch Quaternion with the new angle
 | 
			
		||||
            pitch.fromAngleAxis(angle, axis);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Apply the pitch rotation to the spatial
 | 
			
		||||
        spatial.setLocalRotation(pitch);
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,8 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
 | 
			
		||||
    private static final File CONFIG_FILE = new File("server.properties");
 | 
			
		||||
 | 
			
		||||
    private static int port;
 | 
			
		||||
 | 
			
		||||
    private final BattleshipConfig config = new BattleshipConfig();
 | 
			
		||||
    private Server myServer;
 | 
			
		||||
    private final ServerGameLogic logic;
 | 
			
		||||
@@ -62,18 +64,12 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the Battleships server.
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        new BattleshipServer().run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the server.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipServer() {
 | 
			
		||||
    public BattleshipServer(int port) {
 | 
			
		||||
        config.readFromIfExists(CONFIG_FILE);
 | 
			
		||||
        BattleshipServer.port = port;
 | 
			
		||||
        LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
 | 
			
		||||
        logic = new ServerGameLogic(this, config);
 | 
			
		||||
    }
 | 
			
		||||
@@ -87,7 +83,7 @@ public void run() {
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        try {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
            myServer = Network.createServer(config.getPort());
 | 
			
		||||
            myServer = Network.createServer(port);
 | 
			
		||||
            initializeSerializables();
 | 
			
		||||
            myServer.start();
 | 
			
		||||
            registerListeners();
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 32 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 MiB  | 
@@ -19,6 +19,7 @@
 | 
			
		||||
            library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme')
 | 
			
		||||
            library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme')
 | 
			
		||||
            library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme')
 | 
			
		||||
            library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme')
 | 
			
		||||
 | 
			
		||||
            library('lemur', 'com.simsilica:lemur:1.16.0')
 | 
			
		||||
            library('lemur-proto', 'com.simsilica:lemur-proto:1.13.0')
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user