DEATH AND DESTRUCTION

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

View File

@@ -1,44 +0,0 @@
package mindustry.ai.formations;
import arc.struct.*;
/**
* {@code BoundedSlotAssignmentStrategy} is an abstract implementation of {@link SlotAssignmentStrategy} that supports roles.
* Generally speaking, there are hard and soft roles. Hard roles cannot be broken, soft roles can.
* <p>
* This abstract class provides an implementation of the {@link #calculateNumberOfSlots(Seq) calculateNumberOfSlots} method that
* is more general (and costly) than the simplified implementation in {@link FreeSlotAssignmentStrategy}. It scans the assignment
* list to find the number of filled slots, which is the highest slot number in the assignments.
* @author davebaol
*/
public abstract class BoundedSlotAssignmentStrategy implements SlotAssignmentStrategy{
@Override
public abstract void updateSlotAssignments(Seq<SlotAssignment> assignments);
@Override
public int calculateNumberOfSlots(Seq<SlotAssignment> assignments){
// Find the number of filled slots: it will be the
// highest slot number in the assignments
int filledSlots = -1;
for(int i = 0; i < assignments.size; i++){
SlotAssignment assignment = assignments.get(i);
if(assignment.slotNumber >= filledSlots) filledSlots = assignment.slotNumber;
}
// Add one to go from the index of the highest slot to the number of slots needed.
return filledSlots + 1;
}
@Override
public void removeSlotAssignment(Seq<SlotAssignment> assignments, int index){
int sn = assignments.get(index).slotNumber;
for(int i = 0; i < assignments.size; i++){
SlotAssignment sa = assignments.get(i);
if(sa.slotNumber >= sn) sa.slotNumber--;
}
assignments.remove(index);
}
}

View File

@@ -1,51 +0,0 @@
package mindustry.ai.formations;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
public class DistanceAssignmentStrategy implements SlotAssignmentStrategy{
private final Vec3 vec = new Vec3();
private final FormationPattern form;
public DistanceAssignmentStrategy(FormationPattern form){
this.form = form;
}
@Override
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
IntSeq slots = IntSeq.range(0, assignments.size);
for(SlotAssignment slot : assignments){
int mindex = 0;
float mcost = Float.MAX_VALUE;
for(int i = 0; i < slots.size; i++){
float cost = cost(slot.member, slots.get(i));
if(cost < mcost){
mcost = cost;
mindex = i;
}
}
slot.slotNumber = slots.get(mindex);
slots.removeIndex(mindex);
}
}
@Override
public int calculateNumberOfSlots(Seq<SlotAssignment> assignments){
return assignments.size;
}
@Override
public void removeSlotAssignment(Seq<SlotAssignment> assignments, int index){
assignments.remove(index);
}
float cost(FormationMember member, int slot){
form.calculateSlotLocation(vec, slot);
return Mathf.dst2(member.formationPos().x, member.formationPos().y, vec.x, vec.y);
}
}

View File

@@ -1,214 +0,0 @@
package mindustry.ai.formations;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
/**
* A {@code Formation} coordinates the movement of a group of characters so that they retain some group organization. Characters
* belonging to a formation must implement the {@link FormationMember} interface. At its simplest, a formation can consist of
* moving in a fixed geometric pattern such as a V or line abreast, but it is not limited to that. Formations can also make use of
* the environment. Squads of characters can move between cover points using formation steering with only minor modifications, for
* example.
* <p>
* Formation motion is used in team sports games, squad-based games, real-time strategy games, and sometimes in first-person
* shooters, driving games, and action adventures too. It is a simple and flexible technique that is much quicker to write and
* execute and can produce much more stable behavior than collaborative tactical decision making.
* @author davebaol
*/
public class Formation{
/** A list of slots assignments. */
public Seq<SlotAssignment> slotAssignments;
/** The anchor point of this formation. */
public Vec3 anchor;
/** The formation pattern */
public FormationPattern pattern;
/** The strategy used to assign a member to his slot */
public SlotAssignmentStrategy slotAssignmentStrategy;
/** The formation motion moderator */
public FormationMotionModerator motionModerator;
private final Vec2 positionOffset;
private final Mat orientationMatrix = new Mat();
/** The location representing the drift offset for the currently filled slots. */
private final Vec3 driftOffset;
/**
* Creates a {@code Formation} for the specified {@code pattern} using a {@link FreeSlotAssignmentStrategy} and no motion
* moderator.
* @param anchor the anchor point of this formation, Cannot be {@code null}.
* @param pattern the pattern of this formation
* @throws IllegalArgumentException if the anchor point is {@code null}
*/
public Formation(Vec3 anchor, FormationPattern pattern){
this(anchor, pattern, new FreeSlotAssignmentStrategy(), null);
}
/**
* Creates a {@code Formation} for the specified {@code pattern} and {@code slotAssignmentStrategy} using no motion moderator.
* @param anchor the anchor point of this formation, Cannot be {@code null}.
* @param pattern the pattern of this formation
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
* @throws IllegalArgumentException if the anchor point is {@code null}
*/
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy){
this(anchor, pattern, slotAssignmentStrategy, null);
}
/**
* Creates a {@code Formation} for the specified {@code pattern}, {@code slotAssignmentStrategy} and {@code moderator}.
* @param anchor the anchor point of this formation, Cannot be {@code null}.
* @param pattern the pattern of this formation
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
* @param motionModerator the motion moderator. Can be {@code null} if moderation is not needed
* @throws IllegalArgumentException if the anchor point is {@code null}
*/
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy,
FormationMotionModerator motionModerator){
if(anchor == null) throw new IllegalArgumentException("The anchor point cannot be null");
this.anchor = anchor;
this.pattern = pattern;
this.slotAssignmentStrategy = slotAssignmentStrategy;
this.motionModerator = motionModerator;
this.slotAssignments = new Seq<>();
this.driftOffset = new Vec3();
this.positionOffset = new Vec2(anchor.x, anchor.y).cpy();
}
/** Updates the assignment of members to slots */
public void updateSlotAssignments(){
pattern.slots = slotAssignments.size;
// Apply the strategy to update slot assignments
slotAssignmentStrategy.updateSlotAssignments(slotAssignments);
// Set the newly calculated number of slots
pattern.slots = slotAssignmentStrategy.calculateNumberOfSlots(slotAssignments);
// Update the drift offset if a motion moderator is set
if(motionModerator != null) motionModerator.calculateDriftOffset(driftOffset, slotAssignments, pattern);
}
/**
* Changes the pattern of this formation and updates slot assignments if the number of member is supported by the given
* pattern.
* @param pattern the pattern to set
* @return {@code true} if the pattern has effectively changed; {@code false} otherwise.
*/
public boolean changePattern(FormationPattern pattern){
// Find out how many slots we have occupied
int occupiedSlots = slotAssignments.size;
// Check if the pattern supports one more slot
if(pattern.supportsSlots(occupiedSlots)){
this.pattern = pattern;
// Update the slot assignments and return success
updateSlotAssignments();
return true;
}
return false;
}
/** Much more efficient than adding a single member.
* @return number of members added. */
public int addMembers(Iterable<? extends FormationMember> members){
int added = 0;
for(FormationMember member : members){
if(pattern.supportsSlots(slotAssignments.size + 1)){
slotAssignments.add(new SlotAssignment(member, slotAssignments.size));
added ++;
}
}
updateSlotAssignments();
return added;
}
/**
* Adds a new member to the first available slot and updates slot assignments if the number of member is supported by the
* current pattern.
* @param member the member to add
* @return {@code false} if no more slots are available; {@code true} otherwise.
*/
public boolean addMember(FormationMember member){
// Check if the pattern supports one more slot
if(pattern.supportsSlots(slotAssignments.size + 1)){
// Add a new slot assignment
slotAssignments.add(new SlotAssignment(member, slotAssignments.size));
// Update the slot assignments and return success
updateSlotAssignments();
return true;
}
return false;
}
/**
* Removes a member from its slot and updates slot assignments.
* @param member the member to remove
*/
public void removeMember(FormationMember member){
// Find the member's slot
int slot = findMemberSlot(member);
// Make sure we've found a valid result
if(slot >= 0){
// Remove the slot
// slotAssignments.removeIndex(slot);
slotAssignmentStrategy.removeSlotAssignment(slotAssignments, slot);
// Update the assignments
updateSlotAssignments();
}
}
private int findMemberSlot(FormationMember member){
for(int i = 0; i < slotAssignments.size; i++){
if(slotAssignments.get(i).member == member) return i;
}
return -1;
}
/** Writes new slot locations to each member */
public void updateSlots(){
positionOffset.set(anchor);
float orientationOffset = anchor.z;
if(motionModerator != null){
positionOffset.sub(driftOffset);
orientationOffset -= driftOffset.z;
}
// Get the orientation of the anchor point as a matrix
orientationMatrix.idt().rotate(anchor.z);
// Go through each member in turn
for(int i = 0; i < slotAssignments.size; i++){
SlotAssignment slotAssignment = slotAssignments.get(i);
// Retrieve the location reference of the formation member to calculate the new value
Vec3 relativeLoc = slotAssignment.member.formationPos();
float z = relativeLoc.z;
// Ask for the location of the slot relative to the anchor point
pattern.calculateSlotLocation(relativeLoc, slotAssignment.slotNumber);
// Transform it by the anchor point's position and orientation
relativeLoc.mul(orientationMatrix);
// Add the anchor and drift components
relativeLoc.add(positionOffset.x, positionOffset.y, 0);
relativeLoc.z = z + orientationOffset;
}
// Possibly reset the anchor point if a moderator is set
if(motionModerator != null){
motionModerator.updateAnchorPoint(anchor);
}
}
}

View File

@@ -1,16 +0,0 @@
package mindustry.ai.formations;
import arc.math.geom.*;
/**
* Game characters coordinated by a {@link Formation} must implement this interface. Any {@code FormationMember} has a target
* location which is the place where it should be in order to stay in formation. This target location is calculated by the
* formation itself.
* @author davebaol
*/
public interface FormationMember{
/** Returns the target location of this formation member. */
Vec3 formationPos();
float formationSize();
}

View File

@@ -1,52 +0,0 @@
package mindustry.ai.formations;
import arc.math.geom.*;
import arc.struct.*;
/**
* A {@code FormationMotionModerator} moderates the movement of the formation based on the current positions of the members in its
* slots: in effect to keep the anchor point on a leash. If the members in the slots are having trouble reaching their targets,
* then the formation as a whole should be held back to give them a chance to catch up.
* @author davebaol
*/
public abstract class FormationMotionModerator{
private Vec3 tempLocation;
/**
* Update the anchor point to moderate formation motion. This method is called at each frame.
* @param anchor the anchor point
*/
public abstract void updateAnchorPoint(Vec3 anchor);
/**
* Calculates the drift offset when members are in the given set of slots for the specified pattern.
* @param centerOfMass the output location set to the calculated drift offset
* @param slotAssignments the set of slots
* @param pattern the pattern
* @return the given location for chaining.
*/
public Vec3 calculateDriftOffset(Vec3 centerOfMass, Seq<SlotAssignment> slotAssignments, FormationPattern pattern){
// Clear the center of mass
centerOfMass.x = centerOfMass.y = 0;
float centerOfMassOrientation = 0;
// Make sure tempLocation is instantiated
if(tempLocation == null) tempLocation = new Vec3();
// Go through each assignment and add its contribution to the center
float numberOfAssignments = slotAssignments.size;
for(int i = 0; i < numberOfAssignments; i++){
pattern.calculateSlotLocation(tempLocation, slotAssignments.get(i).slotNumber);
centerOfMass.add(tempLocation);
centerOfMassOrientation += tempLocation.z;
}
// Divide through to get the drift offset.
centerOfMass.scl(1f / numberOfAssignments);
centerOfMassOrientation /= numberOfAssignments;
centerOfMass.z = centerOfMassOrientation;
return centerOfMass;
}
}

View File

@@ -1,29 +0,0 @@
package mindustry.ai.formations;
import arc.math.geom.*;
/**
* The {@code FormationPattern} interface represents the shape of a formation and generates the slot offsets, relative to its
* anchor point. Since formations can be scalable the pattern must be able to determine if a given number of slots is supported.
* <p>
* Each particular pattern (such as a V, wedge, circle) needs its own instance of a class that implements this
* {@code FormationPattern} interface.
* @author davebaol
*/
public abstract class FormationPattern{
public int slots;
/** Spacing between members. */
public float spacing = 20f;
/** Returns the location of the given slot index. */
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);
/**
* Returns true if the pattern can support the given number of slots
* @param slotCount the number of slots
* @return {@code true} if this pattern can support the given number of slots; {@code false} othervwise.
*/
public boolean supportsSlots(int slotCount){
return true;
}
}

View File

@@ -1,34 +0,0 @@
package mindustry.ai.formations;
import arc.struct.*;
/**
* {@code FreeSlotAssignmentStrategy} is the simplest implementation of {@link SlotAssignmentStrategy}. It simply go through
* each assignment in the list and assign sequential slot numbers. The number of slots is just the length of the list.
* <p>
* Because each member can occupy any slot this implementation does not support roles.
* @author davebaol
*/
public class FreeSlotAssignmentStrategy implements SlotAssignmentStrategy{
@Override
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
// A very simple assignment algorithm: we simply go through
// each assignment in the list and assign sequential slot numbers
for(int i = 0; i < assignments.size; i++){
assignments.get(i).slotNumber = i;
}
}
@Override
public int calculateNumberOfSlots(Seq<SlotAssignment> assignments){
return assignments.size;
}
@Override
public void removeSlotAssignment(Seq<SlotAssignment> assignments, int index){
assignments.remove(index);
}
}

View File

@@ -1,29 +0,0 @@
package mindustry.ai.formations;
/**
* A {@code SlotAssignment} instance represents the assignment of a single {@link FormationMember} to its slot in the
* {@link Formation}.
* @author davebaol
*/
public class SlotAssignment{
public FormationMember member;
public int slotNumber;
/**
* Creates a {@code SlotAssignment} for the given {@code member}.
* @param member the member of this slot assignment
*/
public SlotAssignment(FormationMember member){
this(member, 0);
}
/**
* Creates a {@code SlotAssignment} for the given {@code member} and {@code slotNumber}.
* @param member the member of this slot assignment
*/
public SlotAssignment(FormationMember member, int slotNumber){
this.member = member;
this.slotNumber = slotNumber;
}
}

View File

@@ -1,20 +0,0 @@
package mindustry.ai.formations;
import arc.struct.*;
/**
* This interface defines how each {@link FormationMember} is assigned to a slot in the {@link Formation}.
* @author davebaol
*/
public interface SlotAssignmentStrategy{
/** Updates the assignment of members to slots */
void updateSlotAssignments(Seq<SlotAssignment> assignments);
/** Calculates the number of slots from the assignment data. */
int calculateNumberOfSlots(Seq<SlotAssignment> assignments);
/** Removes the slot assignment at the specified index. */
void removeSlotAssignment(Seq<SlotAssignment> assignments, int index);
}

View File

@@ -1,164 +0,0 @@
package mindustry.ai.formations;
import arc.struct.*;
import arc.util.*;
/**
* {@code SoftRoleSlotAssignmentStrategy} is a concrete implementation of {@link BoundedSlotAssignmentStrategy} that supports soft
* roles, i.e. roles that can be broken. Rather than a member having a list of roles it can fulfill, it has a set of values
* representing how difficult it would find it to fulfill every role. The value is known as the slot cost. To make a slot
* impossible for a member to fill, its slot cost should be infinite (you can even set a threshold to ignore all slots whose cost
* is too high; this will reduce computation time when several costs are exceeding). To make a slot ideal for a member, its slot
* cost should be zero. We can have different levels of unsuitable assignment for one member.
* <p>
* Slot costs do not necessarily have to depend only on the member and the slot roles. They can be generalized to include any
* difficulty a member might have in taking up a slot. If a formation is spread out, for example, a member may choose a slot that
* is close by over a more distant slot. Distance can be directly used as a slot cost.
* <p>
* <b>IMPORTANVec2 NOTES:</b>
* <ul>
* <li>In order for the algorithm to work properly the slot costs can not be negative.</li>
* <li>This algorithm is often not fast enough to be used regularly. However, slot assignment happens relatively seldom (when the
* player selects a new pattern, for example, or adds a member to the formation, or a member is removed from the formation).</li>
* </ul>
* @author davebaol
*/
public class SoftRoleSlotAssignmentStrategy extends BoundedSlotAssignmentStrategy{
protected SlotCostProvider slotCostProvider;
protected float costThreshold;
private BoolSeq filledSlots;
/**
* Creates a {@code SoftRoleSlotAssignmentStrategy} with the given slot cost provider and no cost threshold.
* @param slotCostProvider the slot cost provider
*/
public SoftRoleSlotAssignmentStrategy(SlotCostProvider slotCostProvider){
this(slotCostProvider, Float.POSITIVE_INFINITY);
}
/**
* Creates a {@code SoftRoleSlotAssignmentStrategy} with the given slot cost provider and cost threshold.
* @param slotCostProvider the slot cost provider
* @param costThreshold is a slot-cost limit, beyond which a slot is considered to be too expensive to consider occupying.
*/
public SoftRoleSlotAssignmentStrategy(SlotCostProvider slotCostProvider, float costThreshold){
this.slotCostProvider = slotCostProvider;
this.costThreshold = costThreshold;
this.filledSlots = new BoolSeq();
}
@Override
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
// Holds a list of member and slot data for each member.
Seq<MemberAndSlots> memberData = new Seq<>();
// Compile the member data
int numberOfAssignments = assignments.size;
for(int i = 0; i < numberOfAssignments; i++){
SlotAssignment assignment = assignments.get(i);
// Create a new member datum, and fill it
MemberAndSlots datum = new MemberAndSlots(assignment.member);
// Add each valid slot to it
for(int j = 0; j < numberOfAssignments; j++){
// Get the cost of the slot
float cost = slotCostProvider.getCost(assignment.member, j);
// Make sure the slot is valid
if(cost >= costThreshold) continue;
SlotAssignment slot = assignments.get(j);
// Store the slot information
CostAndSlot slotDatum = new CostAndSlot(cost, slot.slotNumber);
datum.costAndSlots.add(slotDatum);
// Add it to the member's ease of assignment
datum.assignmentEase += 1f / (1f + cost);
}
// Add member datum
memberData.add(datum);
}
// Reset the array to keep track of which slots we have already filled.
if(numberOfAssignments > filledSlots.size) filledSlots.ensureCapacity(numberOfAssignments - filledSlots.size);
filledSlots.size = numberOfAssignments;
for(int i = 0; i < numberOfAssignments; i++)
filledSlots.set(i, false);
// Arrange members in order of ease of assignment, with the least easy first.
memberData.sort();
MEMBER_LOOP:
for(int i = 0; i < memberData.size; i++){
MemberAndSlots memberDatum = memberData.get(i);
// Choose the first slot in the list that is still empty (non-filled)
memberDatum.costAndSlots.sort();
int m = memberDatum.costAndSlots.size;
for(int j = 0; j < m; j++){
int slotNumber = memberDatum.costAndSlots.get(j).slotNumber;
// Check if this slot is valid
if(!filledSlots.get(slotNumber)){
// Fill this slot
SlotAssignment slot = assignments.get(slotNumber);
slot.member = memberDatum.member;
slot.slotNumber = slotNumber;
// Reserve the slot
filledSlots.set(slotNumber, true);
// Go to the next member
continue MEMBER_LOOP;
}
}
// If we reach here, it's because a member has no valid assignment.
//
// TODO
// Some sensible action should be taken, such as reporting to the player.
throw new ArcRuntimeException("SoftRoleSlotAssignmentStrategy cannot find valid slot assignment for member " + memberDatum.member);
}
}
static class CostAndSlot implements Comparable<CostAndSlot>{
float cost;
int slotNumber;
public CostAndSlot(float cost, int slotNumber){
this.cost = cost;
this.slotNumber = slotNumber;
}
@Override
public int compareTo(CostAndSlot other){
return Float.compare(cost, other.cost);
}
}
static class MemberAndSlots implements Comparable<MemberAndSlots>{
FormationMember member;
float assignmentEase;
Seq<CostAndSlot> costAndSlots;
public MemberAndSlots(FormationMember member){
this.member = member;
this.assignmentEase = 0f;
this.costAndSlots = new Seq<>();
}
@Override
public int compareTo(MemberAndSlots other){
return Float.compare(assignmentEase, other.assignmentEase);
}
}
public interface SlotCostProvider{
float getCost(FormationMember member, int slotNumber);
}
}

View File

@@ -1,22 +0,0 @@
package mindustry.ai.formations.patterns;
import arc.math.*;
import arc.math.geom.*;
import mindustry.ai.formations.*;
public class CircleFormation extends FormationPattern{
@Override
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
if(slots > 1){
float angle = (360f * slotNumber) / slots + (slots == 8 ? 22.5f : 0);
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
}else{
outLocation.set(0, spacing * 1.1f, 360f * slotNumber);
}
return outLocation;
}
}

View File

@@ -1,25 +0,0 @@
package mindustry.ai.formations.patterns;
import arc.math.*;
import arc.math.geom.*;
import mindustry.ai.formations.*;
public class SquareFormation extends FormationPattern{
@Override
public Vec3 calculateSlotLocation(Vec3 out, int slot){
//side of each square of formation
int side = Mathf.ceil(Mathf.sqrt(slots + 1));
int cx = slot % side, cy = slot / side;
//don't hog the middle spot
if(cx == side /2 && cy == side/2 && (side%2)==1){
slot = slots;
cx = slot % side;
cy = slot / side;
}
return out.set(cx - (side/2f - 0.5f), cy - (side/2f - 0.5f), 0).scl(spacing);
}
}

View File

@@ -28,12 +28,9 @@ public class DefenderAI extends AIController{
@Override
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
//find unit to follow if not in rally mode
if(command() != UnitCommand.rally){
//Sort by max health and closer target.
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type, (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
if(result != null) return result;
}
//Sort by max health and closer target.
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type, (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
if(result != null) return result;
//find rally point
var block = targetFlag(unit.x, unit.y, BlockFlag.rally, false);

View File

@@ -14,7 +14,7 @@ public class FlyingAI extends AIController{
public void updateMovement(){
unloadPayloads();
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(target != null && unit.hasWeapons()){
if(!unit.type.circleTarget){
moveTo(target, unit.type.range * 0.8f);
unit.lookAt(target);
@@ -23,13 +23,9 @@ public class FlyingAI extends AIController{
}
}
if(target == null && command() == UnitCommand.attack && state.rules.waves && unit.team == state.rules.defaultTeam){
if(target == null && state.rules.waves && unit.team == state.rules.defaultTeam){
moveTo(getClosestSpawner(), state.rules.dropZoneRadius + 130f);
}
if(command() == UnitCommand.rally){
moveTo(targetFlag(unit.x, unit.y, BlockFlag.rally, false), 60f);
}
}
@Override

View File

@@ -1,105 +0,0 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.ai.formations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.blocks.storage.CoreBlock.*;
public class FormationAI extends AIController implements FormationMember{
public Unit leader;
private Vec3 target = new Vec3();
private @Nullable Formation formation;
public FormationAI(Unit leader, Formation formation){
this.leader = leader;
this.formation = formation;
}
@Override
public void init(){
target.set(unit.x, unit.y, 0);
}
@Override
public void updateUnit(){
if(leader == null || leader.dead){
unit.resetController();
return;
}
if(unit.type.canBoost){
unit.elevation = Mathf.approachDelta(unit.elevation,
unit.onSolid() ? 1f : //definitely cannot land
unit.isFlying() && !unit.canLand() ? unit.elevation : //try to maintain altitude
leader.type.canBoost ? leader.elevation : //follow leader
0f,
unit.type.riseSpeed);
}
unit.controlWeapons(true, leader.isShooting);
unit.aim(leader.aimX(), leader.aimY());
if(unit.type.rotateShooting){
unit.lookAt(leader.aimX(), leader.aimY());
}else if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
Vec2 realtarget = vec.set(target).add(leader.vel);
float speed = unit.speed() * Time.delta;
unit.approach(Mathf.arrive(unit.x, unit.y, realtarget.x, realtarget.y, unit.vel, speed, 0f, speed, 1f).scl(1f / Time.delta));
if(unit.canMine() && leader.canMine()){
if(leader.mineTile != null && unit.validMine(leader.mineTile)){
unit.mineTile(leader.mineTile);
CoreBuild core = unit.team.core();
if(core != null && leader.mineTile.drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(leader.mineTile.drop())){
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
unit.clearItem();
}
}
}else{
unit.mineTile(null);
}
}
if(unit.canBuild() && leader.canBuild() && leader.activelyBuilding()){
unit.clearBuilding();
unit.addBuild(leader.buildPlan());
}
}
@Override
public void removed(Unit unit){
if(formation != null){
formation.removeMember(this);
unit.resetController();
}
}
@Override
public float formationSize(){
return unit.hitSize * 1.3f;
}
@Override
public boolean isBeingControlled(Unit player){
return leader == player;
}
@Override
public Vec3 formationPos(){
return target;
}
}

View File

@@ -5,7 +5,6 @@ import mindustry.ai.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -25,7 +24,7 @@ public class GroundAI extends AIController{
}
}
if((core == null || !unit.within(core, unit.type.range * 0.5f)) && command() == UnitCommand.attack){
if((core == null || !unit.within(core, unit.type.range * 0.5f))){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
@@ -42,14 +41,6 @@ public class GroundAI extends AIController{
if(move) pathfind(Pathfinder.fieldCore);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
}
}
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
}

View File

@@ -8,7 +8,6 @@ import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -28,41 +27,31 @@ public class HugAI extends AIController{
}
}
if(command() == UnitCommand.attack){
boolean move = true;
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
//raycast for target
if(target != null && unit.within(target, unit.type.range) && !Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
for(Point2 p : Geometry.d4c){
if(!unit.canPass(x + p.x, y + p.y)){
return true;
}
}
return false;
})){
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
//circle target
unit.movePref(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
}else{
//move toward target in a straight line
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
}
}else if(move){
pathfind(Pathfinder.fieldCore);
}
if(state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
//raycast for target
if(target != null && unit.within(target, unit.type.range) && !Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
for(Point2 p : Geometry.d4c){
if(!unit.canPass(x + p.x, y + p.y)){
return true;
}
}
return false;
})){
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
//circle target
unit.movePref(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
}else{
//move toward target in a straight line
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
}
}else if(move){
pathfind(Pathfinder.fieldCore);
}
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){

View File

@@ -8,7 +8,6 @@ import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -72,7 +71,7 @@ public class LogicAI extends AIController{
case pathfind -> {
Building core = unit.closestEnemyCore();
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
if((core == null || !unit.within(core, unit.range() * 0.5f))){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
@@ -82,14 +81,6 @@ public class LogicAI extends AIController{
if(move) pathfind(Pathfinder.fieldCore);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
}
}
}
case stop -> {
unit.clearBuilding();

View File

@@ -4,7 +4,6 @@ import arc.math.geom.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
@@ -77,26 +76,18 @@ public class SuicideAI extends GroundAI{
}
if(!moveToTarget){
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
boolean move = true;
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
//stop moving toward the drop zone if applicable
if(core == null && state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)){
move = false;
}
}else if(command() == UnitCommand.attack){
boolean move = true;
}
//stop moving toward the drop zone if applicable
if(core == null && state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)){
move = false;
}
}
if(move){
pathfind(Pathfinder.fieldCore);
}
if(move){
pathfind(Pathfinder.fieldCore);
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -63,7 +63,6 @@ public class Planets{
clearSectorOnLose = true;
hiddenItems.addAll(Items.serpuloItems).removeAll(Items.erekirItems);
ruleSetter = r -> {
r.unitCommand = true;
r.placeRangeCheck = true;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,6 @@ public interface UnitController{
return true;
}
default void command(UnitCommand command){
}
default void updateUnit(){
}

View File

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

View File

@@ -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. */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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][];

View File

@@ -42,7 +42,6 @@ public enum LAccess{
flag,
controlled,
controller,
commanded,
name,
payloadCount,
payloadType,

View File

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

View File

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

View File

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

View File

@@ -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 + "'.");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ public class DirectionalUnloader extends Block{
@Override
public void setBars(){
super.setBars();
bars.remove("items");
removeBar("items");
}
@Override

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ public class LiquidJunction extends LiquidBlock{
@Override
public void setBars(){
super.setBars();
bars.remove("liquid");
removeBar("liquid");
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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