Merge branch 'master' of https://github.com/Anuken/Mindustry into 7.0-features

 Conflicts:
	core/src/mindustry/content/Blocks.java
	core/src/mindustry/net/CrashSender.java
	core/src/mindustry/ui/Links.java
	core/src/mindustry/ui/dialogs/SchematicsDialog.java
	core/src/mindustry/world/blocks/power/LightBlock.java
	gradle.properties
This commit is contained in:
Anuken
2021-07-23 17:58:02 -04:00
90 changed files with 690 additions and 389 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

After

Width:  |  Height:  |  Size: 685 B

View File

@@ -566,6 +566,7 @@ sectors.unexplored = [lightgray]Unexplored
sectors.resources = Resources:
sectors.production = Production:
sectors.export = Export:
sectors.import = Import:
sectors.time = Time:
sectors.threat = Threat:
sectors.wave = Wave:
@@ -725,7 +726,7 @@ stat.maxconsecutive = Max Consecutive
stat.buildcost = Build Cost
stat.inaccuracy = Inaccuracy
stat.shots = Shots
stat.reload = Shots/Second
stat.reload = Firing Rate
stat.ammo = Ammo
stat.shieldhealth = Shield Health
stat.cooldowntime = Cooldown Time
@@ -794,7 +795,7 @@ bullet.damage = [stat]{0}[lightgray] damage
bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles
bullet.incendiary = [stat]incendiary
bullet.homing = [stat]homing
bullet.frag = [stat]frag
bullet.frags = [stat]{0}[lightgray]x frag bullets:
bullet.lightning = [stat]{0}[lightgray]x lightning ~ [stat]{1}[lightgray] damage
bullet.buildingdamage = [stat]{0}%[lightgray] building damage
bullet.knockback = [stat]{0}[lightgray] knockback
@@ -846,7 +847,6 @@ setting.doubletapmine.name = Double-Tap to Mine
setting.modcrashdisable.name = Disable Mods On Startup Crash
setting.animatedwater.name = Animated Surfaces
setting.animatedshields.name = Animated Shields
setting.antialias.name = Antialias[lightgray] (requires restart)[]
setting.playerindicators.name = Player Indicators
setting.indicators.name = Enemy Indicators
setting.autotarget.name = Auto-Target
@@ -855,7 +855,8 @@ setting.touchscreen.name = Touchscreen Controls
setting.fpscap.name = Max FPS
setting.fpscap.none = None
setting.fpscap.text = {0} FPS
setting.uiscale.name = UI Scaling[lightgray] (restart required)[]
setting.uiscale.name = UI Scaling
setting.uiscale.description = Restart required to apply changes.
setting.swapdiagonal.name = Always Diagonal Placement
setting.difficulty.training = Training
setting.difficulty.easy = Easy
@@ -873,7 +874,8 @@ setting.saveinterval.name = Save Interval
setting.seconds = {0} seconds
setting.milliseconds = {0} milliseconds
setting.fullscreen.name = Fullscreen
setting.borderlesswindow.name = Borderless Window[lightgray] (restart may be required)
setting.borderlesswindow.name = Borderless Window
setting.borderlesswindow.description = Restart may be required to apply changes.
setting.fps.name = Show FPS & Ping
setting.smoothcamera.name = Smooth Camera
setting.vsync.name = VSync
@@ -996,6 +998,7 @@ rules.wavetimer = Wave Timer
rules.waves = Waves
rules.attack = Attack Mode
rules.buildai = AI Building
rules.cleanupdeadteams = Clean Up Defeated Team Buildings (PvP)
rules.corecapture = Capture Core On Destruction
rules.polygoncoreprotection = Polygonal Core Protection
rules.enemyCheat = Infinite AI (Red Team) Resources
@@ -1293,7 +1296,6 @@ block.meltdown.name = Meltdown
block.foreshadow.name = Foreshadow
block.container.name = Container
block.launch-pad.name = Launch Pad
block.launch-pad-large.name = Large Launch Pad
block.segment.name = Segment
block.command-center.name = Command Center
block.ground-factory.name = Ground Factory
@@ -1314,11 +1316,11 @@ block.payload-source.name = Payload Source
block.disassembler.name = Disassembler
block.silicon-crucible.name = Silicon Crucible
block.overdrive-dome.name = Overdrive Dome
block.interplanetary-accelerator.name = Interplanetary Accelerator
#experimental, may be removed
block.block-forge.name = Block Forge
block.block-loader.name = Block Loader
block.block-unloader.name = Block Unloader
block.interplanetary-accelerator.name = Interplanetary Accelerator
block.switch.name = Switch
block.micro-processor.name = Micro Processor
@@ -1548,6 +1550,8 @@ block.memory-bank.description = Stores information for a logic processor. High c
block.logic-display.description = Displays arbitrary graphics from a logic processor.
block.large-logic-display.description = Displays arbitrary graphics from a logic processor.
block.interplanetary-accelerator.description = A massive electromagnetic railgun tower. Accelerates cores to escape velocity for interplanetary deployment.
block.repair-turret.description = Continuously repairs the closest damaged unit in its vicinity. Optionally accepts coolant.
block.payload-propulsion-tower.description = Long-range payload transport structure. Shoots payloads to other linked payload propulsion towers.
unit.dagger.description = Fires standard bullets at all nearby enemies.
unit.mace.description = Fires streams of flame at all nearby enemies.
@@ -1582,6 +1586,11 @@ unit.omura.description = Fires a long-range piercing railgun bolt at enemies. Co
unit.alpha.description = Defends the Shard core from enemies. Builds structures.
unit.beta.description = Defends the Foundation core from enemies. Builds structures.
unit.gamma.description = Defends the Nucleus core from enemies. Builds structures.
unit.retusa.description = Places proximity mines. Repairs allied units.
unit.oxynoe.description = Fires structure-repairing streams of flame at nearby enemies. Targets nearby enemy projectiles with a point defense turret.
unit.cyerce.description = Fires seeking cluster-missiles at enemies. Repairs allied units.
unit.aegires.description = Shocks all enemy units and structures that enter its energy field. Repairs all allies.
unit.navanax.description = Fires explosive EMP projectiles, dealing significant damage to enemy power networks and repairing allied structures. Melts nearby enemies with 4 autonomous laser turrets.
lst.read = Read a number from a linked memory cell.
lst.write = Write a number to a linked memory cell.

View File

@@ -100,7 +100,8 @@ joingame = 게임 참여
customgame = 사용자 지정 게임
newgame = 새 게임
none = < 없음 >
none.found = [lightgray]< 없거나 찾을 수 없음 >
none.found = [lightgray]< 찾을 수 없음 >
none.inmap = [lightgray]< 맵에 없음 >
minimap = 미니맵
position = 위치
close = 닫기

View File

@@ -845,7 +845,6 @@ setting.doubletapmine.name = Добыча руды двойным нажатие
setting.modcrashdisable.name = Отключение модификаций после вылета при запуске
setting.animatedwater.name = Анимированные поверхности
setting.animatedshields.name = Анимированные щиты
setting.antialias.name = Сглаживание[lightgray] (требует перезапуска)[]
setting.playerindicators.name = Индикаторы направления игроков
setting.indicators.name = Индикаторы направления врагов
setting.autotarget.name = Автозахват цели
@@ -854,7 +853,7 @@ setting.touchscreen.name = Сенсорное управление
setting.fpscap.name = Максимальный FPS
setting.fpscap.none = Неограниченный
setting.fpscap.text = {0} FPS
setting.uiscale.name = Масштаб пользовательского интерфейса[lightgray] (необходим перезапуск)[]
setting.uiscale.name = Масштаб пользовательского интерфейса
setting.swapdiagonal.name = Всегда диагональное размещение
setting.difficulty.training = Обучение
setting.difficulty.easy = Лёгкая
@@ -872,7 +871,7 @@ setting.saveinterval.name = Интервал сохранения
setting.seconds = {0} секунд
setting.milliseconds = {0} миллисекунд
setting.fullscreen.name = Полноэкранный режим
setting.borderlesswindow.name = Безрамочное окно[lightgray] (может потребоваться перезапуск)
setting.borderlesswindow.name = Безрамочное окно
setting.fps.name = Показывать FPS и пинг
setting.smoothcamera.name = Плавная камера
setting.vsync.name = Вертикальная синхронизация

View File

@@ -55,7 +55,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Log.info("[GL] Max texture size: @", maxTextureSize);
Log.info("[GL] Using @ context.", gl30 != null ? "OpenGL 3" : "OpenGL 2");
if(maxTextureSize < 4096) Log.warn("[GL] Your maximum texture size is below the recommended minimum of 4096. This will cause severe performance issues.");
Log.info("[JAVA] Version: @", System.getProperty("java.version"));
Log.info("[JAVA] Version: @", OS.javaVersion);
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;

View File

@@ -48,6 +48,7 @@ public class BlockIndexer{
private Seq<Building> breturnArray = new Seq<>(Building.class);
public BlockIndexer(){
clearFlags();
Events.on(TilePreChangeEvent.class, event -> {
removeIndex(event.tile);
@@ -62,11 +63,7 @@ public class BlockIndexer{
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
activeTeams = new Seq<>(Team.class);
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
flagMap[i][j] = new TileArray();
}
}
clearFlags();
allOres.clear();
ores = new IntSeq[content.items().size][][];
@@ -160,6 +157,14 @@ public class BlockIndexer{
return blocksPresent != null && blocksPresent[block.id];
}
private void clearFlags(){
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
flagMap[i][j] = new TileArray();
}
}
}
private TileArray[] getFlagged(Team team){
return flagMap[team.id];
}
@@ -206,13 +211,12 @@ public class BlockIndexer{
}
public boolean eachBlock(@Nullable Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
returnBool = false;
breturnArray.clear();
if(team == null){
allBuildings(wx, wy, range, b -> {
if(pred.get(b)){
returnBool = true;
cons.get(b);
breturnArray.add(b);
}
});
}else{
@@ -220,13 +224,20 @@ public class BlockIndexer{
if(buildings == null) return false;
buildings.intersect(wx - range, wy - range, range*2f, range*2f, b -> {
if(b.within(wx, wy, range + b.hitSize() / 2f) && pred.get(b)){
returnBool = true;
cons.get(b);
breturnArray.add(b);
}
});
}
return returnBool;
int size = breturnArray.size;
var items = breturnArray.items;
for(int i = 0; i < size; i++){
cons.get(items[i]);
items[i] = null;
}
breturnArray.size = 0;
return size > 0;
}
/** Get all enemy blocks with a flag. */
@@ -269,31 +280,50 @@ public class BlockIndexer{
}
public void allBuildings(float x, float y, float range, Cons<Building> cons){
breturnArray.clear();
for(int i = 0; i < activeTeams.size; i++){
Team team = activeTeams.items[i];
var buildings = team.data().buildings;
if(buildings == null) continue;
buildings.intersect(x - range, y - range, range*2f, range*2f, b -> {
if(b.within(x, y, range + b.hitSize()/2f)){
cons.get(b);
}
});
buildings.intersect(x - range, y - range, range*2f, range*2f, breturnArray);
}
var items = breturnArray.items;
int size = breturnArray.size;
for(int i = 0; i < size; i++){
var b = items[i];
if(b.within(x, y, range + b.hitSize()/2f)){
cons.get(b);
}
items[i] = null;
}
breturnArray.size = 0;
}
public Building findEnemyTile(@Nullable Team team, float x, float y, float range, Boolf<Building> pred){
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
Building target = null;
float targetDist = 0;
for(int i = 0; i < activeTeams.size; i++){
Team enemy = activeTeams.items[i];
if(enemy == team || (team == Team.derelict && !state.rules.coreCapture)) continue;
Building entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
Building candidate = indexer.findTile(enemy, x, y, range, pred, true);
if(candidate == null) continue;
//if a block has the same priority, the closer one should be targeted
float dist = candidate.dst(x, y) - candidate.hitSize() / 2f;
if(target == null ||
//if its closer and is at least equal priority
(dist < targetDist && candidate.block.priority.ordinal() >= target.block.priority.ordinal()) ||
// block has higher priority (so range doesnt matter)
(candidate.block.priority.ordinal() > target.block.priority.ordinal())){
target = candidate;
targetDist = dist;
}
}
return null;
return target;
}
public Building findTile(Team team, float x, float y, float range, Boolf<Building> pred){

View File

@@ -180,6 +180,7 @@ public class WaveSpawner{
private void spawnEffect(Unit unit){
unit.rotation = unit.angleTo(world.width()/2f * tilesize, world.height()/2f * tilesize);
unit.apply(StatusEffects.unmoving, 30f);
unit.apply(StatusEffects.invincible, 60f);
unit.add();
Call.spawnEffect(unit.x, unit.y, unit.rotation, unit.type);

View File

@@ -19,6 +19,7 @@ public class BuilderAI extends AIController{
@Nullable Unit following;
@Nullable Teamc enemy;
float retreatTimer;
@Nullable BlockPlan lastPlan;
@Override
public void updateMovement(){
@@ -43,6 +44,7 @@ public class BuilderAI extends AIController{
//set to follower's first build plan, whatever that is
unit.plans.clear();
unit.plans.addFirst(following.buildPlan());
lastPlan = null;
}else if(unit.buildPlan() == null){
//not following anyone or building
if(timer.get(timerTarget4, 40)){
@@ -78,10 +80,11 @@ public class BuilderAI extends AIController{
}
boolean valid =
(req.tile() != null && req.tile().build instanceof ConstructBuild cons && cons.current == req.block) ||
(req.breaking ?
Build.validBreak(unit.team(), req.x, req.y) :
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));
!(lastPlan != null && lastPlan.removed) &&
((req.tile() != null && req.tile().build instanceof ConstructBuild cons && cons.current == req.block) ||
(req.breaking ?
Build.validBreak(unit.team(), req.x, req.y) :
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation)));
if(valid){
//move toward the request
@@ -89,6 +92,7 @@ public class BuilderAI extends AIController{
}else{
//discard invalid request
unit.plans.removeFirst();
lastPlan = null;
}
}else{
@@ -127,6 +131,7 @@ public class BuilderAI extends AIController{
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block().id == block.block){
blocks.removeFirst();
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
lastPlan = block;
//add build request.
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
//shift build plan to tail so next unit builds something else.

View File

@@ -6,10 +6,14 @@ import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class DefenderAI extends AIController{
@Override
public void updateMovement(){
unloadPayloads();
if(target != null){
moveTo(target, (target instanceof Sized s ? s.hitSize()/2f * 1.1f : 0f) + unit.hitSize/2f + 15f, 50f);
unit.lookAt(target);
@@ -23,6 +27,7 @@ public class DefenderAI extends AIController{
@Override
protected 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.
@@ -34,6 +39,14 @@ public class DefenderAI extends AIController{
var block = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(block != null) return block;
//return core if found
return unit.closestCore();
var core = unit.closestCore();
if(core != null) return core;
//for enemies, target the enemy core.
if(state.rules.waves && unit.team == state.rules.waveTeam){
return unit.closestEnemyCore();
}
return null;
}
}

View File

@@ -11,6 +11,8 @@ public class FlyingAI extends AIController{
@Override
public void updateMovement(){
unloadPayloads();
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(!unit.type.circleTarget){
moveTo(target, unit.type.range * 0.8f);

View File

@@ -17,7 +17,7 @@ public class LogicAI extends AIController{
/** Minimum delay between item transfers. */
public static final float transferDelay = 60f * 1.5f;
/** Time after which the unit resets its controlled and reverts to a normal unit. */
public static final float logicControlTimeout = 10f * 60f;
public static final float logicControlTimeout = 60f * 10f;
public LUnitControl control = LUnitControl.idle;
public float moveX, moveY, moveRad;

View File

@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
/** Controls playback of multiple audio tracks.*/
public class SoundControl{
protected static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.6f, musicWaveChance = 0.46f;
protected static final float finTime = 120f, foutTime = 120f, musicInterval = 3f * Time.toMinutes, musicChance = 0.6f, musicWaveChance = 0.46f;
/** normal, ambient music, plays at any time */
public Seq<Music> ambientMusic = Seq.with();

View File

@@ -365,7 +365,7 @@ public class Blocks implements ContentList{
shale = new Floor("shale"){{
variants = 3;
attributes.set(Attribute.oil, 1.2f);
attributes.set(Attribute.oil, 1.6f);
}};
moss = new Floor("moss"){{
@@ -544,6 +544,9 @@ public class Blocks implements ContentList{
darkMetal = new StaticWall("dark-metal");
Seq.with(metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor4, metalFloor5, darkPanel1, darkPanel2, darkPanel3, darkPanel4, darkPanel5, darkPanel6)
.each(b -> b.asFloor().wall = darkMetal);
pebbles = new DoubleOverlayFloor("pebbles");
tendrils = new OverlayFloor("tendrils");
@@ -858,7 +861,7 @@ public class Blocks implements ContentList{
coalCentrifuge = new GenericCrafter("coal-centrifuge"){{
requirements(Category.crafting, with(Items.titanium, 20, Items.graphite, 40, Items.lead, 30));
craftEffect = Fx.smeltsmoke;
craftEffect = Fx.coalSmeltsmoke;
outputItem = new ItemStack(Items.coal, 1);
craftTime = 30f;
size = 2;
@@ -1190,7 +1193,6 @@ public class Blocks implements ContentList{
reloadTime = 200f;
range = 440f;
consumes.power(1.75f);
bullet = new MassDriverBolt();
}};
//special transport blocks
@@ -1337,6 +1339,7 @@ public class Blocks implements ContentList{
requirements(Category.power, with(Items.copper, 40, Items.graphite, 35, Items.lead, 50, Items.silicon, 35, Items.metaglass, 40));
powerProduction = 1.8f;
generateEffect = Fx.redgeneratespark;
effectChance = 0.011f;
size = 2;
floating = true;
ambientSound = Sounds.hum;
@@ -1581,14 +1584,14 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
health = size * size * 55;
health = size * size * 60;
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
health = size * size * 55;
health = size * size * 60;
}};
unloader = new Unloader("unloader"){{

View File

@@ -1526,13 +1526,16 @@ public class Fx{
});
}),
redgeneratespark = new Effect(18, e -> {
randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> {
float len = e.fout() * 4f;
color(Pal.redSpark, Color.gray, e.fin());
Fill.circle(e.x + x, e.y + y, len/2f);
});
}),
redgeneratespark = new Effect(90, e -> {
color(Pal.redSpark);
alpha(e.fslope());
rand.setSeed(e.id);
for(int i = 0; i < 2; i++){
v.trns(rand.random(360f), rand.random(e.finpow() * 9f)).add(e.x, e.y);
Fill.circle(v.x, v.y, rand.random(1.4f, 2.4f));
}
}).layer(Layer.bullet - 1f),
generatespark = new Effect(18, e -> {
randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> {
@@ -1622,6 +1625,13 @@ public class Fx{
});
}),
coalSmeltsmoke = new Effect(40f, e -> {
randLenVectors(e.id, 0.2f + e.fin(), 4, 6.3f, (x, y, fin, out) -> {
color(Color.darkGray, Pal.coalBlack, e.finpowdown());
Fill.circle(e.x + x, e.y + y, out * 2f + 0.25f);
});
}),
formsmoke = new Effect(40, e -> {
randLenVectors(e.id, 6, 5f + e.fin() * 8f, (x, y) -> {
color(Pal.plasticSmoke, Color.lightGray, e.fin());

View File

@@ -12,7 +12,7 @@ import mindustry.graphics.*;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed, disarmed, electrified;
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed, disarmed, electrified, invincible;
@Override
public void load(){
@@ -188,5 +188,9 @@ public class StatusEffects implements ContentList{
color = Color.valueOf("e9ead3");
disarm = true;
}};
invincible = new StatusEffect("invincible"){{
healthMultiplier = Float.POSITIVE_INFINITY;
}};
}
}

View File

@@ -49,6 +49,7 @@ public class Logic implements ApplicationListener{
BlockPlan b = it.next();
Block block = content.block(b.block);
if(event.tile.block().bounds(event.tile.x, event.tile.y, Tmp.r1).overlaps(block.bounds(b.x, b.y, Tmp.r2))){
b.removed = true;
it.remove();
}
}
@@ -128,25 +129,7 @@ public class Logic implements ApplicationListener{
Events.on(SectorCaptureEvent.class, e -> {
if(!net.client() && e.sector == state.getSector() && e.sector.isBeingPlayed()){
for(Tile tile : world.tiles){
//convert all blocks to neutral, randomly killing them
if(tile.isCenter() && tile.build != null && tile.build.team == state.rules.waveTeam){
Building b = tile.build;
Call.setTeam(b, Team.derelict);
Time.run(Mathf.random(0f, 60f * 6f), () -> {
if(Mathf.chance(0.25)){
b.kill();
}
});
}
}
//kill all units
Groups.unit.each(u -> {
if(u.team == state.rules.waveTeam){
Time.run(Mathf.random(0f, 60f * 5f), u::kill);
}
});
state.rules.waveTeam.data().destroyToDerelict();
}
});
@@ -162,6 +145,12 @@ public class Logic implements ApplicationListener{
}
});
//listen to core changes; if all cores have been destroyed, set to derelict.
Events.on(CoreChangeEvent.class, e -> Core.app.post(() -> {
if(state.rules.cleanupDeadTeams && state.rules.pvp && !e.core.isAdded() && e.core.team != Team.derelict && e.core.team.cores().isEmpty()){
e.core.team.data().destroyToDerelict();
}
}));
}
/** Adds starting items, resets wave time, and sets state to playing. */

View File

@@ -159,6 +159,22 @@ public class NetClient implements ApplicationListener{
clientPacketReliable(type, contents);
}
//TODO enable in build 129
/*
@Remote(variants = Variant.both, unreliable = true)
public static void sound(Sound sound, float volume, float pitch, float pan){
if(sound == null) return;
sound.play(volume * Core.settings.getInt("sfxvol") / 100f, pitch, pan);
}
@Remote(variants = Variant.both, unreliable = true)
public static void soundAt(Sound sound, float x, float y, float volume, float pitch){
if(sound == null) return;
sound.at(x, y, pitch, volume);
}*/
@Remote(variants = Variant.both, unreliable = true)
public static void effect(Effect effect, float x, float y, float rotation, Color color){
if(effect == null) return;
@@ -274,6 +290,7 @@ public class NetClient implements ApplicationListener{
@Remote(called = Loc.client, variants = Variant.one)
public static void connect(String ip, int port){
if(!steam && ip.startsWith("steam:")) return;
netClient.disconnectQuietly();
logic.reset();

View File

@@ -7,6 +7,7 @@ import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
@@ -45,6 +46,7 @@ public class Renderer implements ApplicationListener{
private Color clearColor = new Color(0f, 0f, 0f, 1f);
private float targetscale = Scl.scl(4), camerascale = targetscale, landscale, landTime, weatherAlpha, minZoomScl = Scl.scl(0.01f);
private float shakeIntensity, shaketime;
private Vec2 camShakeOffset = new Vec2();
public Renderer(){
camera = new Camera();
@@ -112,12 +114,25 @@ public class Renderer implements ApplicationListener{
landTime = 0f;
graphics.clear(Color.black);
}else{
updateShake(0.75f);
if(shaketime > 0){
float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * 0.75f;
camShakeOffset.setToRandomDirection().scl(Mathf.random(intensity));
camera.position.add(camShakeOffset);
shakeIntensity -= 0.25f * Time.delta;
shaketime -= Time.delta;
shakeIntensity = Mathf.clamp(shakeIntensity, 0f, 100f);
}else{
camShakeOffset.setZero();
shakeIntensity = 0f;
}
if(pixelator.enabled()){
pixelator.drawPixelate();
}else{
draw();
}
camera.position.sub(camShakeOffset);
}
}
@@ -172,18 +187,6 @@ public class Renderer implements ApplicationListener{
}
}
void updateShake(float scale){
if(shaketime > 0){
float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * scale;
camera.position.add(Mathf.range(intensity), Mathf.range(intensity));
shakeIntensity -= 0.25f * Time.delta;
shaketime -= Time.delta;
shakeIntensity = Mathf.clamp(shakeIntensity, 0f, 100f);
}else{
shakeIntensity = 0f;
}
}
public void draw(){
Events.fire(Trigger.preDraw);

View File

@@ -527,8 +527,7 @@ public class World{
private class Context implements WorldContext{
Context(){
}
Context(){}
@Override
public Tile tile(int index){

View File

@@ -560,10 +560,12 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
t.top();
t.add("@editor.brush");
var label = new Label("@editor.brush");
label.setAlignment(Align.center);
label.touchable = Touchable.disabled;
t.top().stack(slider, label).width(size * 3f - 20).padTop(4f);
t.row();
t.add(slider).width(size * 3f - 20).padTop(4f);
}).padTop(5).growX().top();
mid.row();

View File

@@ -15,6 +15,7 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.maps.filters.*;
import mindustry.maps.filters.GenerateFilter.*;
import mindustry.ui.*;
@@ -26,12 +27,6 @@ import static mindustry.Vars.*;
@SuppressWarnings("unchecked")
public class MapGenerateDialog extends BaseDialog{
final Prov<GenerateFilter>[] filterTypes = new Prov[]{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
EnemySpawnFilter::new, SpawnPathFilter::new
};
final boolean applied;
Pixmap pixmap;
@@ -333,7 +328,7 @@ public class MapGenerateDialog extends BaseDialog{
p.marginRight(14);
p.defaults().size(195f, 56f);
int i = 0;
for(var gen : filterTypes){
for(var gen : Maps.allFilterTypes){
var filter = gen.get();
var icon = filter.icon();

View File

@@ -141,27 +141,23 @@ public class Effect{
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.clip).setCenter(x, y);
if(headless || effect == Fx.none || !Core.settings.getBool("effects")) return;
if(view.overlaps(pos)){
if(!effect.initialized){
effect.initialized = true;
effect.init();
}
EffectState entity = EffectState.create();
entity.effect = effect;
entity.rotation = rotation;
entity.data = data;
entity.lifetime = effect.lifetime;
entity.set(x, y);
entity.color.set(color);
if(effect.followParent && data instanceof Posc) entity.parent = ((Posc)data);
entity.add();
if(Core.camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(x, y, effect.clip))){
if(!effect.initialized){
effect.initialized = true;
effect.init();
}
EffectState entity = EffectState.create();
entity.effect = effect;
entity.rotation = rotation;
entity.data = data;
entity.lifetime = effect.lifetime;
entity.set(x, y);
entity.color.set(color);
if(effect.followParent && data instanceof Posc p) entity.parent = p;
entity.add();
}
}

View File

@@ -9,8 +9,8 @@ import mindustry.gen.*;
* Class for predicting shoot angles based on velocities of targets.
*/
public class Predict{
private static Vec2 vec = new Vec2();
private static Vec2 vresult = new Vec2();
private static final Vec2 vec = new Vec2();
private static final Vec2 vresult = new Vec2();
/**
* Calculates of intercept of a stationary and moving target. Do not call from multiple threads!
@@ -52,6 +52,10 @@ public class Predict{
}
public static Vec2 intercept(Position src, Position dst, float v){
return intercept(src, dst, 0, 0, v);
}
public static Vec2 intercept(Position src, Position dst, float offsetx, float offsety, float v){
float ddx = 0, ddy = 0;
if(dst instanceof Hitboxc h){
ddx += h.deltaX();
@@ -61,7 +65,7 @@ public class Predict{
ddx -= h.deltaX();
ddy -= h.deltaY();
}
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, v);
return intercept(src.getX(), src.getY(), dst.getX() + offsetx, dst.getY() + offsety, ddx, ddy, v);
}
/**

View File

@@ -184,7 +184,7 @@ public class Units{
}
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
/** Returns the closest target enemy. First, units are checked, then buildings. */
public static Teamc bestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Building> tilePred, Sortf sort){
if(team == Team.derelict) return null;

View File

@@ -371,7 +371,7 @@ public class BulletType extends Content implements Cloneable{
e -> e.checkTarget(collidesAir, collidesGround) && e.team != b.team,
t -> collidesGround && (t.team != b.team || t.damaged()));
}else{
target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround), t -> collidesGround);
target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround) && !b.hasCollided(e.id), t -> collidesGround && !b.hasCollided(t.id));
}
if(target != null){

View File

@@ -9,7 +9,7 @@ import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 40f;
static final float warpDst = 30f;
@Import float x, y;

View File

@@ -178,7 +178,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || team.rules().infiniteResources || request.breaking || core == null || request.isRotation(team)) return false;
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item) && Mathf.round(i.amount * state.rules.buildCostMultiplier) > 0) && !request.initialized);
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item, Math.min(i.amount, 15)) && Mathf.round(i.amount * state.rules.buildCostMultiplier) > 0) && !request.initialized);
}
void removeBuild(int x, int y, boolean breaking){

View File

@@ -1018,6 +1018,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** @return the cap for item amount calculations, used when this block explodes. */
public int explosionItemCap(){
return block.itemCapacity;
}
/** Called when the block is destroyed. The tile is still intact at this stage. */
public void onDestroyed(){
float explosiveness = block.baseExplosiveness;
@@ -1026,7 +1031,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(block.hasItems){
for(Item item : content.items()){
int amount = items.get(item);
int amount = Math.min(items.get(item), explosionItemCap());
explosiveness += item.explosiveness * amount;
flammability += item.flammability * amount;
power += item.charge * amount * 100f;

View File

@@ -80,6 +80,10 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
remove();
}
public boolean hasCollided(int id){
return collided.size != 0 && !collided.contains(id);
}
@Replace
public float clipSize(){
return type.drawSize;
@@ -90,7 +94,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc t && t.team() != team)
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
&& !(type.pierce && collided.contains(other.id())); //prevent multiple collisions
&& !(type.pierce && hasCollided(other.id())); //prevent multiple collisions
}
@MethodPriority(100)
@@ -116,16 +120,16 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
if(type.collidesTiles && type.collides && type.collidesGround){
world.raycastEach(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Building tile = world.build(x, y);
if(tile == null || !isAdded()) return false;
Building build = world.build(x, y);
if(build == null || !isAdded()) return false;
if(tile.collide(self()) && type.testCollision(self(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team) && !(type.pierceBuilding && collided.contains(tile.id))){
if(build.collide(self()) && type.testCollision(self(), build) && !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
boolean remove = false;
float health = tile.health;
float health = build.health;
if(tile.team != team){
remove = tile.collision(self());
if(build.team != team){
remove = build.collision(self());
}
if(remove || type.collidesTeam){
@@ -133,11 +137,11 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
hit = true;
remove();
}else{
collided.add(tile.id);
collided.add(build.id);
}
}
type.hitTile(self(), tile, health, true);
type.hitTile(self(), build, health, true);
return !type.pierceBuilding;
}

View File

@@ -9,6 +9,7 @@ import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -178,6 +179,14 @@ public class AIController implements UnitController{
return Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
protected void unloadPayloads(){
if(unit instanceof Payloadc pay && pay.hasPayload() && target instanceof Building && pay.payloads().peek() instanceof UnitPayload){
if(target.within(unit, Math.max(unit.type().range + 1f, 75f))){
pay.dropLastPayload();
}
}
}
protected void circle(Position target, float circleLength){
circle(target, circleLength, unit.speed());
}

View File

@@ -70,6 +70,8 @@ public class Rules{
public float enemyCoreBuildRadius = 400f;
/** If true, no-build zones are calculated based on the closest core. */
public boolean polygonCoreProtection = false;
/** If true, dead teams in PvP automatically have their blocks & units converted to derelict upon death. */
public boolean cleanupDeadTeams = true;
/** Radius around enemy wave drop zones.*/
public float dropZoneRadius = 300f;
/** Time between waves in ticks. */

View File

@@ -55,7 +55,7 @@ public class SectorInfo{
/** Waves this sector can survive if under attack. Based on wave in info. <0 means uncalculated. */
public int wavesSurvived = -1;
/** Time between waves. */
public float waveSpacing = 60 * 60 * 2;
public float waveSpacing = 2 * Time.toMinutes;
/** Damage dealt to sector. */
public float damage;
/** How many waves have passed while the player was away. */
@@ -117,11 +117,6 @@ public class SectorInfo{
export.get(item, ExportStat::new).counter += amount;
}
/** Subtracts from export statistics. */
public void handleItemImport(Item item, int amount){
export.get(item, ExportStat::new).counter -= amount;
}
public float getExport(Item item){
return export.get(item, ExportStat::new).mean;
}
@@ -270,6 +265,25 @@ public class SectorInfo{
return map;
}
/** @return a newly allocated map with import statistics. Use sparingly. */
//TODO this can be a float map
public ObjectMap<Item, ExportStat> importStats(){
ObjectMap<Item, ExportStat> imports = new ObjectMap<>();
//for all sectors on all planets that have bases and export to this sector
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
Sector dest = sector.info.getRealDestination();
if(sector.hasBase() && sector.info != this && dest != null && dest.info == this){
//add their exports to our imports
sector.info.export.each((item, stat) -> {
imports.get(item, ExportStat::new).mean += stat.mean;
});
}
}
}
return imports;
}
public static class ExportStat{
public transient float counter;
public transient WindowedMean means = new WindowedMean(valueWindow);

View File

@@ -1,5 +1,6 @@
package mindustry.game;
import arc.struct.*;
import arc.util.*;
import arc.util.serialization.*;
import arc.util.serialization.Json.*;
@@ -39,12 +40,12 @@ public class SpawnGroup implements JsonSerializable{
public float shieldScaling = 0f;
/** Amount of enemies spawned initially, with no scaling */
public int unitAmount = 1;
/** Seq of payloads that this unit will spawn with. */
public @Nullable Seq<UnitType> payloads;
/** Status effect applied to the spawned unit. Null to disable. */
@Nullable
public StatusEffect effect;
public @Nullable StatusEffect effect;
/** Items this unit spawns with. Null to disable. */
@Nullable
public ItemStack items;
public @Nullable ItemStack items;
public SpawnGroup(UnitType type){
this.type = type;
@@ -85,6 +86,15 @@ public class SpawnGroup implements JsonSerializable{
unit.shield = getShield(wave);
//load up spawn payloads
if(payloads != null && unit instanceof Payloadc pay){
for(var type : payloads){
if(type == null) continue;
Unit payload = type.create(unit.team);
pay.pickup(payload);
}
}
return unit;
}
@@ -101,6 +111,9 @@ public class SpawnGroup implements JsonSerializable{
if(shieldScaling != 0) json.writeValue("shieldScaling", shieldScaling);
if(unitAmount != 1) json.writeValue("amount", unitAmount);
if(effect != null) json.writeValue("effect", effect.name);
if(payloads != null && payloads.size > 0){
json.writeValue("payloads", payloads.map(u -> u.name).toArray(String.class));
}
}
@Override
@@ -117,6 +130,9 @@ public class SpawnGroup implements JsonSerializable{
shields = data.getFloat("shields", 0);
shieldScaling = data.getFloat("shieldScaling", 0);
unitAmount = data.getInt("amount", 1);
if(data.has("payloads")){
payloads = Seq.with(json.readValue(String[].class, data.get("payloads"))).map(s -> content.getByName(ContentType.unit, s));
}
//old boss effect ID
if(data.has("effect") && data.get("effect").isNumber() && data.getInt("effect", -1) == 8){

View File

@@ -1,6 +1,7 @@
package mindustry.game;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.Queue;
import arc.struct.*;
@@ -260,6 +261,34 @@ public class Teams{
this.ai = new BaseAI(this);
}
/** Destroys this team's presence on the map, killing part of its buildings and converting everything to 'derelict'. */
public void destroyToDerelict(){
//grab all buildings from quadtree.
var builds = new Seq<Building>();
if(buildings != null){
buildings.getObjects(builds);
}
//convert all team tiles to neutral, randomly killing them
for(var b : builds){
//TODO this may cause a lot of packet spam, optimize?
Call.setTeam(b, Team.derelict);
if(Mathf.chance(0.25)){
Time.run(Mathf.random(0f, 60f * 6f), b::kill);
}
}
//kill all units randomly
units.each(u -> Time.run(Mathf.random(0f, 60f * 5f), () -> {
//ensure unit hasn't switched teams for whatever reason
if(u.team == team){
u.kill();
}
}));
}
@Nullable
public Seq<Unit> unitCache(UnitType type){
if(unitsByType == null || unitsByType.length <= type.id || unitsByType[type.id] == null) return null;
@@ -320,6 +349,7 @@ public class Teams{
public static class BlockPlan{
public final short x, y, rotation, block;
public final Object config;
public boolean removed;
public BlockPlan(int x, int y, short rotation, short block, Object config){
this.x = (short)x;

View File

@@ -41,6 +41,7 @@ public class Drawf{
Draw.reset();
}
/** Sets Draw.z to the text layer, and returns the previous layer. */
public static float text(){
float z = Draw.z();
if(renderer.pixelator.enabled()){
@@ -203,20 +204,24 @@ public class Drawf{
Draw.color();
}
public static void laser(Team team, TextureRegion line, TextureRegion edge, float x, float y, float x2, float y2, float scale){
laser(team, line, edge, x, y, x2, y2, Mathf.angle(x2 - x, y2 - y), scale);
}
public static void laser(Team team, TextureRegion line, TextureRegion edge, float x, float y, float x2, float y2){
laser(team, line, edge, x, y, x2, y2, Mathf.angle(x2 - x, y2 - y), 1f);
laser(team, line, edge, edge, x, y, x2, y2, 1f);
}
public static void laser(Team team, TextureRegion line, TextureRegion edge, float x, float y, float x2, float y2, float rotation, float scale){
float scl = 8f * scale * Draw.scl;
float vx = Mathf.cosDeg(rotation) * scl, vy = Mathf.sinDeg(rotation) * scl;
public static void laser(Team team, TextureRegion line, TextureRegion start, TextureRegion end, float x, float y, float x2, float y2){
laser(team, line, start, end, x, y, x2, y2, 1f);
}
Draw.rect(edge, x, y, edge.width * scale * Draw.scl, edge.height * scale * Draw.scl, rotation + 180);
Draw.rect(edge, x2, y2, edge.width * scale * Draw.scl, edge.height * scale * Draw.scl, rotation);
public static void laser(Team team, TextureRegion line, TextureRegion edge, float x, float y, float x2, float y2, float scale){
laser(team, line, edge, edge, x, y, x2, y2, scale);
}
public static void laser(Team team, TextureRegion line, TextureRegion start, TextureRegion end, float x, float y, float x2, float y2, float scale){
float scl = 8f * scale * Draw.scl, rot = Mathf.angle(x2 - x, y2 - y);
float vx = Mathf.cosDeg(rot) * scl, vy = Mathf.sinDeg(rot) * scl;
Draw.rect(start, x, y, start.width * scale * Draw.scl, start.height * scale * Draw.scl, rot + 180);
Draw.rect(end, x2, y2, end.width * scale * Draw.scl, end.height * scale * Draw.scl, rot);
Lines.stroke(12f * scale);
Lines.line(line, x + vx, y + vy, x2 - vx, y2 - vy, false);
@@ -274,4 +279,13 @@ public class Drawf{
Draw.reset();
}
/** Draws a sprite that should be light-wise correct, when rotated. Provided sprite must be symmetrical in shape. */
public static void spinSprite(TextureRegion region, float x, float y, float r){
r = Mathf.mod(r, 90f);
Draw.rect(region, x, y, r);
Draw.alpha(r / 90f);
Draw.rect(region, x, y, r - 90f);
Draw.alpha(1f);
}
}

View File

@@ -6,6 +6,7 @@ public class Pal{
public static Color
thoriumPink = Color.valueOf("f9a3c7"),
coalBlack = Color.valueOf("272727"),
items = Color.valueOf("2ea756"),
command = Color.valueOf("eab678"),

View File

@@ -145,6 +145,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
for(int pos : positions){
if(req.x == Point2.x(pos) && req.y == Point2.y(pos)){
req.removed = true;
it.remove();
continue outer;
}
@@ -893,6 +894,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Block block = content.block(req.block);
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
removed.add(Point2.pack(req.x, req.y));
req.removed = true;
broken.remove();
}
}
@@ -1025,7 +1027,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return !Core.scene.hasMouse()
&& tile.drop() != null
&& player.unit().validMine(tile)
&& !(tile.floor().playerUnmineable && tile.overlay().itemDrop == null)
&& !((!Core.settings.getBool("doubletapmine") && tile.floor().playerUnmineable) && tile.overlay().itemDrop == null)
&& player.unit().acceptsItem(tile.drop())
&& tile.block() == Blocks.air;
}

View File

@@ -88,7 +88,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else{
Building tile = world.buildWorld(x, y);
if((tile != null && player.team().isEnemy(tile.team) && tile.team != Team.derelict) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){
if((tile != null && player.team().isEnemy(tile.team) && (tile.team != Team.derelict || state.rules.coreCapture)) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){
player.unit().mineTile = null;
target = tile;
}
@@ -938,7 +938,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else if(target == null){
player.shooting = false;
if(Core.settings.getBool("autotarget") && !(player.unit() instanceof BlockUnitUnit u && u.tile() instanceof ControlBlock c && !c.shouldAutoTarget())){
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.team != Team.derelict, u -> u.team != Team.derelict);
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.checkTarget(type.targetAir, type.targetGround), u -> type.targetGround);
if(allowHealing && target == null){
target = Geometry.findClosest(unit.x, unit.y, indexer.getDamaged(Team.sharded));

View File

@@ -1,11 +1,13 @@
package mindustry.io;
import arc.util.*;
import arc.util.serialization.*;
import arc.util.serialization.Json.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -215,6 +217,12 @@ public class JsonIO{
return item != null ? item : liquid;
}
});
//use short names for all filter types
for(var filter : Maps.allFilterTypes){
var i = filter.get();
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Filter", "")), i.getClass());
}
}
static class CustomJson extends Json{

View File

@@ -69,7 +69,6 @@ public class SaveIO{
getMeta(stream);
return true;
}catch(Throwable e){
Log.err(e);
return false;
}
}

View File

@@ -1,5 +1,6 @@
package mindustry.io;
import arc.audio.*;
import arc.graphics.*;
import arc.math.geom.*;
import arc.struct.*;
@@ -501,6 +502,15 @@ public class TypeIO{
return id == -1 ? null : content.item(id);
}
//note that only the standard sound constants in Sounds are supported; modded sounds are not.
public static void writeSound(Writes write, Sound sound){
write.s(Sounds.getSoundId(sound));
}
public static Sound readSound(Reads read){
return Sounds.getSound(read.s());
}
public static void writeWeather(Writes write, Weather item){
write.s(item == null ? -1 : item.id);
}
@@ -633,7 +643,7 @@ public class TypeIO{
}
}
/** Representes a building that has not been resolved yet. */
/** Represents a building that has not been resolved yet. */
public static class BuildingBox{
public int pos;

View File

@@ -16,6 +16,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.LStatements.*;
import mindustry.ui.*;
public class LCanvas extends Table{
@@ -395,11 +396,20 @@ public class LCanvas extends Table{
}
public void copy(){
st.saveUI();
LStatement copy = st.copy();
if(copy instanceof JumpStatement st && st.destIndex != -1){
int index = statements.getChildren().indexOf(this);
if(index != -1 && index < st.destIndex){
st.destIndex ++;
}
}
if(copy != null){
StatementElem s = new StatementElem(copy);
statements.addChildAfter(StatementElem.this,s);
statements.addChildAfter(StatementElem.this, s);
statements.layout();
copy.elem = s;
copy.setupUI();

View File

@@ -24,23 +24,30 @@ import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
import java.util.concurrent.*;
import static mindustry.Vars.*;
public class Maps{
/** All generation filter types. */
public static Prov<GenerateFilter>[] allFilterTypes = new Prov[]{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
EnemySpawnFilter::new, SpawnPathFilter::new
};
/** List of all built-in maps. Filenames only. */
private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "mudFlats", "moltenLake", "archipelago", "debrisField", "veins", "glacier", "passage"};
/** Maps tagged as PvP */
static final String[] pvpMaps = {"veins", "glacier", "passage"};
private static String[] pvpMaps = {"veins", "glacier", "passage"};
/** All maps stored in an ordered array. */
private Seq<Map> maps = new Seq<>();
/** Serializer for meta. */
private Json json = new Json();
private ShuffleMode shuffleMode = ShuffleMode.all;
private @Nullable MapProvider shuffler;
private AsyncExecutor executor = new AsyncExecutor(2);
private ExecutorService executor = Threads.executor(3);
private ObjectSet<Map> previewList = new ObjectSet<>();
public ShuffleMode getShuffleMode(){
@@ -352,20 +359,20 @@ public class Maps{
if(groups == null) return "[]";
StringWriter buffer = new StringWriter();
json.setWriter(new JsonWriter(buffer));
JsonIO.json.setWriter(new JsonWriter(buffer));
json.writeArrayStart();
JsonIO.json.writeArrayStart();
for(int i = 0; i < groups.size; i++){
json.writeObjectStart(SpawnGroup.class, SpawnGroup.class);
groups.get(i).write(json);
json.writeObjectEnd();
JsonIO.json.writeObjectStart(SpawnGroup.class, SpawnGroup.class);
groups.get(i).write(JsonIO.json);
JsonIO.json.writeObjectEnd();
}
json.writeArrayEnd();
JsonIO.json.writeArrayEnd();
return buffer.toString();
}
public Seq<SpawnGroup> readWaves(String str){
return str == null ? null : str.equals("[]") ? new Seq<>() : Seq.with(json.fromJson(SpawnGroup[].class, str));
return str == null ? null : str.equals("[]") ? new Seq<>() : Seq.with(JsonIO.json.fromJson(SpawnGroup[].class, str));
}
public void loadPreviews(){

View File

@@ -3,6 +3,7 @@ package mindustry.maps.filters;
import arc.*;
import arc.func.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
@@ -60,16 +61,19 @@ public abstract class FilterOption{
@Override
public void build(Table table){
Label label;
Element base;
if(!display){
label = new Label("@filter.option." + name);
Label l = new Label("@filter.option." + name);
l.setWrap(true);
l.setStyle(Styles.outlineLabel);
base = l;
}else{
label = new Label(() -> Core.bundle.get("filter.option." + name) + ": " + Strings.autoFixed(getter.get(), 2));
Table t = new Table().marginLeft(11f).marginRight(11f);
base = t;
t.add("@filter.option." + name).growX().wrap().style(Styles.outlineLabel);
t.label(() -> Strings.autoFixed(getter.get(), 2)).style(Styles.outlineLabel).right().labelAlign(Align.right).padLeft(6);
}
label.setWrap(true);
label.setAlignment(Align.center);
label.touchable = Touchable.disabled;
label.setStyle(Styles.outlineLabel);
base.touchable = Touchable.disabled;
Slider slider = new Slider(min, max, step, false);
slider.moved(setter);
@@ -80,7 +84,7 @@ public abstract class FilterOption{
slider.released(changed);
}
table.stack(slider, label).colspan(2).pad(3).growX().row();
table.stack(slider, base).colspan(2).pad(3).growX().row();
}
}

View File

@@ -75,8 +75,7 @@ public abstract class GenerateFilter{
/** localized display name */
public String name(){
var s = simpleName();
return Core.bundle.get("filter." + s);
return Core.bundle.get("filter." + simpleName());
}
public char icon(){

View File

@@ -162,12 +162,27 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
connected.add(this);
}
void con(int x1, int y1, int x2, int y2){
float nscl = rand.random(100f, 140f) * 6f;
int stroke = rand.random(3, 9);
brush(pathfind(x1, y1, x2, y2, tile -> (tile.solid() ? 50f : 0f) + noise(tile.x, tile.y, 2, 0.4f, 1f / nscl) * 500, Astar.manhattan), stroke);
}
void connect(Room to){
if(!connected.add(to)) return;
float nscl = rand.random(100f, 140f);
int stroke = rand.random(3, 9);
brush(pathfind(x, y, to.x, to.y, tile -> (tile.solid() ? 5f : 0f) + noise(tile.x, tile.y, 2, 0.4, 1f / nscl) * 500, Astar.manhattan), stroke);
Vec2 midpoint = Tmp.v1.set(to.x, to.y).add(x, y).scl(0.5f);
rand.nextFloat();
//add randomized offset to avoid straight lines
midpoint.add(Tmp.v2.setToRandomDirection(rand).scl(Tmp.v1.dst(x, y)));
midpoint.sub(width/2f, height/2f).limit(width / 2f / Mathf.sqrt3).add(width/2f, height/2f);
int mx = (int)midpoint.x, my = (int)midpoint.y;
con(x, y, mx, my);
con(mx, my, to.x, to.y);
}
}
@@ -244,8 +259,6 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
cells(1);
distort(10f, 6f);
inverseFloodFill(tiles.getn(spawn.x, spawn.y));
Seq<Block> ores = Seq.with(Blocks.oreCopper, Blocks.oreLead);
float poles = Math.abs(sector.tile.v.y);
float nmag = 0.5f;
@@ -296,6 +309,8 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
median(2);
inverseFloodFill(tiles.getn(spawn.x, spawn.y));
tech();
pass((x, y) -> {

View File

@@ -29,17 +29,17 @@ public class CrashSender{
report += "Report this at " + Vars.reportIssueURL + "\n\n";
}
return report
+ "Version: " + Version.combined() + (Vars.headless ? " (Server)" : "") + "\n"
+ "OS: " + OS.osName + " x" + (OS.osArchBits) + " (" + OS.osArch + ")\n"
+ "Java Version: " + OS.javaVersion + "\n"
+ (mods == null ? "<no mod init>" : mods.list().size + " Mods" + (mods.list().isEmpty() ? "" : ": " + mods.list().toString(", ", mod -> mod.name + ":" + mod.meta.version)))
+ "\n\n" + error;
+ "Version: " + Version.combined() + (Vars.headless ? " (Server)" : "") + "\n"
+ "OS: " + OS.osName + " x" + (OS.osArchBits) + " (" + OS.osArch + ")\n"
+ "Java Version: " + OS.javaVersion + "\n"
+ (mods == null ? "<no mod init>" : mods.list().size + " Mods" + (mods.list().isEmpty() ? "" : ": " + mods.list().toString(", ", mod -> mod.name + ":" + mod.meta.version)))
+ "\n\n" + error;
}
public static void log(Throwable exception){
try{
Core.settings.getDataDirectory().child("crashes").child("crash_" + System.currentTimeMillis() + ".txt")
.writeString(createReport(Strings.neatError(exception)));
.writeString(createReport(Strings.neatError(exception)));
}catch(Throwable ignored){
}
}

View File

@@ -76,7 +76,7 @@ public class StatusEffect extends UnlockableContent{
if(reloadMultiplier != 1) stats.addPercent(Stat.reloadMultiplier, reloadMultiplier);
if(buildSpeedMultiplier != 1) stats.addPercent(Stat.buildSpeedMultiplier, buildSpeedMultiplier);
if(damage > 0) stats.add(Stat.damage, damage * 60f, StatUnit.perSecond);
if(damage < 0) stats.add(Stat.healing, -(damage * 60f), StatUnit.perSecond);
if(damage < 0) stats.add(Stat.healing, -damage * 60f, StatUnit.perSecond);
boolean reacts = false;

View File

@@ -281,7 +281,7 @@ public class UnitType extends UnlockableContent{
if(mineTier >= 1){
stats.addPercent(Stat.mineSpeed, mineSpeed);
stats.add(Stat.mineTier, StatValues.blocks(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= mineTier && !f.playerUnmineable));
stats.add(Stat.mineTier, StatValues.blocks(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= mineTier && (!f.playerUnmineable || Core.settings.getBool("doubletapmine"))));
}
if(buildSpeed > 0){
stats.addPercent(Stat.buildSpeed, buildSpeed);

View File

@@ -128,7 +128,7 @@ public class Weapon implements Cloneable{
t.add("[lightgray]" + Stat.inaccuracy.localized() + ": [white]" + (int)inaccuracy + " " + StatUnit.degrees.localized());
}
t.row();
t.add("[lightgray]" + Stat.reload.localized() + ": " + (mirror ? "2x " : "") + "[white]" + Strings.autoFixed(60f / reload * shots, 2));
t.add("[lightgray]" + Stat.reload.localized() + ": " + (mirror ? "2x " : "") + "[white]" + Strings.autoFixed(60f / reload * shots, 2) + " " + StatUnit.perSecond.localized());
StatValues.ammo(ObjectMap.of(u, bullet)).display(t);
}

View File

@@ -55,30 +55,30 @@ public class Links{
private static String report(){
return "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&body=" +
Strings.encode(Strings.format(
"""
**Platform**: `@`
**Build**: `@`
**Issue**: *Explain your issue in detail.*
**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*
**Link(s) to mod(s) used**: `@`
**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME.*
**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*
---
*Place an X (no spaces) between the brackets to confirm that you have read the line below.*
- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**
- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**
""",
OS.isAndroid ? "Android " + Core.app.getVersion() : (OS.osName + " x" + OS.osArchBits),
Version.combined(),
Vars.mods.list().any() ? Vars.mods.list().select(LoadedMod::enabled).map(l -> l.meta.author + "/" + l.name + ":" + l.meta.version) : "none"));
Strings.encode(Strings.format(
"""
**Platform**: `@`
**Build**: `@`
**Issue**: *Explain your issue in detail.*
**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*
**Link(s) to mod(s) used**: `@`
**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME.*
**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*
---
*Place an X (no spaces) between the brackets to confirm that you have read the line below.*
- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**
- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**
""",
OS.isAndroid ? "Android " + Core.app.getVersion() : (OS.osName + " x" + OS.osArchBits),
Version.combined(),
Vars.mods.list().any() ? Vars.mods.list().select(LoadedMod::enabled).map(l -> l.meta.author + "/" + l.name + ":" + l.meta.version) : "none"));
}
}

View File

@@ -142,6 +142,7 @@ public class CustomRulesDialog extends BaseDialog{
check("@rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
check("@rules.schematic", b -> rules.schematicsAllowed = b, () -> rules.schematicsAllowed);
check("@rules.coreincinerates", b -> rules.coreIncinerates = b, () -> rules.coreIncinerates);
check("@rules.cleanupdeadteams", b -> rules.cleanupDeadTeams = b, () -> rules.cleanupDeadTeams, () -> rules.pvp);
number("@rules.buildcostmultiplier", false, f -> rules.buildCostMultiplier = f, () -> rules.buildCostMultiplier, () -> !rules.infiniteResources);
number("@rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier, 0.001f, 50f);
number("@rules.deconstructrefundmultiplier", false, f -> rules.deconstructRefundMultiplier = f, () -> rules.deconstructRefundMultiplier, () -> !rules.infiniteResources);
@@ -262,7 +263,7 @@ public class CustomRulesDialog extends BaseDialog{
rebuild[0] = () -> {
base.clearChildren();
int cols = Math.max(1, Core.graphics.getWidth() / 460);
int cols = Math.max(1, (int)(Core.graphics.getWidth() / Scl.scl(450)));
int idx = 0;
for(WeatherEntry entry : rules.weather){

View File

@@ -616,34 +616,33 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
selectAlpha = Mathf.lerpDelta(selectAlpha, Mathf.num(planets.zoom < 1.9f), 0.1f);
}
void displayItems(Table c, float scl, ObjectMap<Item, ExportStat> stats, String name){
Table t = new Table().left();
int i = 0;
for(var item : content.items()){
var stat = stats.get(item);
if(stat == null) continue;
int total = (int)(stat.mean * 60 * scl);
if(total > 1){
t.image(item.uiIcon).padRight(3);
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray).padRight(3);
if(++i % 3 == 0){
t.row();
}
}
}
if(t.getChildren().any()){
c.add(name).left().row();
c.add(t).padLeft(10f).left().row();
}
}
void showStats(Sector sector){
BaseDialog dialog = new BaseDialog(sector.name());
dialog.cont.pane(c -> {
Cons2<ObjectMap<Item, ExportStat>, String> display = (stats, name) -> {
Table t = new Table().left();
float scl = sector.getProductionScale();
int[] i = {0};
stats.each((item, stat) -> {
int total = (int)(stat.mean * 60 * scl);
if(total > 1){
t.image(item.uiIcon).padRight(3);
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray).padRight(3);
if(++i[0] % 3 == 0){
t.row();
}
}
});
if(t.getChildren().any()){
c.add(name).left().row();
c.add(t).padLeft(10f).left().row();
}
};
c.defaults().padBottom(5);
c.add(Core.bundle.get("sectors.time") + " [accent]" + sector.save.getPlayTime()).left().row();
@@ -666,10 +665,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
//production
display.get(sector.info.production, "@sectors.production");
displayItems(c, sector.getProductionScale(), sector.info.production, "@sectors.production");
//export
display.get(sector.info.export, "@sectors.export");
displayItems(c, sector.getProductionScale(), sector.info.export, "@sectors.export");
//import
if(sector.hasBase()){
displayItems(c, 1f, sector.info.importStats(), "@sectors.import");
}
ItemSeq items = sector.items();
@@ -748,7 +752,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
updateSelected();
}).checked(sector.info.icon == null);
int cols = (int)Math.min(20, Core.graphics.getWidth() / 52f);
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
int i = 1;
for(var key : defaultIcons){

View File

@@ -415,16 +415,14 @@ public class SchematicsDialog extends BaseDialog{
t.marginRight(19f);
t.defaults().size(48f);
int cols = (int)Math.min(20, Core.graphics.getWidth() / 52f);
int i;
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
for(ContentType ctype : defaultContentIcons){
t.row();
t.image().colspan(cols).growX().width(Float.NEGATIVE_INFINITY).height(3f).color(Pal.accent);
t.row();
i = 0;
int i = 0;
for(UnlockableContent u : content.getBy(ctype).<UnlockableContent>as()){
if(!u.isHidden() && u.unlockedNow() && u.hasEmoji() && !tags.contains(u.emoji())){
t.button(new TextureRegionDrawable(u.uiIcon), Styles.cleari, iconMed, () -> {

View File

@@ -6,6 +6,8 @@ import arc.func.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.input.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.TextButton.*;
@@ -28,7 +30,6 @@ import java.io.*;
import java.util.zip.*;
import static arc.Core.*;
import static mindustry.Vars.net;
import static mindustry.Vars.*;
public class SettingsMenuDialog extends Dialog{
@@ -283,9 +284,9 @@ public class SettingsMenuDialog extends Dialog{
}
void addSettings(){
sound.sliderPref("musicvol", bundle.get("setting.musicvol.name", "Music Volume"), 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("sfxvol", bundle.get("setting.sfxvol.name", "SFX Volume"), 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("ambientvol", bundle.get("setting.ambientvol.name", "Ambient Volume"), 100, 0, 100, 1, i -> i + "%");
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 + "%");
game.sliderPref("saveinterval", 60, 10, 5 * 120, 10, i -> Core.bundle.format("setting.seconds", i));
@@ -562,52 +563,26 @@ public class SettingsMenuDialog extends Dialog{
rebuild();
}
public SliderSetting sliderPref(String name, String title, int def, int min, int max, StringProcessor s){
return sliderPref(name, title, def, min, max, 1, s);
}
public SliderSetting sliderPref(String name, String title, int def, int min, int max, int step, StringProcessor s){
SliderSetting res;
list.add(res = new SliderSetting(name, title, def, min, max, step, s));
settings.defaults(name, def);
rebuild();
return res;
}
public SliderSetting sliderPref(String name, int def, int min, int max, StringProcessor s){
return sliderPref(name, def, min, max, 1, s);
}
public SliderSetting sliderPref(String name, int def, int min, int max, int step, StringProcessor s){
SliderSetting res;
list.add(res = new SliderSetting(name, bundle.get("setting." + name + ".name"), def, min, max, step, s));
list.add(res = new SliderSetting(name, def, min, max, step, s));
settings.defaults(name, def);
rebuild();
return res;
}
public void checkPref(String name, String title, boolean def){
list.add(new CheckSetting(name, title, def, null));
settings.defaults(name, def);
rebuild();
}
public void checkPref(String name, String title, boolean def, Boolc changed){
list.add(new CheckSetting(name, title, def, changed));
settings.defaults(name, def);
rebuild();
}
/** Localized title. */
public void checkPref(String name, boolean def){
list.add(new CheckSetting(name, bundle.get("setting." + name + ".name"), def, null));
list.add(new CheckSetting(name, def, null));
settings.defaults(name, def);
rebuild();
}
/** Localized title. */
public void checkPref(String name, boolean def, Boolc changed){
list.add(new CheckSetting(name, bundle.get("setting." + name + ".name"), def, changed));
list.add(new CheckSetting(name, def, changed));
settings.defaults(name, def);
rebuild();
}
@@ -631,17 +606,41 @@ public class SettingsMenuDialog extends Dialog{
public abstract static class Setting{
public String name;
public String title;
public @Nullable String description;
Setting(String name){
this.name = name;
title = bundle.get("setting." + name + ".name");
description = bundle.getOrNull("setting." + name + ".description");
}
public abstract void add(SettingsTable table);
public void addDesc(Element elem){
if(description == null) return;
elem.addListener(new Tooltip(t -> t.background(Styles.black8).margin(4f).add(description).color(Color.lightGray)){
{
allowMobile = true;
}
@Override
protected void setContainerPosition(Element element, float x, float y){
this.targetActor = element;
Vec2 pos = element.localToStageCoordinates(Tmp.v1.set(0, 0));
container.pack();
container.setPosition(pos.x, pos.y, Align.topLeft);
container.setOrigin(0, element.getHeight());
}
});
}
}
public static class CheckSetting extends Setting{
boolean def;
Boolc changed;
CheckSetting(String name, String title, boolean def, Boolc changed){
this.name = name;
this.title = title;
CheckSetting(String name, boolean def, Boolc changed){
super(name);
this.def = def;
this.changed = changed;
}
@@ -660,7 +659,7 @@ public class SettingsMenuDialog extends Dialog{
});
box.left();
table.add(box).left().padTop(3f);
addDesc(table.add(box).left().padTop(3f).get());
table.row();
}
}
@@ -669,9 +668,8 @@ public class SettingsMenuDialog extends Dialog{
int def, min, max, step;
StringProcessor sp;
SliderSetting(String name, String title, int def, int min, int max, int step, StringProcessor s){
this.name = name;
this.title = title;
SliderSetting(String name, int def, int min, int max, int step, StringProcessor s){
super(name);
this.def = def;
this.min = min;
this.max = max;
@@ -685,21 +683,21 @@ public class SettingsMenuDialog extends Dialog{
slider.setValue(settings.getInt(name));
Label value = new Label("");
value.setStyle(Styles.outlineLabel);
value.touchable = Touchable.disabled;
Label value = new Label("", Styles.outlineLabel);
Table content = new Table();
content.add(title, Styles.outlineLabel).left().growX().wrap();
content.add(value).padLeft(10f).right();
content.margin(3f, 33f, 3f, 33f);
content.touchable = Touchable.disabled;
slider.changed(() -> {
settings.put(name, (int)slider.getValue());
value.setText(title + ": " + sp.get((int)slider.getValue()));
value.setText(sp.get((int)slider.getValue()));
});
value.setAlignment(Align.center);
value.setWrap(true);
slider.change();
table.stack(slider, value).width(Math.min(Core.graphics.getWidth() / 1.2f, 460f)).left().padTop(4);
addDesc(table.stack(slider, content).width(Math.min(Core.graphics.getWidth() / 1.2f, 460f)).left().padTop(4f).get());
table.row();
}
}

View File

@@ -1,4 +0,0 @@
package mindustry.world;
public class TileData{
}

View File

@@ -4,7 +4,7 @@ import mindustry.content.*;
public class TileGen{
public Block floor;
public Block block ;
public Block block;
public Block overlay;
{

View File

@@ -41,6 +41,7 @@ public class ConstructBlock extends Block{
health = 20;
consumesTap = true;
solidifes = true;
inEditor = false;
consBlocks[size - 1] = this;
sync = true;
}

View File

@@ -18,7 +18,7 @@ import static mindustry.Vars.*;
public class MendProjector extends Block{
public final int timerUse = timers++;
public Color baseColor = Color.valueOf("84f491");
public Color phaseColor = Color.valueOf("ffd59e");
public Color phaseColor = baseColor;
public @Load("@-top") TextureRegion topRegion;
public float reload = 250f;
public float range = 60f;
@@ -92,7 +92,7 @@ public class MendProjector extends Block{
float realRange = range + phaseHeat * phaseRangeBoost;
charge = 0f;
indexer.eachBlock(this, realRange, other -> other.damaged(), other -> {
indexer.eachBlock(this, realRange, Building::damaged, other -> {
other.heal(other.maxHealth() * (healPercent + phaseHeat * phaseBoost) / 100f * efficiency());
Fx.healBlockFull.at(other.x, other.y, other.block.size, baseColor);
});

View File

@@ -51,7 +51,7 @@ public class PointDefenseTurret extends ReloadTurret{
public void setStats(){
super.setStats();
stats.add(Stat.reload, 60f / reloadTime, StatUnit.none);
stats.add(Stat.reload, 60f / reloadTime, StatUnit.perSecond);
}
public class PointDefenseBuild extends ReloadTurretBuild{

View File

@@ -20,10 +20,6 @@ import static mindustry.Vars.*;
public class TractorBeamTurret extends BaseTurret{
public final int timerTarget = timers++;
public float retargetTime = 5f;
public @Load("block-@size") TextureRegion baseRegion;
public @Load("@-laser") TextureRegion laser;
public @Load("@-laser-end") TextureRegion laserEnd;
public float shootCone = 6f;
public float shootLength = 5f;
@@ -39,6 +35,11 @@ public class TractorBeamTurret extends BaseTurret{
public Sound shootSound = Sounds.tractorbeam;
public float shootSoundVolume = 0.9f;
public @Load("block-@size") TextureRegion baseRegion;
public @Load("@-laser") TextureRegion laser;
public @Load(value = "@-laser-start", fallback = "@-laser-end") TextureRegion laserStart;
public @Load("@-laser-end") TextureRegion laserEnd;
public TractorBeamTurret(String name){
super(name);
@@ -151,7 +152,7 @@ public class TractorBeamTurret extends BaseTurret{
Draw.mixcol(laserColor, Mathf.absin(4f, 0.6f));
Drawf.laser(team, laser, laserEnd,
Drawf.laser(team, laser, laserStart, laserEnd,
x + Angles.trnsx(ang, shootLength), y + Angles.trnsy(ang, shootLength),
lastX, lastY, strength * efficiency() * laserWidth);

View File

@@ -60,6 +60,8 @@ public class Turret extends ReloadTurret{
public float minRange = 0f;
public float burstSpacing = 0;
public boolean alternate = false;
/** If true, this turret will accurately target moving targets with respect to charge time. */
public boolean accurateDelay = false;
public boolean targetAir = true;
public boolean targetGround = true;
@@ -106,7 +108,7 @@ public class Turret extends ReloadTurret{
super.setStats();
stats.add(Stat.inaccuracy, (int)inaccuracy, StatUnit.degrees);
stats.add(Stat.reload, 60f / (reloadTime) * (alternate ? 1 : shots), StatUnit.none);
stats.add(Stat.reload, 60f / (reloadTime) * (alternate ? 1 : shots), StatUnit.perSecond);
stats.add(Stat.targetsAir, targetAir);
stats.add(Stat.targetsGround, targetGround);
if(ammoPerShot != 1) stats.add(Stat.ammoUse, ammoPerShot, StatUnit.perShot);
@@ -216,11 +218,16 @@ public class Turret extends ReloadTurret{
public void targetPosition(Posc pos){
if(!hasAmmo() || pos == null) return;
BulletType bullet = peekAmmo();
float speed = bullet.speed;
//slow bullets never intersect
if(speed < 0.1f) speed = 9999999f;
targetPos.set(Predict.intercept(this, pos, speed));
var offset = Tmp.v1.setZero();
//when delay is accurate, assume unit has moved by chargeTime already
if(accurateDelay && pos instanceof Hitboxc h){
offset.set(h.deltaX(), h.deltaY()).scl(chargeTime / Time.delta);
}
targetPos.set(Predict.intercept(this, pos, offset.x, offset.y, bullet.speed <= 0.01f ? 99999999f : bullet.speed));
if(targetPos.isZero()){
targetPos.set(pos);
}

View File

@@ -29,7 +29,7 @@ public class MassDriver extends Block{
public int minDistribute = 10;
public float knockback = 4f;
public float reloadTime = 100f;
public MassDriverBolt bullet;
public MassDriverBolt bullet = new MassDriverBolt();
public float bulletSpeed = 5.5f;
public float bulletLifetime = 200f;
public Effect shootEffect = Fx.shootBig2;
@@ -60,7 +60,7 @@ public class MassDriver extends Block{
super.setStats();
stats.add(Stat.shootRange, range / tilesize, StatUnit.blocks);
stats.add(Stat.reload, 60f / reloadTime, StatUnit.none);
stats.add(Stat.reload, 60f / reloadTime, StatUnit.perSecond);
}
@Override

View File

@@ -30,6 +30,7 @@ public class OreBlock extends OverlayFloor{
/** For mod use only!*/
public OreBlock(String name){
super(name);
this.useColor = true;
variants = 3;
}

View File

@@ -7,6 +7,7 @@ public class LegacyBlock extends Block{
public LegacyBlock(String name){
super(name);
inEditor = false;
}
/** Removes this block from the world, or replaces it with something else. */

View File

@@ -1,13 +1,10 @@
package mindustry.world.blocks.liquid;
import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
public class ArmoredConduit extends Conduit{
public @Load("@-cap") TextureRegion capRegion;
public ArmoredConduit(String name){
super(name);
@@ -21,17 +18,6 @@ public class ArmoredConduit extends Conduit{
}
public class ArmoredConduitBuild extends ConduitBuild{
@Override
public void draw(){
super.draw();
//draw the cap when a conduit would normally leak
Building next = front();
if(next != null && next.team == team && next.block.hasLiquids) return;
Draw.rect(capRegion, x, y, rotdeg());
}
@Override
public boolean acceptLiquid(Building source, Liquid liquid){
return super.acceptLiquid(source, liquid) && (tile == null || source.block instanceof Conduit ||

View File

@@ -28,6 +28,7 @@ public class Conduit extends LiquidBlock implements Autotiler{
public @Load(value = "@-top-#", length = 5) TextureRegion[] topRegions;
public @Load(value = "@-bottom-#", length = 5, fallback = "conduit-bottom-#") TextureRegion[] botRegions;
public @Load("@-cap") TextureRegion capRegion;
public boolean leaks = true;
@@ -83,6 +84,7 @@ public class Conduit extends LiquidBlock implements Autotiler{
public class ConduitBuild extends LiquidBuild implements ChainedBuilding{
public float smoothLiquid;
public int blendbits, xscl, yscl, blending;
public boolean capped;
@Override
public void draw(){
@@ -104,6 +106,8 @@ public class Conduit extends LiquidBlock implements Autotiler{
Draw.scl(xscl, yscl);
drawAt(x, y, blendbits, rotation, SliceMode.none);
Draw.reset();
if(capped && capRegion.found()) Draw.rect(capRegion, x, y, rotdeg());
}
protected void drawAt(float x, float y, int bits, float rotation, SliceMode slice){
@@ -124,6 +128,9 @@ public class Conduit extends LiquidBlock implements Autotiler{
xscl = bits[1];
yscl = bits[2];
blending = bits[4];
Building next = front();
capped = next == null || next.team != team || !next.block.hasLiquids;
}
@Override

View File

@@ -252,18 +252,6 @@ public class LogicBlock extends Block{
}
}
@Override
public void onProximityAdded(){
super.onProximityAdded();
//unbox buildings after reading
for(var v : executor.vars){
if(v.objval instanceof BuildingBox b){
v.objval = world.build(b.pos);
}
}
}
public String findLinkName(Block block){
String bname = getLinkName(block);
Bits taken = new Bits(links.size);
@@ -598,7 +586,7 @@ public class LogicBlock extends Block{
for(int i = 0; i < varcount; i++){
BVar dest = asm.getVar(names[i]);
if(dest != null && !dest.constant){
dest.value = values[i];
dest.value = values[i] instanceof BuildingBox box ? world.build(box.pos) : values[i];
}
}
});

View File

@@ -3,12 +3,15 @@ package mindustry.world.blocks.power;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.logic.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -27,6 +30,7 @@ public class LightBlock extends Block{
configurable = true;
saveConfig = true;
envEnabled |= Env.space;
swapDiagonalPlacement = true;
config(Integer.class, (LightBuild tile, Integer value) -> tile.color = value);
}
@@ -38,6 +42,19 @@ public class LightBlock extends Block{
super.init();
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
Drawf.dashCircle(x * tilesize + offset, y * tilesize + offset, radius * 0.75f, Pal.placing);
}
@Override
public void changePlacementPath(Seq<Point2> points, int rotation){
var placeRadius2 = Mathf.pow(radius * 0.7f / tilesize, 2f) * 3;
Placement.calculateNodes(points, this, rotation, (point, other) -> point.dst2(other) <= placeRadius2);
}
public class LightBuild extends Building{
public int color = Pal.accent.rgba();
public float smoothTime = 1f;

View File

@@ -12,6 +12,7 @@ import mindustry.world.meta.*;
public class ThermalGenerator extends PowerGenerator{
public Effect generateEffect = Fx.none;
public float effectChance = 0.05f;
public Attribute attribute = Attribute.heat;
public ThermalGenerator(String name){
@@ -52,7 +53,7 @@ public class ThermalGenerator extends PowerGenerator{
public void updateTile(){
productionEfficiency = sum + attribute.env();
if(productionEfficiency > 0.1f && Mathf.chance(0.05 * delta())){
if(productionEfficiency > 0.1f && Mathf.chanceDelta(effectChance)){
generateEffect.at(x + Mathf.range(3f), y + Mathf.range(3f));
}
}

View File

@@ -54,6 +54,7 @@ public class Drill extends Block{
public float updateEffectChance = 0.02f;
public boolean drawRim = false;
public boolean drawSpinSprite = true;
public Color heatColor = Color.valueOf("ff5512");
public @Load("@-rim") TextureRegion rimRegion;
public @Load("@-rotator") TextureRegion rotatorRegion;
@@ -314,7 +315,11 @@ public class Drill extends Block{
Draw.color();
}
Draw.rect(rotatorRegion, x, y, timeDrilled * rotateSpeed);
if(drawSpinSprite){
Drawf.spinSprite(rotatorRegion, x, y, timeDrilled * rotateSpeed);
}else{
Draw.rect(rotatorRegion, x, y, timeDrilled * rotateSpeed);
}
Draw.rect(topRegion, x, y);

View File

@@ -53,7 +53,7 @@ public class Fracker extends SolidPump{
Drawf.liquid(liquidRegion, x, y, liquids.get(result) / liquidCapacity, result.color);
Draw.rect(rotatorRegion, x, y, pumpTime);
Drawf.spinSprite(rotatorRegion, x, y, pumpTime);
Draw.rect(topRegion, x, y);
}

View File

@@ -120,7 +120,7 @@ public class GenericCrafter extends Block{
warmup = Mathf.approachDelta(warmup, 1f, warmupSpeed);
if(Mathf.chanceDelta(updateEffectChance)){
updateEffect.at(getX() + Mathf.range(size * 4f), getY() + Mathf.range(size * 4));
updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4));
}
}else{
warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed);

View File

@@ -49,7 +49,7 @@ public class LiquidConverter extends GenericCrafter{
if(cons.valid()){
if(Mathf.chanceDelta(updateEffectChance)){
updateEffect.at(getX() + Mathf.range(size * 4f), getY() + Mathf.range(size * 4));
updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4));
}
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);

View File

@@ -91,7 +91,7 @@ public class SolidPump extends Pump{
public void draw(){
Draw.rect(region, x, y);
Drawf.liquid(liquidRegion, x, y, liquids.total() / liquidCapacity, liquids.current().color);
Draw.rect(rotatorRegion, x, y, pumpTime * rotateSpeed);
Drawf.spinSprite(rotatorRegion, x, y, pumpTime * rotateSpeed);
Draw.rect(topRegion, x, y);
}
@@ -110,7 +110,7 @@ public class SolidPump extends Pump{
lastPump = maxPump;
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);
if(Mathf.chance(delta() * updateEffectChance))
updateEffect.at(getX() + Mathf.range(size * 2f), getY() + Mathf.range(size * 2f));
updateEffect.at(x + Mathf.range(size * 2f), y + Mathf.range(size * 2f));
}else{
warmup = Mathf.lerpDelta(warmup, 0f, 0.02f);
lastPump = 0f;

View File

@@ -282,8 +282,11 @@ public class CoreBlock extends StorageBlock{
public void afterDestroyed(){
if(state.rules.coreCapture){
tile.setNet(block, lastDamage, 0);
//core is invincible for several seconds to prevent recapture
((CoreBuild)tile.build).iframes = captureInvicibility;
//building does not exist on client yet
if(!net.client()){
//core is invincible for several seconds to prevent recapture
((CoreBuild)tile.build).iframes = captureInvicibility;
}
}
}

View File

@@ -84,6 +84,12 @@ public class StorageBlock extends Block{
return itemCapacity;
}
@Override
public int explosionItemCap(){
//when linked to a core, containers/vaults are made significantly less explosive.
return linkedCore != null ? Math.min(itemCapacity/60, 6) : itemCapacity;
}
@Override
public void drawSelect(){
if(linkedCore != null){

View File

@@ -64,6 +64,7 @@ public class Reconstructor extends UnitBlock{
@Override
public void setStats(){
stats.timePeriod = constructTime;
super.setStats();
stats.add(Stat.productionTime, constructTime / 60f, StatUnit.seconds);

View File

@@ -22,8 +22,6 @@ public class DrawArcSmelter extends DrawBlock{
Draw.rect(bottom, build.x, build.y);
if(build.warmup > 0f && flameColor.a > 0.001f){
Lines.stroke(circleStroke * build.warmup);
float si = Mathf.absin(flameRadiusScl, flameRadiusMag);

View File

@@ -2,16 +2,22 @@ package mindustry.world.draw;
import arc.*;
import arc.graphics.g2d.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
public class DrawRotator extends DrawBlock{
public TextureRegion rotator, top;
public boolean drawSpinSprite = false;
@Override
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y);
Draw.rect(rotator, build.x, build.y, build.totalProgress * 2f);
if(drawSpinSprite){
Drawf.spinSprite(rotator, build.x, build.y, build.totalProgress * 2f);
}else{
Draw.rect(rotator, build.x, build.y, build.totalProgress * 2f);
}
if(top.found()) Draw.rect(top, build.x, build.y);
}

View File

@@ -96,7 +96,7 @@ public class StatValues{
for(int i = 0; i < list.size; i++){
Item item = list.get(i);
table.add(timePeriod <= 0 ? new ItemDisplay(item) : new ItemDisplay(item, 0, timePeriod, true)).padRight(5);
table.add(timePeriod <= 0 ? new ItemDisplay(item) : new ItemDisplay(item, 1, timePeriod, true)).padRight(5);
if(i != list.size - 1){
table.add("/");
@@ -260,6 +260,10 @@ public class StatValues{
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map){
return ammo(map, 0);
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map, int indent){
return table -> {
table.row();
@@ -268,12 +272,12 @@ public class StatValues{
orderedKeys.sort();
for(T t : orderedKeys){
boolean unit = t instanceof UnitType;
boolean compact = t instanceof UnitType || indent > 0;
BulletType type = map.get(t);
//no point in displaying unit icon twice
if(!unit & !(t instanceof PowerTurret)){
if(!compact && !(t instanceof PowerTurret)){
table.image(icon(t)).size(3 * 8).padRight(4).right().top();
table.add(t.localizedName).padRight(10).left().top();
}
@@ -297,11 +301,11 @@ public class StatValues{
sep(bt, Core.bundle.format("bullet.splashdamage", (int)type.splashDamage, Strings.fixed(type.splashDamageRadius / tilesize, 1)));
}
if(!unit && !Mathf.equal(type.ammoMultiplier, 1f) && type.displayAmmoMultiplier){
if(!compact && !Mathf.equal(type.ammoMultiplier, 1f) && type.displayAmmoMultiplier){
sep(bt, Core.bundle.format("bullet.multiplier", (int)type.ammoMultiplier));
}
if(!Mathf.equal(type.reloadMultiplier, 1f)){
if(!compact && !Mathf.equal(type.reloadMultiplier, 1f)){
sep(bt, Core.bundle.format("bullet.reload", Strings.autoFixed(type.reloadMultiplier, 2)));
}
@@ -310,7 +314,7 @@ public class StatValues{
}
if(type.healPercent > 0f){
sep(bt, Core.bundle.format("bullet.healpercent", (int)type.healPercent));
sep(bt, Core.bundle.format("bullet.healpercent", Strings.autoFixed(type.healPercent, 2)));
}
if(type.pierce || type.pierceCap != -1){
@@ -329,14 +333,17 @@ public class StatValues{
sep(bt, Core.bundle.format("bullet.lightning", type.lightning, type.lightningDamage < 0 ? type.damage : type.lightningDamage));
}
if(type.fragBullet != null){
sep(bt, "@bullet.frag");
}
if(type.status != StatusEffects.none){
sep(bt, (type.minfo.mod == null ? type.status.emoji() : "") + "[stat]" + type.status.localizedName);
}
}).padTop(unit ? 0 : -9).left().get().background(unit ? null : Tex.underline);
if(type.fragBullet != null){
sep(bt, Core.bundle.format("bullet.frags", type.fragBullets));
bt.row();
ammo(ObjectMap.of(t, type.fragBullet), indent + 1).display(bt);
}
}).padTop(compact ? 0 : -9).padLeft(indent * 8).left().get().background(compact ? null : Tex.underline);
table.row();
}