From 37abb12e98b1c177ce1a2cf496a126ec8036caf5 Mon Sep 17 00:00:00 2001 From: valyrie97 Date: Sat, 31 Dec 2022 16:26:56 -0500 Subject: [PATCH] weighted average function --- .fleet/run.json | 5 + .../java/xyz/valnet/engine/util/Math.java | 11 ++ .../xyz/valnet/hadean/gameobjects/Clock.java | 64 ++++++ .../xyz/valnet/hadean/gameobjects/Job.java | 33 ++++ .../valnet/hadean/gameobjects/JobBoard.java | 50 +++-- .../hadean/gameobjects/SelectionUI.java | 12 +- .../gameobjects/worldobjects/FarmPlot.java | 5 +- .../hadean/gameobjects/worldobjects/Log.java | 6 +- .../hadean/gameobjects/worldobjects/Pawn.java | 149 -------------- .../hadean/gameobjects/worldobjects/Rice.java | 6 +- .../gameobjects/worldobjects/Stockpile.java | 6 +- .../hadean/gameobjects/worldobjects/Tree.java | 12 +- .../worldobjects/agents/Agent.java | 30 ++- .../worldobjects/pawn/Activity.java | 20 ++ .../worldobjects/pawn/JobActivity.java | 123 ++++++++++++ .../gameobjects/worldobjects/pawn/Needs.java | 37 ++++ .../gameobjects/worldobjects/pawn/Pawn.java | 185 ++++++++++++++++++ .../worldobjects/pawn/SleepActivity.java | 77 ++++++++ .../worldobjects/pawn/WanderActivity.java | 56 ++++++ .../valnet/hadean/interfaces/ISelectable.java | 3 +- .../xyz/valnet/hadean/interfaces/IWorker.java | 8 - .../xyz/valnet/hadean/scenes/GameScene.java | 6 +- .../java/xyz/valnet/hadean/util/Assets.java | 2 +- .../hadean/util/detail/BooleanDetail.java | 14 ++ .../xyz/valnet/hadean/util/detail/Detail.java | 33 ++++ .../hadean/util/detail/ObjectDetail.java | 17 ++ .../hadean/util/detail/PercentDetail.java | 21 ++ 27 files changed, 778 insertions(+), 213 deletions(-) create mode 100644 .fleet/run.json create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/Clock.java delete mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Pawn.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Activity.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/JobActivity.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Needs.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Pawn.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/SleepActivity.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/WanderActivity.java delete mode 100644 src/main/java/xyz/valnet/hadean/interfaces/IWorker.java create mode 100644 src/main/java/xyz/valnet/hadean/util/detail/BooleanDetail.java create mode 100644 src/main/java/xyz/valnet/hadean/util/detail/Detail.java create mode 100644 src/main/java/xyz/valnet/hadean/util/detail/ObjectDetail.java create mode 100644 src/main/java/xyz/valnet/hadean/util/detail/PercentDetail.java diff --git a/.fleet/run.json b/.fleet/run.json new file mode 100644 index 0000000..822a3ba --- /dev/null +++ b/.fleet/run.json @@ -0,0 +1,5 @@ +{ + "configurations": [ + + ] +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/engine/util/Math.java b/src/main/java/xyz/valnet/engine/util/Math.java index 4efe852..4e8e38e 100644 --- a/src/main/java/xyz/valnet/engine/util/Math.java +++ b/src/main/java/xyz/valnet/engine/util/Math.java @@ -13,4 +13,15 @@ public class Math { float scale = (t - minT) / (maxT - minT); return outMin + (scale * (outMax - outMin)); } + + public static class WeightedAverage { + private float value = 0, weight = 0; + public void add(float value, float weight) { + this.value += value * weight; + this.weight += weight; + } + public float calculate() { + return value / weight; + } + } } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Clock.java b/src/main/java/xyz/valnet/hadean/gameobjects/Clock.java new file mode 100644 index 0000000..059afde --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Clock.java @@ -0,0 +1,64 @@ +package xyz.valnet.hadean.gameobjects; + +import xyz.valnet.engine.Game; +import xyz.valnet.engine.graphics.Drawing; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.hadean.HadeanGame; +import xyz.valnet.hadean.util.Assets; +import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.PercentDetail; + +public class Clock extends GameObject { + + private float time = 7; + + @Override + public void start() { + + } + + @Override + public void update(float dTime) { + time += 0.0004f; + while (time >= 24) time -= 24; + } + + public String toString() { + int hour = (int) Math.floor(time); + int minutes = (int) Math.floor((time % 1) * 60); + String hs = hour < 10 ? " " + hour : "" + hour; + String ms = minutes < 10 ? "0" + minutes : "" + minutes; + return "" + hs + ":" + ms; + } + + @Override + public void render() { + Drawing.setLayer(Layers.GENERAL_UI); + String str = toString() + " (Light: " + Math.round(getSunlight() * 100) + "%)"; + int left = 440; + Assets.flat.pushColor(Vector4f.black); + Assets.font.drawString(str, left - 1, 9); + Assets.font.drawString(str, left, 9); + Assets.font.drawString(str, left + 1, 9); + Assets.font.drawString(str, left - 1, 10); + Assets.font.drawString(str, left + 1, 10); + Assets.font.drawString(str, left - 1, 11); + Assets.font.drawString(str, left, 11); + Assets.font.drawString(str, left + 1, 11); + Assets.flat.swapColor(Vector4f.one); + Assets.font.drawString(str, left, 10); + Assets.flat.popColor(); + } + + public float getSunlight() { + float k = 2; + float w = 1; + float u = (k * (float) Math.sin((Math.PI*(time - 7))/(12))) + w; + double kp = Math.atan(k); + float m0 = (float)((Math.atan(k + w)) / kp); + float m1 = (float)((Math.atan(k - w)) / kp); + float t = (float)((Math.atan(u)) / kp); + return (m1 + t) / (m1 + m0); + } +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Job.java b/src/main/java/xyz/valnet/hadean/gameobjects/Job.java index f0596a3..b5af3d0 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/Job.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Job.java @@ -23,6 +23,7 @@ public class Job extends GameObject { public void next() { that.nextStep(); } + public abstract boolean isValid(); } public class PickupItem extends JobStep { @@ -37,6 +38,11 @@ public class Job extends GameObject { public Vector2i[] getLocations() { return new Vector2i[] { item.getWorldPosition().asInt() }; } + + @Override + public boolean isValid() { + return true; + } } public class DropoffAtStockpile extends JobStep { @@ -52,6 +58,11 @@ public class Job extends GameObject { pile.getFreeTile() }; } + + @Override + public boolean isValid() { + return that.get(Stockpile.class) != null; + } } public class Work extends JobStep { @@ -66,6 +77,11 @@ public class Job extends GameObject { public boolean doWork() { return subject.doWork(); } + + @Override + public boolean isValid() { + return true; + } } private List steps; @@ -120,7 +136,24 @@ public class Job extends GameObject { public void apply(); } + public void close() { + for(Callback callback : closedListeners) { + callback.apply(); + } + } + public void registerClosedListener(Callback callback) { closedListeners.add(callback); } + + public void unregisterClosedListener(Callback callback) { + closedListeners.remove(callback); + } + + public boolean isValid() { + for(JobStep step : steps) { + if(!step.isValid()) return false; + } + return true; + } } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/JobBoard.java b/src/main/java/xyz/valnet/hadean/gameobjects/JobBoard.java index 2bb5d26..2d2582b 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/JobBoard.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/JobBoard.java @@ -1,28 +1,26 @@ package xyz.valnet.hadean.gameobjects; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.function.BinaryOperator; -import java.util.stream.Stream; import java.util.Set; +import java.util.stream.Stream; import xyz.valnet.engine.math.Vector2f; import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.hadean.gameobjects.worldobjects.pawn.Pawn; import xyz.valnet.hadean.interfaces.IWorkable; -import xyz.valnet.hadean.interfaces.IWorker; import xyz.valnet.hadean.util.Pair; public class JobBoard extends GameObject { private Set availableJobs = new HashSet(); private List toRemove = new ArrayList(); - private Map allocations = new HashMap(); + private Map allocations = new HashMap(); public Job postSimpleWorkJob(String name, IWorkable subject) { Job job = add(new Job(name)); @@ -37,15 +35,15 @@ public class JobBoard extends GameObject { public void rescindJob(Job job) { if(allocations.values().contains(job)) { - List toFire = new ArrayList(); + List toFire = new ArrayList(); - for(IWorker worker : allocations.keySet()) { + for(Pawn worker : allocations.keySet()) { if(allocations.get(worker) == job) { toFire.add(worker); } } - for(IWorker worker : toFire) { + for(Pawn worker : toFire) { allocations.remove(worker); } } @@ -53,14 +51,20 @@ public class JobBoard extends GameObject { if(availableJobs.contains(job)) { availableJobs.remove(job); } + + job.close(); } - public void requestJob(IWorker worker) { - // TODO worker has capabilities? + public boolean jobsAvailableForWorker(Pawn worker) { + return availableJobs.size() != 0; + } + + public Job requestJob(Pawn worker) { Vector2f workerLocation = worker.getWorldPosition(); List workables = availableJobs .stream() + .filter(job -> job.isValid()) .map(job -> new Pair( job, Stream.of(job.getLocations()) @@ -85,17 +89,21 @@ public class JobBoard extends GameObject { Job firstJob = workables.get(0); availableJobs.remove(firstJob); allocations.put(worker, firstJob); - return; + return firstJob; } + return null; } public void completeJob(Job job) { this.rescindJob(job); } - public void completeJob(IWorker worker) { - if(!workerHasJob(worker)) return; - rescindJob(getJob(worker)); + public void quitJob(Pawn worker, Job job) { + if(!allocations.containsKey(worker)) return; + Job foundJob = allocations.get(worker); + if(foundJob != job) return; + availableJobs.add(job); + allocations.remove(worker); } @Override @@ -112,22 +120,22 @@ public class JobBoard extends GameObject { toRemove.clear(); } - public boolean workerHasJob(IWorker worker) { + public boolean workerHasJob(Pawn worker) { return allocations.containsKey(worker); } - public Job getJob(IWorker worker) { - if(allocations.containsKey(worker)) { - return allocations.get(worker); - } else return null; - } + // public Job getJob(Pawn worker) { + // if(allocations.containsKey(worker)) { + // return allocations.get(worker); + // } else return null; + // } public String details() { String takenJobsString = ""; String availableJobsString = ""; - for(Entry allocation : allocations.entrySet()) { + for(Entry allocation : allocations.entrySet()) { takenJobsString += " " + allocation.getKey().getName() + ": " + allocation.getValue().getJobName() + "\n"; } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java b/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java index 4c4148e..46dfb39 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java @@ -16,6 +16,7 @@ import xyz.valnet.hadean.interfaces.ISelectionChangeListener; import xyz.valnet.hadean.util.Action; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.Detail; public class SelectionUI extends GameObject implements ISelectionChangeListener, IButtonListener, IMouseCaptureArea { @@ -53,19 +54,12 @@ public class SelectionUI extends GameObject implements ISelectionChangeListener, Assets.uiFrame.draw(10, 576 - BottomBar.bottomBarHeight - height - padding, width, height); - // int i = 0; - // for(String name : selectedTypes.keySet()) { - // int n = selectedTypes.get(name); - // Assets.font.drawString("" + n + "x " + name, 26, 376 + 16 * i); - // i ++; - // } - - if(selectedTypes.size() == 1) { Assets.font.drawString("" + count + "x " + name, 26, 576 - BottomBar.bottomBarHeight - height); if(count == 1) { - String details = selected.get(0).details(); + + String details = Detail.renderDetails(selected.get(0).getDetails()); Assets.font.drawString(details, 26, 576 - BottomBar.bottomBarHeight - height + 32); } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/FarmPlot.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/FarmPlot.java index 1a2aaaa..435c956 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/FarmPlot.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/FarmPlot.java @@ -9,6 +9,7 @@ import xyz.valnet.hadean.interfaces.ITileThing; import xyz.valnet.hadean.util.Action; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.Detail; @BuildableMetadata(category = "Zones", name = "Farm Plot") public class FarmPlot extends WorldObject implements ISelectable, ITileThing, IBuildable { @@ -45,8 +46,8 @@ public class FarmPlot extends WorldObject implements ISelectable, ITileThing, IB } @Override - public String details() { - return ""; + public Detail[] getDetails() { + return new Detail[] {}; } @Override diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Log.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Log.java index 6e56af6..b8b34af 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Log.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Log.java @@ -3,9 +3,9 @@ package xyz.valnet.hadean.gameobjects.worldobjects; import xyz.valnet.hadean.gameobjects.worldobjects.items.Item; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.Detail; public class Log extends Item { - @Override public void start() { @@ -36,8 +36,8 @@ public class Log extends Item { public void onRemove() {} @Override - public String details() { - return "A fat log"; + public Detail[] getDetails() { + return new Detail[] {}; } @Override diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Pawn.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Pawn.java deleted file mode 100644 index 66ade82..0000000 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Pawn.java +++ /dev/null @@ -1,149 +0,0 @@ -package xyz.valnet.hadean.gameobjects.worldobjects; - -import java.util.ArrayList; -import java.util.List; - -import xyz.valnet.engine.math.Vector2f; -import xyz.valnet.engine.math.Vector2i; -import xyz.valnet.engine.math.Vector4f; -import xyz.valnet.hadean.gameobjects.Job; -import xyz.valnet.hadean.gameobjects.JobBoard; -import xyz.valnet.hadean.gameobjects.Terrain; -import xyz.valnet.hadean.gameobjects.Job.JobStep; -import xyz.valnet.hadean.gameobjects.worldobjects.agents.Agent; -import xyz.valnet.hadean.gameobjects.worldobjects.items.Item; -import xyz.valnet.hadean.interfaces.ITileThing; -import xyz.valnet.hadean.interfaces.IWorker; -import xyz.valnet.hadean.util.Action; -import xyz.valnet.hadean.util.Assets; -import xyz.valnet.hadean.util.Layers; - -public class Pawn extends Agent implements IWorker { - - private static int pawnCount = 0; - private String name = "Pawn " + (++ pawnCount); - - @Override - public void start() { - super.start(); - jobboard = get(JobBoard.class); - x = (int) (Math.random() * Terrain.WORLD_SIZE); - y = (int) (Math.random() * Terrain.WORLD_SIZE); - } - - @Override - public void render() { - super.render(); - camera.draw(Layers.PAWNS, Assets.pawn, getCalculatedPosition()); - } - - @Override - public void runAction(Action action) {} - - @Override - public String details() { - return ""; - } - - @Override - public Vector2f getWorldPosition() { - return new Vector2f(x, y); - } - - @Override - public String getName() { - return name; - } - - @Override - public Vector4f getWorldBox() { - Vector2f pos = getCalculatedPosition(); - return new Vector4f(pos.x, pos.y, pos.x+1, pos.y+1); - } - - private JobBoard jobboard; - - @Override - protected void think() { - super.think(); - - // if we dont have a job - if(!jobboard.workerHasJob(this)) { - jobboard.requestJob(this); // try to get one - } - - // if we have a job and need to go to it and we're not pathing to it - if(jobboard.workerHasJob(this) && !isAtJobStepLocation() && !isPathingToJobLocation()) { - goToJobStepLocation(); // start pathing there. - return; // and dont think about anything else. - } - - // if we still dont have a job and we're not moving around - if(!jobboard.workerHasJob(this) && !isPathing()) { - if(Math.random() < 0.001f) wander(); // have a chance of wandering! - return; // and dont think about anything else. - } - } - - private boolean isPathingToJobLocation() { - if(!isPathing()) return false; - return getDestination().isOneOf(jobboard.getJob(this).getCurrentStep().getLocations()); - } - - private boolean isAtJobStepLocation() { - return this.getWorldPosition().asInt().isOneOf(jobboard.getJob(this).getCurrentStep().getLocations()); - } - - private void goToJobStepLocation() { - goToClosest(jobboard - .getJob(this) - .getCurrentStep() - .getLocations() - ); - } - - // TODO at some point rewrite this to use an actor component array - // where we loop through until something _does_ sometihng. - @Override - protected boolean act() { - if(super.act()) return true; - if(doJob()) return true; - return false; - } - - private List inventory = new ArrayList(); - - private boolean doJob() { - if(!jobboard.workerHasJob(this)) return false; - JobStep step = jobboard.getJob(this).getCurrentStep(); - // if we're not at the location of the job... - if(!isAtJobStepLocation()) return false; - - if(step instanceof Job.Work) { - Job.Work workStep = (Job.Work)step; - if(workStep.doWork()) step.next(); - return true; - } else if(step instanceof Job.PickupItem) { - Job.PickupItem pickupStep = (Job.PickupItem) step; - Item item = getTile().removeThing(pickupStep.item); - remove(item); - inventory.add(item); - step.next(); - return true; - } else if(step instanceof Job.DropoffAtStockpile) { - if(!getTile().isTileFree()) return false; - Job.DropoffAtStockpile dropoffStep = (Job.DropoffAtStockpile) step; - Item item = dropoffStep.item; - if(!inventory.contains(item)) { - return false; - } - inventory.remove(item); - add(item); - getTile().placeThing(item); - step.next(); - return true; - } - - return false; - } -} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Rice.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Rice.java index 138d47e..8672233 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Rice.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Rice.java @@ -3,13 +3,13 @@ package xyz.valnet.hadean.gameobjects.worldobjects; import xyz.valnet.engine.math.Vector2f; import xyz.valnet.engine.math.Vector4f; import xyz.valnet.hadean.gameobjects.JobBoard; -import xyz.valnet.hadean.gameobjects.Tile; import xyz.valnet.hadean.gameobjects.worldobjects.items.Item; import xyz.valnet.hadean.interfaces.ISelectable; import xyz.valnet.hadean.interfaces.ITileThing; import xyz.valnet.hadean.util.Action; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.Detail; public class Rice extends Item implements ITileThing, ISelectable { @@ -79,8 +79,8 @@ public class Rice extends Item implements ITileThing, ISelectable { } @Override - public String details() { - return "Bag of Rice"; + public Detail[] getDetails() { + return new Detail[] {}; } @Override diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Stockpile.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Stockpile.java index 083d1c3..8f98b87 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Stockpile.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Stockpile.java @@ -10,6 +10,7 @@ import xyz.valnet.hadean.interfaces.ITileThing; import xyz.valnet.hadean.util.Action; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.Detail; @BuildableMetadata(category = "Zones", name = "Stockpile") public class Stockpile extends WorldObject implements ISelectable, ITileThing, IBuildable { @@ -82,9 +83,8 @@ public class Stockpile extends WorldObject implements ISelectable, ITileThing, I } @Override - public String details() { - - return ""; + public Detail[] getDetails() { + return new Detail[] {}; } @Override diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Tree.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Tree.java index 56dce69..5e645b0 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Tree.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/Tree.java @@ -11,6 +11,9 @@ import xyz.valnet.hadean.interfaces.IWorkable; import xyz.valnet.hadean.util.Action; import xyz.valnet.hadean.util.Assets; import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.detail.BooleanDetail; +import xyz.valnet.hadean.util.detail.Detail; +import xyz.valnet.hadean.util.detail.PercentDetail; public class Tree extends WorldObject implements ITileThing, ISelectable, IWorkable { @@ -89,10 +92,11 @@ public class Tree extends WorldObject implements ITileThing, ISelectable, IWorka } @Override - public String details() { - return "" + name + "\n" + - "Chop Flag | " + (chopJob != null) + "\n" + - "Progress | " + (String.format("%.2f", getProgress() * 100)) + "%"; + public Detail[] getDetails() { + return new Detail[] { + new BooleanDetail("Chop Flag", chopJob != null), + new PercentDetail("Progress", getProgress()) + }; } @Override diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/agents/Agent.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/agents/Agent.java index 43f029f..089eadb 100644 --- a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/agents/Agent.java +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/agents/Agent.java @@ -33,11 +33,13 @@ public abstract class Agent extends WorldObject implements ISelectable { private IPathfinder pathfinder; private Path path = null; - protected boolean isPathing() { + private boolean stopPathingFlag = false; + + public boolean isPathing() { return path != null && !path.isComplete(); } - protected Vector2f getCalculatedPosition() { + public Vector2f getCalculatedPosition() { if(path == null || path.isComplete()) return getWorldPosition(); Vector2f nextPos = path.peek().getPosition().asFloat(); return new Vector2f( @@ -57,8 +59,11 @@ public abstract class Agent extends WorldObject implements ISelectable { public void update(float dTime) { think(); act(); + postAct(); } + protected abstract void postAct(); + private void move() { frameCounter++; if(frameCounter >= speed) { @@ -71,9 +76,19 @@ public abstract class Agent extends WorldObject implements ISelectable { } if(path.isComplete()) path = null; frameCounter = 0; + if(stopPathingFlag) { + path = null; + nextPath = null; + stopPathingFlag = false; + } } } + protected void stopPathing() { + stopPathingFlag = true; + nextPath = null; + } + private void correctPath() { if(path != null && path.isComplete()) path = null; if(path == null) return; @@ -108,7 +123,7 @@ public abstract class Agent extends WorldObject implements ISelectable { private Path nextPath = null; - protected void goTo(int x, int y) { + public void goTo(int x, int y) { Path newPath = pathfinder.getPath((int)this.x, (int)this.y, x, y); if(path == null) { path = newPath; @@ -118,7 +133,7 @@ public abstract class Agent extends WorldObject implements ISelectable { } } - protected void goToClosest(Vector2i[] destinations) { + public void goToClosest(Vector2i[] destinations) { Path newPath = pathfinder.getBestPath(this.getWorldPosition().asInt(), destinations); if(path == null) { path = newPath; @@ -128,11 +143,11 @@ public abstract class Agent extends WorldObject implements ISelectable { } } - protected void goTo(Vector2i location) { + public void goTo(Vector2i location) { goTo(location.x, location.y); } - protected void wander() { + public void wander() { int randomX = (int)Math.floor(Math.random() * Terrain.WORLD_SIZE); int randomY = (int)Math.floor(Math.random() * Terrain.WORLD_SIZE); path = pathfinder.getPath((int)x, (int)y, randomX, randomY); @@ -184,7 +199,8 @@ public abstract class Agent extends WorldObject implements ISelectable { return new Action[0]; } - protected Vector2i getDestination() { + public Vector2i getDestination() { + if(nextPath != null) return nextPath.getDestination().getPosition(); if(path == null) return null; return path.getDestination().getPosition(); } diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Activity.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Activity.java new file mode 100644 index 0000000..b612081 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Activity.java @@ -0,0 +1,20 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import xyz.valnet.engine.math.Vector2i; + +public abstract class Activity { + + @FunctionalInterface + public interface ActivityCancellationCallback { + public void apply(Activity activity); + } + + public abstract boolean isUrgent(); + public abstract float getBenefit(); + public abstract boolean isValid(); + public abstract void act(); + public abstract void begin(ActivityCancellationCallback callback); + public abstract void end(); + public abstract String toString(); + public abstract Vector2i[] getTargetLocations(); +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/JobActivity.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/JobActivity.java new file mode 100644 index 0000000..d513320 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/JobActivity.java @@ -0,0 +1,123 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import xyz.valnet.engine.math.Vector2i; +import xyz.valnet.hadean.gameobjects.Job; +import xyz.valnet.hadean.gameobjects.Job.JobStep; +import xyz.valnet.hadean.gameobjects.JobBoard; + +public class JobActivity extends Activity { + + private JobBoard jobboard; + private Pawn worker; + private Job job; + + public JobActivity(Pawn worker, JobBoard jobboard) { + this.worker = worker; + this.jobboard = jobboard; + } + + @Override + public boolean isUrgent() { + return false; + } + + @Override + public boolean isValid() { + return jobboard.jobsAvailableForWorker(worker); + } + + @Override + public float getBenefit() { + return 0.5f; + } + + @Override + public void act() { + if (doJob()) return; + } + + + private boolean isPathingToJobLocation() { + if(!worker.isPathing()) return false; + return worker.getDestination().isOneOf(job.getCurrentStep().getLocations()); + } + + private boolean isAtJobStepLocation() { + return worker.getWorldPosition().asInt().isOneOf(job.getCurrentStep().getLocations()); + } + + private void goToJobStepLocation() { + worker.goToClosest(job + .getCurrentStep() + .getLocations() + ); + } + + ActivityCancellationCallback callback; + + @Override + public void begin(ActivityCancellationCallback callback) { + + this.callback = callback; + job = jobboard.requestJob(worker); + if(job == null) callback.apply(this); + job.registerClosedListener(() -> { + System.out.println("job cancelled"); + callback.apply(this); + }); + + // if we dont have a job + if(!jobboard.workerHasJob(worker)) { + } + + // if we have a job and need to go to it and we're not pathing to it + if(jobboard.workerHasJob(worker) && !isAtJobStepLocation() && !isPathingToJobLocation()) { + goToJobStepLocation(); // start pathing there. + return; // and dont think about anything else. + } + } + + @Override + public void end() { + jobboard.quitJob(worker, job); + job = null; + } + + private boolean doJob() { + if(!jobboard.workerHasJob(worker)) return false; + JobStep step = job.getCurrentStep(); + // if we're not at the location of the job... + if(!isAtJobStepLocation()) return false; + + if(step instanceof Job.Work) { + Job.Work workStep = (Job.Work)step; + if(workStep.doWork()) step.next(); + return true; + } else if(step instanceof Job.PickupItem) { + Job.PickupItem pickupStep = (Job.PickupItem) step; + worker.pickupItem(pickupStep.item); + step.next(); + return true; + } else if(step instanceof Job.DropoffAtStockpile) { + if(!worker.getTile().isTileFree()) return false; + Job.DropoffAtStockpile dropoffStep = (Job.DropoffAtStockpile) step; + worker.dropoffItem(dropoffStep.item); + step.next(); + return true; + } + + return false; + } + + @Override + public String toString() { + if(job == null) return "Activity (Null Job)"; + return job.getJobName(); + } + + @Override + public Vector2i[] getTargetLocations() { + return job.getCurrentStep().getLocations(); + } + +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Needs.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Needs.java new file mode 100644 index 0000000..38ea678 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Needs.java @@ -0,0 +1,37 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import xyz.valnet.hadean.util.detail.Detail; +import xyz.valnet.hadean.util.detail.PercentDetail; + +public class Needs { + + private float energy = 0.5f + (float)Math.random() * 0.5f; + private float recreation = 0.5f + (float)Math.random() * 0.5f; + + private float restRatio = 6; + private float decay = 0.00001f; + + public void update(float dTime) { + energy = Math.max(energy - decay, 0); + recreation = Math.max(recreation - decay, 0); + } + + public void sleep() { + energy = Math.min(energy + decay * restRatio, 1); + } + + public float getSleepNeed() { + return 1 - energy; + } + + // public + + public Detail[] getDetails() { + return new Detail[] { + new PercentDetail("Energy", energy, 1), + new PercentDetail("Fun", recreation, 1) + }; + } + + +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Pawn.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Pawn.java new file mode 100644 index 0000000..4c084e4 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/Pawn.java @@ -0,0 +1,185 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import static xyz.valnet.hadean.util.detail.Detail.mergeDetails; + +import java.util.ArrayList; +import java.util.List; + +import xyz.valnet.engine.math.Vector2f; +import xyz.valnet.engine.math.Vector2i; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.hadean.gameobjects.Clock; +import xyz.valnet.hadean.gameobjects.JobBoard; +import xyz.valnet.hadean.gameobjects.Terrain; +import xyz.valnet.hadean.gameobjects.worldobjects.agents.Agent; +import xyz.valnet.hadean.gameobjects.worldobjects.items.Item; +import xyz.valnet.hadean.util.Action; +import xyz.valnet.hadean.util.Assets; +import xyz.valnet.hadean.util.Layers; +import xyz.valnet.hadean.util.Pair; +import xyz.valnet.hadean.util.detail.BooleanDetail; +import xyz.valnet.hadean.util.detail.Detail; +import xyz.valnet.hadean.util.detail.ObjectDetail; +import xyz.valnet.hadean.util.detail.PercentDetail; + +public class Pawn extends Agent { + + private static int pawnCount = 0; + private String name = "Pawn " + (++ pawnCount); + private Needs needs = new Needs(); + + // private float workEthic = (float) Math.random(); + // private float selfWorth = (float) Math.random(); + + private List activities = new ArrayList(); + private Activity currentActivity = null; + + public void pickupItem(Item i) { + Item item = getTile().removeThing(i); + if(item == null) return; + remove(item); + inventory.add(item); + } + + public void dropoffItem(Item item) { + if(!inventory.contains(item)) { + return; + } + inventory.remove(item); + add(item); + getTile().placeThing(item); + } + + @Override + public void start() { + super.start(); + x = (int) (Math.random() * Terrain.WORLD_SIZE); + y = (int) (Math.random() * Terrain.WORLD_SIZE); + + activities.add(new JobActivity(this, get(JobBoard.class))); + activities.add(new SleepActivity(this, needs, get(Clock.class))); + // activities.add(new WanderActivity()); + } + + @Override + public void render() { + super.render(); + if(currentActivity instanceof SleepActivity) { + Assets.flat.pushColor(new Vector4f(0.5f, 0.5f, 0.5f, 1.0f)); + } else { + Assets.flat.pushColor(Vector4f.one); + } + camera.draw(Layers.PAWNS, Assets.pawn, getCalculatedPosition()); + Assets.flat.popColor(); + } + + @Override + public void runAction(Action action) {} + + @Override + public Detail[] getDetails() { + // return needs.getDetails(); + return mergeDetails(needs.getDetails(), new Detail[] { + new ObjectDetail("Activity", currentActivity), + new PercentDetail("Sleep Value", activities.get(1).getBenefit(), 2), + new BooleanDetail("Pathing", isPathing()) + }); + } + + @Override + public Vector2f getWorldPosition() { + return new Vector2f(x, y); + } + + @Override + public String getName() { + return name; + } + + @Override + public Vector4f getWorldBox() { + Vector2f pos = getCalculatedPosition(); + return new Vector4f(pos.x, pos.y, pos.x+1, pos.y+1); + } + + @Override + protected void think() { + super.think(); + decideActivity(); + pathToActivity(); + } + + private void pathToActivity() { + if(currentActivity == null) return; + Vector2i[] locations = currentActivity.getTargetLocations(); + if(locations == null || locations.length == 0) return; + if(isPathing() && getDestination().isOneOf(locations)) return; + goToClosest(locations); + } + + private void decideActivity() { + + List urgentActivities = activities.stream() + .filter(a -> a.isValid() && a.isUrgent()) + .toList(); + + if(urgentActivities.size() > 0) { + switchActivity(urgentActivities.get(0)); + return; + } + + if(currentActivity != null) return; + + currentActivity = null; + + List valueSortedActivities = activities.stream() + .filter(a -> a.isValid()) + .map(a -> new Pair(a, a.getBenefit())) + .filter(a -> a.second() >= 0) + .sorted((a, b) -> a.second() > b.second() ? -1 : 1) + .map(p -> p.first()) + .toList(); + + if(valueSortedActivities.size() == 0) return; + + switchActivity(valueSortedActivities.get(0)); + } + + private void switchActivity(Activity activity) { + if(currentActivity != null) currentActivity.end(); + currentActivity = activity; + stopPathing(); + currentActivity.begin(a -> endActivity(a)); + } + + private void endActivity() { + endActivity(currentActivity); + } + + private void endActivity(Activity activity) { + activity.end(); + stopPathing(); + if(currentActivity == activity) { + currentActivity = null; + } + } + + // TODO at some point rewrite this to use an actor component array + // where we loop through until something _does_ sometihng. + @Override + protected boolean act() { + if(super.act()) return true; + // if(doJob()) return true; + if(currentActivity != null) { + currentActivity.act(); + return true; + } + return false; + } + + protected void postAct() { + needs.update(1); + } + + private List inventory = new ArrayList(); +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/SleepActivity.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/SleepActivity.java new file mode 100644 index 0000000..a6d68de --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/SleepActivity.java @@ -0,0 +1,77 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import xyz.valnet.engine.math.Vector2i; +import xyz.valnet.engine.util.Math.WeightedAverage; +import xyz.valnet.hadean.gameobjects.Clock; +import xyz.valnet.hadean.gameobjects.worldobjects.agents.Agent; + +import static xyz.valnet.engine.util.Math.lerp; + +public class SleepActivity extends Activity { + + private Agent agent; + private Needs needs; + private Clock clock; + + private float circadianStrength = (float)Math.random() * 5f; + + private int stage; + + public SleepActivity(Agent agent, Needs needs, Clock clock) { + this.needs = needs; + this.agent = agent; + this.clock = clock; + } + + @Override + public boolean isUrgent() { + return needs.getSleepNeed() >= 0.97f; + } + + @Override + public float getBenefit() { + float minSleepRechargePerCycle = 0.2f; + // subtract because sleeping for only 5 minutes when + // you're not that tired to hit 100% is undesireable. + // as it will induce oversleep + WeightedAverage average = new WeightedAverage(); + average.add(needs.getSleepNeed() - minSleepRechargePerCycle, 1); + // System.out.println(1 - 2 * clock.getSunlight()); + average.add(1 - 2 * clock.getSunlight(), circadianStrength); + return average.calculate(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void act() { + needs.sleep(); + if(needs.getSleepNeed() == 0) { + callback.apply(this); + } + } + + ActivityCancellationCallback callback; + + @Override + public void begin(ActivityCancellationCallback callback) { + this.callback = callback; + } + + @Override + public void end() { } + + @Override + public String toString() { + return "Sleeping"; + } + + @Override + public Vector2i[] getTargetLocations() { + return null; + } + +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/WanderActivity.java b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/WanderActivity.java new file mode 100644 index 0000000..3590285 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/worldobjects/pawn/WanderActivity.java @@ -0,0 +1,56 @@ +package xyz.valnet.hadean.gameobjects.worldobjects.pawn; + +import xyz.valnet.engine.math.Vector2i; +import xyz.valnet.hadean.gameobjects.worldobjects.agents.Agent; + +public class WanderActivity extends Activity { + + private Agent agent; + private Needs needs; + + public WanderActivity(Agent agent, Needs needs) { + this.needs = needs; + this.agent = agent; + } + + @Override + public boolean isUrgent() { + return false; + } + + @Override + public float getBenefit() { + return 0.0f; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void act() { + // since wandering is literally just pathing. + } + + ActivityCancellationCallback callback; + + @Override + public void begin(ActivityCancellationCallback callback) { + this.callback = callback; + } + + @Override + public void end() { } + + @Override + public String toString() { + return "Sleeping"; + } + + @Override + public Vector2i[] getTargetLocations() { + return null; + } + +} diff --git a/src/main/java/xyz/valnet/hadean/interfaces/ISelectable.java b/src/main/java/xyz/valnet/hadean/interfaces/ISelectable.java index b0ab7b3..aaaaff5 100644 --- a/src/main/java/xyz/valnet/hadean/interfaces/ISelectable.java +++ b/src/main/java/xyz/valnet/hadean/interfaces/ISelectable.java @@ -2,11 +2,12 @@ package xyz.valnet.hadean.interfaces; import xyz.valnet.engine.math.Vector4f; import xyz.valnet.hadean.util.Action; +import xyz.valnet.hadean.util.detail.Detail; public interface ISelectable { public Vector4f getWorldBox(); public Action[] getActions(); public void runAction(Action action); - public String details(); + public Detail[] getDetails(); public default void selectedRender() {} } \ No newline at end of file diff --git a/src/main/java/xyz/valnet/hadean/interfaces/IWorker.java b/src/main/java/xyz/valnet/hadean/interfaces/IWorker.java deleted file mode 100644 index 9e7e5d8..0000000 --- a/src/main/java/xyz/valnet/hadean/interfaces/IWorker.java +++ /dev/null @@ -1,8 +0,0 @@ -package xyz.valnet.hadean.interfaces; - -import xyz.valnet.engine.math.Vector2f; - -public interface IWorker { - public Vector2f getWorldPosition(); - public String getName(); -} diff --git a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java index 509dcb5..5772ab7 100644 --- a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java +++ b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java @@ -3,6 +3,7 @@ package xyz.valnet.hadean.scenes; import xyz.valnet.engine.scenegraph.SceneGraph; import xyz.valnet.hadean.gameobjects.BottomBar; import xyz.valnet.hadean.gameobjects.Camera; +import xyz.valnet.hadean.gameobjects.Clock; import xyz.valnet.hadean.gameobjects.JobBoard; import xyz.valnet.hadean.gameobjects.SelectionUI; import xyz.valnet.hadean.gameobjects.Terrain; @@ -12,7 +13,7 @@ import xyz.valnet.hadean.gameobjects.ui.HoverQuery; import xyz.valnet.hadean.gameobjects.ui.tabs.BuildTab; import xyz.valnet.hadean.gameobjects.ui.tabs.JobBoardTab; import xyz.valnet.hadean.gameobjects.ui.tabs.MenuTab; -import xyz.valnet.hadean.gameobjects.worldobjects.Pawn; +import xyz.valnet.hadean.gameobjects.worldobjects.pawn.Pawn; // TODO BIG IDEAS // have caches of types that ill need (Like IMouseListener) @@ -31,8 +32,9 @@ public class GameScene extends SceneGraph { objects.add(new Terrain()); objects.add(new Camera()); objects.add(new JobBoard()); + objects.add(new Clock()); - for(int i = 0; i < 5; i ++) { + for(int i = 0; i < 1; i ++) { objects.add(new Pawn()); } diff --git a/src/main/java/xyz/valnet/hadean/util/Assets.java b/src/main/java/xyz/valnet/hadean/util/Assets.java index ca1f0e7..1a8aa0b 100644 --- a/src/main/java/xyz/valnet/hadean/util/Assets.java +++ b/src/main/java/xyz/valnet/hadean/util/Assets.java @@ -79,7 +79,7 @@ public class Assets { bed = new Sprite(atlas, 0, 120, 8, 16); egg = new Sprite(atlas, 8, 104, 8, 8); bigRock = new Sprite(atlas, 16, 104, 8, 8); - lilPickaxe = new Sprite(atlas, 8, 120, 16, 16) + lilPickaxe = new Sprite(atlas, 8, 120, 16, 16); Map charset = new HashMap(); diff --git a/src/main/java/xyz/valnet/hadean/util/detail/BooleanDetail.java b/src/main/java/xyz/valnet/hadean/util/detail/BooleanDetail.java new file mode 100644 index 0000000..347b174 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/util/detail/BooleanDetail.java @@ -0,0 +1,14 @@ +package xyz.valnet.hadean.util.detail; + +public class BooleanDetail extends Detail { + private boolean value; + + public BooleanDetail(String key, boolean value) { + this.key = key; + this.value = value; + } + + public String toString(int keyWidth) { + return key + " ".repeat(keyWidth - getKeyWidth()) + " | " + java.lang.Boolean.toString(value); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/hadean/util/detail/Detail.java b/src/main/java/xyz/valnet/hadean/util/detail/Detail.java new file mode 100644 index 0000000..b39f97d --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/util/detail/Detail.java @@ -0,0 +1,33 @@ +package xyz.valnet.hadean.util.detail; + +public abstract class Detail { + + public abstract String toString(int keyWidth); + + protected String key; + + public int getKeyWidth() { + return key.length(); + } + + public static String renderDetails(Detail[] details) { + if(details.length == 0) return ""; + int keyWidth = 0; + for(Detail d : details) { + keyWidth = Math.max(d.getKeyWidth(), keyWidth); + } + + String str = ""; + for(Detail d : details) { + str += d.toString(keyWidth) + "\n"; + } + return str.stripTrailing(); + } + + public static Detail[] mergeDetails(Detail[] a, Detail[] b) { + Detail[] c = new Detail[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } +} diff --git a/src/main/java/xyz/valnet/hadean/util/detail/ObjectDetail.java b/src/main/java/xyz/valnet/hadean/util/detail/ObjectDetail.java new file mode 100644 index 0000000..8d2c7d0 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/util/detail/ObjectDetail.java @@ -0,0 +1,17 @@ +package xyz.valnet.hadean.util.detail; + +public class ObjectDetail extends Detail { + private T value; + + public ObjectDetail(String key, T value) { + this.key = key; + this.value = value; + } + + public String toString(int keyWidth) { + String prefix = key + " ".repeat(keyWidth - getKeyWidth()) + " | "; + if(value == null) + return prefix + "null"; + return prefix + value.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/hadean/util/detail/PercentDetail.java b/src/main/java/xyz/valnet/hadean/util/detail/PercentDetail.java new file mode 100644 index 0000000..e807252 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/util/detail/PercentDetail.java @@ -0,0 +1,21 @@ +package xyz.valnet.hadean.util.detail; + +public class PercentDetail extends Detail { + private float value; + private int sigFigs; + + public PercentDetail(String key, float value) { + this(key, value, 0); + } + + public PercentDetail(String key, float value, int sigFigs) { + this.key = key; + this.value = value; + this.sigFigs = sigFigs; + } + + public String toString(int keyWidth) { + double sigFigMul = Math.pow(10, sigFigs); + return key + " ".repeat(keyWidth - getKeyWidth()) + " | " + ((Math.round(value * (sigFigMul * 100))) / sigFigMul) + "%"; + } +} \ No newline at end of file