From f49f7b61e8359f5c2e82df68866ed5cd9adb8eb0 Mon Sep 17 00:00:00 2001 From: jsrobson10 Date: Tue, 22 Sep 2020 14:19:27 +1000 Subject: [PATCH] Initial commit --- .classpath | 46 +++ .gitignore | 1 + .project | 17 + .settings/org.eclipse.jdt.core.prefs | 11 + src/antsim/display/DisplayCamera.java | 11 + src/antsim/display/DisplayWindow.java | 150 ++++++++ src/antsim/entity/Entity.java | 126 +++++++ src/antsim/entity/EntityAnt.java | 390 ++++++++++++++++++++ src/antsim/entity/EntityEdible.java | 12 + src/antsim/entity/EntityFood.java | 66 ++++ src/antsim/entity/EntityNestEntrance.java | 96 +++++ src/antsim/init/Entities.java | 33 ++ src/antsim/init/Models.java | 15 + src/antsim/init/Resources.java | 18 + src/antsim/model/IModel.java | 22 ++ src/antsim/model/Model.java | 160 ++++++++ src/antsim/model/ModelFlat.java | 83 +++++ src/antsim/nest/Nest.java | 87 +++++ src/antsim/start/Start.java | 92 +++++ src/antsim/start/StartClient.java | 110 ++++++ src/antsim/start/StartServer.java | 97 +++++ src/antsim/util/AStar.java | 178 +++++++++ src/antsim/util/AStarSearch.java | 6 + src/antsim/util/ClassBdf.java | 9 + src/antsim/worker/WorkerComType.java | 6 + src/antsim/worker/WorkerServer.java | 109 ++++++ src/antsim/worker/WorkerSocket.java | 137 +++++++ src/antsim/worker/WorkerSocketCallback.java | 8 + src/antsim/world/World.java | 56 +++ src/resources/shader/background.fsh | 21 ++ src/resources/shader/background.vsh | 12 + src/resources/shader/renderer.fsh | 15 + src/resources/shader/renderer.vsh | 19 + src/resources/texture/ant.png | Bin 0 -> 5697 bytes src/resources/texture/ant_eat_ant.png | Bin 0 -> 1905 bytes src/resources/texture/ant_food.png | Bin 0 -> 1814 bytes src/resources/texture/big.png | Bin 0 -> 612 bytes src/resources/texture/food.png | Bin 0 -> 1531 bytes src/resources/texture/grass.png | Bin 0 -> 2642 bytes src/resources/texture/list.txt | 8 + src/resources/texture/nest.png | Bin 0 -> 1068 bytes 41 files changed, 2227 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 src/antsim/display/DisplayCamera.java create mode 100644 src/antsim/display/DisplayWindow.java create mode 100644 src/antsim/entity/Entity.java create mode 100644 src/antsim/entity/EntityAnt.java create mode 100644 src/antsim/entity/EntityEdible.java create mode 100644 src/antsim/entity/EntityFood.java create mode 100644 src/antsim/entity/EntityNestEntrance.java create mode 100644 src/antsim/init/Entities.java create mode 100644 src/antsim/init/Models.java create mode 100644 src/antsim/init/Resources.java create mode 100644 src/antsim/model/IModel.java create mode 100644 src/antsim/model/Model.java create mode 100644 src/antsim/model/ModelFlat.java create mode 100644 src/antsim/nest/Nest.java create mode 100644 src/antsim/start/Start.java create mode 100644 src/antsim/start/StartClient.java create mode 100644 src/antsim/start/StartServer.java create mode 100644 src/antsim/util/AStar.java create mode 100644 src/antsim/util/AStarSearch.java create mode 100644 src/antsim/util/ClassBdf.java create mode 100644 src/antsim/worker/WorkerComType.java create mode 100644 src/antsim/worker/WorkerServer.java create mode 100644 src/antsim/worker/WorkerSocket.java create mode 100644 src/antsim/worker/WorkerSocketCallback.java create mode 100644 src/antsim/world/World.java create mode 100644 src/resources/shader/background.fsh create mode 100644 src/resources/shader/background.vsh create mode 100644 src/resources/shader/renderer.fsh create mode 100644 src/resources/shader/renderer.vsh create mode 100644 src/resources/texture/ant.png create mode 100644 src/resources/texture/ant_eat_ant.png create mode 100644 src/resources/texture/ant_food.png create mode 100644 src/resources/texture/big.png create mode 100644 src/resources/texture/food.png create mode 100644 src/resources/texture/grass.png create mode 100644 src/resources/texture/list.txt create mode 100644 src/resources/texture/nest.png diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..43bae09 --- /dev/null +++ b/.classpath @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin diff --git a/.project b/.project new file mode 100644 index 0000000..3db1100 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + AntSim + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3a21537 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/src/antsim/display/DisplayCamera.java b/src/antsim/display/DisplayCamera.java new file mode 100644 index 0000000..0c8dc46 --- /dev/null +++ b/src/antsim/display/DisplayCamera.java @@ -0,0 +1,11 @@ +package antsim.display; + +import gl_engine.vec.Vec2d; + +public class DisplayCamera +{ + public static DisplayCamera CAMERA = new DisplayCamera(); + + public double zoom = 0.05; + public Vec2d pos = new Vec2d(0, 0); +} diff --git a/src/antsim/display/DisplayWindow.java b/src/antsim/display/DisplayWindow.java new file mode 100644 index 0000000..c889976 --- /dev/null +++ b/src/antsim/display/DisplayWindow.java @@ -0,0 +1,150 @@ +package antsim.display; + +import java.nio.IntBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL33; + +import antsim.entity.Entity; +import antsim.init.Models; +import antsim.model.Model; +import antsim.start.Start; +import antsim.world.World; +import gl_engine.graphics.GraphicsHelpers; +import gl_engine.graphics.GraphicsShader; +import gl_engine.matrix.Matrix4; +import gl_engine.texture.TextureRef3D; +import gl_engine.vec.Vec2d; +import gl_engine.vec.Vec3d; +import mainloop.task.IMainloopTask; + +public class DisplayWindow implements IMainloopTask +{ + public static final DisplayWindow WINDOW = new DisplayWindow(); + + private long window; + private long monitor; + private int width; + private int height; + + public int fps; + public GraphicsShader renderer; + public GraphicsShader background; + + public int glsl_matrix; + public int glsl_projection; + + public int glsl_bg_scale; + public int glsl_bg_translate; + + private boolean hasDoneSetup = false; + + public void init() + { + // Initialize GLFW + if(!GLFW.glfwInit()) { + throw new RuntimeException("Failed to initialize GLFW"); + } + + // Get the monitor size + IntBuffer w = BufferUtils.createIntBuffer(1); + IntBuffer h = BufferUtils.createIntBuffer(1); + monitor = GLFW.glfwGetPrimaryMonitor(); + GLFW.glfwGetMonitorPhysicalSize(monitor, w, h); + width = w.get()*4; + height = h.get()*4; + + //GLFW.glfwWindowHint(GLFW.GLFW_DOUBLEBUFFER, GLFW.GLFW_FALSE); + + // Create the window + window = GraphicsHelpers.initWindow("Ant Sim", width, height, 0); + + renderer = new GraphicsShader("/resources/shader/renderer"); + renderer.use(); + + glsl_matrix = GL33.glGetUniformLocation(renderer.program, "matrix"); + glsl_projection = GL33.glGetUniformLocation(renderer.program, "projection"); + + background = new GraphicsShader("/resources/shader/background"); + background.use(); + + glsl_bg_scale = GL33.glGetUniformLocation(background.program, "scale"); + glsl_bg_translate = GL33.glGetUniformLocation(background.program, "translate"); + + // Make the context current + GLFW.glfwMakeContextCurrent(this.window); + } + + @Override + public boolean MainLoopDelay(long millis) { + return millis > Start.mspf; + } + + @Override + public boolean MainLoopRepeat() { + return true; + } + + @Override + public void MainLoopUpdate() + { + int w[] = {0}; + int h[] = {0}; + GLFW.glfwGetFramebufferSize(this.window, w, h); + width = w[0]; + height = h[0]; + + GL33.glBindFramebuffer(GL33.GL_FRAMEBUFFER, 0); + GL33.glClear(GL33.GL_DEPTH_BUFFER_BIT | GL33.GL_COLOR_BUFFER_BIT); + GL33.glViewport(0, 0, width, height); + + background.use(); + + if(!hasDoneSetup) + { + hasDoneSetup = true; + + int glsl_grass_tex = GL33.glGetUniformLocation(background.program, "tex"); + TextureRef3D grass_tex = Models.GRASS.getTextures()[0]; + + GL33.glUniform4f(glsl_grass_tex, grass_tex.sx, grass_tex.sy, grass_tex.ex, grass_tex.ey); + } + + double camera_zoom = DisplayCamera.CAMERA.zoom; + Vec2d camera_pos = DisplayCamera.CAMERA.pos; + + GL33.glUniform2f(glsl_bg_scale, (float)camera_zoom * (float)height / (float)width, (float)camera_zoom); + GL33.glUniform2f(glsl_bg_translate, (float)camera_pos.x, (float)camera_pos.y); + + Models.GRASS.setModel(Matrix4.identity()); + Models.GRASS.render(); + + renderer.use(); + + + + GL33.glUniformMatrix4fv(glsl_projection, true, Matrix4.multiply( + Matrix4.translate(camera_pos.x, camera_pos.y, 0), + Matrix4.scale(new Vec3d(camera_zoom * (double)height / (double)width, camera_zoom, 1))).getArray()); + + for(Entity e : World.WORLD.entities) + { + Model model = e.getModel(); + model.setModel(Matrix4.multiply(e.getMatrix(), Matrix4.translate(e.pos.x, e.pos.y, 0))); + model.render(); + } + + GLFW.glfwSwapBuffers(window); + + fps += 1; + } + + public void pollEvents() { + GLFW.glfwPollEvents(); + } + + public boolean shouldClose() { + return GLFW.glfwWindowShouldClose(window); + } +} diff --git a/src/antsim/entity/Entity.java b/src/antsim/entity/Entity.java new file mode 100644 index 0000000..d1558cc --- /dev/null +++ b/src/antsim/entity/Entity.java @@ -0,0 +1,126 @@ +package antsim.entity; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import antsim.init.Entities; +import antsim.model.Model; +import antsim.start.Start; +import antsim.start.StartServer; +import antsim.util.ClassBdf; +import antsim.world.World; +import bdf.types.BdfNamedList; +import bdf.types.BdfObject; +import gl_engine.matrix.Matrix4; +import gl_engine.vec.Vec2d; + +public abstract class Entity implements ClassBdf +{ + public Vec2d pos = new Vec2d(0, 0); + public Vec2d velocity = new Vec2d(0, 0); + private boolean isDead; + private boolean dirty; + + public int getID() { + for(int i=0;i ec = Entities.ENTITIES.get(i); + if(ec == this.getClass()) { + return i; + } + } + return -1; + } + + public static Entity loadEntity(BdfObject bdf) + { + try + { + // Load the entity id + BdfNamedList nl = bdf.getNamedList(); + int id = (int)nl.get("id").getAutoInt(); + + // Send back null if the id is out of range + if(id < 0 || id >= Entities.ENTITIES.size()) { + System.out.println("Warning: Invalid ID detected: " + id); + return null; + } + + // Get the class and the constructor + Class ecl = Entities.ENTITIES.get(id); + Constructor econ = ecl.getConstructor(BdfObject.class); + + // Send back the new entity + return econ.newInstance(bdf); + } + + catch( + NoSuchMethodException | + InstantiationException | + IllegalAccessException | + IllegalArgumentException | + InvocationTargetException e) + { + // Send null if there was an issue + e.printStackTrace(); + return null; + } + } + + public abstract Model getModel(); + + public boolean isDirty() { + return dirty; + } + + protected void markDirty() { + dirty = true; + } + + public Matrix4 getMatrix() { + return Matrix4.identity(); + } + + public void push(Vec2d amount) { + velocity = velocity.add(amount); + } + + public void update() + { + pos = pos.add(velocity); + velocity = velocity.multiply(0.8); + } + + @Override + public void loadBDF(BdfObject bdf) + { + BdfNamedList nl = bdf.getNamedList(); + pos = new Vec2d(nl.get("pos")); + velocity = new Vec2d(nl.get("velocity")); + } + + @Override + public void saveBDF(BdfObject bdf) + { + BdfNamedList nl = bdf.getNamedList(); + pos.BdfClassSave(nl.get("pos")); + velocity.BdfClassSave(nl.get("velocity")); + nl.set("id", bdf.newObject().setAutoInt(getID())); + } + + public void kill() + { + isDead = true; + + if(Start.isServer) { + StartServer.markNeedToUpdate(); + } + } + + public boolean isDead() { + return isDead; + } + + public void notDead() { + isDead = false; + } +} diff --git a/src/antsim/entity/EntityAnt.java b/src/antsim/entity/EntityAnt.java new file mode 100644 index 0000000..b627a50 --- /dev/null +++ b/src/antsim/entity/EntityAnt.java @@ -0,0 +1,390 @@ +package antsim.entity; + +import java.util.Random; + +import antsim.init.Entities; +import antsim.init.Models; +import antsim.model.Model; +import antsim.nest.Nest; +import antsim.start.Start; +import antsim.start.StartServer; +import antsim.world.World; +import bdf.types.BdfNamedList; +import bdf.types.BdfObject; +import gl_engine.matrix.Matrix4; +import gl_engine.vec.Vec2d; + +public class EntityAnt extends Entity implements EntityEdible +{ + private static final Random RANDOM = new Random(); + + private Random random; + private long seed; + public Nest nest; + private Vec2d target; + private double angle; + private EntityEdible food_target; + private int hasFood = 0; + private double speed; + private double hunger = 1; + private boolean antDead; + private int pregnant = -2; + private boolean fighting; + + public EntityAnt(Nest nest, Vec2d pos) + { + this.nest = nest; + this.pos = pos; + this.target = pos; + this.speed = Math.random() * 0.1 + 0.9; + + seed = RANDOM.nextLong(); + random = new Random(seed); + } + + public EntityAnt(BdfObject bdf) { + loadBDF(bdf, true); + } + + public EntityAnt(BdfObject bdf, boolean dontLoadNest) { + loadBDF(bdf, dontLoadNest); + } + + @Override + public void saveBDF(BdfObject bdf) { + super.saveBDF(bdf); + + BdfNamedList nl = bdf.getNamedList(); + nl.set("seed", bdf.newObject().setLong(seed)); + nl.set("angle", bdf.newObject().setDouble(angle)); + nl.set("hasFood", bdf.newObject().setAutoInt(hasFood)); + nl.set("hunger", bdf.newObject().setDouble(hunger)); + nl.set("speed", bdf.newObject().setDouble(speed)); + nl.set("pregnant", bdf.newObject().setAutoInt(pregnant)); + nl.set("nest", bdf.newObject().setAutoInt(nest.id)); + nl.set("fighting", bdf.newObject().setBoolean(fighting)); + nl.set("antDead", bdf.newObject().setBoolean(antDead)); + + target.BdfClassSave(nl.get("target")); + } + + @Override + public void loadBDF(BdfObject bdf) { + loadBDF(bdf, true); + } + + public void loadBDF(BdfObject bdf, boolean loadNest) { + super.loadBDF(bdf); + + BdfNamedList nl = bdf.getNamedList(); + seed = nl.get("seed").getLong(); + angle = nl.get("angle").getDouble(); + hasFood = (int)nl.get("hasFood").getAutoInt(); + hunger = nl.get("hunger").getDouble(); + speed = nl.get("speed").getDouble(); + pregnant = (int)nl.get("pregnant").getAutoInt(); + fighting = nl.get("fighting").getBoolean(); + antDead = nl.get("antDead").getBoolean(); + random = new Random(seed); + + target = new Vec2d(nl.get("target")); + + if(loadNest) { + nest = World.WORLD.nests.get((int)nl.get("nest").getAutoInt()); + } + } + + @Override + public Model getModel() + { + switch(hasFood) + { + case 1: return Models.ANT_FOOD; + case 2: return Models.ANT_EAT_ANT; + } + + return Models.ANT; + } + + @Override + public Matrix4 getMatrix() { + return Matrix4.rotate(angle, 0, 0, 1); + } + + @Override + public void update() + { + super.update(); + + if(antDead) { + return; + } + + hunger -= 0.0015; + + if(hunger < 0.125) + { + if(hasFood > 0) + { + hasFood = 0; + hunger += 0.25; + target = pos; + } + } + + if(hunger <= 0) { + antDead = true; + } + + Vec2d toTarget = target.subtract(pos); + double distanceToTarget = toTarget.length(); + + if(pregnant == -1) + { + for(Entity e : World.WORLD.entities) + { + if( + e instanceof EntityAnt && ((EntityAnt) e).nest == nest && + e != this && e.pos.squareDistance(pos) < 0.05) + { + pregnant = 20; + ((EntityAnt)e).pregnant = -2; + distanceToTarget = 0; + break; + } + } + } + + else if(pregnant > 0) { + pregnant -= 1; + } + + else if(pregnant == 0) + { + if(Start.isServer) { + World.WORLD.entities.add(new EntityAnt(nest, pos.copy())); + StartServer.markNeedToUpdate(); + } + + hunger -= 0.25; + pregnant = -2; + } + + if(distanceToTarget < 0.5) + { + for(Entity e : World.WORLD.entities) + { + if(e instanceof EntityNestEntrance && ((EntityNestEntrance) e).nest == nest) + { + EntityNestEntrance entrance = (EntityNestEntrance)e; + + if(entrance.nest == nest && entrance.pos.squareDistance(pos) < 1) + { + hasFood = 0; + nest.addFood(); + nest.ants.add(this); + kill(); + + return; + } + } + + if(fighting && e instanceof EntityAnt) + { + EntityAnt ea = (EntityAnt)e; + + if(ea.fighting && ea.nest != nest && !ea.antDead && ea.hunger < hunger) + { + ea.antDead = true; + hunger -= 0.25; + } + } + } + + if(pregnant == -1) { + pregnant = -2; + } + + if(food_target != null && !food_target.isDead() && food_target.onEaten()) + { + if(food_target instanceof EntityAnt) { + hasFood = 2; + } + + else { + hasFood = 1; + } + } + + food_target = null; + + double targetType = random.nextDouble(); + + if(targetType > 0.6 && hunger > 0.5 && pregnant == -2) + { + EntityAnt closest = null; + double distance = 50; + boolean fight = false; + + for(Entity e : World.WORLD.entities) + { + if(e instanceof EntityAnt && e != this) + { + EntityAnt ea = (EntityAnt)e; + double d = e.pos.squareDistance(pos) * (ea.nest != nest ? 0.25 : 1); + + if(d < distance && !ea.antDead && ea.hunger > 0.25 && ea.pregnant == -2) { + distance = d; + closest = (EntityAnt)e; + fight = ea.nest != nest; + } + } + } + + if(closest == null) { + return; + } + + if(fight) { + fighting = true; + closest.fighting = true; + } + + else { + pregnant = -1; + closest.pregnant = -1; + } + + target = closest.pos.copy(); + closest.target = pos.copy(); + + return; + } + + if(targetType > 0.6 && hasFood == 0) + { + // Get the closest food item + + EntityEdible closest = null; + double distance = 1000; + + for(Entity e : World.WORLD.entities) + { + if(!(e instanceof EntityEdible)) { + continue; + } + + EntityEdible ee = (EntityEdible)e; + + if(!ee.isEdible(this)) { + continue; + } + + double d = e.pos.squareDistance(pos) / ee.getWeight(); + + if(d < distance) + { + distance = d; + closest = ee; + } + } + + if(closest != null) { + target = closest.getPos().copy(); + food_target = closest; + } + + else { + target = pos; + } + } + + else if((targetType > 0.6 && hasFood > 0) || (hasFood == 0 && targetType < 1 - hunger)) + { + // Find the nearest nest + + EntityNestEntrance entrance = null; + double distance = 0; + + for(Entity e : World.WORLD.entities) + { + if(e instanceof EntityNestEntrance && ((EntityNestEntrance) e).nest == nest) + { + double d = e.pos.squareDistance(pos); + + if(d < distance || entrance == null) + { + entrance = (EntityNestEntrance)e; + distance = d; + } + } + } + + if(entrance != null) { + target = entrance.pos.copy(); + } + } + + else { + target = pos.add(new Vec2d(random.nextDouble() * 20 - 10, random.nextDouble() * 20 - 10)); + } + + toTarget = target.subtract(pos); + distanceToTarget = toTarget.length(); + + angle = Math.toDegrees(Math.atan2(toTarget.x, toTarget.y)); + + seed = random.nextLong(); + random = new Random(seed); + } + + push(toTarget.normalize().multiply(0.05 * speed)); + } + + public void exitNest(Vec2d pos) + { + if(Start.isServer) + { + this.pos = pos; + + notDead(); + + target = pos.add(new Vec2d(random.nextDouble() * 20 - 10, random.nextDouble() * 20 - 10)); + + Vec2d toTarget = target.subtract(pos); + + angle = Math.toDegrees(Math.atan2(toTarget.x, toTarget.y)); + + while(hunger < 0.75 && nest.takeFood()) { + hunger += 0.25; + } + + seed = random.nextLong(); + random = new Random(seed); + } + } + + @Override + public boolean isEdible(Entity entity) { + return antDead; + } + + @Override + public Vec2d getPos() { + return pos; + } + + @Override + public double getWeight() { + return 2; + } + + @Override + public boolean onEaten() + { + kill(); + + return true; + } + +} diff --git a/src/antsim/entity/EntityEdible.java b/src/antsim/entity/EntityEdible.java new file mode 100644 index 0000000..ed8f440 --- /dev/null +++ b/src/antsim/entity/EntityEdible.java @@ -0,0 +1,12 @@ +package antsim.entity; + +import gl_engine.vec.Vec2d; + +public interface EntityEdible +{ + public boolean isEdible(Entity entity); + public boolean isDead(); + public Vec2d getPos(); + public double getWeight(); + public boolean onEaten(); +} diff --git a/src/antsim/entity/EntityFood.java b/src/antsim/entity/EntityFood.java new file mode 100644 index 0000000..c428113 --- /dev/null +++ b/src/antsim/entity/EntityFood.java @@ -0,0 +1,66 @@ +package antsim.entity; + +import antsim.init.Models; +import antsim.model.Model; +import antsim.start.Start; +import bdf.types.BdfObject; +import gl_engine.vec.Vec2d; + +public class EntityFood extends Entity implements EntityEdible +{ + private int age; + + public EntityFood(Vec2d pos) { + this.pos = pos; + this.age = 6000; // Stay for a minute + } + + public EntityFood(BdfObject bdf) { + loadBDF(bdf); + } + + @Override + public Model getModel() { + return Models.FOOD; + } + + @Override + public boolean isEdible(Entity entity) { + return true; + } + + @Override + public Vec2d getPos() { + return pos; + } + + @Override + public void update() + { + super.update(); + + if(!Start.isServer) { + return; + } + + age -= 1; + + if(age <= 0) { + kill(); + } + } + + @Override + public double getWeight() { + return 1; + } + + @Override + public boolean onEaten() + { + kill(); + + return true; + } + +} diff --git a/src/antsim/entity/EntityNestEntrance.java b/src/antsim/entity/EntityNestEntrance.java new file mode 100644 index 0000000..bf4f776 --- /dev/null +++ b/src/antsim/entity/EntityNestEntrance.java @@ -0,0 +1,96 @@ +package antsim.entity; + +import java.util.ArrayList; + +import antsim.init.Models; +import antsim.model.Model; +import antsim.nest.Nest; +import antsim.start.Start; +import antsim.start.StartServer; +import antsim.world.World; +import bdf.types.BdfArray; +import bdf.types.BdfNamedList; +import bdf.types.BdfObject; +import gl_engine.vec.Vec2d; + +public class EntityNestEntrance extends Entity implements EntityEdible +{ + private int cooldown; + public Nest nest; + + public EntityNestEntrance(BdfObject bdf) { + loadBDF(bdf); + } + + public EntityNestEntrance(Nest nest, Vec2d pos) { + this.nest = nest; + this.pos = pos; + } + + @Override + public void loadBDF(BdfObject bdf) { + super.loadBDF(bdf); + + BdfNamedList nl = bdf.getNamedList(); + + nest = World.WORLD.nests.get(nl.get("nest").getInteger()); + } + + @Override + public void saveBDF(BdfObject bdf) { + super.saveBDF(bdf); + + BdfNamedList nl = bdf.getNamedList(); + + nl.set("nest", bdf.newObject().setInteger(nest.id)); + } + + @Override + public void update() + { + super.update(); + + if(Start.isServer && nest.ants.size() > 0) + { + cooldown -= 1; + + if(cooldown <= 0) + { + EntityAnt ant = nest.ants.remove(0); + World.WORLD.entities.add(ant); + ant.nest = nest; + ant.exitNest(pos); + + StartServer.markNeedToUpdate(); + + cooldown = 50; + } + } + } + + @Override + public Model getModel() { + return Models.NEST; + } + + @Override + public Vec2d getPos() { + return pos; + } + + @Override + public double getWeight() { + return 4; + } + + @Override + public boolean isEdible(Entity entity) { + return entity instanceof EntityAnt && ((EntityAnt)entity).nest == nest && nest.getFood() > 0; + } + + @Override + public boolean onEaten() { + return nest.takeFood(); + } + +} diff --git a/src/antsim/init/Entities.java b/src/antsim/init/Entities.java new file mode 100644 index 0000000..828551b --- /dev/null +++ b/src/antsim/init/Entities.java @@ -0,0 +1,33 @@ +package antsim.init; + +import java.util.ArrayList; + +import antsim.entity.Entity; +import antsim.entity.EntityAnt; +import antsim.entity.EntityFood; +import antsim.entity.EntityNestEntrance; +import bdf.types.BdfObject; + +public class Entities +{ +public static final ArrayList> ENTITIES = new ArrayList<>(); + + private static void register(Class e) + { + try { + e.getConstructor(BdfObject.class); + } catch (NoSuchMethodException | SecurityException err) { + err.printStackTrace(); + System.exit(1); + } + + ENTITIES.add(e); + } + + public static void init() + { + register(EntityAnt.class); + register(EntityFood.class); + register(EntityNestEntrance.class); + } +} diff --git a/src/antsim/init/Models.java b/src/antsim/init/Models.java new file mode 100644 index 0000000..857744e --- /dev/null +++ b/src/antsim/init/Models.java @@ -0,0 +1,15 @@ +package antsim.init; + +import antsim.model.Model; +import antsim.model.ModelFlat; +import gl_engine.vec.Vec2d; + +public class Models +{ + public static final Model ANT = new ModelFlat(Resources.ATLAS.get("/ant.png")); + public static final Model ANT_FOOD = new ModelFlat(Resources.ATLAS.get("/ant_food.png")); + public static final Model ANT_EAT_ANT = new ModelFlat(Resources.ATLAS.get("/ant_eat_ant.png")); + public static final Model GRASS = new ModelFlat(Resources.ATLAS.get("/grass.png"), new Vec2d(2, 2)); + public static final Model FOOD = new ModelFlat(Resources.ATLAS.get("/food.png")); + public static final Model NEST = new ModelFlat(Resources.ATLAS.get("/nest.png")); +} diff --git a/src/antsim/init/Resources.java b/src/antsim/init/Resources.java new file mode 100644 index 0000000..d9c5385 --- /dev/null +++ b/src/antsim/init/Resources.java @@ -0,0 +1,18 @@ +package antsim.init; + +import gl_engine.texture.TextureAtlas3D; +import gl_engine.texture.TextureRef3D; + +public class Resources +{ + public static TextureAtlas3D ATLAS; + public static TextureRef3D TEX_EMPTY; + + public static void init() + { + ATLAS = TextureAtlas3D.loadAll("/resources/texture/list.txt"); + ATLAS.generate(); + + TEX_EMPTY = ATLAS.get(""); + } +} diff --git a/src/antsim/model/IModel.java b/src/antsim/model/IModel.java new file mode 100644 index 0000000..fa1fda9 --- /dev/null +++ b/src/antsim/model/IModel.java @@ -0,0 +1,22 @@ +package antsim.model; + +import gl_engine.matrix.Matrix4; +import gl_engine.texture.TextureRef3D; + +public interface IModel +{ + public int[] getIndicies(); + public float[] getVerticies(); + public TextureRef3D[] getTextures(); + public TextureRef3D getRandomChunk(int[] tex_id); + public double getWidth(); + public double getHeight(); + public int getIndexSize(); + public int getSize(); + + public void bind(); + public void render(); + public void free(); + public void setModel(Matrix4 model); + public boolean isLoaded(); +} diff --git a/src/antsim/model/Model.java b/src/antsim/model/Model.java new file mode 100644 index 0000000..92e5304 --- /dev/null +++ b/src/antsim/model/Model.java @@ -0,0 +1,160 @@ +package antsim.model; + +import java.util.Random; + +import org.lwjgl.opengl.GL33; + +import antsim.display.DisplayWindow; +import antsim.init.Resources; +import gl_engine.MathHelpers; +import gl_engine.matrix.Matrix4; +import gl_engine.texture.TextureRef3D; + +public abstract class Model implements IModel +{ + private static final Random rand = new Random(); + + public static final float OFFSET = 0.00001f; + public static final int SIZE = 14; + public static IModel bound = null; + + int vao, vbo, ibo; + boolean loaded = false; + + public static void setGLArrayAttributes() + { + // aPos + GL33.glVertexAttribPointer(0, 2, GL33.GL_FLOAT, false, Float.BYTES * SIZE, 0); + GL33.glEnableVertexAttribArray(0); + + // aTex + GL33.glVertexAttribPointer(1, 3, GL33.GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 2); + GL33.glEnableVertexAttribArray(1); + + // aTexY + GL33.glVertexAttribPointer(2, 2, GL33.GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 5); + GL33.glEnableVertexAttribArray(2); + + // aOffset + GL33.glVertexAttribPointer(3, 2, GL33.GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 7); + GL33.glEnableVertexAttribArray(3); + + // aAnimate + GL33.glVertexAttribPointer(4, 2, GL33.GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 9); + GL33.glEnableVertexAttribArray(4); + + // aFlags + GL33.glVertexAttribPointer(5, 3, GL33.GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 11); + GL33.glEnableVertexAttribArray(5); + } + + protected void generate() + { + if(loaded) { + return; + } + + int[] indicies = this.getIndicies(); + float[] verticies = this.getVerticies(); + TextureRef3D[] refs = this.getTextures(); + + if(verticies.length % SIZE != 0 || refs.length * SIZE != verticies.length) { + System.err.println("Invalid model: " + verticies.length + ", " + refs.length + ", " + indicies.length); + System.exit(1); + return; + } + + int size = verticies.length/SIZE; + double k = OFFSET; + + for(int i=0;i ants; + + public Nest(int id) + { + this.id = id; + + population = 10; + food = 0; + + ants = new ArrayList(population); + + for(int i=0;i(ants_bdf.size()); + food = nl.get("food").getInteger(); + population = nl.get("population").getInteger(); + id = nl.get("id").getInteger(); + + for(BdfObject ant_bdf : ants_bdf) { + ants.add(new EntityAnt(ant_bdf, false)); + } + } + + public void saveBDF(BdfObject bdf) + { + BdfNamedList nl = bdf.getNamedList(); + BdfArray ants_bdf = bdf.newArray(ants.size()); + + nl.set("food", bdf.newObject().setInteger(food)); + nl.set("population", bdf.newObject().setInteger(population)); + nl.set("ants", bdf.newObject().setArray(ants_bdf)); + nl.set("id", bdf.newObject().setInteger(id)); + + for(int i=0;i 0) { + food -= 1; + return true; + } + + else { + return false; + } + } + +} diff --git a/src/antsim/start/Start.java b/src/antsim/start/Start.java new file mode 100644 index 0000000..9192ac1 --- /dev/null +++ b/src/antsim/start/Start.java @@ -0,0 +1,92 @@ +package antsim.start; + +import antsim.entity.Entity; +import antsim.entity.EntityAnt; +import antsim.entity.EntityFood; +import antsim.world.World; +import gl_engine.MathHelpers; +import gl_engine.vec.Vec2d; +import mainloop.event.IMainloopEvent; +import mainloop.manager.MainloopManager; +import mainloop.task.IMainloopTask; + +public class Start implements IMainloopEvent, IMainloopTask +{ + public static final Start START = new Start(); + public static final int DEFAULT_PORT = 14972; + + public static boolean isServer; + public static MainloopManager mainloop; + public static int mspf = 1000 / 60; + + int counter = 0; + + @Override + public void onClose() { + System.out.println("Mainloop closed cleanly"); + } + + @Override + public void onEarly() + { + mspf -= 1; + + if(mspf < 1) { + mspf = 1; + } + } + + @Override + public void onLate() + { + mspf += 1; + + if(mspf > 1000) { + mspf = 1000; + } + } + + @Override + public void onStart() { + System.out.println("Mainloop started"); + } + + @Override + public boolean MainLoopDelay(long millis) { + return millis > 10; + } + + @Override + public boolean MainLoopRepeat() { + return true; + } + + @Override + public void MainLoopUpdate() + { + for(int i=0;i 25) { + counter -= 25; + + World.WORLD.entities.add(new EntityFood(MathHelpers.moveTowards2( + Math.sqrt(Math.random()) * 40, Math.random() * MathHelpers.TWO_PI))); + + StartServer.markNeedToUpdate(); + } + } + } +} diff --git a/src/antsim/start/StartClient.java b/src/antsim/start/StartClient.java new file mode 100644 index 0000000..98cc4ed --- /dev/null +++ b/src/antsim/start/StartClient.java @@ -0,0 +1,110 @@ +package antsim.start; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; + +import org.lwjgl.glfw.GLFW; + +import antsim.display.DisplayCamera; +import antsim.display.DisplayWindow; +import antsim.entity.Entity; +import antsim.entity.EntityAnt; +import antsim.init.Entities; +import antsim.init.Resources; +import antsim.worker.WorkerComType; +import antsim.worker.WorkerSocket; +import antsim.world.World; +import bdf.types.BdfArray; +import bdf.types.BdfIndent; +import bdf.types.BdfNamedList; +import bdf.types.BdfObject; +import bdf.types.BdfReader; +import gl_engine.MathHelpers; +import gl_engine.vec.Vec2d; +import mainloop.manager.MainloopManager; +import mainloop.task.IMainloopTask; + +public class StartClient implements IMainloopTask +{ + private static WorkerSocket socket; + + public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException + { + MathHelpers.init(); + DisplayWindow.WINDOW.init(); + Resources.init(); + Entities.init(); + + String ip = "localhost"; + int port = Start.DEFAULT_PORT; + + if(args.length > 0) { + ip = args[0]; + } + + if(args.length > 1) { + port = Integer.parseInt(args[1]); + } + + System.out.println("IP: " + ip + ", Port: " + port); + + Start.isServer = false; + + socket = new WorkerSocket(ip, port, reader -> { + onRecieveData(reader); + }); + + socket.start(); + + Start.mainloop = new MainloopManager(Start.START); + Start.mainloop.register(new StartClient()); + Start.mainloop.register(DisplayWindow.WINDOW); + Start.mainloop.register(Start.START); + + Start.mainloop.start(); + + socket.close(); + } + + private static void onRecieveData(BdfReader reader) + { + BdfObject bdf = reader.getObject(); + BdfNamedList nl = bdf.getNamedList(); + + int type = (int)nl.get("type").getAutoInt(); + + switch(type) + { + case WorkerComType.UPDATE: + World.WORLD.loadBDF(bdf); + break; + } + } + + @Override + public boolean MainLoopDelay(long millis) { + return millis > 10; + } + + @Override + public boolean MainLoopRepeat() { + return true; + } + + @Override + public void MainLoopUpdate() + { + if(DisplayWindow.WINDOW.shouldClose()) { + Start.mainloop.stop(); + return; + } + + DisplayWindow.WINDOW.pollEvents(); + + socket.update(); + } + +} diff --git a/src/antsim/start/StartServer.java b/src/antsim/start/StartServer.java new file mode 100644 index 0000000..44c5a21 --- /dev/null +++ b/src/antsim/start/StartServer.java @@ -0,0 +1,97 @@ +package antsim.start; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import antsim.entity.EntityNestEntrance; +import antsim.init.Entities; +import antsim.nest.Nest; +import antsim.worker.WorkerComType; +import antsim.worker.WorkerServer; +import antsim.worker.WorkerSocket; +import antsim.world.World; +import bdf.types.BdfArray; +import bdf.types.BdfNamedList; +import bdf.types.BdfObject; +import bdf.types.BdfReader; +import gl_engine.MathHelpers; +import gl_engine.vec.Vec2d; +import mainloop.manager.MainloopManager; +import mainloop.task.IMainloopTask; + +public class StartServer implements IMainloopTask +{ + private static WorkerServer server; + private static AtomicBoolean needToUpdate = new AtomicBoolean(); + + public static void main(String[] args) throws IOException, InterruptedException + { + MathHelpers.init(); + Entities.init(); + + Start.isServer = true; + + int port = Start.DEFAULT_PORT; + + if(args.length > 0) { + port = Integer.parseInt(args[0]); + } + + server = new WorkerServer(port); + server.start(); + + Start.mainloop = new MainloopManager(Start.START); + Start.mainloop.register(new StartServer()); + Start.mainloop.register(Start.START); + + for(int i=0;i<2;i++) + { + Nest nest = new Nest(i); + Vec2d area_pos = new Vec2d(Math.random() * 50 - 25, Math.random() * 50 - 25); + + for(int j=0;j<50;j++) + { + World.WORLD.nests.add(nest); + World.WORLD.entities.add(new EntityNestEntrance( + nest, area_pos.add(new Vec2d(Math.random() * 20 - 10, Math.random() * 20 - 10)))); + } + } + + Start.mainloop.start(); + + server.close(); + } + + public static void markNeedToUpdate() { + needToUpdate.set(true); + } + + @Override + public boolean MainLoopDelay(long millis) { + return millis > 10; + } + + @Override + public boolean MainLoopRepeat() { + return true; + } + + @Override + public void MainLoopUpdate() + { + server.update(); + + if(needToUpdate.getAndSet(false)) + { + BdfReader reader = new BdfReader(); + BdfObject bdf = reader.getObject(); + BdfNamedList nl = bdf.getNamedList(); + + nl.set("type", bdf.newObject().setAutoInt(WorkerComType.UPDATE)); + + World.WORLD.saveBDF(bdf); + + server.broadcast(reader); + } + } +} diff --git a/src/antsim/util/AStar.java b/src/antsim/util/AStar.java new file mode 100644 index 0000000..e3e65f1 --- /dev/null +++ b/src/antsim/util/AStar.java @@ -0,0 +1,178 @@ +package antsim.util; +import java.util.ArrayList; +import java.util.List; + +import gl_engine.vec.Vec2i; + +class Node +{ + int x, y; + int g, h, s; + Node parent; + + public Node(Node parent, int x, int y, int g, int h, int s) { + this.parent = parent; + this.x = x; + this.y = y; + this.g = g; + this.h = h; + this.s = s; + } +} + +public class AStar +{ + AStarSearch search; + + Vec2i start; + Vec2i goal; + int radius; + + List open; + List closed; + + Node now; + Node found; + + public AStar(Vec2i start, int radius, AStarSearch search) + { + this.search = search; + this.start = start; + this.radius = radius; + } + + public Vec2i[] getPath(Vec2i goal) + { + // Goal out of range + if(this.posOutOfRange(goal.x, goal.y)) { + return null; + } + + // Start is a wall + if(search.getWeight(start.x, start.y) == 100) { + return null; + } + + this.goal = goal; + this.open = new ArrayList(radius*4); + this.closed = new ArrayList(radius*4); + + now = new Node(null, start.x, start.y, 0, 0, 1); + closed.add(now); + + addNeighboursToList(); + + while(open.size() > 0) + { + // Found the goal + if(found != null) + { + Node n = found; + Vec2i[] path = new Vec2i[n.s]; + + for(int i=0;n!=null;i++) { + path[i] = new Vec2i(n.x, n.y); + n = n.parent; + } + + return path; + } + + int nid = getEasiestNode(); + Node n = open.get(nid); + + open.remove(nid); + closed.add(n); + now = n; + + addNeighboursToList(); + } + + return null; + } + + private boolean neighbourInList(List nodes, int x, int y) { + for(Node n : nodes) { + if(n.x == x && n.y == y) { + return true; + } + } + return false; + } + + private boolean posOutOfRange(int x, int y) { + return ( + x > start.x + radius || + x < start.x - radius || + y > start.y + radius || + y < start.y - radius); + } + + private void addNeighboursToList() + { + int neighbours[] = { + 1, 0, + -1, 0, + 0, 1, + 0, -1 + }; + + for(int i=0;i sockets; + ServerSocket ss; + + public WorkerServer(int port) throws IOException + { + ss = new ServerSocket(port); + sockets = new Vector(); + sockets_reading = new AtomicBoolean(false); + } + + public void update() + { + sockets_reading.set(true); + + // Update the sockets + for(int i=0;i + { + if(socket.isAlive()) + { + try { + socket.send(reader); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/src/antsim/worker/WorkerSocket.java b/src/antsim/worker/WorkerSocket.java new file mode 100644 index 0000000..7cdb25c --- /dev/null +++ b/src/antsim/worker/WorkerSocket.java @@ -0,0 +1,137 @@ +package antsim.worker; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import bdf.data.BdfDatabase; +import bdf.types.BdfObject; +import bdf.types.BdfReader; +import bdf.types.BdfTypes; +import bdf.util.DataHelpers; + +public class WorkerSocket extends Thread +{ + private Socket socket; + private InputStream in; + private OutputStream out; + private ArrayList queue; + private AtomicBoolean queue_reading; + private WorkerSocketCallback callback; + + public WorkerSocket(String ip, int port, WorkerSocketCallback callback) throws UnknownHostException, IOException + { + this.socket = new Socket(ip, port); + this.in = socket.getInputStream(); + this.out = socket.getOutputStream(); + this.queue = new ArrayList(); + this.queue_reading = new AtomicBoolean(false); + this.callback = callback; + } + + public WorkerSocket(Socket socket, WorkerSocketCallback callback) throws IOException + { + this.socket = socket; + this.in = socket.getInputStream(); + this.out = socket.getOutputStream(); + this.queue = new ArrayList(); + this.queue_reading = new AtomicBoolean(false); + this.callback = callback; + } + + public void update() + { + BdfReader next = this.next(); + + if(next == null) { + return; + } + + callback.onRecieve(next); + } + + public void send(BdfReader reader) throws IOException + { + BdfDatabase db = reader.serialize(); + + out.write(ByteBuffer.allocate(4).putInt(db.size()).array()); + db.writeToStream(out); + } + + public BdfReader next() + { + queue_reading.set(true); + + if(queue.size() == 0) + { + queue_reading.set(false); + + return null; + } + + BdfReader reader = queue.get(0); + + queue.remove(0); + + queue_reading.set(false); + + return reader; + } + + @Override + public void run() + { + try + { + while(!socket.isClosed()) + { + int size; + int buffer_size = 0; + byte[] buffer_size_bytes = new byte[4]; + + in.read(buffer_size_bytes); + buffer_size = ByteBuffer.wrap(buffer_size_bytes).getInt(0); + + BdfDatabase database = new BdfDatabase(buffer_size); + + for(int i=0;i entities = new ArrayList(); + public ArrayList nests = new ArrayList(); + + public void loadBDF(BdfObject bdf) + { + BdfNamedList nl = bdf.getNamedList(); + + BdfArray nests_bdf = nl.get("nests").getArray(); + BdfArray entities_bdf = nl.get("entities").getArray(); + + entities = new ArrayList(entities_bdf.size()); + nests = new ArrayList(nests_bdf.size()); + + for(BdfObject nest_bdf : nests_bdf) { + nests.add(new Nest(nest_bdf)); + } + + for(BdfObject entity_bdf : entities_bdf) { + entities.add(Entity.loadEntity(entity_bdf)); + } + } + + public void saveBDF(BdfObject bdf) + { + BdfNamedList nl = bdf.getNamedList(); + + BdfArray entities_bdf = bdf.newArray(entities.size()); + BdfArray nests_bdf = bdf.newArray(nests.size()); + + for(int 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 XoucyvPuunpl~6 zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Yumg^`C{nsjb36?+%%V9jHchJl4%ivU!m!v9{ zXJQM3kc93P4z>UO?eq^W5f!rPgNx1`xNNjh5<1~nSDH^g?0oK*ASP*s%`u;0MQPTZ za7R2D9dTh6f-~{ zfLre3L4|4ph*3ZlgHh?7b}T&o#_6wcfal6k6^1d!l5ZRRMCn!Qebf>&?V8~Qu#7T9 z7z4TSgc=axzLTmm!DqWAxUr=--=*R zLzWN6M%nSyo~JI(%QcQu5=f|V#seD^ zE5Nc=!5?n}IF!_=RaK{6gGNoxI7_H7=e!%oZSuxjuikm@gO5H16Kv4nf)63YkfKC` z(^ws!~lw9amR<4K>!( zxPeN|Hl^m8Z=uDOI=I$Nw_UsIzK0%rx}dgE{iD5s8gJBOA~p2<1vQL%za&k*z{tQ1 zh>2w&?gs)Ov<%FSz}Ct@ZeVsqK~<Aq! zK=&bX6E|vlh1&zv>iBGziP);bk<-JV`Z|{+0Y7|ACD7R!(r6nJM6kW0Am$6qOewi0 z(^2sC)QSm*Radr(^BKoTsI%+jVsCp&9Z7v={a(`jz@2-f@GJZwPrpsDa~I=y!9SXe zFKq*;DX-~nEHW1Dinm+*mGIf`ISKgEHQ7t6#!^-vpP)>S?N9Tk)|d9Ra+uD*?0SnLB zqoLGJQY^iTTJ?`T&~$OnfoIw;lsY5@oO`Xs^EVbWV}Ur72qJ4@2%*;$f) z1NEbr=`V0`E~3xNb$Ow8cl6`$+HcuJ@|R$v56FDujh?%Ej}-K)lsvFTpC@`*egE)( z5*vM1^mk|WH(+=C0gUuNfj~hjmI*760004mX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&Mm zKpe$iQ>7{`3KkS`$WWauh>AFB6^c+H)C#RSm|Xe=O&XFG7e~Rh;NZt%)xpJCR|i)? z5c~jfa&%I3krMxx6k5c1aNLh~_a1le0HI!Dn$f_we!cF3PjK&;2<k~y#OK6gCS8#D zk?V@fZ=CZE3p_Jyrjql-VPY}g#!4Hrf~gTt5l2)_r+gvpvdVdjvsS9I<~{ifgIRrL znd>x%5yv8yAVGwJDvBtff+(#zDHf8nAM@}JIev*;3b~440{s+IiwQ`da zZc;D?biUa3#|RME1sXNm{yw(t#tGnm2Cnp$zg!1qK1r{&w8#c`thv24_i_3Fq^PUJ4RCM>jOHnO-Q(R|?Y;ebrrF;Q za{qFtsDb*P00006VoOIv0RI600RN!9r;`8x010qNS#tmYE+YT{E+YYWr9XB6000Mc zNliru02y>eSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{ z007}hL_t(I%cYY&YQsPfM&Ae(ZX8#EV3=|)vL^|oHCafNCRZ@ns5STmK7uczQe~>d zO}a^wH7iB-3iMUW{Jw8sMO6VDVApj55LNxFcK@hvVfMi>gFbkE#wJr>r4nH20K~RH?BE0W-T|zP&yQz{mSq()%wbJ%X_t z05l>>ro`%7lcy3? rCI2N`_0+@NiVy#Sm#hqHDrV{z_Yiojz*m5y00000NkvXXu0mjf{2!1S literal 0 HcmV?d00001 diff --git a/src/resources/texture/ant_food.png b/src/resources/texture/ant_food.png new file mode 100644 index 0000000000000000000000000000000000000000..0bcad21d8c92cacaff5605ff6853305058632f1e GIT binary patch literal 1814 zcmV+x2kH2UP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1bxcEdOb{bv=k1c4q%%aQdsXLc~lpQ}gQByH2A zsdX%5jJb#lN=xm(e>?qyON51-*BDX=2A4}NNyZ|Yb>%s;adX}e=CZia)A>T6vXFK? z#<2AbdV#%cec668^#00GYWu};`yy{g5;wpFaRXUi0H$t7c{{SQk==SeHgZT&ueVSy zJBV8o*G*$`Rwekr>M%Posv%(=#W+RY>Ik$KN#i?zEI;?pbTJjgB+arlbB-%YyY7rT zVP+tjVKX%rv)GkiL!b;HAo8?x+sv8Go7|kNe0|X~+uG%DL-)DbWk8=^+?7FSTW{~! z3#XiVx_`m`Yq}?=R}rZ@f#oJ5aG*DvjvsgLHTK@t?y?Kma@d=??1X7j+3PWiV15=b z6t~A(=SUb4?NU%<)f97J6izIlue0OInLBZti^2slI%xueMWl$Gx(F_k6gx2XSe32%c{aD!$AS0B+245_6hBn zVoE7zU}kvEfimnfG>07aaECv_p+`K@kqgSFxZ+DFT4G5hSE;t5k87w}V@)+TZYZVZ znx__;w%Ah39kteNcis2Uwa1=%K3UtWei|>Z#+x;nObt6dS%X$}Nt*QnCxbIECXT_l z4hB%rGB`7los+@b;LIdJS9n2?esELSWDerUy2j!=pWJ;g_Y!Xu+(X{vS>|X^_a<|b zH){Thw=Y<$<7c}}##R@OoEnSn>-ZIsnB^!QUr{yl3FqJs(pEDUxjEZylJ41g9L(UO zVnrkOr)}wN*&e_M&q7Q|&%`1tlpbO)C3dfCOD)7&j_O8ej!ZfHTAGrNx$-e~&f7Sf zrq-`OaX|NqGhjX#b0g_q@q{dKfz*s-iLDccfRi##_%nBx4XqrG6xx#a-cX3 zaJ~zVF7c(XaFSZI?3uQU{g3IR2l;zR(yd~XsfW{s4z(+?@ps&k>W;ngd{x${YI>pO z9!h$u1}_0uy$$2V%6Tm38@;)sQ_|D<`9adB7tg?dA?ck2U!&4Xun#1?pPkR= z(eJXe(djj+zfTqYK`l;W`fY;mQRuzu`H=K}yDqlq4~qJ`Ui)1K)7vN7SC#!kOb@-$ zZxem*mHx!b-<;XYD}CD^9e=IiUwHZ-hcz-z-KGM^0004mX+uL$Nkc;*aB^>EX>4Tx z0C=2zkv&MmKpe$iQ>7{`3KkS`$WWauh>AFB6^c+H)C#RSm|Xe=O&XFG7e~Rh;NZt% z)xpJCR|i)?5c~jfa&%I3krMxx6k5c1aNLh~_a1le0HI!Dn$f_we!cF3PjK&;2<k~y z#OK6gCS8#Dk?V@fZ=CZE3p_Jyrjql-VPY}g#!4Hrf~gTt5l2)_r+gvpvdVdjvsS9I z<~{ifgIRrLnd>x%5yv8yAVGwJDvBtff+(#zDHf8nAM@}JIev*;3b~440 z{s+IiwQ`daZc;D?biUa3#|RME1sXNm{yw(t#tGnm2Cnp$zg!1qK1r{&w8#c`thv24_i_3Fq^PUJ4RCM>jOHnO-Q(R| z?Y;ebrrF;Qa{qFtsDb*P00006VoOIv0RI600RN!9r;`8x010qNS#tmYE+YT{E+YYW zr9XB6000McNlirueSad^gZEa<4bO1wgWnpw>WFU8GbZ8() zNlj2!fese{006Q{L_t(I%VS)}fE`?_)%wrR$HDON(+>s)1_lPb=4*`D)G=Wb%$Jq@ z4`WM7@G|i8aiEA1GyoZV{q`H~3T%J{mjpi_2Lm@3E0V}(tm+tXg~1djr~kKa-)3N7 zU|>*HRb|N8w~vt|1CYaF;^W7RxYRHqE5;R2=!PPz!*Bs1L(pAAu@@+I0Si9ch!JDK zWiqiYU?SvyLf&E_HsElHqXc5Utn7b6UO`vG#6Tec0I)JxE1zgjS^xk507*qoM6N<$ Ef@?WZ2LJ#7 literal 0 HcmV?d00001 diff --git a/src/resources/texture/big.png b/src/resources/texture/big.png new file mode 100644 index 0000000000000000000000000000000000000000..48c47a1ee5ea62902fbbbb6a1b33543e36938fba GIT binary patch literal 612 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLH@YFt%noI|q0=J1Z0v<)>xlq%tsQ zOst(~>v7mYqV4`ak5DZ>F_F>}0dsXax-7Cpy8;xiaILkP5%Y`PS89@oxckAa4<4LP zx_a_Nyl_t5;CFVp* zcCLC+=UChS+88G-3I2RUFlph1V@1-N7cCU>7jzd5U0hRt zEVcEsSo+qO)U(x}#M_E*oie(f$*bJxs^i$gAQ?I(psiKnt(2x(P>J045Byej6Jv~a zdL=xGaA4I^InMu&eSdZKGj;VlOA2IORBU|PFQ~AE+azn--`bqpy}}9~cw#bj>zDGK zshJd)oq17tLAmsf9eJ6brHmbgZgIOpf)rskC}I2WZRmZYXAlxLP?D7bt2281{A zi31h+db&7zopr0G#9NasU7T literal 0 HcmV?d00001 diff --git a/src/resources/texture/food.png b/src/resources/texture/food.png new file mode 100644 index 0000000000000000000000000000000000000000..92fa01901290ed0db84b50a304cb2cb30fc05a5e GIT binary patch literal 1531 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=HGa^)xthTmDmECC6G#B#V+%?@Vy^Mg4~PLl3U zSLdyBh%uIBN$8X8xbfe=)BS@V`xIRiwUkrx_{AJE7c9(Qzp`iYanI+%KNe4Nci&)$ z0?y&Ov~=VHa)v%1c{_Fr}$D#uXZ<2hc}^>|L% z^z|0{V+VF5b{%d_F(@ZVq!DvuubMK}#y%?Y)>ojjabC|!z3jZbb`MoXOgsVwzh}Fm z4A)U`WXuGQe(7vU*t72ZdIw|(0g>mE+u`qQKIP`7^6}4oW?QEmo^ao*QwHwcjW=bG z#*v3N^wNFaa`*NI$;Y@i(^nB`ydCpXL^<6){A;Z1n6ulQJCM}9fnXOw<6=3Ko**Ae5bE!v>45mx(I$Qd?nz4$u>vujVH0*YnFzgl|DG z6o9}d3POpzGAj`hX#iBn8;8gh1W1HCg5(%N0vj{|Tsu6Yv~^kdlH)B25)x8SutBo| zR*ZuB(P*flB0-{vBvCQRQc}RH79Ez7ruDil8Z@eCQdQHe#ezi>OQvR)tz^ytZ)3@5 z$80(0QsA?|a!Ia0zd%yCh8nBXRJB^owKSAZi%nW;+N|YPI``1g$31oJ)^jg|21;qf zA#S9h!$uxuqSj`Zamq|nr_DUeP3=&9H-3W}9csKuEj_)dL8^M4-L4lHSUlWou|83~o(BEMwO#Ba$-T$JP(X;@SHq!tA z0fcEoLr_UWLm+T+Z)Rz1WdHzpoPCi!NW(xJ#a~mUDlG~Y6miHY#Z1OyVmh-b242yPJ`5m)sLU*5PLdMv9AEeF@%1jsv%Js!IYMgIVt`L1 zo@ItvA>JUK-mDm$_ld)-AgjdZ#A7C1kob}7ipy`D^9~Cotd5g1FsRA0S2E8*_0j0PgBU{fcG={rVP-33-qkHy*2l7 z`T(S;tHcd(a0ra%DSO@H-Cgay{d=a_-w$&Ca;K<)`knv)00v@9M??Vs0RI60puMM) z00009a7bBm001r{001r{0eGc9b^rhX2XskIMF->w5dZ@R5&dW10000PbVXQnLvL+u zWo~o;Lvm$dbY)~9cWHEJAV*0}P*;Ht7XSbNRY^oaR5;6H{Qv(y10{fofkH5900RRH zE{VoE?f?9I91I^n{a|2VU|^_ky2?nB0WcsX!OQUV+i&u{@b%koxGP9<0Y4uH12-2d h!7yMPO^JiR007ls7DW0PedGWD002ovPDHLkV1nLi!=nHI literal 0 HcmV?d00001 diff --git a/src/resources/texture/grass.png b/src/resources/texture/grass.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b0d4f717c1b281fe9e0aadde932348385092a5 GIT binary patch literal 2642 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 AGXMYp literal 0 HcmV?d00001 diff --git a/src/resources/texture/list.txt b/src/resources/texture/list.txt new file mode 100644 index 0000000..8d8ecda --- /dev/null +++ b/src/resources/texture/list.txt @@ -0,0 +1,8 @@ +./food.png +./nest.png +./list.txt +./big.png +./ant_food.png +./ant.png +./grass.png +./ant_eat_ant.png diff --git a/src/resources/texture/nest.png b/src/resources/texture/nest.png new file mode 100644 index 0000000000000000000000000000000000000000..02e8a6f7171c24c0fb959e71938d9cd48ad8c6b9 GIT binary patch literal 1068 zcmV+{1k?M8P)EX>4Tx04R}tkv&MmKpe$iQ>7{`3KkS`$WWauh>AFB6^c+H)C#RSm|Xe=O&XFG z7e~Rh;NZt%)xpJCR|i)?5c~jfa&%I3krMxx6k5c1aNLh~_a1le0HI!Dn$f_we!cF3PjK&;2<k~y#OK6gCS8#Dk?V@fZ=CZE3p_Jyrjql-VPY}g#!4Hrf~gTt5l2)_r+gvp zvdVdjvsS9I<~{ifgIRrLnd>x%5yv8yAVGwJDvBtff+(#zDHf8nAM@}JIev*;3b~44 z0{s+IiwQ`daZc;D?biUa3#|RME1sXNm{yw(t#tGnm2Cnp$zg!1qK1r{& zw8#c`thv24_i_3Fq^PUJ4RCM> zjOHnO-Q(R|?Y;ebrrF;Qa{qFt7IqNR00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00IO_L_t(I%T1C^ZyZMqL|?IcdS_QMY~@2ikYASne@G-7 zu#I@7-I*pUhshxa0(BtBsz>UlKVbyS26YpQfNm-SSum2ELEDNspfyo8yaiK5j78)C z?tsi(z<_1qV&)xOMLP$l08evTg5^UI7wGc`L_qrxR2B1Tex5P_Wpox?1%?-)HiG?H z=p4je^f;R`_#J5QyX0S@L4j_-T_9jHT}jI`opDjA!v%%Fna&X$#na4mt&X zF9^XN$m!Qebzr_h*LL*&5%RCI^K|R1Dd<3$w=`Zsf~Q+U4~Aa;l2)wHU9g_P`w_t| zt}9^B=VQ+P3@(rhrT~};eHC37XK6Vf&}puLS|)d+^)`-ybJM;SJglX%iu6 zC*uH4K>e>8pkw>j#|bH zn9qS4jhso3VhiYWAAsHuh;HH~r5cIzNZ>M55TiJq%sa4K=w<=~3lv5HL17pCIF1l2 mtmj65D44;Vh4a5hZQ@@UUND0|WaMxF0000