Cleanup / Functioning formation
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
/**
|
||||
* {@code BoundedSlotAssignmentStrategy} is an abstract implementation of {@link SlotAssignmentStrategy} that supports roles.
|
||||
* Generally speaking, there are hard and soft roles. Hard roles cannot be broken, soft roles can.
|
||||
* <p>
|
||||
* This abstract class provides an implementation of the {@link #calculateNumberOfSlots(Array) calculateNumberOfSlots} method that
|
||||
* is more general (and costly) than the simplified implementation in {@link FreeSlotAssignmentStrategy}. It scans the assignment
|
||||
* list to find the number of filled slots, which is the highest slot number in the assignments.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class BoundedSlotAssignmentStrategy implements SlotAssignmentStrategy{
|
||||
|
||||
@Override
|
||||
public abstract void updateSlotAssignments(Array<SlotAssignment> assignments);
|
||||
|
||||
@Override
|
||||
public int calculateNumberOfSlots(Array<SlotAssignment> assignments){
|
||||
// Find the number of filled slots: it will be the
|
||||
// highest slot number in the assignments
|
||||
int filledSlots = -1;
|
||||
for(int i = 0; i < assignments.size; i++){
|
||||
SlotAssignment assignment = assignments.get(i);
|
||||
if(assignment.slotNumber >= filledSlots) filledSlots = assignment.slotNumber;
|
||||
}
|
||||
|
||||
// Add one to go from the index of the highest slot to the number of slots needed.
|
||||
return filledSlots + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSlotAssignment(Array<SlotAssignment> assignments, int index){
|
||||
int sn = assignments.get(index).slotNumber;
|
||||
for(int i = 0; i < assignments.size; i++){
|
||||
SlotAssignment sa = assignments.get(i);
|
||||
if(sa.slotNumber >= sn) sa.slotNumber--;
|
||||
}
|
||||
assignments.remove(index);
|
||||
}
|
||||
|
||||
}
|
||||
212
core/src/mindustry/ai/formations/Formation.java
Normal file
212
core/src/mindustry/ai/formations/Formation.java
Normal file
@@ -0,0 +1,212 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
|
||||
/**
|
||||
* A {@code Formation} coordinates the movement of a group of characters so that they retain some group organization. Characters
|
||||
* belonging to a formation must implement the {@link FormationMember} interface. At its simplest, a formation can consist of
|
||||
* moving in a fixed geometric pattern such as a V or line abreast, but it is not limited to that. Formations can also make use of
|
||||
* the environment. Squads of characters can move between cover points using formation steering with only minor modifications, for
|
||||
* example.
|
||||
* <p>
|
||||
* Formation motion is used in team sports games, squad-based games, real-time strategy games, and sometimes in first-person
|
||||
* shooters, driving games, and action adventures too. It is a simple and flexible technique that is much quicker to write and
|
||||
* execute and can produce much more stable behavior than collaborative tactical decision making.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Formation{
|
||||
|
||||
/** A list of slots assignments. */
|
||||
Array<SlotAssignment> slotAssignments;
|
||||
|
||||
/** The anchor point of this formation. */
|
||||
public Vec3 anchor;
|
||||
/** The formation pattern */
|
||||
public FormationPattern pattern;
|
||||
/** The strategy used to assign a member to his slot */
|
||||
public SlotAssignmentStrategy slotAssignmentStrategy;
|
||||
/** The formation motion moderator */
|
||||
public FormationMotionModerator motionModerator;
|
||||
|
||||
private final Vec2 positionOffset;
|
||||
private final Mat orientationMatrix = new Mat();
|
||||
|
||||
/** The location representing the drift offset for the currently filled slots. */
|
||||
private final Vec3 driftOffset;
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern} using a {@link FreeSlotAssignmentStrategy} and no motion
|
||||
* moderator.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Vec3 anchor, FormationPattern pattern){
|
||||
this(anchor, pattern, new FreeSlotAssignmentStrategy(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern} and {@code slotAssignmentStrategy} using no motion moderator.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy){
|
||||
this(anchor, pattern, slotAssignmentStrategy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern}, {@code slotAssignmentStrategy} and {@code moderator}.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
|
||||
* @param motionModerator the motion moderator. Can be {@code null} if moderation is not needed
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy,
|
||||
FormationMotionModerator motionModerator){
|
||||
if(anchor == null) throw new IllegalArgumentException("The anchor point cannot be null");
|
||||
this.anchor = anchor;
|
||||
this.pattern = pattern;
|
||||
this.slotAssignmentStrategy = slotAssignmentStrategy;
|
||||
this.motionModerator = motionModerator;
|
||||
|
||||
this.slotAssignments = new Array<>();
|
||||
this.driftOffset = new Vec3();
|
||||
this.positionOffset = new Vec2(anchor.x, anchor.y).cpy();
|
||||
}
|
||||
|
||||
/** Updates the assignment of members to slots */
|
||||
public void updateSlotAssignments(){
|
||||
// Apply the strategy to update slot assignments
|
||||
slotAssignmentStrategy.updateSlotAssignments(slotAssignments);
|
||||
|
||||
// Set the newly calculated number of slots
|
||||
pattern.slots = slotAssignmentStrategy.calculateNumberOfSlots(slotAssignments);
|
||||
|
||||
// Update the drift offset if a motion moderator is set
|
||||
if(motionModerator != null) motionModerator.calculateDriftOffset(driftOffset, slotAssignments, pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the pattern of this formation and updates slot assignments if the number of member is supported by the given
|
||||
* pattern.
|
||||
* @param pattern the pattern to set
|
||||
* @return {@code true} if the pattern has effectively changed; {@code false} otherwise.
|
||||
*/
|
||||
public boolean changePattern(FormationPattern pattern){
|
||||
// Find out how many slots we have occupied
|
||||
int occupiedSlots = slotAssignments.size;
|
||||
|
||||
// Check if the pattern supports one more slot
|
||||
if(pattern.supportsSlots(occupiedSlots)){
|
||||
this.pattern = pattern;
|
||||
|
||||
// Update the slot assignments and return success
|
||||
updateSlotAssignments();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new member to the first available slot and updates slot assignments if the number of member is supported by the
|
||||
* current pattern.
|
||||
* @param member the member to add
|
||||
* @return {@code false} if no more slots are available; {@code true} otherwise.
|
||||
*/
|
||||
public boolean addMember(FormationMember member){
|
||||
// Find out how many slots we have occupied
|
||||
int occupiedSlots = slotAssignments.size;
|
||||
|
||||
// Check if the pattern supports one more slot
|
||||
if(pattern.supportsSlots(occupiedSlots + 1)){
|
||||
// Add a new slot assignment
|
||||
slotAssignments.add(new SlotAssignment(member, occupiedSlots));
|
||||
|
||||
// Update the slot assignments and return success
|
||||
updateSlotAssignments();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member from its slot and updates slot assignments.
|
||||
* @param member the member to remove
|
||||
*/
|
||||
public void removeMember(FormationMember member){
|
||||
// Find the member's slot
|
||||
int slot = findMemberSlot(member);
|
||||
|
||||
// Make sure we've found a valid result
|
||||
if(slot >= 0){
|
||||
// Remove the slot
|
||||
// slotAssignments.removeIndex(slot);
|
||||
slotAssignmentStrategy.removeSlotAssignment(slotAssignments, slot);
|
||||
|
||||
// Update the assignments
|
||||
updateSlotAssignments();
|
||||
}
|
||||
}
|
||||
|
||||
private int findMemberSlot(FormationMember member){
|
||||
for(int i = 0; i < slotAssignments.size; i++){
|
||||
if(slotAssignments.get(i).member == member) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// debug
|
||||
public SlotAssignment getSlotAssignmentAt(int index){
|
||||
return slotAssignments.get(index);
|
||||
}
|
||||
|
||||
// debug
|
||||
public int getSlotAssignmentCount(){
|
||||
return slotAssignments.size;
|
||||
}
|
||||
|
||||
/** Writes new slot locations to each member */
|
||||
public void updateSlots(){
|
||||
positionOffset.set(anchor);
|
||||
float orientationOffset = anchor.z;
|
||||
if(motionModerator != null){
|
||||
positionOffset.sub(driftOffset);
|
||||
orientationOffset -= driftOffset.z;
|
||||
}
|
||||
|
||||
// Get the orientation of the anchor point as a matrix
|
||||
orientationMatrix.idt().rotate(anchor.z);
|
||||
|
||||
// Go through each member in turn
|
||||
for(int i = 0; i < slotAssignments.size; i++){
|
||||
SlotAssignment slotAssignment = slotAssignments.get(i);
|
||||
|
||||
// Retrieve the location reference of the formation member to calculate the new value
|
||||
Vec3 relativeLoc = slotAssignment.member.formationPos();
|
||||
float z = relativeLoc.z;
|
||||
|
||||
// Ask for the location of the slot relative to the anchor point
|
||||
pattern.calculateSlotLocation(relativeLoc, slotAssignment.slotNumber);
|
||||
|
||||
// Transform it by the anchor point's position and orientation
|
||||
relativeLoc.mul(orientationMatrix);
|
||||
|
||||
// Add the anchor and drift components
|
||||
relativeLoc.add(positionOffset.x, positionOffset.y, 0);
|
||||
relativeLoc.z = z + orientationOffset;
|
||||
}
|
||||
|
||||
// Possibly reset the anchor point if a moderator is set
|
||||
if(motionModerator != null){
|
||||
motionModerator.updateAnchorPoint(anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
core/src/mindustry/ai/formations/FormationMember.java
Normal file
14
core/src/mindustry/ai/formations/FormationMember.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* Game characters coordinated by a {@link Formation} must implement this interface. Any {@code FormationMember} has a target
|
||||
* location which is the place where it should be in order to stay in formation. This target location is calculated by the
|
||||
* formation itself.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface FormationMember{
|
||||
/** Returns the target location of this formation member. */
|
||||
Vec3 formationPos();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
|
||||
/**
|
||||
* A {@code FormationMotionModerator} moderates the movement of the formation based on the current positions of the members in its
|
||||
* slots: in effect to keep the anchor point on a leash. If the members in the slots are having trouble reaching their targets,
|
||||
* then the formation as a whole should be held back to give them a chance to catch up.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class FormationMotionModerator{
|
||||
private Vec3 tempLocation;
|
||||
|
||||
/**
|
||||
* Update the anchor point to moderate formation motion. This method is called at each frame.
|
||||
* @param anchor the anchor point
|
||||
*/
|
||||
public abstract void updateAnchorPoint(Vec3 anchor);
|
||||
|
||||
/**
|
||||
* Calculates the drift offset when members are in the given set of slots for the specified pattern.
|
||||
* @param centerOfMass the output location set to the calculated drift offset
|
||||
* @param slotAssignments the set of slots
|
||||
* @param pattern the pattern
|
||||
* @return the given location for chaining.
|
||||
*/
|
||||
public Vec3 calculateDriftOffset(Vec3 centerOfMass, Array<SlotAssignment> slotAssignments, FormationPattern pattern){
|
||||
// Clear the center of mass
|
||||
centerOfMass.x = centerOfMass.y = 0;
|
||||
float centerOfMassOrientation = 0;
|
||||
|
||||
// Make sure tempLocation is instantiated
|
||||
if(tempLocation == null) tempLocation = new Vec3();
|
||||
|
||||
// Go through each assignment and add its contribution to the center
|
||||
float numberOfAssignments = slotAssignments.size;
|
||||
for(int i = 0; i < numberOfAssignments; i++){
|
||||
pattern.calculateSlotLocation(tempLocation, slotAssignments.get(i).slotNumber);
|
||||
centerOfMass.add(tempLocation);
|
||||
centerOfMassOrientation += tempLocation.z;
|
||||
}
|
||||
|
||||
// Divide through to get the drift offset.
|
||||
centerOfMass.scl(1f / numberOfAssignments);
|
||||
centerOfMassOrientation /= numberOfAssignments;
|
||||
centerOfMass.z = centerOfMassOrientation;
|
||||
|
||||
return centerOfMass;
|
||||
}
|
||||
|
||||
}
|
||||
27
core/src/mindustry/ai/formations/FormationPattern.java
Normal file
27
core/src/mindustry/ai/formations/FormationPattern.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* The {@code FormationPattern} interface represents the shape of a formation and generates the slot offsets, relative to its
|
||||
* anchor point. Since formations can be scalable the pattern must be able to determine if a given number of slots is supported.
|
||||
* <p>
|
||||
* Each particular pattern (such as a V, wedge, circle) needs its own instance of a class that implements this
|
||||
* {@code FormationPattern} interface.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class FormationPattern{
|
||||
public int slots;
|
||||
|
||||
/** Returns the location of the given slot index. */
|
||||
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);
|
||||
|
||||
/**
|
||||
* Returns true if the pattern can support the given number of slots
|
||||
* @param slotCount the number of slots
|
||||
* @return {@code true} if this pattern can support the given number of slots; {@code false} othervwise.
|
||||
*/
|
||||
public boolean supportsSlots(int slotCount){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
/**
|
||||
* {@code FreeSlotAssignmentStrategy} is the simplest implementation of {@link SlotAssignmentStrategy}. It simply go through
|
||||
* each assignment in the list and assign sequential slot numbers. The number of slots is just the length of the list.
|
||||
* <p>
|
||||
* Because each member can occupy any slot this implementation does not support roles.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class FreeSlotAssignmentStrategy implements SlotAssignmentStrategy{
|
||||
|
||||
@Override
|
||||
public void updateSlotAssignments(Array<SlotAssignment> assignments){
|
||||
// A very simple assignment algorithm: we simply go through
|
||||
// each assignment in the list and assign sequential slot numbers
|
||||
for(int i = 0; i < assignments.size; i++)
|
||||
assignments.get(i).slotNumber = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateNumberOfSlots(Array<SlotAssignment> assignments){
|
||||
return assignments.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSlotAssignment(Array<SlotAssignment> assignments, int index){
|
||||
assignments.remove(index);
|
||||
}
|
||||
|
||||
}
|
||||
29
core/src/mindustry/ai/formations/SlotAssignment.java
Normal file
29
core/src/mindustry/ai/formations/SlotAssignment.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
/**
|
||||
* A {@code SlotAssignment} instance represents the assignment of a single {@link FormationMember} to its slot in the
|
||||
* {@link Formation}.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class SlotAssignment{
|
||||
public FormationMember member;
|
||||
public int slotNumber;
|
||||
|
||||
/**
|
||||
* Creates a {@code SlotAssignment} for the given {@code member}.
|
||||
* @param member the member of this slot assignment
|
||||
*/
|
||||
public SlotAssignment(FormationMember member){
|
||||
this(member, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SlotAssignment} for the given {@code member} and {@code slotNumber}.
|
||||
* @param member the member of this slot assignment
|
||||
*/
|
||||
public SlotAssignment(FormationMember member, int slotNumber){
|
||||
this.member = member;
|
||||
this.slotNumber = slotNumber;
|
||||
}
|
||||
}
|
||||
20
core/src/mindustry/ai/formations/SlotAssignmentStrategy.java
Normal file
20
core/src/mindustry/ai/formations/SlotAssignmentStrategy.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
/**
|
||||
* This interface defines how each {@link FormationMember} is assigned to a slot in the {@link Formation}.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface SlotAssignmentStrategy{
|
||||
|
||||
/** Updates the assignment of members to slots */
|
||||
void updateSlotAssignments(Array<SlotAssignment> assignments);
|
||||
|
||||
/** Calculates the number of slots from the assignment data. */
|
||||
int calculateNumberOfSlots(Array<SlotAssignment> assignments);
|
||||
|
||||
/** Removes the slot assignment at the specified index. */
|
||||
void removeSlotAssignment(Array<SlotAssignment> assignments, int index);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
|
||||
/**
|
||||
* {@code SoftRoleSlotAssignmentStrategy} is a concrete implementation of {@link BoundedSlotAssignmentStrategy} that supports soft
|
||||
* roles, i.e. roles that can be broken. Rather than a member having a list of roles it can fulfill, it has a set of values
|
||||
* representing how difficult it would find it to fulfill every role. The value is known as the slot cost. To make a slot
|
||||
* impossible for a member to fill, its slot cost should be infinite (you can even set a threshold to ignore all slots whose cost
|
||||
* is too high; this will reduce computation time when several costs are exceeding). To make a slot ideal for a member, its slot
|
||||
* cost should be zero. We can have different levels of unsuitable assignment for one member.
|
||||
* <p>
|
||||
* Slot costs do not necessarily have to depend only on the member and the slot roles. They can be generalized to include any
|
||||
* difficulty a member might have in taking up a slot. If a formation is spread out, for example, a member may choose a slot that
|
||||
* is close by over a more distant slot. Distance can be directly used as a slot cost.
|
||||
* <p>
|
||||
* <b>IMPORTANVec2 NOTES:</b>
|
||||
* <ul>
|
||||
* <li>In order for the algorithm to work properly the slot costs can not be negative.</li>
|
||||
* <li>This algorithm is often not fast enough to be used regularly. However, slot assignment happens relatively seldom (when the
|
||||
* player selects a new pattern, for example, or adds a member to the formation, or a member is removed from the formation).</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public class SoftRoleSlotAssignmentStrategy extends BoundedSlotAssignmentStrategy{
|
||||
protected SlotCostProvider slotCostProvider;
|
||||
protected float costThreshold;
|
||||
private BooleanArray filledSlots;
|
||||
|
||||
/**
|
||||
* Creates a {@code SoftRoleSlotAssignmentStrategy} with the given slot cost provider and no cost threshold.
|
||||
* @param slotCostProvider the slot cost provider
|
||||
*/
|
||||
public SoftRoleSlotAssignmentStrategy(SlotCostProvider slotCostProvider){
|
||||
this(slotCostProvider, Float.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SoftRoleSlotAssignmentStrategy} with the given slot cost provider and cost threshold.
|
||||
* @param slotCostProvider the slot cost provider
|
||||
* @param costThreshold is a slot-cost limit, beyond which a slot is considered to be too expensive to consider occupying.
|
||||
*/
|
||||
public SoftRoleSlotAssignmentStrategy(SlotCostProvider slotCostProvider, float costThreshold){
|
||||
this.slotCostProvider = slotCostProvider;
|
||||
this.costThreshold = costThreshold;
|
||||
|
||||
this.filledSlots = new BooleanArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSlotAssignments(Array<SlotAssignment> assignments){
|
||||
|
||||
// Holds a list of member and slot data for each member.
|
||||
Array<MemberAndSlots> memberData = new Array<>();
|
||||
|
||||
// Compile the member data
|
||||
int numberOfAssignments = assignments.size;
|
||||
for(int i = 0; i < numberOfAssignments; i++){
|
||||
SlotAssignment assignment = assignments.get(i);
|
||||
|
||||
// Create a new member datum, and fill it
|
||||
MemberAndSlots datum = new MemberAndSlots(assignment.member);
|
||||
|
||||
// Add each valid slot to it
|
||||
for(int j = 0; j < numberOfAssignments; j++){
|
||||
|
||||
// Get the cost of the slot
|
||||
float cost = slotCostProvider.getCost(assignment.member, j);
|
||||
|
||||
// Make sure the slot is valid
|
||||
if(cost >= costThreshold) continue;
|
||||
|
||||
SlotAssignment slot = assignments.get(j);
|
||||
|
||||
// Store the slot information
|
||||
CostAndSlot slotDatum = new CostAndSlot(cost, slot.slotNumber);
|
||||
datum.costAndSlots.add(slotDatum);
|
||||
|
||||
// Add it to the member's ease of assignment
|
||||
datum.assignmentEase += 1f / (1f + cost);
|
||||
}
|
||||
|
||||
// Add member datum
|
||||
memberData.add(datum);
|
||||
}
|
||||
|
||||
// Reset the array to keep track of which slots we have already filled.
|
||||
if(numberOfAssignments > filledSlots.size) filledSlots.ensureCapacity(numberOfAssignments - filledSlots.size);
|
||||
filledSlots.size = numberOfAssignments;
|
||||
for(int i = 0; i < numberOfAssignments; i++)
|
||||
filledSlots.set(i, false);
|
||||
|
||||
// Arrange members in order of ease of assignment, with the least easy first.
|
||||
memberData.sort();
|
||||
MEMBER_LOOP:
|
||||
for(int i = 0; i < memberData.size; i++){
|
||||
MemberAndSlots memberDatum = memberData.get(i);
|
||||
|
||||
// Choose the first slot in the list that is still empty (non-filled)
|
||||
memberDatum.costAndSlots.sort();
|
||||
int m = memberDatum.costAndSlots.size;
|
||||
for(int j = 0; j < m; j++){
|
||||
int slotNumber = memberDatum.costAndSlots.get(j).slotNumber;
|
||||
|
||||
// Check if this slot is valid
|
||||
if(!filledSlots.get(slotNumber)){
|
||||
// Fill this slot
|
||||
SlotAssignment slot = assignments.get(slotNumber);
|
||||
slot.member = memberDatum.member;
|
||||
slot.slotNumber = slotNumber;
|
||||
|
||||
// Reserve the slot
|
||||
filledSlots.set(slotNumber, true);
|
||||
|
||||
// Go to the next member
|
||||
continue MEMBER_LOOP;
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, it's because a member has no valid assignment.
|
||||
//
|
||||
// TODO
|
||||
// Some sensible action should be taken, such as reporting to the player.
|
||||
throw new ArcRuntimeException("SoftRoleSlotAssignmentStrategy cannot find valid slot assignment for member " + memberDatum.member);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CostAndSlot implements Comparable<CostAndSlot>{
|
||||
float cost;
|
||||
int slotNumber;
|
||||
|
||||
public CostAndSlot(float cost, int slotNumber){
|
||||
this.cost = cost;
|
||||
this.slotNumber = slotNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CostAndSlot other){
|
||||
return Float.compare(cost, other.cost);
|
||||
}
|
||||
}
|
||||
|
||||
static class MemberAndSlots implements Comparable<MemberAndSlots>{
|
||||
FormationMember member;
|
||||
float assignmentEase;
|
||||
Array<CostAndSlot> costAndSlots;
|
||||
|
||||
public MemberAndSlots(FormationMember member){
|
||||
this.member = member;
|
||||
this.assignmentEase = 0f;
|
||||
this.costAndSlots = new Array<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MemberAndSlots other){
|
||||
return Float.compare(assignmentEase, other.assignmentEase);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SlotCostProvider{
|
||||
float getCost(FormationMember member, int slotNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class CircleFormation extends FormationPattern{
|
||||
/** The radius of one member. This is needed to determine how close we can pack a given number of members around circle. */
|
||||
public float memberRadius;
|
||||
/** Angle offset. */
|
||||
public float angleOffset = 0;
|
||||
|
||||
public CircleFormation(float memberRadius){
|
||||
this.memberRadius = memberRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
|
||||
if(slots > 1){
|
||||
float angle = (360f * slotNumber) / slots;
|
||||
float radius = memberRadius / (float)Math.sin(180f / slots * Mathf.degRad);
|
||||
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
|
||||
}else{
|
||||
outLocation.set(0, 0, 360f * slotNumber);
|
||||
}
|
||||
|
||||
outLocation.z += angleOffset;
|
||||
|
||||
return outLocation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class SquareFormation extends FormationPattern{
|
||||
public float spacing = 20;
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
//side of each square of formation
|
||||
int side = Mathf.ceil(Mathf.sqrt(slots + 1));
|
||||
int cx = slot % side, cy = slot / side;
|
||||
|
||||
//don't hog the middle spot
|
||||
if(cx == side /2 && cy == side/2 && (side%2)==1){
|
||||
slot = slots;
|
||||
|
||||
cx = slot % side;
|
||||
cy = slot / side;
|
||||
}
|
||||
|
||||
return out.set(cx - (side/2f - 0.5f), cy - (side/2f - 0.5f), 0).scl(spacing);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user