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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -186,7 +186,7 @@ public class Bullets{
|
||||
despawnEffect = Fx.none;
|
||||
}};
|
||||
|
||||
fragPlasticFrag = new BasicBulletType(2.5f, 10, "bullet"){{
|
||||
fragPlasticFrag = new BasicBulletType(2.5f, 12, "bullet"){{
|
||||
width = 10f;
|
||||
height = 12f;
|
||||
shrinkY = 1f;
|
||||
@@ -196,15 +196,15 @@ public class Bullets{
|
||||
despawnEffect = Fx.none;
|
||||
}};
|
||||
|
||||
fragGlass = new FlakBulletType(4f, 3){{
|
||||
fragGlass = new FlakBulletType(4f, 9){{
|
||||
ammoMultiplier = 3f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
reloadMultiplier = 0.8f;
|
||||
width = 6f;
|
||||
height = 8f;
|
||||
hitEffect = Fx.flakExplosion;
|
||||
splashDamage = 18f * 1.5f;
|
||||
splashDamageRadius = 16f;
|
||||
splashDamage = 28f * 1.5f;
|
||||
splashDamageRadius = 22f;
|
||||
fragBullet = fragGlassFrag;
|
||||
fragBullets = 4;
|
||||
explodeRange = 20f;
|
||||
|
||||
@@ -63,7 +63,6 @@ public class Planets{
|
||||
clearSectorOnLose = true;
|
||||
hiddenItems.addAll(Items.serpuloItems).removeAll(Items.erekirItems);
|
||||
ruleSetter = r -> {
|
||||
r.unitCommand = true;
|
||||
r.placeRangeCheck = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -333,9 +333,6 @@ public class SerpuloTechTree{
|
||||
});
|
||||
|
||||
node(groundFactory, () -> {
|
||||
node(commandCenter, () -> {
|
||||
|
||||
});
|
||||
|
||||
node(dagger, () -> {
|
||||
node(mace, () -> {
|
||||
@@ -514,7 +511,6 @@ public class SerpuloTechTree{
|
||||
|
||||
node(saltFlats, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(commandCenter),
|
||||
new Research(groundFactory),
|
||||
new Research(additiveReconstructor),
|
||||
new Research(airFactory),
|
||||
|
||||
@@ -302,7 +302,6 @@ public class UnitTypes{
|
||||
health = 120f;
|
||||
buildSpeed = 0.8f;
|
||||
armor = 1f;
|
||||
commandLimit = 8;
|
||||
|
||||
abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f));
|
||||
ammoType = new PowerAmmoType(1000);
|
||||
@@ -339,7 +338,6 @@ public class UnitTypes{
|
||||
|
||||
mineTier = 2;
|
||||
mineSpeed = 5f;
|
||||
commandLimit = 9;
|
||||
|
||||
abilities.add(new ShieldRegenFieldAbility(20f, 40f, 60f * 5, 60f));
|
||||
ammoType = new PowerAmmoType(1300);
|
||||
@@ -393,7 +391,6 @@ public class UnitTypes{
|
||||
landShake = 2f;
|
||||
riseSpeed = 0.05f;
|
||||
|
||||
commandLimit = 10;
|
||||
mechFrontSway = 0.55f;
|
||||
ammoType = new PowerAmmoType(1500);
|
||||
|
||||
@@ -453,7 +450,6 @@ public class UnitTypes{
|
||||
landShake = 4f;
|
||||
immunities = ObjectSet.with(StatusEffects.burning);
|
||||
|
||||
commandLimit = 8;
|
||||
singleTarget = true;
|
||||
|
||||
weapons.add(new Weapon("vela-weapon"){{
|
||||
@@ -521,8 +517,6 @@ public class UnitTypes{
|
||||
rotateSpeed = 1.5f;
|
||||
drownTimeMultiplier = 6f;
|
||||
|
||||
commandLimit = 8;
|
||||
|
||||
legCount = 4;
|
||||
legLength = 14f;
|
||||
legBaseOffset = 11f;
|
||||
@@ -975,7 +969,6 @@ public class UnitTypes{
|
||||
//as default AI, flares are not very useful in core rushes, they attack nothing in the way
|
||||
playerTargetFlags = new BlockFlag[]{null};
|
||||
targetFlags = new BlockFlag[]{BlockFlag.generator, null};
|
||||
commandLimit = 4;
|
||||
circleTarget = true;
|
||||
hitSize = 7;
|
||||
itemCapacity = 15;
|
||||
@@ -1012,7 +1005,6 @@ public class UnitTypes{
|
||||
//do not rush core, attack closest
|
||||
playerTargetFlags = new BlockFlag[]{null};
|
||||
targetFlags = new BlockFlag[]{BlockFlag.factory, null};
|
||||
commandLimit = 5;
|
||||
circleTarget = true;
|
||||
ammoType = new ItemAmmoType(Items.graphite);
|
||||
|
||||
@@ -1458,7 +1450,6 @@ public class UnitTypes{
|
||||
payloadCapacity = (5.5f * 5.5f) * tilePayload;
|
||||
buildSpeed = 4f;
|
||||
drawShields = false;
|
||||
commandLimit = 6;
|
||||
lowAltitude = true;
|
||||
buildBeamOffset = 43;
|
||||
ammoCapacity = 1;
|
||||
@@ -2325,7 +2316,6 @@ public class UnitTypes{
|
||||
health = 150f;
|
||||
engineOffset = 6f;
|
||||
hitSize = 8f;
|
||||
commandLimit = 3;
|
||||
alwaysUnlocked = true;
|
||||
|
||||
weapons.add(new Weapon("small-basic-weapon"){{
|
||||
@@ -2364,7 +2354,6 @@ public class UnitTypes{
|
||||
hitSize = 9f;
|
||||
rotateShooting = false;
|
||||
lowAltitude = true;
|
||||
commandLimit = 4;
|
||||
|
||||
weapons.add(new Weapon("small-mount-weapon"){{
|
||||
top = false;
|
||||
@@ -2405,7 +2394,6 @@ public class UnitTypes{
|
||||
health = 220f;
|
||||
engineOffset = 6f;
|
||||
hitSize = 11f;
|
||||
commandLimit = 5;
|
||||
|
||||
weapons.add(new Weapon("small-mount-weapon"){{
|
||||
top = false;
|
||||
@@ -3321,7 +3309,6 @@ public class UnitTypes{
|
||||
health = 1;
|
||||
rotateSpeed = 360f;
|
||||
itemCapacity = 0;
|
||||
commandLimit = 0;
|
||||
hidden = true;
|
||||
internal = true;
|
||||
}};
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
@@ -124,11 +123,6 @@ public class Logic implements ApplicationListener{
|
||||
state.rules.waveTeam.rules().ai = true;
|
||||
}
|
||||
|
||||
//TODO unit commanding is not allowed on serpulo until I test it properly
|
||||
if(state.getSector().planet != Planets.serpulo){
|
||||
state.rules.unitCommand = true;
|
||||
}
|
||||
|
||||
state.rules.coreIncinerates = true;
|
||||
state.rules.waveTeam.rules().aiTier = state.getSector().threat * 0.8f;
|
||||
state.rules.waveTeam.rules().infiniteResources = true;
|
||||
|
||||
@@ -504,10 +504,6 @@ public class NetServer implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!player.dead() && player.unit().isCommanding()){
|
||||
player.unit().clearCommand();
|
||||
}
|
||||
|
||||
player.getInfo().lastSyncTime = Time.millis();
|
||||
Call.worldDataBegin(player.con);
|
||||
netServer.sendWorldData(player);
|
||||
|
||||
@@ -442,7 +442,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void consume(){
|
||||
for(Consume cons : block.consumes.all){
|
||||
for(Consume cons : block.consumers){
|
||||
cons.trigger(self());
|
||||
}
|
||||
}
|
||||
@@ -465,7 +465,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public float efficiency(){
|
||||
//disabled -> 0 efficiency
|
||||
if(!enabled) return 0;
|
||||
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
|
||||
return power != null && (block.consPower != null && !block.consPower.buffered) ? power.status : 1f;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,11 +702,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public boolean acceptItem(Building source, Item item){
|
||||
return block.consumes.consumesItem(item) && items.get(item) < getMaximumAccepted(item);
|
||||
return block.consumesItem(item) && items.get(item) < getMaximumAccepted(item);
|
||||
}
|
||||
|
||||
public boolean acceptLiquid(Building source, Liquid liquid){
|
||||
return block.hasLiquids && block.consumes.liquidfilters.get(liquid.id);
|
||||
return block.hasLiquids && block.consumesLiquid(liquid);
|
||||
}
|
||||
|
||||
public void handleLiquid(Building source, Liquid liquid, float amount){
|
||||
@@ -791,7 +791,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
liquids.remove(liquid, flow);
|
||||
return flow;
|
||||
//handle reactions between different liquid types ▼
|
||||
}else if(!next.block.consumes.consumesLiquid(liquid) && next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
|
||||
}else if(!next.block.consumesLiquid(liquid) && next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
|
||||
//TODO !IMPORTANT! uses current(), which is 1) wrong for multi-liquid blocks and 2) causes unwanted reactions, e.g. hydrogen + slag in pump
|
||||
//TODO these are incorrect effect positions
|
||||
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
|
||||
@@ -1043,7 +1043,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void drawStatus(){
|
||||
if(block.enableDrawStatus && block.consumes.any()){
|
||||
if(block.enableDrawStatus && block.consumers.length > 0){
|
||||
float multiplier = block.size > 1 ? 1 : 0.64f;
|
||||
float brcx = x + (block.size * tilesize / 2f) - (tilesize * multiplier / 2f);
|
||||
float brcy = y - (block.size * tilesize / 2f) + (tilesize * multiplier / 2f);
|
||||
@@ -1249,8 +1249,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
explosiveness += liquids.sum((liquid, amount) -> liquid.explosiveness * amount / 2f);
|
||||
}
|
||||
|
||||
if(block.consumes.hasPower() && block.consumes.getPower().buffered){
|
||||
power += this.power.status * block.consumes.getPower().capacity;
|
||||
if(block.consPower != null && block.consPower.buffered){
|
||||
power += this.power.status * block.consPower.capacity;
|
||||
}
|
||||
|
||||
if(block.hasLiquids && state.rules.damageExplosions){
|
||||
@@ -1397,14 +1397,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public void displayConsumption(Table table){
|
||||
table.left();
|
||||
for(Consume cons : block.consumes.all){
|
||||
if(cons.isOptional() && cons.isBoost()) continue;
|
||||
for(Consume cons : block.consumers){
|
||||
if(cons.optional && cons.booster) continue;
|
||||
cons.build(self(), table);
|
||||
}
|
||||
}
|
||||
|
||||
public void displayBars(Table table){
|
||||
for(Func<Building, Bar> bar : block.bars.list()){
|
||||
for(Func<Building, Bar> bar : block.listBars()){
|
||||
table.add(bar.get(self())).growX();
|
||||
table.row();
|
||||
}
|
||||
@@ -1521,7 +1521,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//TODO can lead to ghost graphs?
|
||||
power.graph = new PowerGraph();
|
||||
power.links.clear();
|
||||
if(block.consumes.hasPower() && !block.consumes.getPower().buffered){
|
||||
if(block.consPower != null && !block.consPower.buffered){
|
||||
power.status = 0f;
|
||||
}
|
||||
}
|
||||
@@ -1590,18 +1590,18 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
consOptionalValid = true;
|
||||
boolean docons = shouldConsume() && productionValid();
|
||||
|
||||
for(Consume cons : block.consumes.all){
|
||||
if(cons.isOptional()) continue;
|
||||
for(Consume cons : block.consumers){
|
||||
if(cons.optional) continue;
|
||||
|
||||
if(docons && cons.isUpdate() && prevValid && cons.valid(self())){
|
||||
if(docons && cons.update && prevValid && cons.valid(self())){
|
||||
cons.update(self());
|
||||
}
|
||||
|
||||
consValid &= cons.valid(self());
|
||||
}
|
||||
|
||||
for(Consume cons : block.consumes.optionals){
|
||||
if(docons && cons.isUpdate() && prevValid && cons.valid(self())){
|
||||
for(Consume cons : block.optionalConsumers){
|
||||
if(docons && cons.update && prevValid && cons.valid(self())){
|
||||
cons.update(self());
|
||||
}
|
||||
|
||||
@@ -1693,10 +1693,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
case totalItems -> items == null ? 0 : items.total();
|
||||
//TODO will give wildly fluctuating amounts due to switching of current() for multi-liquid blocks. totalLiquids is inherently bad design, but unfortunately it is useful for conduits/tanks
|
||||
case totalLiquids -> liquids == null ? 0 : liquids.currentAmount();
|
||||
case totalPower -> power == null || !block.consumes.hasPower() ? 0 : power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
|
||||
case totalPower -> power == null || block.consPower == null ? 0 : power.status * (block.consPower.buffered ? block.consPower.capacity : 1f);
|
||||
case itemCapacity -> block.hasItems ? block.itemCapacity : 0;
|
||||
case liquidCapacity -> block.hasLiquids ? block.liquidCapacity : 0;
|
||||
case powerCapacity -> block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
|
||||
case powerCapacity -> block.consPower != null ? block.consPower.capacity : 0f;
|
||||
case powerNetIn -> power == null ? 0 : power.graph.getLastScaledPowerIn() * 60;
|
||||
case powerNetOut -> power == null ? 0 : power.graph.getLastScaledPowerOut() * 60;
|
||||
case powerNetStored -> power == null ? 0 : power.graph.getLastPowerStored();
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
/**
|
||||
* A unit that can command other units.
|
||||
* @deprecated This mechanic is likely to be removed or completely reworked in the future.
|
||||
* */
|
||||
@Deprecated
|
||||
@Component
|
||||
abstract class CommanderComp implements Entityc, Posc{
|
||||
private static final Seq<FormationMember> members = new Seq<>();
|
||||
private static final Seq<Unit> units = new Seq<>();
|
||||
|
||||
@Import float x, y, rotation, hitSize;
|
||||
@Import Team team;
|
||||
@Import UnitType type;
|
||||
|
||||
transient @Nullable Formation formation;
|
||||
transient Seq<Unit> controlling = new Seq<>(10);
|
||||
/** minimum speed of any unit in the formation. */
|
||||
transient float minFormationSpeed;
|
||||
|
||||
public void update(){
|
||||
if(controlling.isEmpty() && !Vars.net.client()){
|
||||
formation = null;
|
||||
}
|
||||
|
||||
if(formation != null){
|
||||
formation.anchor.set(x, y, 0);
|
||||
formation.updateSlots();
|
||||
controlling.removeAll(u -> u.dead || !(u.controller() instanceof FormationAI ai && ai.leader == self()));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(){
|
||||
clearCommand();
|
||||
}
|
||||
|
||||
public void killed(){
|
||||
clearCommand();
|
||||
}
|
||||
|
||||
//make sure to reset command state when the controller is switched
|
||||
public void controller(UnitController next){
|
||||
clearCommand();
|
||||
}
|
||||
|
||||
void commandNearby(FormationPattern pattern){
|
||||
commandNearby(pattern, u -> true);
|
||||
}
|
||||
|
||||
void commandNearby(FormationPattern pattern, Boolf<Unit> include){
|
||||
Formation formation = new Formation(new Vec3(x, y, rotation), pattern);
|
||||
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
|
||||
|
||||
units.clear();
|
||||
|
||||
Units.nearby(team, x, y, type.commandRadius, u -> {
|
||||
if(u.isAI() && include.get(u) && u != self() && u.type.flying == type.flying && u.hitSize <= hitSize * 1.1f && u.type.playerControllable){
|
||||
units.add(u);
|
||||
}
|
||||
});
|
||||
|
||||
if(units.isEmpty()) return;
|
||||
|
||||
//sort by hitbox size, then by distance
|
||||
units.sort(Structs.comps(Structs.comparingFloat(u -> -u.hitSize), Structs.comparingFloat(u -> u.dst2(this))));
|
||||
units.truncate(type.commandLimit);
|
||||
|
||||
command(formation, units);
|
||||
}
|
||||
|
||||
void command(Formation formation, Seq<Unit> units){
|
||||
clearCommand();
|
||||
units.shuffle();
|
||||
|
||||
float spacing = hitSize * 0.9f;
|
||||
minFormationSpeed = type.speed;
|
||||
|
||||
controlling.addAll(units);
|
||||
for(Unit unit : units){
|
||||
FormationAI ai;
|
||||
unit.controller(ai = new FormationAI(self(), formation));
|
||||
spacing = Math.max(spacing, ai.formationSize());
|
||||
minFormationSpeed = Math.min(minFormationSpeed, unit.type.speed);
|
||||
}
|
||||
this.formation = formation;
|
||||
|
||||
//update formation spacing based on max size
|
||||
formation.pattern.spacing = spacing;
|
||||
|
||||
members.clear();
|
||||
for(Unitc u : units){
|
||||
members.add((FormationAI)u.controller());
|
||||
}
|
||||
|
||||
//TODO doesn't handle units that don't fit a formation
|
||||
formation.addMembers(members);
|
||||
}
|
||||
|
||||
boolean isCommanding(){
|
||||
return formation != null;
|
||||
}
|
||||
|
||||
void clearCommand(){
|
||||
//reset controlled units
|
||||
for(Unit unit : controlling){
|
||||
if(unit.controller().isBeingControlled(self())){
|
||||
unit.controller(unit.type.createController(unit));
|
||||
}
|
||||
}
|
||||
|
||||
controlling.clear();
|
||||
formation = null;
|
||||
}
|
||||
}
|
||||
@@ -124,12 +124,8 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
||||
unit.aim(mouseX, mouseY);
|
||||
//this is only necessary when the thing being controlled isn't synced
|
||||
unit.controlWeapons(shooting, shooting);
|
||||
//save previous formation to prevent reset
|
||||
var formation = unit.formation;
|
||||
//extra precaution, necessary for non-synced things
|
||||
unit.controller(this);
|
||||
//keep previous formation
|
||||
unit.formation = formation;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
@@ -32,10 +31,10 @@ import static mindustry.Vars.*;
|
||||
import static mindustry.logic.GlobalConstants.*;
|
||||
|
||||
@Component(base = true)
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc, Builderc{
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable, Ranged, Minerc, Builderc{
|
||||
|
||||
@Import boolean hovering, dead, disarmed;
|
||||
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo, minFormationSpeed, dragMultiplier;
|
||||
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo, dragMultiplier;
|
||||
@Import Team team;
|
||||
@Import int id;
|
||||
@Import @Nullable Tile mineTile;
|
||||
@@ -120,14 +119,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
public float speed(){
|
||||
float strafePenalty = isGrounded() || !isPlayer() ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(vel().angle(), rotation) / 180f);
|
||||
float boost = Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, elevation);
|
||||
//limit speed to minimum formation speed to preserve formation
|
||||
return (isCommanding() ? minFormationSpeed * 0.98f : type.speed) * strafePenalty * boost * floorSpeedMultiplier();
|
||||
}
|
||||
|
||||
/** Iterates through this unit and everything it is controlling. */
|
||||
public void eachGroup(Cons<Unit> cons){
|
||||
cons.get(self());
|
||||
controlling().each(cons);
|
||||
return type.speed * strafePenalty * boost * floorSpeedMultiplier();
|
||||
}
|
||||
|
||||
/** @return where the unit wants to look at. */
|
||||
@@ -185,9 +177,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
case controlled -> !isValid() ? 0 :
|
||||
controller instanceof LogicAI ? ctrlProcessor :
|
||||
controller instanceof Player ? ctrlPlayer :
|
||||
controller instanceof FormationAI ? ctrlFormation :
|
||||
0;
|
||||
case commanded -> controller instanceof FormationAI && isValid() ? 1 : 0;
|
||||
case payloadCount -> ((Object)this) instanceof Payloadc pay ? pay.payloads().size : 0;
|
||||
case size -> hitSize / tilesize;
|
||||
case color -> Color.toDoubleBits(team.color.r, team.color.g, team.color.b, 1f);
|
||||
@@ -201,7 +191,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
case type -> type;
|
||||
case name -> controller instanceof Player p ? p.name : null;
|
||||
case firstItem -> stack().amount == 0 ? null : item();
|
||||
case controller -> !isValid() ? null : controller instanceof LogicAI log ? log.controller : controller instanceof FormationAI form ? form.leader : this;
|
||||
case controller -> !isValid() ? null : controller instanceof LogicAI log ? log.controller : this;
|
||||
case payloadType -> ((Object)this) instanceof Payloadc pay ?
|
||||
(pay.payloads().isEmpty() ? null :
|
||||
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
|
||||
@@ -559,12 +549,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
remove();
|
||||
}
|
||||
|
||||
/** @return name of direct or indirect player controller. */
|
||||
/** @return name of direct or indirect player controller. TODO comamnd support*/
|
||||
@Override
|
||||
public @Nullable String getControllerName(){
|
||||
if(isPlayer()) return getPlayer().name;
|
||||
if(controller instanceof LogicAI ai && ai.controller != null) return ai.controller.lastAccessed;
|
||||
if(controller instanceof FormationAI ai && ai.leader != null && ai.leader.isPlayer()) return ai.leader.getPlayer().name;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,6 @@ public class AIController implements UnitController{
|
||||
return false;
|
||||
}
|
||||
|
||||
public UnitCommand command(){
|
||||
return unit.team.data().command;
|
||||
}
|
||||
|
||||
public void updateVisuals(){
|
||||
if(unit.isFlying()){
|
||||
unit.wobble();
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package mindustry.entities.units;
|
||||
|
||||
import arc.*;
|
||||
|
||||
public enum UnitCommand{
|
||||
attack, rally, idle;
|
||||
|
||||
private final String localized;
|
||||
public static final UnitCommand[] all = values();
|
||||
|
||||
UnitCommand(){
|
||||
localized = Core.bundle.get("command." + name());
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
return localized;
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ public interface UnitController{
|
||||
return true;
|
||||
}
|
||||
|
||||
default void command(UnitCommand command){
|
||||
|
||||
}
|
||||
|
||||
default void updateUnit(){
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.game;
|
||||
import arc.util.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
@@ -121,16 +120,6 @@ public class EventType{
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandIssueEvent{
|
||||
public final Building tile;
|
||||
public final UnitCommand command;
|
||||
|
||||
public CommandIssueEvent(Building tile, UnitCommand command){
|
||||
this.tile = tile;
|
||||
this.command = command;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientPreConnectEvent{
|
||||
public final Host host;
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ public class Rules{
|
||||
public boolean damageExplosions = true;
|
||||
/** Whether fire is enabled. */
|
||||
public boolean fire = true;
|
||||
/** Erekir-specific: If true, unit RTS controls can be used. */
|
||||
public boolean unitCommand = false;
|
||||
/** Whether units use and require ammo. */
|
||||
public boolean unitAmmo = false;
|
||||
/** EXPERIMENTAL! If true, blocks will update in units and share power. */
|
||||
|
||||
@@ -9,7 +9,6 @@ import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.power.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.consumers.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -36,7 +35,7 @@ public class Schematic implements Publishable, Comparable<Schematic>{
|
||||
}
|
||||
|
||||
public float powerConsumption(){
|
||||
return tiles.sumf(s -> s.block.consumes.has(ConsumeType.power) ? s.block.consumes.getPower().usage : 0f);
|
||||
return tiles.sumf(s -> s.block.consPower != null ? s.block.consPower.usage : 0f);
|
||||
}
|
||||
|
||||
public ItemSeq requirements(){
|
||||
|
||||
@@ -8,7 +8,6 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
@@ -231,8 +230,6 @@ public class Teams{
|
||||
public Team[] coreEnemies = {};
|
||||
/** Planned blocks for drones. This is usually only blocks that have been broken. */
|
||||
public Queue<BlockPlan> blocks = new Queue<>();
|
||||
/** The current command for units to follow. */
|
||||
public UnitCommand command = UnitCommand.attack;
|
||||
|
||||
/** Quadtree for all buildings of this team. Null if not active. */
|
||||
public @Nullable QuadTree<Building> buildings;
|
||||
|
||||
@@ -387,7 +387,7 @@ public class BlockRenderer{
|
||||
Draw.z(Layer.block);
|
||||
}
|
||||
|
||||
if(build.team == player.team() && renderer.drawStatus && block.consumes.any()){
|
||||
if(build.team == player.team() && renderer.drawStatus && block.hasConsumers){
|
||||
build.drawStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ public enum Binding implements KeyBind{
|
||||
pickupCargo(KeyCode.leftBracket),
|
||||
dropCargo(KeyCode.rightBracket),
|
||||
|
||||
command(KeyCode.g),
|
||||
|
||||
clear_building(KeyCode.q),
|
||||
pause_building(KeyCode.e),
|
||||
rotate(new Axis(KeyCode.scroll)),
|
||||
|
||||
@@ -278,7 +278,7 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
shouldShoot = !scene.hasMouse() && !locked;
|
||||
|
||||
if(!locked && state.rules.unitCommand && block == null && !scene.hasField()){
|
||||
if(!locked && block == null && !scene.hasField()){
|
||||
commandMode = input.keyDown(Binding.commandMode);
|
||||
}else{
|
||||
commandMode = false;
|
||||
@@ -806,10 +806,5 @@ public class DesktopInput extends InputHandler{
|
||||
tryDropPayload();
|
||||
}
|
||||
}
|
||||
|
||||
//update commander unit
|
||||
if(Core.input.keyTap(Binding.command) && unit.type.commandLimit > 0){
|
||||
Call.unitCommand(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import arc.scene.event.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.formations.patterns.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
@@ -241,14 +240,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
throw new ValidateException(player, "Player cannot request items.");
|
||||
}
|
||||
|
||||
//remove item for every controlling unit
|
||||
player.unit().eachGroup(unit -> {
|
||||
Call.takeItems(build, item, Math.min(unit.maxAccepted(item), amount), unit);
|
||||
|
||||
if(unit == player.unit()){
|
||||
Events.fire(new WithdrawEvent(build, player, item, amount));
|
||||
}
|
||||
});
|
||||
Call.takeItems(build, item, Math.min(player.unit().maxAccepted(item), amount), player.unit());
|
||||
Events.fire(new WithdrawEvent(build, player, item, amount));
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, forward = true, called = Loc.server)
|
||||
@@ -264,16 +257,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
//deposit for every controlling unit
|
||||
player.unit().eachGroup(unit -> {
|
||||
Item item = unit.item();
|
||||
int accepted = build.acceptStack(item, unit.stack.amount, unit);
|
||||
var unit = player.unit();
|
||||
Item item = unit.item();
|
||||
int accepted = build.acceptStack(item, unit.stack.amount, unit);
|
||||
|
||||
Call.transferItemTo(unit, item, accepted, unit.x, unit.y, build);
|
||||
Call.transferItemTo(unit, item, accepted, unit.x, unit.y, build);
|
||||
|
||||
if(unit == player.unit()){
|
||||
Events.fire(new DepositEvent(build, player, item, accepted));
|
||||
}
|
||||
});
|
||||
Events.fire(new DepositEvent(build, player, item, accepted));
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.one)
|
||||
@@ -369,11 +359,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
pay.set(x, y);
|
||||
pay.dropLastPayload();
|
||||
pay.set(prevx, prevy);
|
||||
pay.controlling().each(u -> {
|
||||
if(u instanceof Payloadc){
|
||||
Call.payloadDropped(u, u.x, u.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,10 +370,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
throw new ValidateException(player, "Player cannot drop an item.");
|
||||
}
|
||||
|
||||
player.unit().eachGroup(unit -> {
|
||||
Fx.dropItem.at(unit.x, unit.y, angle, Color.white, unit.item());
|
||||
unit.clearItem();
|
||||
});
|
||||
var unit = player.unit();
|
||||
Fx.dropItem.at(unit.x, unit.y, angle, Color.white, unit.item());
|
||||
unit.clearItem();
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
|
||||
@@ -545,24 +529,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
player.deathTimer = Player.deathDelay + 1f; //for instant respawn
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
public static void unitCommand(Player player){
|
||||
if(player == null || player.dead() || (player.unit() == null)) return;
|
||||
|
||||
//make sure player is allowed to make the command
|
||||
if(net.server() && !netServer.admins.allowAction(player, ActionType.command, action -> {})){
|
||||
throw new ValidateException(player, "Player cannot command a unit.");
|
||||
}
|
||||
|
||||
if(player.unit().isCommanding()){
|
||||
player.unit().clearCommand();
|
||||
}else if(player.unit().type.commandLimit > 0){
|
||||
|
||||
player.unit().commandNearby(new CircleFormation());
|
||||
Fx.commandSend.at(player, player.unit().type.commandRadius);
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds an input lock; if this function returns true, input is locked. Used for mod 'cutscenes' or custom camera panning. */
|
||||
public void addLock(Boolp lock){
|
||||
inputLocks.add(lock);
|
||||
|
||||
@@ -620,20 +620,16 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
if(count == 2){
|
||||
//reset payload target
|
||||
payloadTarget = null;
|
||||
//apply command on double tap when own unit is tapped
|
||||
if(!player.dead() && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 0.6f + 8f) && player.unit().type.commandLimit > 0){
|
||||
Call.unitCommand(player);
|
||||
}else{
|
||||
//control a unit/block detected on first tap of double-tap
|
||||
if(unitTapped != null){
|
||||
Call.unitControl(player, unitTapped);
|
||||
recentRespawnTimer = 1f;
|
||||
}else if(buildingTapped != null){
|
||||
Call.buildingControlSelect(player, buildingTapped);
|
||||
recentRespawnTimer = 1f;
|
||||
}else if(!tryBeginMine(cursor)){
|
||||
tileTapped(linked.build);
|
||||
}
|
||||
|
||||
//control a unit/block detected on first tap of double-tap
|
||||
if(unitTapped != null){
|
||||
Call.unitControl(player, unitTapped);
|
||||
recentRespawnTimer = 1f;
|
||||
}else if(buildingTapped != null){
|
||||
Call.buildingControlSelect(player, buildingTapped);
|
||||
recentRespawnTimer = 1f;
|
||||
}else if(!tryBeginMine(cursor)){
|
||||
tileTapped(linked.build);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,9 +92,6 @@ public class TypeIO{
|
||||
write.b((byte)14);
|
||||
write.i(b.length);
|
||||
write.b(b);
|
||||
}else if(object instanceof UnitCommand c){
|
||||
write.b((byte)15);
|
||||
write.b(c.ordinal());
|
||||
}else if(object instanceof boolean[] b){
|
||||
write.b(16);
|
||||
write.i(b.length);
|
||||
@@ -165,7 +162,6 @@ public class TypeIO{
|
||||
read.b(bytes);
|
||||
yield bytes;
|
||||
}
|
||||
case 15 -> UnitCommand.all[read.b()];
|
||||
case 16 -> {
|
||||
int boollen = read.i();
|
||||
boolean[] bools = new boolean[boollen];
|
||||
@@ -385,9 +381,6 @@ public class TypeIO{
|
||||
if(control instanceof Player p){
|
||||
write.b(0);
|
||||
write.i(p.id);
|
||||
}else if(control instanceof FormationAI form && form.leader != null){
|
||||
write.b(1);
|
||||
write.i(form.leader.id);
|
||||
}else if(control instanceof LogicAI logic && logic.controller != null){
|
||||
write.b(3);
|
||||
write.i(logic.controller.pos());
|
||||
@@ -423,13 +416,6 @@ public class TypeIO{
|
||||
//make sure player exists
|
||||
if(player == null) return prev;
|
||||
return player;
|
||||
}else if(type == 1){ //formation controller
|
||||
int id = read.i();
|
||||
if(prev instanceof FormationAI f){
|
||||
f.leader = Groups.unit.getByID(id);
|
||||
return f;
|
||||
}
|
||||
return new FormationAI(Groups.unit.getByID(id), null);
|
||||
}else if(type == 3){
|
||||
int pos = read.i();
|
||||
if(prev instanceof LogicAI pai){
|
||||
@@ -475,7 +461,7 @@ public class TypeIO{
|
||||
//2: prev controller was a player, so replace this controller with *anything else*
|
||||
//...since AI doesn't update clientside it doesn't matter
|
||||
//TODO I hate this
|
||||
return (!(prev instanceof AIController) || (prev instanceof FormationAI) || (prev instanceof LogicAI)) ? new GroundAI() : prev;
|
||||
return (!(prev instanceof AIController) || (prev instanceof LogicAI)) ? new GroundAI() : prev;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,14 +534,6 @@ public class TypeIO{
|
||||
return Team.get(read.b());
|
||||
}
|
||||
|
||||
public static void writeUnitCommand(Writes write, UnitCommand reason){
|
||||
write.b((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
public static UnitCommand readUnitCommand(Reads read){
|
||||
return UnitCommand.all[read.b()];
|
||||
}
|
||||
|
||||
public static void writeAction(Writes write, AdminAction reason){
|
||||
write.b((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.logic.LExecutor.*;
|
||||
import mindustry.type.*;
|
||||
@@ -20,7 +19,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
/** Stores global constants for logic processors. */
|
||||
public class GlobalConstants{
|
||||
public static final int ctrlProcessor = 1, ctrlPlayer = 2, ctrlFormation = 3;
|
||||
public static final int ctrlProcessor = 1, ctrlPlayer = 2;
|
||||
public static final ContentType[] lookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid};
|
||||
/** Global random state. */
|
||||
public static final Rand rand = new Rand();
|
||||
@@ -50,7 +49,6 @@ public class GlobalConstants{
|
||||
|
||||
put("@ctrlProcessor", ctrlProcessor);
|
||||
put("@ctrlPlayer", ctrlPlayer);
|
||||
put("@ctrlFormation", ctrlFormation);
|
||||
|
||||
//store base content
|
||||
|
||||
@@ -83,10 +81,6 @@ public class GlobalConstants{
|
||||
put("@" + sensor.name(), sensor);
|
||||
}
|
||||
|
||||
for(UnitCommand cmd : UnitCommand.all){
|
||||
put("@command" + Strings.capitalize(cmd.name()), cmd);
|
||||
}
|
||||
|
||||
logicIdToContent = new UnlockableContent[ContentType.all.length][];
|
||||
contentIdToLogicId = new int[ContentType.all.length][];
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ public enum LAccess{
|
||||
flag,
|
||||
controlled,
|
||||
controller,
|
||||
commanded,
|
||||
name,
|
||||
payloadCount,
|
||||
payloadType,
|
||||
|
||||
@@ -338,7 +338,7 @@ public class LExecutor{
|
||||
/** Checks is a unit is valid for logic AI control, and returns the controller. */
|
||||
@Nullable
|
||||
public static LogicAI checkLogicAI(LExecutor exec, Object unitObj){
|
||||
if(unitObj instanceof Unit unit && unit.isValid() && exec.obj(varUnit) == unit && unit.team == exec.team && !unit.isPlayer() && !(unit.controller() instanceof FormationAI)){
|
||||
if(unitObj instanceof Unit unit && unit.isValid() && exec.obj(varUnit) == unit && unit.team == exec.team && !unit.isPlayer()){
|
||||
if(unit.controller() instanceof LogicAI la){
|
||||
la.controller = exec.building(varThis);
|
||||
return la;
|
||||
|
||||
@@ -444,7 +444,6 @@ public class ErekirPlanetGenerator extends PlanetGenerator{
|
||||
//it is very hot
|
||||
state.rules.attributes.set(Attribute.heat, 0.8f);
|
||||
state.rules.environment = sector.planet.defaultEnv;
|
||||
state.rules.unitCommand = true;
|
||||
state.rules.placeRangeCheck = true;
|
||||
|
||||
//TODO remove slag and arkycite around core.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package mindustry.mod;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.world.blocks.legacy.*;
|
||||
import mindustry.world.consumers.*;
|
||||
|
||||
/** Generated class. Maps simple class names to concrete classes. For use in JSON mods. */
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ClassMap{
|
||||
@@ -14,7 +17,6 @@ public class ClassMap{
|
||||
classes.put("DefenderAI", mindustry.ai.types.DefenderAI.class);
|
||||
classes.put("FlyingAI", mindustry.ai.types.FlyingAI.class);
|
||||
classes.put("FlyingFollowAI", mindustry.ai.types.FlyingFollowAI.class);
|
||||
classes.put("FormationAI", mindustry.ai.types.FormationAI.class);
|
||||
classes.put("GroundAI", mindustry.ai.types.GroundAI.class);
|
||||
classes.put("HugAI", mindustry.ai.types.HugAI.class);
|
||||
classes.put("LogicAI", mindustry.ai.types.LogicAI.class);
|
||||
@@ -296,11 +298,11 @@ public class ClassMap{
|
||||
classes.put("BeamNodeBuild", mindustry.world.blocks.power.BeamNode.BeamNodeBuild.class);
|
||||
classes.put("BurnerGenerator", mindustry.world.blocks.power.BurnerGenerator.class);
|
||||
classes.put("BurnerGeneratorBuild", mindustry.world.blocks.power.BurnerGenerator.BurnerGeneratorBuild.class);
|
||||
classes.put("ConditionalConsumePower", mindustry.world.blocks.power.ConditionalConsumePower.class);
|
||||
classes.put("ConditionalConsumePower", ConsumePowerCondition.class);
|
||||
classes.put("ConsumeGenerator", mindustry.world.blocks.power.ConsumeGenerator.class);
|
||||
classes.put("ConsumeGeneratorBuild", mindustry.world.blocks.power.ConsumeGenerator.ConsumeGeneratorBuild.class);
|
||||
classes.put("DecayGenerator", mindustry.world.blocks.power.DecayGenerator.class);
|
||||
classes.put("DynamicConsumePower", mindustry.world.blocks.power.DynamicConsumePower.class);
|
||||
classes.put("DynamicConsumePower", ConsumePowerDynamic.class);
|
||||
classes.put("ImpactReactor", mindustry.world.blocks.power.ImpactReactor.class);
|
||||
classes.put("ImpactReactorBuild", mindustry.world.blocks.power.ImpactReactor.ImpactReactorBuild.class);
|
||||
classes.put("ItemLiquidGenerator", mindustry.world.blocks.power.ItemLiquidGenerator.class);
|
||||
@@ -372,8 +374,8 @@ public class ClassMap{
|
||||
classes.put("Unloader", mindustry.world.blocks.storage.Unloader.class);
|
||||
classes.put("ContainerStat", mindustry.world.blocks.storage.Unloader.ContainerStat.class);
|
||||
classes.put("UnloaderBuild", mindustry.world.blocks.storage.Unloader.UnloaderBuild.class);
|
||||
classes.put("CommandCenter", mindustry.world.blocks.units.CommandCenter.class);
|
||||
classes.put("CommandBuild", mindustry.world.blocks.units.CommandCenter.CommandBuild.class);
|
||||
classes.put("CommandCenter", LegacyCommandCenter.class);
|
||||
classes.put("CommandBuild", LegacyCommandCenter.CommandBuild.class);
|
||||
classes.put("ControlCore", mindustry.world.blocks.units.ControlCore.class);
|
||||
classes.put("DroneCenter", mindustry.world.blocks.units.DroneCenter.class);
|
||||
classes.put("DroneCenterBuild", mindustry.world.blocks.units.DroneCenter.DroneCenterBuild.class);
|
||||
|
||||
@@ -382,19 +382,19 @@ public class ContentParser{
|
||||
if(value.has("consumes") && value.get("consumes").isObject()){
|
||||
for(JsonValue child : value.get("consumes")){
|
||||
switch(child.name){
|
||||
case "item" -> block.consumes.item(find(ContentType.item, child.asString()));
|
||||
case "items" -> block.consumes.add((Consume)parser.readValue(ConsumeItems.class, child));
|
||||
case "liquid" -> block.consumes.add((Consume)parser.readValue(ConsumeLiquid.class, child));
|
||||
case "liquids" -> block.consumes.add((Consume)parser.readValue(ConsumeLiquids.class, child));
|
||||
case "coolant" -> block.consumes.add((Consume)parser.readValue(ConsumeCoolant.class, child));
|
||||
case "item" -> block.consumeItem(find(ContentType.item, child.asString()));
|
||||
case "items" -> block.consume((Consume)parser.readValue(ConsumeItems.class, child));
|
||||
case "liquid" -> block.consume((Consume)parser.readValue(ConsumeLiquid.class, child));
|
||||
case "liquids" -> block.consume((Consume)parser.readValue(ConsumeLiquids.class, child));
|
||||
case "coolant" -> block.consume((Consume)parser.readValue(ConsumeCoolant.class, child));
|
||||
case "power" -> {
|
||||
if(child.isNumber()){
|
||||
block.consumes.power(child.asFloat());
|
||||
block.consumePower(child.asFloat());
|
||||
}else{
|
||||
block.consumes.add((Consume)parser.readValue(ConsumePower.class, child));
|
||||
block.consume((Consume)parser.readValue(ConsumePower.class, child));
|
||||
}
|
||||
}
|
||||
case "powerBuffered" -> block.consumes.powerBuffered(child.asFloat());
|
||||
case "powerBuffered" -> block.consumePowerBuffered(child.asFloat());
|
||||
default -> throw new IllegalArgumentException("Unknown consumption type: '" + child.name + "' for block '" + block.name + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.SectorInfo.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -113,12 +112,6 @@ public class GameService{
|
||||
}
|
||||
}));
|
||||
|
||||
Events.on(CommandIssueEvent.class, e -> {
|
||||
if(campaign() && e.command == UnitCommand.attack){
|
||||
issueAttackCommand.complete();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(BlockBuildEndEvent.class, e -> {
|
||||
if(campaign() && e.unit != null && e.unit.isLocal() && !e.breaking){
|
||||
SStat.blocksBuilt.add();
|
||||
|
||||
@@ -91,7 +91,6 @@ public class UnitType extends UnlockableContent{
|
||||
public boolean targetable = true;
|
||||
public boolean drawBuildBeam = true;
|
||||
public boolean rotateToBuilding = true;
|
||||
public int commandLimit = 8;
|
||||
public float commandRadius = 150f;
|
||||
public float visualElevation = -1f;
|
||||
/** If true and this is a legged unit, this unit can walk over blocks. */
|
||||
@@ -350,7 +349,6 @@ public class UnitType extends UnlockableContent{
|
||||
stats.add(Stat.size, StatValues.squared(hitSize / tilesize, StatUnit.blocksSquared));
|
||||
stats.add(Stat.itemCapacity, itemCapacity);
|
||||
stats.add(Stat.range, (int)(maxRange / tilesize), StatUnit.blocks);
|
||||
stats.add(Stat.commandLimit, commandLimit);
|
||||
|
||||
if(abilities.any()){
|
||||
var unique = new ObjectSet<String>();
|
||||
@@ -685,7 +683,7 @@ public class UnitType extends UnlockableContent{
|
||||
//find reconstructor
|
||||
var rec = (Reconstructor)content.blocks().find(b -> b instanceof Reconstructor re && re.upgrades.contains(u -> u[1] == this));
|
||||
|
||||
if(rec != null && rec.consumes.has(ConsumeType.item) && rec.consumes.get(ConsumeType.item) instanceof ConsumeItems ci){
|
||||
if(rec != null && Structs.find(rec.consumers, i -> i instanceof ConsumeItems) instanceof ConsumeItems ci){
|
||||
if(prevReturn != null){
|
||||
prevReturn[0] = rec.upgrades.find(u -> u[1] == this)[0];
|
||||
}
|
||||
|
||||
@@ -38,17 +38,17 @@ public class PowerAmmoType implements AmmoType{
|
||||
public void resupply(Unit unit){
|
||||
float range = unit.hitSize + this.range;
|
||||
|
||||
Building build = Units.closestBuilding(unit.team, unit.x, unit.y, range, u -> u.block.consumes.hasPower() && u.block.consumes.getPower().buffered);
|
||||
Building build = Units.closestBuilding(unit.team, unit.x, unit.y, range, u -> u.block.consPower != null && u.block.consPower.buffered);
|
||||
|
||||
if(build != null){
|
||||
float amount = build.power.status * build.block.consumes.getPower().capacity;
|
||||
float amount = build.power.status * build.block.consPower.capacity;
|
||||
float powerPerAmmo = totalPower / unit.type.ammoCapacity;
|
||||
float ammoRequired = unit.type.ammoCapacity - unit.ammo;
|
||||
float powerRequired = ammoRequired * powerPerAmmo;
|
||||
float powerTaken = Math.min(amount, powerRequired);
|
||||
|
||||
if(powerTaken > 1){
|
||||
build.power.status -= powerTaken / build.block.consumes.getPower().capacity;
|
||||
build.power.status -= powerTaken / build.block.consPower.capacity;
|
||||
unit.ammo += powerTaken / powerPerAmmo;
|
||||
|
||||
Fx.itemTransfer.at(build.x, build.y, Math.max(powerTaken / 100f, 1f), Pal.power, unit);
|
||||
|
||||
@@ -10,7 +10,6 @@ public class ErekirUnitType extends UnitType{
|
||||
|
||||
public ErekirUnitType(String name){
|
||||
super(name);
|
||||
commandLimit = 0;
|
||||
outlineColor = Pal.darkOutline;
|
||||
envDisabled = Env.space;
|
||||
unitBasedDefaultController = u -> !playerControllable || u.team.isAI() ? defaultController.get() : new CommandAI();
|
||||
|
||||
@@ -171,7 +171,6 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
main.row();
|
||||
|
||||
title("@rules.title.unit");
|
||||
check("@rules.unitcommand", b -> rules.unitCommand = b, () -> rules.unitCommand);
|
||||
check("@rules.unitammo", b -> rules.unitAmmo = b, () -> rules.unitAmmo);
|
||||
check("@rules.unitcapvariable", b -> rules.unitCapVariable = b, () -> rules.unitCapVariable);
|
||||
number("@rules.unitcap", true, f -> rules.unitCap = f, () -> rules.unitCap, -999, 999);
|
||||
|
||||
@@ -165,7 +165,6 @@ public class HintsFragment extends Fragment{
|
||||
boost(visibleDesktop, () -> !player.dead() && player.unit().type.canBoost, () -> Core.input.keyDown(Binding.boost)),
|
||||
blockInfo(() -> !(state.isCampaign() && state.rules.sector == SectorPresets.groundZero.sector && state.wave < 3), () -> ui.content.isShown()),
|
||||
derelict(() -> ui.hints.events.contains("derelictmouse"), () -> false),
|
||||
command(() -> state.rules.defaultTeam.data().units.size > 3 && !net.active(), () -> player.unit().isCommanding()),
|
||||
payloadPickup(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
|
||||
payloadDrop(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),
|
||||
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getFlagged(state.rules.defaultTeam, BlockFlag.extinguisher).size > 0),
|
||||
|
||||
@@ -59,9 +59,6 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
public float liquidCapacity = 10f;
|
||||
public float liquidPressure = 1f;
|
||||
|
||||
public final BlockBars bars = new BlockBars();
|
||||
public final Consumers consumes = new Consumers();
|
||||
|
||||
/** If true, this block outputs to its facing direction, when applicable.
|
||||
* Used for blending calculations. */
|
||||
public boolean outputFacing = true;
|
||||
@@ -288,10 +285,25 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
public @Nullable Class<?> subclass;
|
||||
/** Determines if this block gets a higher unloader priority. */
|
||||
public boolean highUnloadPriority = false;
|
||||
|
||||
public float selectScroll; //scroll position for certain blocks
|
||||
public Prov<Building> buildType = null; //initialized later
|
||||
/** Scroll position for certain blocks. */
|
||||
public float selectScroll;
|
||||
/** Building that is created for this block. Initialized in init() via reflection. Set manually if modded. */
|
||||
public Prov<Building> buildType = null;
|
||||
/** Configuration handlers by type. */
|
||||
public ObjectMap<Class<?>, Cons2> configurations = new ObjectMap<>();
|
||||
/** Consumption filters. */
|
||||
public boolean[] itemFilter, liquidFilter;
|
||||
/** Array of consumers used by this block. Only populated after init(). */
|
||||
public Consume[] consumers = {}, optionalConsumers = {};
|
||||
/** Set to true if this block has any consumers in its array. */
|
||||
public boolean hasConsumers;
|
||||
/** The single power consumer, if applicable. */
|
||||
public @Nullable ConsumePower consPower;
|
||||
|
||||
/** Map of bars by name. */
|
||||
protected OrderedMap<String, Func<Building, Bar>> barMap = new OrderedMap<>();
|
||||
/** List for building up consumption before init(). */
|
||||
protected Seq<Consume> consumeBuilder = new Seq<>();
|
||||
|
||||
protected TextureRegion[] generatedIcons;
|
||||
protected TextureRegion[] editorVariantRegions;
|
||||
@@ -304,7 +316,7 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
public TextureRegion[] teamRegions, variantRegions, variantShadowRegions;
|
||||
|
||||
protected static final Seq<Tile> tempTiles = new Seq<>();
|
||||
protected static final Seq<Building> tempTileEnts = new Seq<>();
|
||||
protected static final Seq<Building> tempBuilds = new Seq<>();
|
||||
|
||||
/** Dump timer ID.*/
|
||||
protected final int timerDump = timers++;
|
||||
@@ -469,15 +481,29 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
stats.add(Stat.maxConsecutive, 2, StatUnit.none);
|
||||
}
|
||||
|
||||
consumes.display(stats);
|
||||
for(var c : consumers){
|
||||
c.display(stats);
|
||||
}
|
||||
|
||||
//Note: Power stats are added by the consumers.
|
||||
if(hasLiquids) stats.add(Stat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits);
|
||||
if(hasItems && itemCapacity > 0) stats.add(Stat.itemCapacity, itemCapacity, StatUnit.items);
|
||||
}
|
||||
|
||||
public <T extends Building> void addBar(String name, Func<T, Bar> sup){
|
||||
barMap.put(name, (Func<Building, Bar>)sup);
|
||||
}
|
||||
|
||||
public void removeBar(String name){
|
||||
barMap.remove(name);
|
||||
}
|
||||
|
||||
public Iterable<Func<Building, Bar>> listBars(){
|
||||
return barMap.values();
|
||||
}
|
||||
|
||||
public void addLiquidBar(Liquid liq){
|
||||
bars.add("liquid-" + liq.name, entity -> new Bar(
|
||||
addBar("liquid-" + liq.name, entity -> new Bar(
|
||||
() -> liq.localizedName,
|
||||
liq::barColor,
|
||||
() -> entity.liquids.get(liq) / liquidCapacity)
|
||||
@@ -486,7 +512,7 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
|
||||
/** Adds a liquid bar that dynamically displays a liquid type. */
|
||||
public <T extends Building> void addLiquidBar(Func<T, Liquid> current){
|
||||
bars.add("liquid", entity -> new Bar(
|
||||
addBar("liquid", entity -> new Bar(
|
||||
() -> current.get((T)entity) == null || entity.liquids.get(current.get((T)entity)) <= 0.001f ? Core.bundle.get("bar.liquid") : current.get((T)entity).localizedName,
|
||||
() -> current.get((T)entity) == null ? Color.clear : current.get((T)entity).barColor(),
|
||||
() -> current.get((T)entity) == null ? 0f : entity.liquids.get(current.get((T)entity)) / liquidCapacity)
|
||||
@@ -494,23 +520,22 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
}
|
||||
|
||||
public void setBars(){
|
||||
bars.add("health", entity -> new Bar("stat.health", Pal.health, entity::healthf).blink(Color.white));
|
||||
addBar("health", entity -> new Bar("stat.health", Pal.health, entity::healthf).blink(Color.white));
|
||||
|
||||
if(hasPower && consumes.hasPower()){
|
||||
ConsumePower cons = consumes.getPower();
|
||||
boolean buffered = cons.buffered;
|
||||
float capacity = cons.capacity;
|
||||
if(consPower != null){
|
||||
boolean buffered = consPower.buffered;
|
||||
float capacity = consPower.capacity;
|
||||
|
||||
bars.add("power", entity -> new Bar(
|
||||
addBar("power", entity -> new Bar(
|
||||
() -> buffered ? Core.bundle.format("bar.poweramount", Float.isNaN(entity.power.status * capacity) ? "<ERROR>" : UI.formatAmount((int)(entity.power.status * capacity))) :
|
||||
Core.bundle.get("bar.power"),
|
||||
() -> Pal.powerBar,
|
||||
() -> Mathf.zero(cons.requestedPower(entity)) && entity.power.graph.getPowerProduced() + entity.power.graph.getBatteryStored() > 0f ? 1f : entity.power.status)
|
||||
() -> Mathf.zero(consPower.requestedPower(entity)) && entity.power.graph.getPowerProduced() + entity.power.graph.getBatteryStored() > 0f ? 1f : entity.power.status)
|
||||
);
|
||||
}
|
||||
|
||||
if(hasItems && configurable){
|
||||
bars.add("items", entity -> new Bar(
|
||||
addBar("items", entity -> new Bar(
|
||||
() -> Core.bundle.format("bar.items", entity.items.total()),
|
||||
() -> Pal.items,
|
||||
() -> (float)entity.items.total() / itemCapacity)
|
||||
@@ -533,9 +558,9 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
|
||||
boolean added = false;
|
||||
|
||||
//TODO handle in consumer
|
||||
//add bars for *specific* consumed liquids
|
||||
if(consumes.has(ConsumeType.liquid)){
|
||||
var consl = consumes.get(ConsumeType.liquid);
|
||||
for(var consl : consumers){
|
||||
if(consl instanceof ConsumeLiquid liq){
|
||||
added = true;
|
||||
addLiquidBar(liq.liquid);
|
||||
@@ -554,6 +579,14 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean consumesItem(Item item){
|
||||
return itemFilter[item.id];
|
||||
}
|
||||
|
||||
public boolean consumesLiquid(Liquid liq){
|
||||
return liquidFilter[liq.id];
|
||||
}
|
||||
|
||||
public boolean canReplace(Block other){
|
||||
if(other.alwaysReplace) return true;
|
||||
return other.replaceable && (other != this || rotate) && this.group != BlockGroup.none && other.group == this.group &&
|
||||
@@ -818,6 +851,67 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
return cacheLayer == CacheLayer.walls;
|
||||
}
|
||||
|
||||
public <T extends Consume> T findConsumer(Boolf<Consume> filter){
|
||||
return consumers.length == 0 ? (T)consumeBuilder.find(filter) : (T)Structs.find(consumers, filter);
|
||||
}
|
||||
|
||||
public ConsumeLiquid consumeLiquid(Liquid liquid, float amount){
|
||||
return consume(new ConsumeLiquid(liquid, amount));
|
||||
}
|
||||
|
||||
public ConsumeLiquids consumeLiquids(LiquidStack... stacks){
|
||||
return consume(new ConsumeLiquids(stacks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which directly uses power without buffering it.
|
||||
* @param powerPerTick The amount of power which is required each tick for 100% efficiency.
|
||||
* @return the created consumer object.
|
||||
*/
|
||||
public ConsumePower consumePower(float powerPerTick){
|
||||
return consume(new ConsumePower(powerPerTick, 0.0f, false));
|
||||
}
|
||||
|
||||
/** Creates a consumer which only consumes power when the condition is met. */
|
||||
public <T extends Building> ConsumePower consumePowerCond(float usage, Boolf<T> cons){
|
||||
return consume(new ConsumePowerCondition(usage, (Boolf<Building>)cons));
|
||||
}
|
||||
|
||||
/** Creates a consumer that consumes a dynamic amount of power. */
|
||||
public <T extends Building> ConsumePower consumePowerDynamic(Floatf<T> usage){
|
||||
return consume(new ConsumePowerDynamic((Floatf<Building>)usage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consumer which stores power.
|
||||
* @param powerCapacity The maximum capacity in power units.
|
||||
*/
|
||||
public ConsumePower consumePowerBuffered(float powerCapacity){
|
||||
return consume(new ConsumePower(0f, powerCapacity, true));
|
||||
}
|
||||
|
||||
public ConsumeItems consumeItem(Item item){
|
||||
return consumeItem(item, 1);
|
||||
}
|
||||
|
||||
public ConsumeItems consumeItem(Item item, int amount){
|
||||
return consume(new ConsumeItems(new ItemStack[]{new ItemStack(item, amount)}));
|
||||
}
|
||||
|
||||
public ConsumeItems consumeItems(ItemStack... items){
|
||||
return consume(new ConsumeItems(items));
|
||||
}
|
||||
|
||||
public <T extends Consume> T consume(T consume){
|
||||
if(consume instanceof ConsumePower){
|
||||
//there can only be one power consumer
|
||||
consumeBuilder.removeAll(b -> b instanceof ConsumePower);
|
||||
consPower = (ConsumePower)consume;
|
||||
}
|
||||
consumeBuilder.add(consume);
|
||||
return consume;
|
||||
}
|
||||
|
||||
public void setupRequirements(Category cat, ItemStack[] stacks){
|
||||
requirements(cat, stacks);
|
||||
}
|
||||
@@ -904,8 +998,8 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
}
|
||||
|
||||
//also requires inputs
|
||||
consumes.each(c -> {
|
||||
if(c.isOptional()) return;
|
||||
for(var c : consumeBuilder){
|
||||
if(c.optional) continue;
|
||||
|
||||
if(c instanceof ConsumeItems i){
|
||||
for(ItemStack stack : i.items){
|
||||
@@ -918,7 +1012,7 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
cons.get(stack.liquid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -966,7 +1060,7 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
clipSize = Math.max(clipSize, lightRadius * 2f);
|
||||
}
|
||||
|
||||
if(group == BlockGroup.transportation || consumes.has(ConsumeType.item) || category == Category.distribution){
|
||||
if(group == BlockGroup.transportation || category == Category.distribution){
|
||||
acceptsItems = true;
|
||||
}
|
||||
|
||||
@@ -981,15 +1075,21 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
|
||||
buildCost *= buildCostMultiplier;
|
||||
|
||||
if(consumes.has(ConsumeType.power)) hasPower = true;
|
||||
if(consumes.has(ConsumeType.item)) hasItems = true;
|
||||
if(consumes.has(ConsumeType.liquid)) hasLiquids = true;
|
||||
consumers = consumeBuilder.toArray(Consume.class);
|
||||
optionalConsumers = consumeBuilder.filter(consume -> consume.optional).toArray(Consume.class);
|
||||
hasConsumers = consumers.length > 0;
|
||||
itemFilter = new boolean[content.items().size];
|
||||
liquidFilter = new boolean[content.liquids().size];
|
||||
|
||||
for(Consume cons : consumers){
|
||||
cons.apply(this);
|
||||
}
|
||||
|
||||
setBars();
|
||||
|
||||
stats.useCategories = true;
|
||||
|
||||
consumes.init();
|
||||
//TODO check for double power consumption
|
||||
|
||||
if(!logicConfigurable){
|
||||
configurations.each((key, val) -> {
|
||||
@@ -999,9 +1099,9 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
});
|
||||
}
|
||||
|
||||
if(!outputsPower && consumes.hasPower() && consumes.getPower().buffered){
|
||||
if(!outputsPower && consPower != null && consPower.buffered){
|
||||
Log.warn("Consumer using buffered power: @. Disabling buffered power.", name);
|
||||
consumes.getPower().buffered = false;
|
||||
consPower.buffered = false;
|
||||
}
|
||||
|
||||
if(buildVisibility == BuildVisibility.sandboxOnly){
|
||||
@@ -1156,7 +1256,7 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
case size -> size * tilesize;
|
||||
case itemCapacity -> itemCapacity;
|
||||
case liquidCapacity -> liquidCapacity;
|
||||
case powerCapacity -> consumes.hasPower() && consumes.getPower().buffered ? consumes.getPower().capacity : 0f;
|
||||
case powerCapacity -> consPower != null && consPower.buffered ? consPower.capacity : 0f;
|
||||
default -> Double.NaN;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class Accelerator extends Block{
|
||||
capacities[stack.item.id] = stack.amount;
|
||||
itemCapacity += stack.amount;
|
||||
}
|
||||
consumes.items(launching.requirements);
|
||||
consumeItems(launching.requirements);
|
||||
super.init();
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,10 @@ public class LaunchPad extends Block{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("items", entity -> new Bar(() -> Core.bundle.format("bar.items", entity.items.total()), () -> Pal.items, () -> (float)entity.items.total() / itemCapacity));
|
||||
addBar("items", entity -> new Bar(() -> Core.bundle.format("bar.items", entity.items.total()), () -> Pal.items, () -> (float)entity.items.total() / itemCapacity));
|
||||
|
||||
//TODO is "bar.launchcooldown" the right terminology?
|
||||
bars.add("progress", (LaunchPadBuild build) -> new Bar(() -> Core.bundle.get("bar.launchcooldown"), () -> Pal.ammo, () -> Mathf.clamp(build.launchCounter / launchTime)));
|
||||
addBar("progress", (LaunchPadBuild build) -> new Bar(() -> Core.bundle.get("bar.launchcooldown"), () -> Pal.ammo, () -> Mathf.clamp(build.launchCounter / launchTime)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -57,7 +57,6 @@ public class BuildTurret extends BaseTurret{
|
||||
hitSize = 0f;
|
||||
health = 1;
|
||||
itemCapacity = 0;
|
||||
commandLimit = 0;
|
||||
rotateSpeed = BuildTurret.this.rotateSpeed;
|
||||
buildBeamOffset = BuildTurret.this.buildBeamOffset;
|
||||
buildRange = BuildTurret.this.range;
|
||||
|
||||
@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
//TODO use completely different layer
|
||||
//TODO consume heat
|
||||
//TODO broken class!!!!!
|
||||
public class DirectionalForceProjector extends Block{
|
||||
protected static final Vec2 intersectOut = new Vec2(), p1 = new Vec2(), p2 = new Vec2();
|
||||
protected static DirectionalForceProjectorBuild paramEntity;
|
||||
@@ -82,7 +83,7 @@ public class DirectionalForceProjector extends Block{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("shield", (DirectionalForceProjectorBuild entity) -> new Bar("stat.shieldhealth", Pal.accent, () -> entity.broken ? 0f : 1f - entity.buildup / (shieldHealth)).blink(Color.white));
|
||||
addBar("shield", (DirectionalForceProjectorBuild entity) -> new Bar("stat.shieldhealth", Pal.accent, () -> entity.broken ? 0f : 1f - entity.buildup / (shieldHealth)).blink(Color.white));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,9 +139,10 @@ public class DirectionalForceProjector extends Block{
|
||||
|
||||
warmup = Mathf.lerpDelta(warmup, efficiency(), 0.1f);
|
||||
|
||||
if(buildup > 0 && consumes.has(ConsumeType.liquid)){
|
||||
//TODO aaaaaaaaaaaaAAAAAAAAAAAAAAaa
|
||||
if(buildup > 0 && false){
|
||||
float scale = !broken ? cooldownNormal : cooldownBrokenBase;
|
||||
Consume cons = consumes.get(ConsumeType.liquid);
|
||||
Consume cons = null;
|
||||
if(cons.valid(this)){
|
||||
cons.update(this);
|
||||
scale *= (cooldownLiquid * (1f + (liquids.current().heatCapacity - 0.4f) * 0.9f));
|
||||
|
||||
@@ -37,6 +37,9 @@ public class ForceProjector extends Block{
|
||||
public Effect shieldBreakEffect = Fx.shieldBreak;
|
||||
public @Load("@-top") TextureRegion topRegion;
|
||||
|
||||
//TODO json support
|
||||
public @Nullable Consume boostConsumer;
|
||||
|
||||
protected static ForceBuild paramEntity;
|
||||
protected static Effect paramEffect;
|
||||
protected static final Cons<Bullet> shieldConsumer = bullet -> {
|
||||
@@ -61,7 +64,7 @@ public class ForceProjector extends Block{
|
||||
ambientSoundVolume = 0.08f;
|
||||
|
||||
if(consumeCoolant){
|
||||
consumes.add(new ConsumeCoolant(coolantConsumption)).boost().update(false);
|
||||
consume(new ConsumeCoolant(coolantConsumption)).boost().update(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +77,7 @@ public class ForceProjector extends Block{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("shield", (ForceBuild entity) -> new Bar("stat.shieldhealth", Pal.accent, () -> entity.broken ? 0f : 1f - entity.buildup / (shieldHealth + phaseShieldBoost * entity.phaseHeat)).blink(Color.white));
|
||||
addBar("shield", (ForceBuild entity) -> new Bar("stat.shieldhealth", Pal.accent, () -> entity.broken ? 0f : 1f - entity.buildup / (shieldHealth + phaseShieldBoost * entity.phaseHeat)).blink(Color.white));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +87,7 @@ public class ForceProjector extends Block{
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
boolean consItems = consumes.has(ConsumeType.item);
|
||||
boolean consItems = boostConsumer != null;
|
||||
|
||||
if(consItems) stats.timePeriod = phaseUseTime;
|
||||
super.setStats();
|
||||
@@ -139,7 +142,7 @@ public class ForceProjector extends Block{
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
boolean phaseValid = consumes.has(ConsumeType.item) && consumes.get(ConsumeType.item).valid(this);
|
||||
boolean phaseValid = boostConsumer != null && boostConsumer.valid(this);
|
||||
|
||||
phaseHeat = Mathf.lerpDelta(phaseHeat, Mathf.num(phaseValid), 0.1f);
|
||||
|
||||
@@ -158,13 +161,15 @@ public class ForceProjector extends Block{
|
||||
if(buildup > 0){
|
||||
float scale = !broken ? cooldownNormal : cooldownBrokenBase;
|
||||
|
||||
//TODO I hate this system
|
||||
/*
|
||||
if(consumes.has(ConsumeType.liquid)){
|
||||
Consume cons = consumes.get(ConsumeType.liquid);
|
||||
if(cons.valid(this)){
|
||||
cons.update(this);
|
||||
scale *= (cooldownLiquid * (1f + (liquids.current().heatCapacity - 0.4f) * 0.9f));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
buildup -= delta() * scale;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class OverdriveProjector extends Block{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("boost", (OverdriveBuild entity) -> new Bar(() -> Core.bundle.format("bar.boost", Mathf.round(Math.max((entity.realBoost() * 100 - 100), 0))), () -> Pal.accent, () -> entity.realBoost() / (hasBoost ? speedBoost + speedBoostPhase : speedBoost)));
|
||||
addBar("boost", (OverdriveBuild entity) -> new Bar(() -> Core.bundle.format("bar.boost", Mathf.round(Math.max((entity.realBoost() * 100 - 100), 0))), () -> Pal.accent, () -> entity.realBoost() / (hasBoost ? speedBoost + speedBoostPhase : speedBoost)));
|
||||
}
|
||||
|
||||
public class OverdriveBuild extends Building implements Ranged{
|
||||
|
||||
@@ -20,6 +20,7 @@ public class BaseTurret extends Block{
|
||||
public float rotateSpeed = 5;
|
||||
|
||||
public float coolantUsage = 0.2f;
|
||||
@Deprecated
|
||||
public boolean acceptCoolant = true;
|
||||
/** Effect displayed when coolant is used. */
|
||||
public Effect coolEffect = Fx.fuelburn;
|
||||
@@ -28,6 +29,8 @@ public class BaseTurret extends Block{
|
||||
/** Liquid that is used by coolant; null to use default. */
|
||||
public @Nullable Liquid coolantOverride;
|
||||
|
||||
protected @Nullable ConsumeLiquidBase coolantConsumer;
|
||||
|
||||
public BaseTurret(String name){
|
||||
super(name);
|
||||
|
||||
@@ -41,13 +44,18 @@ public class BaseTurret extends Block{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
if(acceptCoolant && !consumes.has(ConsumeType.liquid)){
|
||||
//TODO fundamentally flawed
|
||||
if(acceptCoolant && findConsumer(f -> f instanceof ConsumeLiquidBase) == null){
|
||||
hasLiquids = true;
|
||||
consumes.add(coolantOverride != null ? new ConsumeLiquid(coolantOverride, coolantUsage) : new ConsumeCoolant(coolantUsage)).update(false).boost();
|
||||
consume(coolantOverride != null ? new ConsumeLiquid(coolantOverride, coolantUsage) : new ConsumeCoolant(coolantUsage)).update(false).boost();
|
||||
}
|
||||
|
||||
placeOverlapRange = Math.max(placeOverlapRange, range + placeOverlapMargin);
|
||||
super.init();
|
||||
|
||||
if(acceptCoolant){
|
||||
coolantConsumer = findConsumer(c -> c instanceof ConsumeLiquidBase && c.booster);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ContinuousLiquidTurret extends ContinuousTurret{
|
||||
@Override
|
||||
public void init(){
|
||||
//TODO display ammoMultiplier.
|
||||
consumes.add(new ConsumeLiquidFilter(i -> ammoTypes.containsKey(i), liquidConsumed){
|
||||
consume(new ConsumeLiquidFilter(i -> ammoTypes.containsKey(i), liquidConsumed){
|
||||
@Override
|
||||
public boolean valid(Building build){
|
||||
return build.liquids.currentAmount() >= use(build);
|
||||
|
||||
@@ -60,14 +60,9 @@ public class ContinuousTurret extends Turret{
|
||||
public void updateTile(){
|
||||
super.updateTile();
|
||||
|
||||
//unclean way of calculating ammo fraction to display
|
||||
//TODO unclean way of calculating ammo fraction to display
|
||||
float ammoFract = efficiency();
|
||||
var liq = consumes.getOrNull(ConsumeType.liquid);
|
||||
if(liq instanceof ConsumeLiquids cons){
|
||||
for(var stack : cons.liquids){
|
||||
ammoFract = Math.min(ammoFract, liquids.get(stack.liquid) / liquidCapacity);
|
||||
}
|
||||
}else if(liq instanceof ConsumeLiquid cons){
|
||||
if(findConsumer(f -> f instanceof ConsumeLiquidBase) instanceof ConsumeLiquid cons){
|
||||
ammoFract = Math.min(ammoFract, liquids.get(cons.liquid) / liquidCapacity);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ItemTurret extends Turret{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
consumes.add(new ConsumeItemFilter(i -> ammoTypes.containsKey(i)){
|
||||
consume(new ConsumeItemFilter(i -> ammoTypes.containsKey(i)){
|
||||
@Override
|
||||
public void build(Building build, Table table){
|
||||
MultiReqImage image = new MultiReqImage();
|
||||
|
||||
@@ -11,6 +11,7 @@ import mindustry.world.meta.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** A turret that fires a continuous beam with a delay between shots. Liquid coolant is required. Yes, this class name is awful. */
|
||||
@Deprecated
|
||||
public class LaserTurret extends PowerTurret{
|
||||
public float firingMoveFract = 0.25f;
|
||||
public float shootDuration = 100f;
|
||||
@@ -18,7 +19,7 @@ public class LaserTurret extends PowerTurret{
|
||||
public LaserTurret(String name){
|
||||
super(name);
|
||||
|
||||
consumes.add(new ConsumeCoolant(0.01f)).update(false);
|
||||
consume(new ConsumeCoolant(0.01f)).update(false);
|
||||
coolantMultiplier = 1f;
|
||||
}
|
||||
|
||||
@@ -27,7 +28,8 @@ public class LaserTurret extends PowerTurret{
|
||||
super.setStats();
|
||||
|
||||
stats.remove(Stat.booster);
|
||||
stats.add(Stat.input, StatValues.boosters(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, false, l -> consumes.liquidfilters.get(l.id)));
|
||||
//TODO bad
|
||||
stats.add(Stat.input, StatValues.boosters(reloadTime, coolantConsumer.amount, coolantMultiplier, false, l -> consumesLiquid(l)));
|
||||
}
|
||||
|
||||
public class LaserTurretBuild extends PowerTurretBuild{
|
||||
@@ -67,7 +69,7 @@ public class LaserTurret extends PowerTurret{
|
||||
}else if(reload > 0){
|
||||
wasShooting = true;
|
||||
Liquid liquid = liquids.current();
|
||||
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
|
||||
float maxUsed = coolantConsumer.amount;
|
||||
|
||||
float used = (cheating() ? maxUsed : Math.min(liquids.get(liquid), maxUsed)) * delta();
|
||||
reload -= used * liquid.heatCapacity * coolantMultiplier;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class LiquidTurret extends Turret{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
consumes.add(new ConsumeLiquidFilter(i -> ammoTypes.containsKey(i), 1f){
|
||||
consume(new ConsumeLiquidFilter(i -> ammoTypes.containsKey(i), 1f){
|
||||
@Override
|
||||
public boolean valid(Building build){
|
||||
return build.liquids.currentAmount() >= 0.001f;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PayloadTurret extends Turret{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
consumes.add(new ConsumePayloadFilter(i -> ammoTypes.containsKey(i)){
|
||||
consume(new ConsumePayloadFilter(i -> ammoTypes.containsKey(i)){
|
||||
@Override
|
||||
public void build(Building build, Table table){
|
||||
MultiReqImage image = new MultiReqImage();
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.world.blocks.defense.turrets;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -19,8 +18,8 @@ public class ReloadTurret extends BaseTurret{
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
if(acceptCoolant){
|
||||
stats.add(Stat.booster, StatValues.boosters(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, true, l -> l.coolant && consumes.liquidfilters.get(l.id)));
|
||||
if(acceptCoolant && coolantConsumer != null){
|
||||
stats.add(Stat.booster, StatValues.boosters(reloadTime, coolantConsumer.amount, coolantMultiplier, true, l -> l.coolant && consumesLiquid(l)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +35,7 @@ public class ReloadTurret extends BaseTurret{
|
||||
|
||||
protected void updateCooling(){
|
||||
if(reload < reloadTime && acceptCoolant){
|
||||
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
|
||||
float maxUsed = coolantConsumer.amount;
|
||||
Liquid liquid = liquids.current();
|
||||
|
||||
float used = Math.min(liquids.get(liquid), maxUsed * Time.delta) * baseReloadSpeed();
|
||||
|
||||
@@ -12,7 +12,6 @@ import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -88,7 +87,7 @@ public class TractorBeamTurret extends BaseTurret{
|
||||
|
||||
//consume coolant
|
||||
if(target != null && acceptCoolant){
|
||||
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
|
||||
float maxUsed = coolantConsumer.amount;
|
||||
|
||||
Liquid liquid = liquids.current();
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ public class Turret extends ReloadTurret{
|
||||
super.setBars();
|
||||
|
||||
if(heatRequirement > 0){
|
||||
bars.add("heat", (TurretBuild entity) ->
|
||||
addBar("heat", (TurretBuild entity) ->
|
||||
new Bar(() ->
|
||||
Core.bundle.format("bar.heatpercent", (int)entity.heatReq, (int)(Math.min(entity.heatReq / heatRequirement, maxHeatEfficiency) * 100)),
|
||||
() -> Pal.lightOrange,
|
||||
|
||||
@@ -61,7 +61,7 @@ public class DirectionalUnloader extends Block{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.remove("items");
|
||||
removeBar("items");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,7 @@ public class HeatConductor extends Block{
|
||||
super.setBars();
|
||||
|
||||
//TODO show number
|
||||
bars.add("heat", (HeatConductorBuild entity) -> new Bar(() -> Core.bundle.format("bar.heatamount", (int)entity.heat), () -> Pal.lightOrange, () -> entity.heat / visualMaxHeat));
|
||||
addBar("heat", (HeatConductorBuild entity) -> new Bar(() -> Core.bundle.format("bar.heatamount", (int)entity.heat), () -> Pal.lightOrange, () -> entity.heat / visualMaxHeat));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,7 +33,7 @@ public class HeatProducer extends GenericCrafter{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("heat", (HeatProducerBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat / heatOutput));
|
||||
addBar("heat", (HeatProducerBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat / heatOutput));
|
||||
}
|
||||
|
||||
public class HeatProducerBuild extends GenericCrafterBuild implements HeatBlock{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package mindustry.world.blocks.legacy;
|
||||
|
||||
import arc.util.io.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class LegacyCommandCenter extends LegacyBlock{
|
||||
|
||||
public LegacyCommandCenter(String name){
|
||||
super(name);
|
||||
|
||||
update = true;
|
||||
}
|
||||
|
||||
public class CommandBuild extends Building{
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
write.b(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte version){
|
||||
super.read(read, version);
|
||||
read.b();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class LiquidJunction extends LiquidBlock{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.remove("liquid");
|
||||
removeBar("liquid");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,7 +33,7 @@ public abstract class BlockProducer extends PayloadBlock{
|
||||
rotate = true;
|
||||
regionRotated1 = 1;
|
||||
|
||||
consumes.add(new ConsumeItemDynamic((BlockProducerBuild e) -> e.recipe() != null ? e.recipe().requirements : ItemStack.empty));
|
||||
consume(new ConsumeItemDynamic((BlockProducerBuild e) -> e.recipe() != null ? e.recipe().requirements : ItemStack.empty));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +52,7 @@ public abstract class BlockProducer extends PayloadBlock{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("progress", (BlockProducerBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe() == null ? 0f : (entity.progress / entity.recipe().buildCost)));
|
||||
addBar("progress", (BlockProducerBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe() == null ? 0f : (entity.progress / entity.recipe().buildCost)));
|
||||
}
|
||||
|
||||
public abstract class BlockProducerBuild extends PayloadBlockBuild<BuildPayload>{
|
||||
|
||||
@@ -43,7 +43,7 @@ public class PayloadDeconstructor extends PayloadBlock{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("progress", (PayloadDeconstructorBuild e) -> new Bar("bar.progress", Pal.ammo, () -> e.progress));
|
||||
addBar("progress", (PayloadDeconstructorBuild e) -> new Bar("bar.progress", Pal.ammo, () -> e.progress));
|
||||
}
|
||||
|
||||
public class PayloadDeconstructorBuild extends PayloadBlockBuild<Payload>{
|
||||
|
||||
@@ -54,7 +54,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("progress", (PayloadLoaderBuild build) -> new Bar(() ->
|
||||
addBar("progress", (PayloadLoaderBuild build) -> new Bar(() ->
|
||||
Core.bundle.format(build.payload != null && build.payload.block().hasItems ? "bar.items" : "bar.loadprogress",
|
||||
build.payload == null || !build.payload.block().hasItems ? 0 : build.payload.build.items.total()), () -> Pal.items, build::fraction));
|
||||
}
|
||||
@@ -70,8 +70,8 @@ public class PayloadLoader extends PayloadBlock{
|
||||
@Override
|
||||
public void init(){
|
||||
if(loadPowerDynamic){
|
||||
basePowerUse = consumes.hasPower() ? consumes.getPower().usage : 0f;
|
||||
consumes.powerDynamic((PayloadLoaderBuild loader) -> loader.hasBattery() && !loader.exporting ? maxPowerConsumption + basePowerUse : basePowerUse);
|
||||
basePowerUse = consPower != null ? consPower.usage : 0f;
|
||||
consumePowerDynamic((PayloadLoaderBuild loader) -> loader.hasBattery() && !loader.exporting ? maxPowerConsumption + basePowerUse : basePowerUse);
|
||||
}
|
||||
|
||||
super.init();
|
||||
@@ -90,7 +90,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
//liquid container
|
||||
(build.build.block().hasLiquids && build.block().liquidCapacity >= 10f) ||
|
||||
//battery
|
||||
(build.build.block.consumes.hasPower() && build.build.block.consumes.getPower().buffered)
|
||||
(build.build.block.consPower != null && build.build.block.consPower.buffered)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
payload.build.handleItem(payload.build, item);
|
||||
items.remove(item, 1);
|
||||
break;
|
||||
}else if(payload.block().separateItemCapacity || payload.block().consumes.consumesItem(item)){
|
||||
}else if(payload.block().separateItemCapacity || payload.block().consumesItem(item)){
|
||||
exporting = true;
|
||||
break;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
float availableInput = Math.max(powerInput - basePowerUse, 0f);
|
||||
|
||||
//charge the battery
|
||||
float cap = payload.block().consumes.getPower().capacity;
|
||||
float cap = payload.block().consPower.capacity;
|
||||
payload.build.power.status += availableInput / cap * edelta();
|
||||
|
||||
//export if full
|
||||
@@ -212,7 +212,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
}
|
||||
|
||||
public boolean hasBattery(){
|
||||
return payload != null && payload.block().hasPower && payload.block().consumes.getPower().buffered;
|
||||
return payload != null && payload.block().consPower != null && payload.block().consPower.buffered;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -92,7 +92,7 @@ public class PayloadUnloader extends PayloadLoader{
|
||||
}
|
||||
|
||||
if(hasBattery()){
|
||||
float cap = payload.block().consumes.getPower().capacity;
|
||||
float cap = payload.block().consPower.capacity;
|
||||
float total = payload.build.power.status * cap;
|
||||
float unloaded = Math.min(maxPowerUnload * edelta(), total);
|
||||
lastOutputPower = unloaded;
|
||||
|
||||
@@ -39,9 +39,9 @@ public class Battery extends PowerDistributor{
|
||||
@Override
|
||||
public void overwrote(Seq<Building> previous){
|
||||
for(Building other : previous){
|
||||
if(other.power != null && other.block.consumes.hasPower() && other.block.consumes.getPower().buffered){
|
||||
float amount = other.block.consumes.getPower().capacity * other.power.status;
|
||||
power.status = Mathf.clamp(power.status + amount / block.consumes.getPower().capacity);
|
||||
if(other.power != null && other.block.consPower != null && other.block.consPower.buffered){
|
||||
float amount = other.block.consPower.capacity * other.power.status;
|
||||
power.status = Mathf.clamp(power.status + amount / consPower.capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class BeamNode extends PowerBlock{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("power", PowerNode.makePowerBalance());
|
||||
bars.add("batteries", PowerNode.makeBatteryBalance());
|
||||
addBar("power", PowerNode.makePowerBalance());
|
||||
addBar("batteries", PowerNode.makeBatteryBalance());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,9 +50,9 @@ public class ImpactReactor extends PowerGenerator{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("poweroutput", (GeneratorBuild entity) -> new Bar(() ->
|
||||
addBar("poweroutput", (GeneratorBuild entity) -> new Bar(() ->
|
||||
Core.bundle.format("bar.poweroutput",
|
||||
Strings.fixed(Math.max(entity.getPowerProduction() - consumes.getPower().usage, 0) * 60 * entity.timeScale, 1)),
|
||||
Strings.fixed(Math.max(entity.getPowerProduction() - consPower.usage, 0) * 60 * entity.timeScale, 1)),
|
||||
() -> Pal.powerBar,
|
||||
() -> entity.productionEfficiency));
|
||||
}
|
||||
@@ -77,14 +77,14 @@ public class ImpactReactor extends PowerGenerator{
|
||||
@Override
|
||||
public void updateTile(){
|
||||
if(consValid() && power.status >= 0.99f){
|
||||
boolean prevOut = getPowerProduction() <= consumes.getPower().requestedPower(this);
|
||||
boolean prevOut = getPowerProduction() <= consPower.requestedPower(this);
|
||||
|
||||
warmup = Mathf.lerpDelta(warmup, 1f, warmupSpeed * timeScale);
|
||||
if(Mathf.equal(warmup, 1f, 0.001f)){
|
||||
warmup = 1f;
|
||||
}
|
||||
|
||||
if(!prevOut && (getPowerProduction() > consumes.getPower().requestedPower(this))){
|
||||
if(!prevOut && (getPowerProduction() > consPower.requestedPower(this))){
|
||||
Events.fire(Trigger.impactPower);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import static mindustry.Vars.*;
|
||||
* Power generation block which can use items, liquids or both as input sources for power production.
|
||||
* Liquids will take priority over items.
|
||||
*/
|
||||
//TODO remove
|
||||
@Deprecated
|
||||
public class ItemLiquidGenerator extends PowerGenerator{
|
||||
public float minItemEfficiency = 0.2f;
|
||||
/** The time in number of ticks during which a single item will produce power. */
|
||||
@@ -49,11 +51,11 @@ public class ItemLiquidGenerator extends PowerGenerator{
|
||||
|
||||
protected void setDefaults(){
|
||||
if(hasItems){
|
||||
consumes.add(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true, false);
|
||||
consume(new ConsumeItemFilter(item -> getItemEfficiency(item) >= minItemEfficiency)).update(false).optional(true, false);
|
||||
}
|
||||
|
||||
if(hasLiquids){
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, maxLiquidGenerate)).update(false).optional(true, false);
|
||||
consume(new ConsumeLiquidFilter(liquid -> getLiquidEfficiency(liquid) >= minLiquidEfficiency, maxLiquidGenerate)).update(false).optional(true, false);
|
||||
}
|
||||
|
||||
defaults = true;
|
||||
|
||||
@@ -17,7 +17,6 @@ import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -44,6 +43,8 @@ public class NuclearReactor extends PowerGenerator{
|
||||
/** heat removed per unit of coolant */
|
||||
public float coolantPower = 0.5f;
|
||||
|
||||
public Item fuelItem = Items.thorium;
|
||||
|
||||
public @Load("@-top") TextureRegion topRegion;
|
||||
public @Load("@-lights") TextureRegion lightsRegion;
|
||||
|
||||
@@ -71,7 +72,7 @@ public class NuclearReactor extends PowerGenerator{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("heat", (NuclearReactorBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat));
|
||||
addBar("heat", (NuclearReactorBuild entity) -> new Bar("bar.heat", Pal.lightOrange, () -> entity.heat));
|
||||
}
|
||||
|
||||
public class NuclearReactorBuild extends GeneratorBuild{
|
||||
@@ -80,10 +81,7 @@ public class NuclearReactor extends PowerGenerator{
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
ConsumeLiquid cliquid = consumes.get(ConsumeType.liquid);
|
||||
Item item = consumes.getItem().items[0].item;
|
||||
|
||||
int fuel = items.get(item);
|
||||
int fuel = items.get(fuelItem);
|
||||
float fullness = (float)fuel / itemCapacity;
|
||||
productionEfficiency = fullness;
|
||||
|
||||
@@ -97,12 +95,10 @@ public class NuclearReactor extends PowerGenerator{
|
||||
productionEfficiency = 0f;
|
||||
}
|
||||
|
||||
Liquid liquid = cliquid.liquid;
|
||||
|
||||
if(heat > 0){
|
||||
float maxUsed = Math.min(liquids.get(liquid), heat / coolantPower);
|
||||
float maxUsed = Math.min(liquids.currentAmount(), heat / coolantPower);
|
||||
heat -= maxUsed * coolantPower;
|
||||
liquids.remove(liquid, maxUsed);
|
||||
liquids.remove(liquids.current(), maxUsed);
|
||||
}
|
||||
|
||||
if(heat > smokeThreshold){
|
||||
@@ -133,7 +129,7 @@ public class NuclearReactor extends PowerGenerator{
|
||||
|
||||
Sounds.explosionbig.at(this);
|
||||
|
||||
int fuel = items.get(consumes.<ConsumeItems>get(ConsumeType.item).items[0].item);
|
||||
int fuel = items.get(fuelItem);
|
||||
|
||||
if((fuel < 5 && heat < 0.5f) || !state.rules.reactorExplosions) return;
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ public class PowerDiode extends Block{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("back", entity -> new Bar("bar.input", Pal.powerBar, () -> bar(entity.back())));
|
||||
bars.add("front", entity -> new Bar("bar.output", Pal.powerBar, () -> bar(entity.front())));
|
||||
addBar("back", entity -> new Bar("bar.input", Pal.powerBar, () -> bar(entity.back())));
|
||||
addBar("front", entity -> new Bar("bar.output", Pal.powerBar, () -> bar(entity.front())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,8 +47,8 @@ public class PowerGenerator extends PowerDistributor{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
if(hasPower && outputsPower && !consumes.hasPower()){
|
||||
bars.add("power", (GeneratorBuild entity) -> new Bar(() ->
|
||||
if(hasPower && outputsPower && consPower != null){
|
||||
addBar("power", (GeneratorBuild entity) -> new Bar(() ->
|
||||
Core.bundle.format("bar.poweroutput",
|
||||
Strings.fixed(entity.getPowerProduction() * 60 * entity.timeScale(), 1)),
|
||||
() -> Pal.powerBar,
|
||||
|
||||
@@ -95,12 +95,9 @@ public class PowerGraph{
|
||||
public float getPowerNeeded(){
|
||||
float powerNeeded = 0f;
|
||||
for(Building consumer : consumers){
|
||||
Consumers consumes = consumer.block.consumes;
|
||||
if(consumes.hasPower()){
|
||||
ConsumePower consumePower = consumes.getPower();
|
||||
if(otherConsumersAreValid(consumer, consumePower)){
|
||||
powerNeeded += consumePower.requestedPower(consumer) * consumer.delta();
|
||||
}
|
||||
ConsumePower consumePower = consumer.block.consPower;
|
||||
if(otherConsumersAreValid(consumer, consumePower)){
|
||||
powerNeeded += consumePower.requestedPower(consumer) * consumer.delta();
|
||||
}
|
||||
}
|
||||
return powerNeeded;
|
||||
@@ -109,9 +106,8 @@ public class PowerGraph{
|
||||
public float getBatteryStored(){
|
||||
float totalAccumulator = 0f;
|
||||
for(Building battery : batteries){
|
||||
Consumers consumes = battery.block.consumes;
|
||||
if(battery.enabled && consumes.hasPower()){
|
||||
totalAccumulator += battery.power.status * consumes.getPower().capacity;
|
||||
if(battery.enabled){
|
||||
totalAccumulator += battery.power.status * battery.block.consPower.capacity;
|
||||
}
|
||||
}
|
||||
return totalAccumulator;
|
||||
@@ -120,9 +116,8 @@ public class PowerGraph{
|
||||
public float getBatteryCapacity(){
|
||||
float totalCapacity = 0f;
|
||||
for(Building battery : batteries){
|
||||
if(battery.enabled && battery.block.consumes.hasPower()){
|
||||
ConsumePower power = battery.block.consumes.getPower();
|
||||
totalCapacity += (1f - battery.power.status) * power.capacity;
|
||||
if(battery.enabled){
|
||||
totalCapacity += (1f - battery.power.status) * battery.block.consPower.capacity;
|
||||
}
|
||||
}
|
||||
return totalCapacity;
|
||||
@@ -131,8 +126,8 @@ public class PowerGraph{
|
||||
public float getTotalBatteryCapacity(){
|
||||
float totalCapacity = 0f;
|
||||
for(Building battery : batteries){
|
||||
if(battery.enabled && battery.block.consumes.hasPower()){
|
||||
totalCapacity += battery.block.consumes.getPower().capacity;
|
||||
if(battery.enabled){
|
||||
totalCapacity += battery.block.consPower.capacity;
|
||||
}
|
||||
}
|
||||
return totalCapacity;
|
||||
@@ -145,8 +140,7 @@ public class PowerGraph{
|
||||
float used = Math.min(stored, needed);
|
||||
float consumedPowerPercentage = Math.min(1.0f, needed / stored);
|
||||
for(Building battery : batteries){
|
||||
Consumers consumes = battery.block.consumes;
|
||||
if(battery.enabled && consumes.hasPower()){
|
||||
if(battery.enabled){
|
||||
battery.power.status *= (1f-consumedPowerPercentage);
|
||||
}
|
||||
}
|
||||
@@ -160,12 +154,9 @@ public class PowerGraph{
|
||||
if(Mathf.equal(capacity, 0f)) return 0f;
|
||||
|
||||
for(Building battery : batteries){
|
||||
Consumers consumes = battery.block.consumes;
|
||||
if(battery.enabled && consumes.hasPower()){
|
||||
ConsumePower consumePower = consumes.getPower();
|
||||
if(consumePower.capacity > 0f){
|
||||
battery.power.status += (1f- battery.power.status) * chargedPercent;
|
||||
}
|
||||
//TODO why would it be 0
|
||||
if(battery.enabled && battery.block.consPower.capacity > 0f){
|
||||
battery.power.status += (1f - battery.power.status) * chargedPercent;
|
||||
}
|
||||
}
|
||||
return Math.min(excess, capacity);
|
||||
@@ -175,25 +166,23 @@ public class PowerGraph{
|
||||
//distribute even if not needed. this is because some might be requiring power but not using it; it updates consumers
|
||||
float coverage = Mathf.zero(needed) && Mathf.zero(produced) && !charged && Mathf.zero(lastPowerStored) ? 0f : Mathf.zero(needed) ? 1f : Math.min(1, produced / needed);
|
||||
for(Building consumer : consumers){
|
||||
Consumers consumes = consumer.block.consumes;
|
||||
if(consumes.hasPower()){
|
||||
ConsumePower consumePower = consumes.getPower();
|
||||
if(consumePower.buffered){
|
||||
if(!Mathf.zero(consumePower.capacity)){
|
||||
// Add an equal percentage of power to all buffers, based on the global power coverage in this graph
|
||||
float maximumRate = consumePower.requestedPower(consumer) * coverage * consumer.delta();
|
||||
consumer.power.status = Mathf.clamp(consumer.power.status + maximumRate / consumePower.capacity);
|
||||
}
|
||||
}else{
|
||||
//valid consumers get power as usual
|
||||
if(otherConsumersAreValid(consumer, consumePower)){
|
||||
consumer.power.status = coverage;
|
||||
}else{ //invalid consumers get an estimate, if they were to activate
|
||||
consumer.power.status = Math.min(1, produced / (needed + consumePower.usage * consumer.delta()));
|
||||
//just in case
|
||||
if(Float.isNaN(consumer.power.status)){
|
||||
consumer.power.status = 0f;
|
||||
}
|
||||
//TODO how would it even be null
|
||||
ConsumePower cons = consumer.block.consPower;
|
||||
if(cons.buffered){
|
||||
if(!Mathf.zero(cons.capacity)){
|
||||
// Add an equal percentage of power to all buffers, based on the global power coverage in this graph
|
||||
float maximumRate = cons.requestedPower(consumer) * coverage * consumer.delta();
|
||||
consumer.power.status = Mathf.clamp(consumer.power.status + maximumRate / cons.capacity);
|
||||
}
|
||||
}else{
|
||||
//valid consumers get power as usual
|
||||
if(otherConsumersAreValid(consumer, cons)){
|
||||
consumer.power.status = coverage;
|
||||
}else{ //invalid consumers get an estimate, if they were to activate
|
||||
consumer.power.status = Math.min(1, produced / (needed + cons.usage * consumer.delta()));
|
||||
//just in case
|
||||
if(Float.isNaN(consumer.power.status)){
|
||||
consumer.power.status = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,15 +257,17 @@ public class PowerGraph{
|
||||
//there's something to update, add the entity
|
||||
entity.add();
|
||||
|
||||
if(build.block.outputsPower && build.block.consumesPower && !build.block.consumes.getPower().buffered){
|
||||
producers.add(build);
|
||||
consumers.add(build);
|
||||
}else if(build.block.outputsPower && build.block.consumesPower){
|
||||
batteries.add(build);
|
||||
}else if(build.block.outputsPower){
|
||||
producers.add(build);
|
||||
}else if(build.block.consumesPower){
|
||||
consumers.add(build);
|
||||
if(build.block.consPower != null){
|
||||
if(build.block.outputsPower && build.block.consumesPower && !build.block.consPower.buffered){
|
||||
producers.add(build);
|
||||
consumers.add(build);
|
||||
}else if(build.block.outputsPower && build.block.consumesPower){
|
||||
batteries.add(build);
|
||||
}else if(build.block.outputsPower){
|
||||
producers.add(build);
|
||||
}else if(build.block.consumesPower){
|
||||
consumers.add(build);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,8 +343,8 @@ public class PowerGraph{
|
||||
}
|
||||
|
||||
private boolean otherConsumersAreValid(Building build, Consume consumePower){
|
||||
for(Consume cons : build.block.consumes.all){
|
||||
if(cons != consumePower && !cons.isOptional() && !cons.valid(build)){
|
||||
for(Consume cons : build.block.consumers){
|
||||
if(cons != consumePower && !cons.optional && !cons.valid(build)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@ public class PowerNode extends PowerBlock{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("power", makePowerBalance());
|
||||
bars.add("batteries", makeBatteryBalance());
|
||||
addBar("power", makePowerBalance());
|
||||
addBar("batteries", makeBatteryBalance());
|
||||
|
||||
bars.add("connections", entity -> new Bar(() ->
|
||||
addBar("connections", entity -> new Bar(() ->
|
||||
Core.bundle.format("bar.powerlines", entity.power.links.size, maxNodes),
|
||||
() -> Pal.items,
|
||||
() -> (float)entity.power.links.size / (float)maxNodes
|
||||
@@ -217,7 +217,7 @@ public class PowerNode extends PowerBlock{
|
||||
return t != null && t.build == other;
|
||||
});
|
||||
|
||||
tempTileEnts.clear();
|
||||
tempBuilds.clear();
|
||||
graphs.clear();
|
||||
|
||||
//add conducting graphs to prevent double link
|
||||
@@ -234,12 +234,12 @@ public class PowerNode extends PowerBlock{
|
||||
|
||||
Geometry.circle(tile.x, tile.y, (int)(laserRange + 2), (x, y) -> {
|
||||
Building other = world.build(x, y);
|
||||
if(valid.get(other) && !tempTileEnts.contains(other)){
|
||||
tempTileEnts.add(other);
|
||||
if(valid.get(other) && !tempBuilds.contains(other)){
|
||||
tempBuilds.add(other);
|
||||
}
|
||||
});
|
||||
|
||||
tempTileEnts.sort((a, b) -> {
|
||||
tempBuilds.sort((a, b) -> {
|
||||
int type = -Boolean.compare(a.block instanceof PowerNode, b.block instanceof PowerNode);
|
||||
if(type != 0) return type;
|
||||
return Float.compare(a.dst2(tile), b.dst2(tile));
|
||||
@@ -247,7 +247,7 @@ public class PowerNode extends PowerBlock{
|
||||
|
||||
returnInt = 0;
|
||||
|
||||
tempTileEnts.each(valid, t -> {
|
||||
tempBuilds.each(valid, t -> {
|
||||
if(returnInt ++ < maxNodes){
|
||||
graphs.add(t.power.graph);
|
||||
others.get(t);
|
||||
@@ -269,7 +269,7 @@ public class PowerNode extends PowerBlock{
|
||||
return t != null && t.build == other;
|
||||
});
|
||||
|
||||
tempTileEnts.clear();
|
||||
tempBuilds.clear();
|
||||
graphs.clear();
|
||||
|
||||
//add conducting graphs to prevent double link
|
||||
@@ -287,18 +287,18 @@ public class PowerNode extends PowerBlock{
|
||||
|
||||
Geometry.circle(tile.x, tile.y, 13, (x, y) -> {
|
||||
Building other = world.build(x, y);
|
||||
if(valid.get(other) && !tempTileEnts.contains(other)){
|
||||
tempTileEnts.add(other);
|
||||
if(valid.get(other) && !tempBuilds.contains(other)){
|
||||
tempBuilds.add(other);
|
||||
}
|
||||
});
|
||||
|
||||
tempTileEnts.sort((a, b) -> {
|
||||
tempBuilds.sort((a, b) -> {
|
||||
int type = -Boolean.compare(a.block instanceof PowerNode, b.block instanceof PowerNode);
|
||||
if(type != 0) return type;
|
||||
return Float.compare(a.dst2(tile), b.dst2(tile));
|
||||
});
|
||||
|
||||
tempTileEnts.each(valid, t -> {
|
||||
tempBuilds.each(valid, t -> {
|
||||
graphs.add(t.power.graph);
|
||||
others.get(t);
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AttributeCrafter extends GenericCrafter{
|
||||
|
||||
if(!displayEfficiency) return;
|
||||
|
||||
bars.add("efficiency", (AttributeCrafterBuild entity) ->
|
||||
addBar("efficiency", (AttributeCrafterBuild entity) ->
|
||||
new Bar(() ->
|
||||
Core.bundle.format("bar.efficiency", (int)(entity.efficiencyScale() * 100 * displayEfficiencyScale)),
|
||||
() -> Pal.lightOrange,
|
||||
|
||||
@@ -73,7 +73,7 @@ public class BeamDrill extends Block{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("drillspeed", (BeamDrillBuild e) ->
|
||||
addBar("drillspeed", (BeamDrillBuild e) ->
|
||||
new Bar(() -> Core.bundle.format("bar.drillspeed", Strings.fixed(e.lastDrillSpeed * 60, 2)), () -> Pal.ammo, () -> e.warmup));
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ public class Drill extends Block{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("drillspeed", (DrillBuild e) ->
|
||||
addBar("drillspeed", (DrillBuild e) ->
|
||||
new Bar(() -> Core.bundle.format("bar.drillspeed", Strings.fixed(e.lastDrillSpeed * 60 * e.timeScale, 2)), () -> Pal.ammo, () -> e.warmup));
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public class GenericCrafter extends Block{
|
||||
//set up liquid bars for liquid outputs
|
||||
if(outputLiquids != null && outputLiquids.length > 0){
|
||||
//no need for dynamic liquid bar
|
||||
bars.remove("liquid");
|
||||
removeBar("liquid");
|
||||
|
||||
//then display output buffer
|
||||
for(var stack : outputLiquids){
|
||||
|
||||
@@ -24,7 +24,7 @@ public class HeatCrafter extends GenericCrafter{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("heat", (HeatCrafterBuild entity) ->
|
||||
addBar("heat", (HeatCrafterBuild entity) ->
|
||||
new Bar(() ->
|
||||
Core.bundle.format("bar.heatpercent", (int)entity.heat, (int)(entity.efficiencyScale() * 100)),
|
||||
() -> Pal.lightOrange,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package mindustry.world.blocks.production;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class LiquidConverter extends GenericCrafter{
|
||||
protected @Nullable ConsumeLiquid consumer;
|
||||
|
||||
public LiquidConverter(String name){
|
||||
super(name);
|
||||
@@ -18,13 +20,11 @@ public class LiquidConverter extends GenericCrafter{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
if(!consumes.has(ConsumeType.liquid) || !(consumes.get(ConsumeType.liquid) instanceof ConsumeLiquid)){
|
||||
throw new RuntimeException("LiquidsConverters must have a ConsumeLiquid. Note that filters are not supported.");
|
||||
}
|
||||
|
||||
ConsumeLiquid cl = consumes.get(ConsumeType.liquid);
|
||||
cl.update(false);
|
||||
super.init();
|
||||
|
||||
consumer = findConsumer(b -> b instanceof ConsumeLiquid);
|
||||
if(consumer == null) throw new RuntimeException("LiquidConverters must have a ConsumeLiquid.");
|
||||
consumer.update = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,20 +44,18 @@ public class LiquidConverter extends GenericCrafter{
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
ConsumeLiquid cl = consumes.get(ConsumeType.liquid);
|
||||
|
||||
if(consValid()){
|
||||
if(Mathf.chanceDelta(updateEffectChance)){
|
||||
updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4));
|
||||
}
|
||||
|
||||
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);
|
||||
float use = Math.min(cl.amount * edelta(), liquidCapacity - liquids.get(outputLiquid.liquid));
|
||||
float ratio = outputLiquid.amount / cl.amount;
|
||||
float use = Math.min(consumer.amount * edelta(), liquidCapacity - liquids.get(outputLiquid.liquid));
|
||||
float ratio = outputLiquid.amount / consumer.amount;
|
||||
|
||||
liquids.remove(cl.liquid, Math.min(use, liquids.get(cl.liquid)));
|
||||
liquids.remove(consumer.liquid, Math.min(use, liquids.get(consumer.liquid)));
|
||||
|
||||
progress += use / cl.amount;
|
||||
progress += use / consumer.amount;
|
||||
liquids.add(outputLiquid.liquid, use * ratio);
|
||||
if(progress >= craftTime){
|
||||
consume();
|
||||
|
||||
@@ -18,6 +18,8 @@ import mindustry.world.meta.*;
|
||||
* Extracts a random list of items from an input item and an input liquid.
|
||||
*/
|
||||
public class Separator extends Block{
|
||||
protected @Nullable ConsumeItems consItems;
|
||||
|
||||
public ItemStack[] results;
|
||||
public float craftTime;
|
||||
|
||||
@@ -43,6 +45,12 @@ public class Separator extends Block{
|
||||
stats.add(Stat.productionTime, craftTime / 60f, StatUnit.seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
consItems = findConsumer(c -> c instanceof ConsumeItems);
|
||||
}
|
||||
|
||||
public class SeparatorBuild extends Building{
|
||||
public float progress;
|
||||
public float totalProgress;
|
||||
@@ -63,9 +71,8 @@ public class Separator extends Block{
|
||||
public boolean shouldConsume(){
|
||||
int total = items.total();
|
||||
//very inefficient way of allowing separators to ignore input buffer storage
|
||||
if(consumes.has(ConsumeType.item) && consumes.get(ConsumeType.item) instanceof ConsumeItems){
|
||||
ConsumeItems c = consumes.get(ConsumeType.item);
|
||||
for(ItemStack stack : c.items){
|
||||
if(consItems != null){
|
||||
for(ItemStack stack : consItems.items){
|
||||
total -= items.get(stack.item);
|
||||
}
|
||||
}
|
||||
@@ -132,7 +139,7 @@ public class Separator extends Block{
|
||||
|
||||
@Override
|
||||
public boolean canDump(Building to, Item item){
|
||||
return !consumes.consumesItem(item);
|
||||
return !consumesItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,7 +47,7 @@ public class SolidPump extends Pump{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.add("efficiency", (SolidPumpBuild entity) -> new Bar(() -> Core.bundle.formatFloat("bar.pumpspeed",
|
||||
addBar("efficiency", (SolidPumpBuild entity) -> new Bar(() -> Core.bundle.formatFloat("bar.pumpspeed",
|
||||
entity.lastPump * 60, 1),
|
||||
() -> Pal.ammo,
|
||||
() -> entity.warmup * entity.efficiency()));
|
||||
|
||||
@@ -54,7 +54,7 @@ public class WallCrafter extends Block{
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
bars.add("drillspeed", (WallCrafterBuild e) ->
|
||||
addBar("drillspeed", (WallCrafterBuild e) ->
|
||||
new Bar(() -> Core.bundle.format("bar.drillspeed", Strings.fixed(e.lastEfficiency * 60 / drillTime, 2)), () -> Pal.ammo, () -> e.warmup));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ItemSource extends Block{
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
bars.remove("items");
|
||||
removeBar("items");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user