Merge branch 'master' into do-you-hear-the-voices-too

This commit is contained in:
Mythril382
2024-07-06 12:12:51 +08:00
committed by GitHub
62 changed files with 493 additions and 278 deletions

View File

@@ -47,6 +47,8 @@ public class BuilderAI extends AIController{
if(target != null && shouldShoot()){
unit.lookAt(target);
}else if(!unit.type.flying){
unit.lookAt(unit.prefRotation());
}
unit.updateBuilding = true;
@@ -55,6 +57,8 @@ public class BuilderAI extends AIController{
following = assistFollowing;
}
boolean moving = false;
if(following != null){
retreatTimer = 0f;
//try to follow and mimic someone
@@ -83,6 +87,7 @@ public class BuilderAI extends AIController{
var core = unit.closestCore();
if(core != null && !unit.within(core, retreatDst)){
moveTo(core, retreatDst);
moving = true;
}
}
}
@@ -114,7 +119,8 @@ public class BuilderAI extends AIController{
if(valid){
//move toward the plan
moveTo(req.tile(), unit.type.buildRange - 20f);
moveTo(req.tile(), unit.type.buildRange - 20f, 20f);
moving = !unit.within(req.tile(), unit.type.buildRange - 10f);
}else{
//discard invalid plan
unit.plans.removeFirst();
@@ -124,6 +130,7 @@ public class BuilderAI extends AIController{
if(assistFollowing != null){
moveTo(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 60f);
moving = !unit.within(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 65f);
}
//follow someone and help them build
@@ -186,6 +193,10 @@ public class BuilderAI extends AIController{
}
}
}
if(!unit.type.flying){
unit.updateBoosting(moving || unit.floorOn().isDuct || unit.floorOn().damageTaken > 0f);
}
}
protected boolean nearEnemy(int x, int y){

View File

@@ -164,8 +164,11 @@ public class SoundControl{
//this just fades out the last track to make way for ingame music
silence();
//play music at intervals
if(Time.timeSinceMillis(lastPlayed) > 1000 * musicInterval / 60f){
if(Core.settings.getBool("alwaysmusic")){
if(current == null){
playRandom();
}
}else if(Time.timeSinceMillis(lastPlayed) > 1000 * musicInterval / 60f){
//chance to play it per interval
if(Mathf.chance(musicChance)){
lastPlayed = Time.millis();
@@ -213,7 +216,9 @@ public class SoundControl{
/** Plays a random track.*/
public void playRandom(){
if(isDark()){
if(state.boss() != null){
playOnce(bossMusic.random(lastRandomPlayed));
}else if(isDark()){
playOnce(darkMusic.random(lastRandomPlayed));
}else{
playOnce(ambientMusic.random(lastRandomPlayed));

View File

@@ -322,7 +322,7 @@ public class UnitTypes{
speed = 0.55f;
hitSize = 8f;
health = 120f;
buildSpeed = 0.8f;
buildSpeed = 0.35f;
armor = 1f;
abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f));
@@ -354,7 +354,7 @@ public class UnitTypes{
speed = 0.7f;
hitSize = 11f;
health = 320f;
buildSpeed = 0.9f;
buildSpeed = 0.5f;
armor = 4f;
riseSpeed = 0.07f;
@@ -408,7 +408,7 @@ public class UnitTypes{
mineTier = 3;
boostMultiplier = 2f;
health = 640f;
buildSpeed = 1.7f;
buildSpeed = 1.1f;
canBoost = true;
armor = 9f;
mechLandShake = 2f;

View File

@@ -82,7 +82,7 @@ public class ForceFieldAbility extends Ability{
if(unit.shield <= 0f && !wasBroken){
unit.shield -= cooldown * regen;
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.team.color, this);
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), this);
}
wasBroken = unit.shield <= 0f;
@@ -110,7 +110,7 @@ public class ForceFieldAbility extends Ability{
checkRadius(unit);
if(unit.shield > 0){
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
Draw.color(unit.type.shieldColor(unit), Color.white, Mathf.clamp(alpha));
if(Vars.renderer.animateShields){
Draw.z(Layer.shields + 0.001f * alpha);

View File

@@ -31,7 +31,7 @@ public class ShieldArcAbility extends Ability{
if(paramField.data <= b.damage()){
paramField.data -= paramField.cooldown * paramField.regen;
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramUnit.team.color, paramUnit);
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramField.color == null ? paramUnit.type.shieldColor(paramUnit) : paramField.color, paramUnit);
}
paramField.data -= b.damage();
@@ -60,6 +60,8 @@ public class ShieldArcAbility extends Ability{
public boolean drawArc = true;
/** If not null, will be drawn on top. */
public @Nullable String region;
/** Color override of the shield. Uses unit shield colour by default. */
public @Nullable Color color;
/** If true, sprite position will be influenced by x/y. */
public boolean offsetRegion = false;
@@ -109,7 +111,7 @@ public class ShieldArcAbility extends Ability{
if(widthScale > 0.001f){
Draw.z(Layer.shields);
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
Draw.color(color == null ? unit.type.shieldColor(unit) : color, Color.white, Mathf.clamp(alpha));
var pos = paramPos.set(x, y).rotate(unit.rotation - 90f).add(unit);
if(!Vars.renderer.animateShields){

View File

@@ -48,13 +48,13 @@ public class ShieldRegenFieldAbility extends Ability{
if(other.shield < max){
other.shield = Math.min(other.shield + amount, max);
other.shieldAlpha = 1f; //TODO may not be necessary
applyEffect.at(unit.x, unit.y, 0f, unit.team.color, parentizeEffects ? other : null);
applyEffect.at(other.x, other.y, 0f, other.type.shieldColor(other), parentizeEffects ? other : null);
applied = true;
}
});
if(applied){
activeEffect.at(unit.x, unit.y, unit.team.color);
activeEffect.at(unit.x, unit.y, unit.type.shieldColor(unit));
}
timer = 0f;

View File

@@ -87,6 +87,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient float optionalEfficiency;
/** The efficiency this block *would* have if shouldConsume() returned true. */
transient float potentialEfficiency;
/** Whether there are any consumers (aside from power) that have efficiency > 0. */
transient boolean shouldConsumePower;
transient float healSuppressionTime = -1f;
transient float lastHealTime = -120f * 10f;
@@ -1773,6 +1775,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(!block.hasConsumers || cheating()){
potentialEfficiency = enabled && productionValid() ? 1f : 0f;
efficiency = optionalEfficiency = shouldConsume() ? potentialEfficiency : 0f;
shouldConsumePower = true;
updateEfficiencyMultiplier();
return;
}
@@ -1780,6 +1783,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//disabled -> nothing works
if(!enabled){
potentialEfficiency = efficiency = optionalEfficiency = 0f;
shouldConsumePower = false;
return;
}
@@ -1789,10 +1793,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//assume efficiency is 1 for the calculations below
efficiency = optionalEfficiency = 1f;
shouldConsumePower = true;
//first pass: get the minimum efficiency of any consumer
for(var cons : block.nonOptionalConsumers){
minEfficiency = Math.min(minEfficiency, cons.efficiency(self()));
float result = cons.efficiency(self());
if(cons != block.consPower && result <= 0.0000001f){
shouldConsumePower = false;
}
minEfficiency = Math.min(minEfficiency, result);
}
//same for optionals

View File

@@ -47,6 +47,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient float deathTimer;
transient String lastText = "";
transient float textFadeTime;
transient Ratekeeper itemDepositRate = new Ratekeeper();
transient private Unit lastReadUnit = Nulls.unit;
transient private int wrongReadUnits;

View File

@@ -61,7 +61,7 @@ abstract class ShieldComp implements Healthc, Posc{
}
if(hadShields && shield <= 0.0001f){
Fx.unitShieldBreak.at(x, y, 0, team.color, this);
Fx.unitShieldBreak.at(x, y, 0, type.shieldColor(self()), this);
}
}
}

View File

@@ -13,6 +13,7 @@ public enum Gamemode{
}, map -> map.spawns > 0),
sandbox(rules -> {
rules.infiniteResources = true;
rules.allowEditRules = true;
rules.waves = true;
rules.waveTimer = false;
}),

View File

@@ -19,6 +19,8 @@ import mindustry.world.blocks.*;
* Does not store game state, just configuration.
*/
public class Rules{
/** Allows editing the rules in-game. Essentially a cheat mode toggle. */
public boolean allowEditRules = false;
/** Sandbox mode: Enables infinite resources, build range and build speed. */
public boolean infiniteResources;
/** Team-specific rules. */
@@ -105,6 +107,8 @@ public class Rules{
public boolean cleanupDeadTeams = true;
/** If true, items can only be deposited in the core. */
public boolean onlyDepositCore = false;
/** Cooldown, in seconds, of item depositing for players. */
public float itemDepositCooldown = 0.5f;
/** If true, every enemy block in the radius of the (enemy) core is destroyed upon death. Used for campaign maps. */
public boolean coreDestroyClear = false;
/** If true, banned blocks are hidden from the build menu. */

View File

@@ -241,7 +241,9 @@ public class OverlayRenderer{
Draw.reset();
Building build = world.buildWorld(v.x, v.y);
if(input.canDropItem() && build != null && build.interactable(player.team()) && build.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(build, itemTransferRange)){
if(input.canDropItem() && build != null && build.interactable(player.team()) && build.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(build, itemTransferRange) &&
input.itemDepositCooldown <= 0f){
boolean invalid = (state.rules.onlyDepositCore && !(build instanceof CoreBuild));
Lines.stroke(3f, Pal.gray);

View File

@@ -114,12 +114,12 @@ public class DesktopInput extends InputHandler{
//draw break selection
if(mode == breaking){
drawBreakSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize, false);
drawBreakSelection(selectX, selectY, cursorX, cursorY, !(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1) ? maxLength : Vars.maxSchematicSize, false);
}
if(!Core.scene.hasKeyboard() && mode != breaking){
if(Core.input.keyDown(Binding.schematic_select)){
if(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1){
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
}else if(Core.input.keyDown(Binding.rebuild_select)){
drawRebuildSelection(schemX, schemY, cursorX, cursorY);
@@ -594,7 +594,7 @@ public class DesktopInput extends InputHandler{
selectPlans.clear();
}
if( !Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
if(!Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
if(Core.input.keyRelease(Binding.schematic_select)){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);

View File

@@ -83,6 +83,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean overrideLineRotation;
public int rotation;
public boolean droppingItem;
public float itemDepositCooldown;
public Group uiGroup;
public boolean isBuilding = true, buildWasAutoPaused = false, wasShooting = false;
public @Nullable UnitType controlledType;
@@ -142,6 +143,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.on(ResetEvent.class, e -> {
logicCutscene = false;
itemDepositCooldown = 0f;
Arrays.fill(controlGroups, null);
});
}
@@ -423,6 +425,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(player == null || build == null || !player.within(build, itemTransferRange) || build.items == null || player.dead() || (state.rules.onlyDepositCore && !(build instanceof CoreBuild))) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, build) ||
//to avoid rejecting deposit packets that happen to overlap due to packet speed differences, the actual cap is double the cooldown with 2 deposits.
(!player.isLocal() && !player.itemDepositRate.allow((long)(state.rules.itemDepositCooldown * 1000 * 2), 2)) ||
!netServer.admins.allowAction(player, ActionType.depositItem, build.tile, action -> {
action.itemAmount = player.unit().stack.amount;
action.item = player.unit().item();
@@ -796,6 +801,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
logicCutsceneZoom = -1f;
}
itemDepositCooldown -= Time.delta / 60f;
commandBuildings.removeAll(b -> !b.isValid());
if(!commandMode){
@@ -1859,8 +1866,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(build != null && build.acceptStack(stack.item, stack.amount, player.unit()) > 0 && build.interactable(player.team()) &&
build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild))){
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild)) && itemDepositCooldown <= 0f){
Call.transferInventory(player, build);
itemDepositCooldown = state.rules.itemDepositCooldown;
}
}else{
Call.dropItem(player.angleTo(x, y));

View File

@@ -1923,7 +1923,7 @@ public class LStatements{
table.add(" on ");
fields(table, channel, str -> channel = str);
table.add(", reliable ");
fields(table, channel, str -> channel = str);
fields(table, reliable, str -> reliable = str);
}
@Override

View File

@@ -99,7 +99,7 @@ public class StatusEffect extends UnlockableContent{
boolean reacts = false;
for(var e : opposites.toSeq().sort()){
stats.add(Stat.opposites, e.emoji() + "" + e);
stats.add(Stat.opposites, e.emoji() + e);
}
if(reactive){

View File

@@ -259,6 +259,8 @@ public class UnitType extends UnlockableContent implements Senseable{
public Color healColor = Pal.heal;
/** Color of light that this unit produces when lighting is enabled in the map. */
public Color lightColor = Pal.powerLight;
/** override for unit shield colour. */
public @Nullable Color shieldColor;
/** sound played when this unit explodes (*not* when it is shot down) */
public Sound deathSound = Sounds.bang;
/** sound played on loop when this unit is around. */
@@ -826,6 +828,10 @@ public class UnitType extends UnlockableContent implements Senseable{
if(canBoost){
cmds.add(UnitCommand.boostCommand);
if(buildSpeed > 0f){
cmds.add(UnitCommand.rebuildCommand, UnitCommand.assistCommand);
}
}
//healing, mining and building is only supported for flying units; pathfinding to ambiguously reachable locations is hard.
@@ -1305,6 +1311,12 @@ public class UnitType extends UnlockableContent implements Senseable{
Draw.reset();
}
//...where do I put this
public Color shieldColor(Unit unit){
return shieldColor == null ? unit.team.color : shieldColor;
}
public void drawMining(Unit unit){
if(!unit.mining()) return;
@@ -1345,7 +1357,7 @@ public class UnitType extends UnlockableContent implements Senseable{
float radius = unit.hitSize() * 1.3f;
Fill.light(unit.x, unit.y, Lines.circleVertices(radius), radius,
Color.clear,
Tmp.c2.set(unit.team.color).lerp(Color.white, Mathf.clamp(unit.hitTime() / 2f)).a(0.7f * alpha)
Tmp.c2.set(unit.type.shieldColor(unit)).lerp(Color.white, Mathf.clamp(unit.hitTime() / 2f)).a(0.7f * alpha)
);
}

View File

@@ -30,16 +30,24 @@ public class CustomRulesDialog extends BaseDialog{
private Table main;
private Prov<Rules> resetter;
private LoadoutDialog loadoutDialog;
public boolean showRuleEditRule;
public Seq<Table> categories;
public Table current;
public Seq<String> categoryNames;
public String currentName;
public String currentName = "";
public String ruleSearch = "";
public Seq<Runnable> additionalSetup; // for modding to easily add new rules
public CustomRulesDialog(){
this(false);
}
public CustomRulesDialog(boolean showRuleEditRule){
super("@mode.custom");
this.showRuleEditRule = showRuleEditRule;
loadoutDialog = new LoadoutDialog();
setFillParent(true);
@@ -49,8 +57,6 @@ public class CustomRulesDialog extends BaseDialog{
additionalSetup = new Seq<>();
categories = new Seq<>();
categoryNames = new Seq<>();
currentName = "";
ruleSearch = "";
buttons.button("@edit", Icon.pencil, () -> {
BaseDialog dialog = new BaseDialog("@waves.edit");
@@ -209,7 +215,6 @@ public class CustomRulesDialog extends BaseDialog{
main.left().defaults().fillX().left();
main.row();
category("waves");
check("@rules.waves", b -> rules.waves = b, () -> rules.waves);
check("@rules.wavesending", b -> rules.waveSending = b, () -> rules.waveSending, () -> rules.waves);
@@ -352,6 +357,10 @@ public class CustomRulesDialog extends BaseDialog{
category("teams");
//not sure where else to put this
if(showRuleEditRule){
check("@rules.allowedit", b -> rules.allowEditRules = b, () -> rules.allowEditRules);
}
team("@rules.playerteam", t -> rules.defaultTeam = t, () -> rules.defaultTeam);
team("@rules.enemyteam", t -> rules.waveTeam = t, () -> rules.waveTeam);

View File

@@ -14,7 +14,7 @@ import static mindustry.Vars.*;
public class MapPlayDialog extends BaseDialog{
public @Nullable Runnable playListener;
CustomRulesDialog dialog = new CustomRulesDialog();
CustomRulesDialog dialog = new CustomRulesDialog(true);
Rules rules;
Gamemode selectedGamemode = Gamemode.survival;
Map lastMap;

View File

@@ -1,7 +1,10 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.scene.ui.layout.*;
import mindustry.*;
import mindustry.editor.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -10,12 +13,28 @@ public class PausedDialog extends BaseDialog{
private MapProcessorsDialog processors = new MapProcessorsDialog();
private SaveDialog save = new SaveDialog();
private LoadDialog load = new LoadDialog();
private boolean wasClient = false;
private CustomRulesDialog rulesDialog = new CustomRulesDialog();
public PausedDialog(){
super("@menu");
shouldPause = true;
clearChildren();
add(titleTable).growX().row();
stack(cont, new Table(t -> {
t.bottom().left();
t.button(Icon.book, () -> {
Rules toEdit = Vars.state.rules.copy();
rulesDialog.show(toEdit, () -> state.rules.copy());
rulesDialog.hidden(() -> {
//apply rule changes only once it is hidden
Vars.state.rules = toEdit;
Call.setRules(toEdit);
});
}).size(70f).tooltip("@customize").visible(() -> state.rules.allowEditRules && (net.server() || !net.active()));
})).grow().row();
shown(this::rebuild);
addCloseListener();
@@ -130,7 +149,7 @@ public class PausedDialog extends BaseDialog{
}
public void runExitSave(){
wasClient = net.client();
boolean wasClient = net.client();
if(net.client()) netClient.disconnectQuietly();
if(state.isEditor() && !wasClient){

View File

@@ -297,6 +297,7 @@ public class SettingsMenuDialog extends BaseDialog{
}
void addSettings(){
sound.checkPref("alwaysmusic", false);
sound.sliderPref("musicvol", 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("sfxvol", 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("ambientvol", 100, 0, 100, 1, i -> i + "%");

View File

@@ -91,7 +91,7 @@ public class Build{
}
//repair derelict tile
if(tile.team() == Team.derelict && tile.block == result && tile.build != null && tile.block.allowDerelictRepair && state.rules.derelictRepair){
if(tile.team() == Team.derelict && team != Team.derelict && tile.block == result && tile.build != null && tile.block.allowDerelictRepair && state.rules.derelictRepair){
float healthf = tile.build.healthf();
var config = tile.build.config();
@@ -231,11 +231,11 @@ public class Build{
(type.size == 2 && world.getDarkness(wx, wy) >= 3) ||
(state.rules.staticFog && state.rules.fog && !fogControl.isDiscovered(team, wx, wy)) ||
(check.floor().isDeep() && !type.floating && !type.requiresWater && !type.placeableLiquid) || //deep water
(type == check.block() && check.build != null && rotation == check.build.rotation && type.rotate && !((type == check.block && check.team() == Team.derelict))) || //same block, same rotation
(type == check.block() && check.build != null && rotation == check.build.rotation && type.rotate && !((type == check.block && team != Team.derelict && check.team() == Team.derelict))) || //same block, same rotation
!check.interactable(team) || //cannot interact
!check.floor().placeableOn || //solid floor
(!checkVisible && !check.block().alwaysReplace) || //replacing a block that should be replaced (e.g. payload placement)
!(((type.canReplace(check.block()) || (type == check.block && state.rules.derelictRepair && check.team() == Team.derelict)) || //can replace type OR can replace derelict block of same type
!(((type.canReplace(check.block()) || (type == check.block && team != Team.derelict && state.rules.derelictRepair && check.team() == Team.derelict)) || //can replace type OR can replace derelict block of same type
(check.build instanceof ConstructBuild build && build.current == type && check.centerX() == tile.x && check.centerY() == tile.y)) && //same type in construction
type.bounds(tile.x, tile.y, Tmp.r1).grow(0.01f).contains(check.block.bounds(check.centerX(), check.centerY(), Tmp.r2))) || //no replacement
(type.requiresWater && check.floor().liquidDrop != Liquids.water) //requires water but none found

View File

@@ -4,7 +4,6 @@ import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.world.consumers.*;
public class PowerGraph{
private static final Queue<Building> queue = new Queue<>();
@@ -109,7 +108,7 @@ public class PowerGraph{
for(int i = 0; i < consumers.size; i++){
var consumer = items[i];
var consumePower = consumer.block.consPower;
if(otherConsumersAreValid(consumer, consumePower)){
if(consumer.shouldConsumePower){
powerNeeded += consumePower.requestedPower(consumer) * consumer.delta();
}
}
@@ -201,7 +200,7 @@ public class PowerGraph{
}
}else{
//valid consumers get power as usual
if(otherConsumersAreValid(consumer, cons)){
if(consumer.shouldConsumePower){
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()));
@@ -381,24 +380,6 @@ public class PowerGraph{
return graphID;
}
@Deprecated
private boolean otherConsumersAreValid(Building build, Consume consumePower){
if(!build.enabled) return false;
float f = build.efficiency;
//hack so liquids output positive efficiency values
build.efficiency = 1f;
for(Consume cons : build.block.nonOptionalConsumers){
//TODO fix this properly
if(cons != consumePower && cons.efficiency(build) <= 0.0000001f){
build.efficiency = f;
return false;
}
}
build.efficiency = f;
return true;
}
@Override
public String toString(){
return "PowerGraph{" +