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:
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 |
@@ -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.
|
||||
|
||||
@@ -100,7 +100,8 @@ joingame = 게임 참여
|
||||
customgame = 사용자 지정 게임
|
||||
newgame = 새 게임
|
||||
none = < 없음 >
|
||||
none.found = [lightgray]< 없거나 찾을 수 없음 >
|
||||
none.found = [lightgray]< 찾을 수 없음 >
|
||||
none.inmap = [lightgray]< 맵에 없음 >
|
||||
minimap = 미니맵
|
||||
position = 위치
|
||||
close = 닫기
|
||||
|
||||
@@ -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 = Вертикальная синхронизация
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"){{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -527,8 +527,7 @@ public class World{
|
||||
|
||||
private class Context implements WorldContext{
|
||||
|
||||
Context(){
|
||||
}
|
||||
Context(){}
|
||||
|
||||
@Override
|
||||
public Tile tile(int index){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -69,7 +69,6 @@ public class SaveIO{
|
||||
getMeta(stream);
|
||||
return true;
|
||||
}catch(Throwable e){
|
||||
Log.err(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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){
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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, () -> {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package mindustry.world;
|
||||
|
||||
public class TileData{
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import mindustry.content.*;
|
||||
|
||||
public class TileGen{
|
||||
public Block floor;
|
||||
public Block block ;
|
||||
public Block block;
|
||||
public Block overlay;
|
||||
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ public class ConstructBlock extends Block{
|
||||
health = 20;
|
||||
consumesTap = true;
|
||||
solidifes = true;
|
||||
inEditor = false;
|
||||
consBlocks[size - 1] = this;
|
||||
sync = true;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,7 @@ public class OreBlock extends OverlayFloor{
|
||||
/** For mod use only!*/
|
||||
public OreBlock(String name){
|
||||
super(name);
|
||||
this.useColor = true;
|
||||
variants = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user