DEATH AND DESTRUCTION
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
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(Seq) 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(Seq<SlotAssignment> assignments);
|
||||
|
||||
@Override
|
||||
public int calculateNumberOfSlots(Seq<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(Seq<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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
|
||||
public class DistanceAssignmentStrategy implements SlotAssignmentStrategy{
|
||||
private final Vec3 vec = new Vec3();
|
||||
private final FormationPattern form;
|
||||
|
||||
public DistanceAssignmentStrategy(FormationPattern form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
|
||||
IntSeq slots = IntSeq.range(0, assignments.size);
|
||||
|
||||
for(SlotAssignment slot : assignments){
|
||||
int mindex = 0;
|
||||
float mcost = Float.MAX_VALUE;
|
||||
|
||||
for(int i = 0; i < slots.size; i++){
|
||||
float cost = cost(slot.member, slots.get(i));
|
||||
if(cost < mcost){
|
||||
mcost = cost;
|
||||
mindex = i;
|
||||
}
|
||||
}
|
||||
|
||||
slot.slotNumber = slots.get(mindex);
|
||||
slots.removeIndex(mindex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateNumberOfSlots(Seq<SlotAssignment> assignments){
|
||||
return assignments.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSlotAssignment(Seq<SlotAssignment> assignments, int index){
|
||||
assignments.remove(index);
|
||||
}
|
||||
|
||||
float cost(FormationMember member, int slot){
|
||||
form.calculateSlotLocation(vec, slot);
|
||||
return Mathf.dst2(member.formationPos().x, member.formationPos().y, vec.x, vec.y);
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
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. */
|
||||
public Seq<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 Seq<>();
|
||||
this.driftOffset = new Vec3();
|
||||
this.positionOffset = new Vec2(anchor.x, anchor.y).cpy();
|
||||
}
|
||||
|
||||
/** Updates the assignment of members to slots */
|
||||
public void updateSlotAssignments(){
|
||||
pattern.slots = slotAssignments.size;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/** Much more efficient than adding a single member.
|
||||
* @return number of members added. */
|
||||
public int addMembers(Iterable<? extends FormationMember> members){
|
||||
int added = 0;
|
||||
for(FormationMember member : members){
|
||||
if(pattern.supportsSlots(slotAssignments.size + 1)){
|
||||
slotAssignments.add(new SlotAssignment(member, slotAssignments.size));
|
||||
added ++;
|
||||
}
|
||||
}
|
||||
|
||||
updateSlotAssignments();
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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){
|
||||
// Check if the pattern supports one more slot
|
||||
if(pattern.supportsSlots(slotAssignments.size + 1)){
|
||||
// Add a new slot assignment
|
||||
slotAssignments.add(new SlotAssignment(member, slotAssignments.size));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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();
|
||||
|
||||
float formationSize();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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, Seq<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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
/** Spacing between members. */
|
||||
public float spacing = 20f;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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(Seq<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(Seq<SlotAssignment> assignments){
|
||||
return assignments.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSlotAssignment(Seq<SlotAssignment> assignments, int index){
|
||||
assignments.remove(index);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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(Seq<SlotAssignment> assignments);
|
||||
|
||||
/** Calculates the number of slots from the assignment data. */
|
||||
int calculateNumberOfSlots(Seq<SlotAssignment> assignments);
|
||||
|
||||
/** Removes the slot assignment at the specified index. */
|
||||
void removeSlotAssignment(Seq<SlotAssignment> assignments, int index);
|
||||
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
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 BoolSeq 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 BoolSeq();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
|
||||
// Holds a list of member and slot data for each member.
|
||||
Seq<MemberAndSlots> memberData = new Seq<>();
|
||||
|
||||
// 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;
|
||||
Seq<CostAndSlot> costAndSlots;
|
||||
|
||||
public MemberAndSlots(FormationMember member){
|
||||
this.member = member;
|
||||
this.assignmentEase = 0f;
|
||||
this.costAndSlots = new Seq<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MemberAndSlots other){
|
||||
return Float.compare(assignmentEase, other.assignmentEase);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SlotCostProvider{
|
||||
float getCost(FormationMember member, int slotNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class CircleFormation extends FormationPattern{
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
|
||||
if(slots > 1){
|
||||
float angle = (360f * slotNumber) / slots + (slots == 8 ? 22.5f : 0);
|
||||
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
|
||||
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
|
||||
}else{
|
||||
outLocation.set(0, spacing * 1.1f, 360f * slotNumber);
|
||||
}
|
||||
|
||||
return outLocation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class SquareFormation extends FormationPattern{
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,9 @@ public class DefenderAI extends AIController{
|
||||
@Override
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
|
||||
//find unit to follow if not in rally mode
|
||||
if(command() != UnitCommand.rally){
|
||||
//Sort by max health and closer target.
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type, (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
|
||||
if(result != null) return result;
|
||||
}
|
||||
//Sort by max health and closer target.
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type, (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
|
||||
if(result != null) return result;
|
||||
|
||||
//find rally point
|
||||
var block = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
@@ -14,7 +14,7 @@ public class FlyingAI extends AIController{
|
||||
public void updateMovement(){
|
||||
unloadPayloads();
|
||||
|
||||
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
|
||||
if(target != null && unit.hasWeapons()){
|
||||
if(!unit.type.circleTarget){
|
||||
moveTo(target, unit.type.range * 0.8f);
|
||||
unit.lookAt(target);
|
||||
@@ -23,13 +23,9 @@ public class FlyingAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
if(target == null && command() == UnitCommand.attack && state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
if(target == null && state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
moveTo(getClosestSpawner(), state.rules.dropZoneRadius + 130f);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
moveTo(targetFlag(unit.x, unit.y, BlockFlag.rally, false), 60f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
public class FormationAI extends AIController implements FormationMember{
|
||||
public Unit leader;
|
||||
|
||||
private Vec3 target = new Vec3();
|
||||
private @Nullable Formation formation;
|
||||
|
||||
public FormationAI(Unit leader, Formation formation){
|
||||
this.leader = leader;
|
||||
this.formation = formation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
target.set(unit.x, unit.y, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
|
||||
if(leader == null || leader.dead){
|
||||
unit.resetController();
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.type.canBoost){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation,
|
||||
unit.onSolid() ? 1f : //definitely cannot land
|
||||
unit.isFlying() && !unit.canLand() ? unit.elevation : //try to maintain altitude
|
||||
leader.type.canBoost ? leader.elevation : //follow leader
|
||||
0f,
|
||||
unit.type.riseSpeed);
|
||||
}
|
||||
|
||||
unit.controlWeapons(true, leader.isShooting);
|
||||
|
||||
unit.aim(leader.aimX(), leader.aimY());
|
||||
|
||||
if(unit.type.rotateShooting){
|
||||
unit.lookAt(leader.aimX(), leader.aimY());
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
|
||||
Vec2 realtarget = vec.set(target).add(leader.vel);
|
||||
|
||||
float speed = unit.speed() * Time.delta;
|
||||
unit.approach(Mathf.arrive(unit.x, unit.y, realtarget.x, realtarget.y, unit.vel, speed, 0f, speed, 1f).scl(1f / Time.delta));
|
||||
|
||||
if(unit.canMine() && leader.canMine()){
|
||||
if(leader.mineTile != null && unit.validMine(leader.mineTile)){
|
||||
unit.mineTile(leader.mineTile);
|
||||
|
||||
CoreBuild core = unit.team.core();
|
||||
|
||||
if(core != null && leader.mineTile.drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(leader.mineTile.drop())){
|
||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||
Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
|
||||
unit.clearItem();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
unit.mineTile(null);
|
||||
}
|
||||
}
|
||||
|
||||
if(unit.canBuild() && leader.canBuild() && leader.activelyBuilding()){
|
||||
unit.clearBuilding();
|
||||
unit.addBuild(leader.buildPlan());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Unit unit){
|
||||
if(formation != null){
|
||||
formation.removeMember(this);
|
||||
unit.resetController();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float formationSize(){
|
||||
return unit.hitSize * 1.3f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeingControlled(Unit player){
|
||||
return leader == player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 formationPos(){
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import mindustry.ai.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -25,7 +24,7 @@ public class GroundAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
if((core == null || !unit.within(core, unit.type.range * 0.5f)) && command() == UnitCommand.attack){
|
||||
if((core == null || !unit.within(core, unit.type.range * 0.5f))){
|
||||
boolean move = true;
|
||||
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
@@ -42,14 +41,6 @@ public class GroundAI extends AIController{
|
||||
if(move) pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
}
|
||||
}
|
||||
|
||||
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -28,41 +27,31 @@ public class HugAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.attack){
|
||||
boolean move = true;
|
||||
boolean move = true;
|
||||
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
|
||||
}
|
||||
|
||||
//raycast for target
|
||||
if(target != null && unit.within(target, unit.type.range) && !Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
|
||||
for(Point2 p : Geometry.d4c){
|
||||
if(!unit.canPass(x + p.x, y + p.y)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})){
|
||||
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
|
||||
//circle target
|
||||
unit.movePref(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
|
||||
}else{
|
||||
//move toward target in a straight line
|
||||
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
|
||||
}
|
||||
}else if(move){
|
||||
pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
//raycast for target
|
||||
if(target != null && unit.within(target, unit.type.range) && !Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
|
||||
for(Point2 p : Geometry.d4c){
|
||||
if(!unit.canPass(x + p.x, y + p.y)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})){
|
||||
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
|
||||
//circle target
|
||||
unit.movePref(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
|
||||
}else{
|
||||
//move toward target in a straight line
|
||||
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
|
||||
}
|
||||
}else if(move){
|
||||
pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
|
||||
|
||||
@@ -8,7 +8,6 @@ import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class LogicAI extends AIController{
|
||||
case pathfind -> {
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
|
||||
if((core == null || !unit.within(core, unit.range() * 0.5f))){
|
||||
boolean move = true;
|
||||
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
@@ -82,14 +81,6 @@ public class LogicAI extends AIController{
|
||||
|
||||
if(move) pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
}
|
||||
}
|
||||
}
|
||||
case stop -> {
|
||||
unit.clearBuilding();
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.math.geom.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.distribution.*;
|
||||
@@ -77,26 +76,18 @@ public class SuicideAI extends GroundAI{
|
||||
}
|
||||
|
||||
if(!moveToTarget){
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
boolean move = true;
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
//stop moving toward the drop zone if applicable
|
||||
if(core == null && state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)){
|
||||
move = false;
|
||||
}
|
||||
}else if(command() == UnitCommand.attack){
|
||||
boolean move = true;
|
||||
}
|
||||
|
||||
//stop moving toward the drop zone if applicable
|
||||
if(core == null && state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)){
|
||||
move = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(move){
|
||||
pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
if(move){
|
||||
pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user