DEATH AND DESTRUCTION

This commit is contained in:
Anuken
2022-02-15 16:46:32 -05:00
parent 3babe7686b
commit c324f2124b
135 changed files with 704 additions and 2080 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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()){

View File

@@ -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();

View File

@@ -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);
}
}