Merge branch 'master' of https://github.com/Anuken/Mindustry into map_4
This commit is contained in:
@@ -316,7 +316,8 @@ public class Vars implements Loadable{
|
||||
logicVars = new GlobalVars();
|
||||
javaPath =
|
||||
new Fi(OS.prop("java.home")).child("bin/java").exists() ? new Fi(OS.prop("java.home")).child("bin/java").absolutePath() :
|
||||
Core.files.local("jre/bin/java").exists() ? Core.files.local("jre/bin/java").absolutePath() :
|
||||
Core.files.local("jre/bin/java").exists() ? Core.files.local("jre/bin/java").absolutePath() : // Unix
|
||||
Core.files.local("jre/bin/java.exe").exists() ? Core.files.local("jre/bin/java.exe").absolutePath() : // Windows
|
||||
"java";
|
||||
|
||||
state = new GameState();
|
||||
|
||||
@@ -26,8 +26,8 @@ public class RtsAI{
|
||||
static final Seq<Unit> squad = new Seq<>(false);
|
||||
static final IntSet used = new IntSet();
|
||||
static final IntSet assignedTargets = new IntSet();
|
||||
static final float squadRadius = 120f;
|
||||
static final int timeUpdate = 0, timerSpawn = 1;
|
||||
static final float squadRadius = 140f;
|
||||
static final int timeUpdate = 0, timerSpawn = 1, maxTargetsChecked = 15;
|
||||
|
||||
//in order of priority??
|
||||
static final BlockFlag[] flags = {BlockFlag.generator, BlockFlag.factory, BlockFlag.core, BlockFlag.battery};
|
||||
@@ -259,6 +259,10 @@ public class RtsAI{
|
||||
|
||||
weights.clear();
|
||||
|
||||
//only check a maximum number of targets to prevent hammering the CPU with estimateStats calls
|
||||
targets.shuffle();
|
||||
targets.truncate(maxTargetsChecked);
|
||||
|
||||
for(var target : targets){
|
||||
weights.put(target, estimateStats(x, y, target.x, target.y, dps, health));
|
||||
}
|
||||
@@ -281,6 +285,7 @@ public class RtsAI{
|
||||
return result;
|
||||
}
|
||||
|
||||
//TODO extremely slow especially with many squads.
|
||||
float estimateStats(float fromX, float fromY, float x, float y, float selfDps, float selfHealth){
|
||||
float[] health = {0f}, dps = {0f};
|
||||
float extraRadius = 50f;
|
||||
|
||||
@@ -67,8 +67,28 @@ public class CommandAI extends AIController{
|
||||
return;
|
||||
}
|
||||
|
||||
//acquiring naval targets isn't supported yet, so use the fallback dumb AI
|
||||
if(unit.team.isAI() && unit.team.rules().rtsAi && unit.type.naval){
|
||||
if(fallback == null) fallback = new GroundAI();
|
||||
|
||||
if(fallback.unit() != unit) fallback.unit(unit);
|
||||
fallback.updateUnit();
|
||||
return;
|
||||
}
|
||||
|
||||
updateVisuals();
|
||||
updateTargeting();
|
||||
//only autotarget if the unit supports it
|
||||
if(targetPos == null || unit.type.autoFindTarget){
|
||||
updateTargeting();
|
||||
}else if(attackTarget == null){
|
||||
//if the unit does not have an attack target, is currently moving, and does not have autotargeting, stop attacking stuff
|
||||
target = null;
|
||||
for(var mount : unit.mounts){
|
||||
if(mount.weapon.controllable){
|
||||
mount.target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(attackTarget != null && invalid(attackTarget)){
|
||||
attackTarget = null;
|
||||
@@ -170,7 +190,7 @@ public class CommandAI extends AIController{
|
||||
public void hit(Bullet bullet){
|
||||
if(unit.team.isAI() && bullet.owner instanceof Teamc teamc && teamc.team() != unit.team && attackTarget == null &&
|
||||
//can only counter-attack every few seconds to prevent rapidly changing targets
|
||||
!(teamc instanceof Unit u && !u.checkTarget(unit.type.targetAir, unit.type.targetGround)) && timer.get(timerTarget4, 60f * 12f)){
|
||||
!(teamc instanceof Unit u && !u.checkTarget(unit.type.targetAir, unit.type.targetGround)) && timer.get(timerTarget4, 60f * 10f)){
|
||||
commandTarget(teamc, true);
|
||||
}
|
||||
}
|
||||
@@ -216,6 +236,8 @@ public class CommandAI extends AIController{
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
//TODO ひどい
|
||||
|
||||
@@ -28,7 +28,8 @@ public class DefenderAI extends AIController{
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
|
||||
//Sort by max health and closer target.
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type && u.targetable(unit.team), (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type && u.targetable(unit.team) && u.type.playerControllable,
|
||||
(u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
|
||||
if(result != null) return result;
|
||||
|
||||
//return core if found
|
||||
|
||||
@@ -8,8 +8,8 @@ import mindustry.entities.*;
|
||||
import mindustry.entities.abilities.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.entities.effect.*;
|
||||
import mindustry.entities.part.*;
|
||||
import mindustry.entities.part.DrawPart.*;
|
||||
import mindustry.entities.part.*;
|
||||
import mindustry.entities.pattern.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -1042,7 +1042,7 @@ public class Blocks{
|
||||
|
||||
craftTime = 10f;
|
||||
hasLiquids = hasPower = true;
|
||||
drawer = new DrawMulti(new DrawDefault(), new DrawLiquidRegion());
|
||||
drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(), new DrawDefault());
|
||||
|
||||
consumePower(1f);
|
||||
consumeItem(Items.scrap, 1);
|
||||
@@ -1062,6 +1062,8 @@ public class Blocks{
|
||||
|
||||
consumePower(1.1f);
|
||||
consumeLiquid(Liquids.slag, 4f / 60f);
|
||||
|
||||
drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(), new DrawRegion("-spinner", 3, true), new DrawDefault());
|
||||
}};
|
||||
|
||||
disassembler = new Separator("disassembler"){{
|
||||
@@ -1080,6 +1082,8 @@ public class Blocks{
|
||||
consumePower(4f);
|
||||
consumeItem(Items.scrap);
|
||||
consumeLiquid(Liquids.slag, 0.12f);
|
||||
|
||||
drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(), new DrawRegion("-spinner", 3, true), new DrawDefault());
|
||||
}};
|
||||
|
||||
sporePress = new GenericCrafter("spore-press"){{
|
||||
@@ -2135,6 +2139,8 @@ public class Blocks{
|
||||
liquidRouter = new LiquidRouter("liquid-router"){{
|
||||
requirements(Category.liquid, with(Items.graphite, 4, Items.metaglass, 2));
|
||||
liquidCapacity = 20f;
|
||||
underBullets = true;
|
||||
solid = false;
|
||||
}};
|
||||
|
||||
liquidContainer = new LiquidRouter("liquid-container"){{
|
||||
@@ -2226,6 +2232,7 @@ public class Blocks{
|
||||
liquidPadding = 3f/4f;
|
||||
researchCostMultiplier = 3;
|
||||
underBullets = true;
|
||||
solid = false;
|
||||
}};
|
||||
|
||||
reinforcedLiquidContainer = new LiquidRouter("reinforced-liquid-container"){{
|
||||
@@ -5746,7 +5753,6 @@ public class Blocks{
|
||||
requirements(Category.logic, with(Items.copper, 90, Items.lead, 50, Items.silicon, 50));
|
||||
|
||||
instructionsPerTick = 2;
|
||||
|
||||
size = 1;
|
||||
}};
|
||||
|
||||
@@ -5754,9 +5760,7 @@ public class Blocks{
|
||||
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 80, Items.graphite, 60, Items.thorium, 50));
|
||||
|
||||
instructionsPerTick = 8;
|
||||
|
||||
range = 8 * 22;
|
||||
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
@@ -5801,7 +5805,7 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
canvas = new CanvasBlock("canvas"){{
|
||||
requirements(Category.logic, BuildVisibility.debugOnly, with(Items.silicon, 50));
|
||||
requirements(Category.logic, BuildVisibility.shown, with(Items.silicon, 40, Items.graphite, 10));
|
||||
|
||||
canvasSize = 12;
|
||||
padding = 7f / 4f * 2f;
|
||||
|
||||
@@ -54,10 +54,10 @@ public class Fx{
|
||||
unitSpawn = new Effect(30f, e -> {
|
||||
if(!(e.data instanceof UnitType unit)) return;
|
||||
|
||||
float scl = 1f + e.fout() * 2f;
|
||||
|
||||
TextureRegion region = unit.fullIcon;
|
||||
|
||||
float scl = (1f + e.fout() * 2f) * region.scl();
|
||||
|
||||
alpha(e.fout());
|
||||
mixcol(Color.white, e.fin());
|
||||
|
||||
@@ -67,7 +67,7 @@ public class Fx{
|
||||
|
||||
alpha(e.fin());
|
||||
|
||||
rect(region, e.x, e.y, region.width * Draw.scl * scl, region.height * Draw.scl * scl, e.rotation - 90);
|
||||
rect(region, e.x, e.y, region.width * scl, region.height * scl, e.rotation - 90);
|
||||
}),
|
||||
|
||||
unitCapKill = new Effect(80f, e -> {
|
||||
|
||||
@@ -64,6 +64,7 @@ public class Planets{
|
||||
lightDstFrom = 0.2f;
|
||||
clearSectorOnLose = true;
|
||||
defaultCore = Blocks.coreBastion;
|
||||
iconColor = Color.valueOf("ff9266");
|
||||
hiddenItems.addAll(Items.serpuloItems).removeAll(Items.erekirItems);
|
||||
|
||||
//TODO SHOULD there be lighting?
|
||||
@@ -104,7 +105,9 @@ public class Planets{
|
||||
generator = new TantrosPlanetGenerator();
|
||||
meshLoader = () -> new HexMesh(this, 4);
|
||||
accessible = false;
|
||||
visible = false;
|
||||
atmosphereColor = Color.valueOf("3db899");
|
||||
iconColor = Color.valueOf("597be3");
|
||||
startSector = 10;
|
||||
atmosphereRadIn = -0.01f;
|
||||
atmosphereRadOut = 0.3f;
|
||||
@@ -114,7 +117,6 @@ public class Planets{
|
||||
};
|
||||
}};
|
||||
|
||||
//TODO hide beryllium and others on load in rules
|
||||
serpulo = new Planet("serpulo", sun, 1f, 3){{
|
||||
generator = new SerpuloPlanetGenerator();
|
||||
meshLoader = () -> new HexMesh(this, 6);
|
||||
@@ -139,6 +141,7 @@ public class Planets{
|
||||
r.attributes.clear();
|
||||
r.showSpawns = false;
|
||||
};
|
||||
iconColor = Color.valueOf("7d4dff");
|
||||
atmosphereColor = Color.valueOf("3c1b8f");
|
||||
atmosphereRadIn = 0.02f;
|
||||
atmosphereRadOut = 0.3f;
|
||||
@@ -154,10 +157,6 @@ public class Planets{
|
||||
gen.carbonChance = 0.1f;
|
||||
gen.ferricChance = 0f;
|
||||
});
|
||||
|
||||
//define launch candidates after all planets initialize
|
||||
//TODO how will it use the nucleus???
|
||||
serpulo.launchCandidates.add(erekir);
|
||||
}
|
||||
|
||||
private static Planet makeAsteroid(String name, Planet parent, Block base, Block tint, float tintThresh, int pieces, float scale, Cons<AsteroidGenerator> cgen){
|
||||
@@ -171,11 +170,12 @@ public class Planets{
|
||||
accessible = false;
|
||||
clipRadius = 2f;
|
||||
defaultEnv = Env.space;
|
||||
|
||||
icon = "commandRally";
|
||||
generator = new AsteroidGenerator();
|
||||
cgen.get((AsteroidGenerator)generator);
|
||||
|
||||
meshLoader = () -> {
|
||||
iconColor = tint.mapColor;
|
||||
Color tinted = tint.mapColor.cpy().a(1f - tint.mapColor.a);
|
||||
Seq<GenericMesh> meshes = new Seq<>();
|
||||
Color color = base.mapColor;
|
||||
|
||||
@@ -93,6 +93,8 @@ public class TechTree{
|
||||
public Seq<Objective> objectives = new Seq<>();
|
||||
/** Nodes that depend on this node. */
|
||||
public final Seq<TechNode> children = new Seq<>();
|
||||
/** Planet associated with this tech node. Null to auto-detect, or use Serpulo if no associated planet is found. */
|
||||
public @Nullable Planet planet;
|
||||
|
||||
public TechNode(@Nullable TechNode parent, UnlockableContent content, ItemStack[] requirements){
|
||||
if(parent != null){
|
||||
|
||||
@@ -83,7 +83,7 @@ public class Renderer implements ApplicationListener{
|
||||
camera = new Camera();
|
||||
Shaders.init();
|
||||
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
shakeTime = shakeIntensity = 0f;
|
||||
camShakeOffset.setZero();
|
||||
});
|
||||
@@ -484,7 +484,7 @@ public class Renderer implements ApplicationListener{
|
||||
TextureRegion reg = block.fullIcon;
|
||||
float scl = Scl.scl(4f) / camerascale;
|
||||
float shake = 0f;
|
||||
float s = reg.width * Draw.scl * scl * 3.6f * Interp.pow2Out.apply(fout);
|
||||
float s = reg.width * reg.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
|
||||
float rotation = Interp.pow2In.apply(fout) * 135f, x = build.x + Mathf.range(shake), y = build.y + Mathf.range(shake);
|
||||
float thrustOpen = 0.25f;
|
||||
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
|
||||
|
||||
@@ -119,7 +119,7 @@ public class MapRenderer implements Disposable{
|
||||
if(wall != Blocks.air && useSyntheticWall){
|
||||
region = !center ? clearEditor : getIcon(wall, idxWall);
|
||||
|
||||
float width = region.width * Draw.scl, height = region.height * Draw.scl, ox = wall.offset + (tilesize - width) / 2f, oy = wall.offset + (tilesize - height) / 2f;
|
||||
float width = region.width * region.scl(), height = region.height * region.scl(), ox = wall.offset + (tilesize - width) / 2f, oy = wall.offset + (tilesize - height) / 2f;
|
||||
|
||||
//force fit to tile
|
||||
if(overlay.wallOre && !wall.synthetic()){
|
||||
@@ -152,8 +152,8 @@ public class MapRenderer implements Disposable{
|
||||
region = ((Cliff)Blocks.cliff).editorCliffs[tile.data & 0xff];
|
||||
}
|
||||
|
||||
offsetX = tilesize / 2f - region.width / 2f * Draw.scl;
|
||||
offsetY = tilesize / 2f - region.height / 2f * Draw.scl;
|
||||
offsetX = tilesize / 2f - region.width * region.scl() / 2f;
|
||||
offsetY = tilesize / 2f - region.height * region.scl() / 2f;
|
||||
}else if((wall == Blocks.air || overlay.wallOre) && !overlay.isAir()){
|
||||
if(floor.isLiquid){
|
||||
mesh.setColor(Tmp.c1.set(1f, 1f, 1f, floor.overlayAlpha));
|
||||
@@ -163,7 +163,7 @@ public class MapRenderer implements Disposable{
|
||||
region = clearEditor;
|
||||
}
|
||||
|
||||
float width = region.width * Draw.scl, height = region.height * Draw.scl;
|
||||
float width = region.width * region.scl(), height = region.height * region.scl();
|
||||
if(!wall.synthetic() && wall != Blocks.air && !wall.isMultiblock()){
|
||||
offsetX = offsetY = 0f;
|
||||
width = height = tilesize;
|
||||
|
||||
@@ -466,7 +466,7 @@ public class Damage{
|
||||
}
|
||||
|
||||
/** Damages all entities and blocks in a radius that are enemies of the team. */
|
||||
public static void damage(Team team, float x, float y, float radius, float damage, boolean complete, boolean air, boolean ground, boolean scaled, Bullet source){
|
||||
public static void damage(Team team, float x, float y, float radius, float damage, boolean complete, boolean air, boolean ground, boolean scaled, @Nullable Bullet source){
|
||||
Cons<Unit> cons = entity -> {
|
||||
if(entity.team == team || !entity.checkTarget(air, ground) || !entity.hittable() || !entity.within(x, y, radius + (scaled ? entity.hitSize / 2f : 0f))){
|
||||
return;
|
||||
@@ -474,6 +474,9 @@ public class Damage{
|
||||
|
||||
float amount = calculateDamage(scaled ? Math.max(0, entity.dst(x, y) - entity.type.hitSize/2) : entity.dst(x, y), radius, damage);
|
||||
entity.damage(amount);
|
||||
if(source != null){
|
||||
entity.controller().hit(source);
|
||||
}
|
||||
//TODO better velocity displacement
|
||||
float dst = vec.set(entity.x - x, entity.y - y).len();
|
||||
entity.vel.add(vec.setLength((1f - dst / radius) * 2f / entity.mass()));
|
||||
@@ -504,9 +507,7 @@ public class Damage{
|
||||
}
|
||||
|
||||
public static void tileDamage(Team team, int x, int y, float baseRadius, float damage, @Nullable Bullet source){
|
||||
|
||||
Core.app.post(() -> {
|
||||
|
||||
var in = world.build(x, y);
|
||||
//spawned inside a multiblock. this means that damage needs to be dealt directly.
|
||||
//why? because otherwise the building would absorb everything in one cell, which means much less damage than a nearby explosion.
|
||||
|
||||
@@ -16,11 +16,8 @@ public class EntityCollisions{
|
||||
private static final float seg = 1f;
|
||||
|
||||
//tile collisions
|
||||
private Rect tmp = new Rect();
|
||||
private Vec2 vector = new Vec2();
|
||||
private Vec2 l1 = new Vec2();
|
||||
private Rect r1 = new Rect();
|
||||
private Rect r2 = new Rect();
|
||||
private Vec2 vector = new Vec2(), l1 = new Vec2();
|
||||
private Rect r1 = new Rect(), r2 = new Rect(), tmp = new Rect();
|
||||
|
||||
//entity collisions
|
||||
private Seq<Hitboxc> arrOut = new Seq<>(Hitboxc.class);
|
||||
|
||||
@@ -43,7 +43,8 @@ abstract class LaunchCoreComp implements Drawc, Timedc{
|
||||
Draw.z(Layer.weather - 1);
|
||||
|
||||
TextureRegion region = block.fullIcon;
|
||||
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
|
||||
scale *= region.scl();
|
||||
float rw = region.width * scale, rh = region.height * scale;
|
||||
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(region, cx, cy, rw, rh, rotation - 45);
|
||||
|
||||
@@ -69,7 +69,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
|
||||
boolean canPickup(Building build){
|
||||
return payloadUsed() + build.block.size * build.block.size * Vars.tilesize * Vars.tilesize <= type.payloadCapacity + 0.001f && build.canPickup();
|
||||
return payloadUsed() + build.block.size * build.block.size * Vars.tilesize * Vars.tilesize <= type.payloadCapacity + 0.001f && build.canPickup() && build.team == team;
|
||||
}
|
||||
|
||||
boolean canPickupPayload(Payload pay){
|
||||
|
||||
@@ -63,6 +63,12 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
|
||||
}
|
||||
|
||||
public void updateBoosting(boolean boost){
|
||||
if(!type.canBoost) return;
|
||||
|
||||
elevation = Mathf.approachDelta(elevation, type.canBoost ? Mathf.num(boost || onSolid() || (isFlying() && !canLand())) : 0f, type.riseSpeed);
|
||||
}
|
||||
|
||||
/** Move based on preferred unit movement type. */
|
||||
public void movePref(Vec2 movement){
|
||||
if(type.omniMovement){
|
||||
|
||||
@@ -23,7 +23,7 @@ public enum Gamemode{
|
||||
rules.waveTimer = true;
|
||||
|
||||
rules.waveSpacing = 2f * Time.toMinutes;
|
||||
rules.teams.get(rules.waveTeam).infiniteResources = true;
|
||||
rules.waveTeam.rules().infiniteResources = true;
|
||||
}, map -> map.teams.size > 1),
|
||||
pvp(rules -> {
|
||||
rules.pvp = true;
|
||||
|
||||
@@ -25,6 +25,7 @@ public class Objectives{
|
||||
@Override
|
||||
public String display(){
|
||||
return Core.bundle.format("requirement.research",
|
||||
//TODO broken for multi tech nodes.
|
||||
(content.techNode == null || content.techNode.parent == null || content.techNode.parent.content.unlocked()) && !(content instanceof Item) ?
|
||||
(content.emoji() + " " + content.localizedName) : "???");
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class Team implements Comparable<Team>{
|
||||
|
||||
/** @return whether this team is supposed to be AI-controlled. */
|
||||
public boolean isAI(){
|
||||
return (state.rules.waves || state.rules.attackMode) && this == state.rules.waveTeam && !state.rules.pvp;
|
||||
return (state.rules.waves || state.rules.attackMode) && this != state.rules.defaultTeam && !state.rules.pvp;
|
||||
}
|
||||
|
||||
/** @return whether this team is solely comprised of AI (with no players possible). */
|
||||
|
||||
@@ -278,21 +278,6 @@ public class Universe{
|
||||
save();
|
||||
}
|
||||
|
||||
/** This method is expensive to call; only do so sparingly. */
|
||||
public ItemSeq getGlobalResources(){
|
||||
ItemSeq count = new ItemSeq();
|
||||
|
||||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasSave()){
|
||||
count.add(sector.items());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void updateNetSeconds(int value){
|
||||
netSeconds = value;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ public class Drawf{
|
||||
|
||||
/** Bleeds a mod pixmap if linear filtering is enabled. */
|
||||
public static void checkBleed(Pixmap pixmap){
|
||||
if(Core.settings.getBool("linear", true)) Pixmaps.bleed(pixmap);
|
||||
if(Core.settings.getBool("linear", true)){
|
||||
Pixmaps.bleed(pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO offset unused
|
||||
@@ -400,8 +402,8 @@ public class Drawf{
|
||||
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);
|
||||
Draw.rect(start, x, y, start.width * scale * start.scl(), start.height * scale * start.scl(), rot + 180);
|
||||
Draw.rect(end, x2, y2, end.width * scale * end.scl(), end.height * scale * end.scl(), rot);
|
||||
|
||||
Lines.stroke(12f * scale);
|
||||
Lines.line(line, x + vx, y + vy, x2 - vx, y2 - vy, false);
|
||||
|
||||
@@ -243,12 +243,12 @@ public class MenuRenderer implements Disposable{
|
||||
|
||||
TextureRegion icon = flyerType.fullIcon;
|
||||
|
||||
float size = Math.max(icon.width, icon.height) * Draw.scl * 1.6f;
|
||||
|
||||
flyers((x, y) -> {
|
||||
Draw.rect(icon, x - 12f, y - 13f, flyerRot - 90);
|
||||
});
|
||||
|
||||
float size = Math.max(icon.width, icon.height) * icon.scl() * 1.6f;
|
||||
|
||||
flyers((x, y) -> {
|
||||
Draw.rect("circle-shadow", x, y, size, size);
|
||||
});
|
||||
|
||||
@@ -123,7 +123,7 @@ public class MinimapRenderer{
|
||||
rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize);
|
||||
|
||||
for(Unit unit : units){
|
||||
if(unit.inFogTo(player.team())) continue;
|
||||
if(unit.inFogTo(player.team()) || !unit.type.drawMinimap) continue;
|
||||
|
||||
float rx = !withLabels ? (unit.x - rect.x) / rect.width * w : unit.x / (world.width() * tilesize) * w;
|
||||
float ry = !withLabels ? (unit.y - rect.y) / rect.width * h : unit.y / (world.height() * tilesize) * h;
|
||||
|
||||
@@ -1653,20 +1653,26 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public void drawArrow(Block block, int x, int y, int rotation, boolean valid){
|
||||
float trns = (block.size / 2) * tilesize;
|
||||
int dx = Geometry.d4(rotation).x, dy = Geometry.d4(rotation).y;
|
||||
float offsetx = x * tilesize + block.offset + dx*trns;
|
||||
float offsety = y * tilesize + block.offset + dy*trns;
|
||||
|
||||
Draw.color(!valid ? Pal.removeBack : Pal.accentBack);
|
||||
Draw.rect(Core.atlas.find("place-arrow"),
|
||||
x * tilesize + block.offset + dx*trns,
|
||||
y * tilesize + block.offset - 1 + dy*trns,
|
||||
Core.atlas.find("place-arrow").width * Draw.scl,
|
||||
Core.atlas.find("place-arrow").height * Draw.scl, rotation * 90 - 90);
|
||||
TextureRegion regionArrow = Core.atlas.find("place-arrow");
|
||||
|
||||
Draw.rect(regionArrow,
|
||||
offsetx,
|
||||
offsety - 1,
|
||||
regionArrow.width * regionArrow.scl(),
|
||||
regionArrow.height * regionArrow.scl(),
|
||||
rotation * 90 - 90);
|
||||
|
||||
Draw.color(!valid ? Pal.remove : Pal.accent);
|
||||
Draw.rect(Core.atlas.find("place-arrow"),
|
||||
x * tilesize + block.offset + dx*trns,
|
||||
y * tilesize + block.offset + dy*trns,
|
||||
Core.atlas.find("place-arrow").width * Draw.scl,
|
||||
Core.atlas.find("place-arrow").height * Draw.scl, rotation * 90 - 90);
|
||||
Draw.rect(regionArrow,
|
||||
offsetx,
|
||||
offsety,
|
||||
regionArrow.width * regionArrow.scl(),
|
||||
regionArrow.height * regionArrow.scl(),
|
||||
rotation * 90 - 90);
|
||||
}
|
||||
|
||||
void iterateLine(int startX, int startY, int endX, int endY, Cons<PlaceLine> cons){
|
||||
|
||||
@@ -80,6 +80,7 @@ public class ContentParser{
|
||||
put(Blending.class, (type, data) -> field(Blending.class, data));
|
||||
put(CacheLayer.class, (type, data) -> field(CacheLayer.class, data));
|
||||
put(Attribute.class, (type, data) -> Attribute.get(data.asString()));
|
||||
put(BuildVisibility.class, (type, data) -> field(BuildVisibility.class, data));
|
||||
put(Schematic.class, (type, data) -> {
|
||||
Object result = fieldOpt(Loadouts.class, data);
|
||||
if(result != null){
|
||||
@@ -380,6 +381,11 @@ public class ContentParser{
|
||||
}
|
||||
}
|
||||
|
||||
//try to parse Rect as array
|
||||
if(type == Rect.class && jsonData.isArray() && jsonData.size == 4){
|
||||
return (T)new Rect(jsonData.get(0).asFloat(), jsonData.get(1).asFloat(), jsonData.get(2).asFloat(), jsonData.get(3).asFloat());
|
||||
}
|
||||
|
||||
if(Content.class.isAssignableFrom(type)){
|
||||
ContentType ctype = contentTypes.getThrow(type, () -> new IllegalArgumentException("No content type for class: " + type.getSimpleName()));
|
||||
String prefix = currentMod != null ? currentMod.name + "-" : "";
|
||||
|
||||
@@ -40,6 +40,7 @@ public class Mods implements Loadable{
|
||||
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override");
|
||||
|
||||
private int totalSprites;
|
||||
private static ObjectFloatMap<String> textureResize = new ObjectFloatMap<>();
|
||||
private MultiPacker packer;
|
||||
private ModClassLoader mainLoader = new ModClassLoader(getClass().getClassLoader());
|
||||
|
||||
@@ -83,7 +84,7 @@ public class Mods implements Loadable{
|
||||
|
||||
/** @return the loaded mod found by class, or null if not found. */
|
||||
public @Nullable LoadedMod getMod(Class<? extends Mod> type){
|
||||
return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type);
|
||||
return mods.find(m -> m.main != null && m.main.getClass() == type);
|
||||
}
|
||||
|
||||
/** Imports an external mod file. Folders are not supported here. */
|
||||
@@ -180,7 +181,8 @@ public class Mods implements Loadable{
|
||||
}
|
||||
|
||||
private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<Future<Runnable>> tasks){
|
||||
boolean linear = Core.settings.getBool("linear", true);
|
||||
boolean bleed = Core.settings.getBool("linear", true) && !mod.meta.pregenerated;
|
||||
float textureScale = mod.meta.texturescale;
|
||||
|
||||
for(Fi file : sprites){
|
||||
String name = file.nameWithoutExtension();
|
||||
@@ -204,12 +206,16 @@ public class Mods implements Loadable{
|
||||
try{
|
||||
Pixmap pix = new Pixmap(file.readBytes());
|
||||
//only bleeds when linear filtering is on at startup
|
||||
if(linear){
|
||||
if(bleed){
|
||||
Pixmaps.bleed(pix, 2);
|
||||
}
|
||||
//this returns a *runnable* which actually packs the resulting pixmap; this has to be done synchronously outside the method
|
||||
return () -> {
|
||||
packer.add(getPage(file), (prefix ? mod.name + "-" : "") + name, new PixmapRegion(pix));
|
||||
String fullName = (prefix ? mod.name + "-" : "") + name;
|
||||
packer.add(getPage(file), fullName, new PixmapRegion(pix));
|
||||
if(textureScale != 1.0f){
|
||||
textureResize.put(fullName, textureScale);
|
||||
}
|
||||
pix.dispose();
|
||||
};
|
||||
}catch(Exception e){
|
||||
@@ -336,7 +342,7 @@ public class Mods implements Loadable{
|
||||
if(c instanceof UnlockableContent u && c.minfo.mod != null){
|
||||
u.load();
|
||||
u.loadIcon();
|
||||
if(u.generateIcons){
|
||||
if(u.generateIcons && !c.minfo.mod.meta.pregenerated){
|
||||
u.createIcons(packer);
|
||||
}
|
||||
}
|
||||
@@ -344,13 +350,15 @@ public class Mods implements Loadable{
|
||||
}
|
||||
Log.debug("Time to generate icons: @", Time.elapsed());
|
||||
|
||||
packer.printStats();
|
||||
|
||||
//dispose old atlas data
|
||||
Core.atlas = packer.flush(filter, new TextureAtlas());
|
||||
|
||||
textureResize.each(e -> Core.atlas.find(e.key).scale = e.value);
|
||||
|
||||
Core.atlas.setErrorRegion("error");
|
||||
Log.debug("Total pages: @", Core.atlas.getTextures().size);
|
||||
|
||||
packer.printStats();
|
||||
}
|
||||
|
||||
packer.dispose();
|
||||
@@ -1141,6 +1149,10 @@ public class Mods implements Loadable{
|
||||
public boolean java;
|
||||
/** If true, -outline regions for units are kept when packing. Only use if you know exactly what you are doing. */
|
||||
public boolean keepOutlines;
|
||||
/** To rescale textures with a different size. Represents the size in pixels of the sprite of a 1x1 block. */
|
||||
public float texturescale = 1.0f;
|
||||
/** If true, bleeding is skipped and no content icons are generated. */
|
||||
public boolean pregenerated;
|
||||
|
||||
public String displayName(){
|
||||
return displayName == null ? name : displayName;
|
||||
@@ -1174,6 +1186,7 @@ public class Mods implements Loadable{
|
||||
", minGameVersion='" + minGameVersion + '\'' +
|
||||
", hidden=" + hidden +
|
||||
", repo=" + repo +
|
||||
", texturescale=" + texturescale +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class CellLiquid extends Liquid{
|
||||
public int cells = 8;
|
||||
|
||||
public @Nullable Liquid spreadTarget;
|
||||
public float maxSpread = 0.5f, spreadConversion = 1f, spreadDamage = 0.1f, removeScaling = 0.25f;
|
||||
public float maxSpread = 0.75f, spreadConversion = 1f, spreadDamage = 0.1f, removeScaling = 0.25f;
|
||||
|
||||
public CellLiquid(String name, Color color){
|
||||
super(name, color);
|
||||
|
||||
@@ -47,6 +47,8 @@ public class Item extends UnlockableContent implements Senseable{
|
||||
/** If true, this material is used by buildings. If false, this material will be incinerated in certain cores. */
|
||||
public boolean buildable = true;
|
||||
public boolean hidden = false;
|
||||
/** For mods. Adds this item to the listed planets' hidden items Seq. */
|
||||
public @Nullable Planet[] hiddenOnPlanets;
|
||||
|
||||
public Item(String name, Color color){
|
||||
super(name);
|
||||
@@ -57,6 +59,17 @@ public class Item extends UnlockableContent implements Senseable{
|
||||
this(name, new Color(Color.black));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
|
||||
if(hiddenOnPlanets != null){
|
||||
for(Planet planet : hiddenOnPlanets){
|
||||
planet.hiddenItems.add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden(){
|
||||
return hidden;
|
||||
|
||||
@@ -89,6 +89,8 @@ public class Planet extends UnlockableContent{
|
||||
public Color lightColor = Color.white.cpy();
|
||||
/** Atmosphere tint for landable planets. */
|
||||
public Color atmosphereColor = new Color(0.3f, 0.7f, 1.0f);
|
||||
/** Icon for appearance in planet list. */
|
||||
public Color iconColor = Color.white.cpy();
|
||||
/** Whether this planet has an atmosphere. */
|
||||
public boolean hasAtmosphere = true;
|
||||
/** Whether to allow users to specify a custom launch schematic for this map. */
|
||||
@@ -107,6 +109,8 @@ public class Planet extends UnlockableContent{
|
||||
public boolean prebuildBase = true;
|
||||
/** If true, waves are created on sector loss. TODO remove. */
|
||||
public boolean allowWaves = false;
|
||||
/** Icon as displayed in the planet selection dialog. This is a string, as drawables are null at load time. */
|
||||
public String icon = "planet";
|
||||
/** Default core block for launching. */
|
||||
public Block defaultCore = Blocks.coreShard;
|
||||
/** Sets up rules on game load for any sector on this planet. */
|
||||
@@ -119,7 +123,7 @@ public class Planet extends UnlockableContent{
|
||||
public Seq<Planet> children = new Seq<>();
|
||||
/** Default root node shown when the tech tree is opened here. */
|
||||
public @Nullable TechNode techTree;
|
||||
/** Planets that can be launched to from this one. Made mutual in init(). */
|
||||
/** TODO remove? Planets that can be launched to from this one. Made mutual in init(). */
|
||||
public Seq<Planet> launchCandidates = new Seq<>();
|
||||
/** Items not available on this planet. */
|
||||
public Seq<Item> hiddenItems = new Seq<>();
|
||||
@@ -169,6 +173,10 @@ public class Planet extends UnlockableContent{
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Sector getStartSector(){
|
||||
return sectors.size == 0 ? null : sectors.get(startSector);
|
||||
}
|
||||
|
||||
public void applyRules(Rules rules){
|
||||
ruleSetter.get(rules);
|
||||
|
||||
|
||||
@@ -212,6 +212,8 @@ public class UnitType extends UnlockableContent{
|
||||
bounded = true,
|
||||
/** if true, this unit is detected as naval - do NOT assign this manually! Initialized in init() */
|
||||
naval = false,
|
||||
/** if false, RTS AI controlled units do not automatically attack things while moving. This is automatically assigned. */
|
||||
autoFindTarget = true,
|
||||
|
||||
/** if true, this modded unit always has a -outline region generated for its base. Normally, outlines are ignored if there are no top = false weapons. */
|
||||
alwaysCreateOutline = false,
|
||||
@@ -226,7 +228,9 @@ public class UnitType extends UnlockableContent{
|
||||
/** if false, the unit shield (usually seen in waves) is not drawn. */
|
||||
drawShields = true,
|
||||
/** if false, the unit body is not drawn. */
|
||||
drawBody = true;
|
||||
drawBody = true,
|
||||
/** if false, the unit is not drawn on the minimap. */
|
||||
drawMinimap = true;
|
||||
|
||||
/** The default AI controller to assign on creation. */
|
||||
public Prov<? extends UnitController> aiController = () -> !flying ? new GroundAI() : new FlyingAI();
|
||||
@@ -686,6 +690,9 @@ public class UnitType extends UnlockableContent{
|
||||
lightRadius = Math.max(60f, hitSize * 2.3f);
|
||||
}
|
||||
|
||||
//if a status effects slows a unit when firing, don't shoot while moving.
|
||||
autoFindTarget = !weapons.contains(w -> w.shootStatus.speedMultiplier < 0.99f);
|
||||
|
||||
clipSize = Math.max(clipSize, lightRadius * 1.1f);
|
||||
singleTarget = weapons.size <= 1 && !forceMultiTarget;
|
||||
|
||||
@@ -905,6 +912,8 @@ public class UnitType extends UnlockableContent{
|
||||
public void createIcons(MultiPacker packer){
|
||||
super.createIcons(packer);
|
||||
|
||||
sample = constructor.get();
|
||||
|
||||
var toOutline = new Seq<TextureRegion>();
|
||||
getRegionsToOutline(toOutline);
|
||||
|
||||
@@ -945,7 +954,6 @@ public class UnitType extends UnlockableContent{
|
||||
}
|
||||
}
|
||||
|
||||
//TODO test
|
||||
if(sample instanceof Tankc){
|
||||
PixmapRegion pix = Core.atlas.getPixmap(treadRegion);
|
||||
|
||||
@@ -1012,7 +1020,7 @@ public class UnitType extends UnlockableContent{
|
||||
//find reconstructor
|
||||
var rec = (Reconstructor)content.blocks().find(b -> b instanceof Reconstructor re && re.upgrades.contains(u -> u[1] == this));
|
||||
|
||||
if(rec != null && Structs.find(rec.consumers, i -> i instanceof ConsumeItems) instanceof ConsumeItems ci){
|
||||
if(rec != null && rec.findConsumer(i -> i instanceof ConsumeItems) instanceof ConsumeItems ci){
|
||||
if(prevReturn != null){
|
||||
prevReturn[0] = rec.upgrades.find(u -> u[1] == this)[0];
|
||||
}
|
||||
@@ -1266,7 +1274,7 @@ public class UnitType extends UnlockableContent{
|
||||
public void drawSoftShadow(float x, float y, float rotation, float alpha){
|
||||
Draw.color(0, 0, 0, 0.4f * alpha);
|
||||
float rad = 1.6f;
|
||||
float size = Math.max(region.width, region.height) * Draw.scl;
|
||||
float size = Math.max(region.width, region.height) * region.scl();
|
||||
Draw.rect(softShadowRegion, x, y, size * rad * Draw.xscl, size * rad * Draw.yscl, rotation - 90);
|
||||
Draw.color();
|
||||
}
|
||||
@@ -1285,11 +1293,12 @@ public class UnitType extends UnlockableContent{
|
||||
size, size, unit.rotation);
|
||||
Draw.mixcol();
|
||||
|
||||
size = (3f + Mathf.absin(Time.time, 5f, 1f)) * unit.itemTime + 0.5f;
|
||||
size = ((3f + Mathf.absin(Time.time, 5f, 1f)) * unit.itemTime + 0.5f) * 2;
|
||||
Draw.color(Pal.accent);
|
||||
Draw.rect(itemCircleRegion,
|
||||
unit.x + Angles.trnsx(unit.rotation + 180f, itemOffsetY),
|
||||
unit.y + Angles.trnsy(unit.rotation + 180f, itemOffsetY), size * 2, size * 2);
|
||||
unit.y + Angles.trnsy(unit.rotation + 180f, itemOffsetY),
|
||||
size, size);
|
||||
|
||||
if(unit.isLocal() && !renderer.pixelator.enabled()){
|
||||
Fonts.outline.draw(unit.stack.amount + "",
|
||||
@@ -1401,7 +1410,7 @@ public class UnitType extends UnlockableContent{
|
||||
|
||||
for(int side : Mathf.signs){
|
||||
Tmp.v1.set(xOffset * side, yOffset).rotate(unit.rotation - 90);
|
||||
Draw.rect(region, unit.x + Tmp.v1.x / 4f, unit.y + Tmp.v1.y / 4f, treadRect.width / 4f, region.height / 4f, unit.rotation - 90);
|
||||
Draw.rect(region, unit.x + Tmp.v1.x / 4f, unit.y + Tmp.v1.y / 4f, treadRect.width / 4f, region.height * region.scale / 4f, unit.rotation - 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1413,7 +1422,7 @@ public class UnitType extends UnlockableContent{
|
||||
|
||||
Leg[] legs = unit.legs();
|
||||
|
||||
float ssize = footRegion.width * Draw.scl * 1.5f;
|
||||
float ssize = footRegion.width * footRegion.scl() * 1.5f;
|
||||
float rotation = unit.baseRotation();
|
||||
float invDrown = 1f - unit.drownTime;
|
||||
|
||||
@@ -1448,10 +1457,10 @@ public class UnitType extends UnlockableContent{
|
||||
Draw.rect(footRegion, leg.base.x, leg.base.y, position.angleTo(leg.base));
|
||||
}
|
||||
|
||||
Lines.stroke(legRegion.height * Draw.scl * flips);
|
||||
Lines.stroke(legRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legRegion, position.x, position.y, leg.joint.x, leg.joint.y, false);
|
||||
|
||||
Lines.stroke(legBaseRegion.height * Draw.scl * flips);
|
||||
Lines.stroke(legBaseRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legBaseRegion, leg.joint.x + Tmp.v1.x, leg.joint.y + Tmp.v1.y, leg.base.x, leg.base.y, false);
|
||||
|
||||
if(jointRegion.found()){
|
||||
@@ -1526,8 +1535,8 @@ public class UnitType extends UnlockableContent{
|
||||
Draw.rect(legRegion,
|
||||
unit.x + Angles.trnsx(mech.baseRotation(), extension * i - boostTrns, -boostTrns*i),
|
||||
unit.y + Angles.trnsy(mech.baseRotation(), extension * i - boostTrns, -boostTrns*i),
|
||||
legRegion.width * i * Draw.scl,
|
||||
legRegion.height * Draw.scl - Math.max(-sin * i, 0) * legRegion.height * 0.5f * Draw.scl,
|
||||
legRegion.width * legRegion.scl() * i,
|
||||
legRegion.height * legRegion.scl() * (1 - Math.max(-sin * i, 0) * 0.5f),
|
||||
mech.baseRotation() - 90 + 35f*i*e);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ public class Weapon implements Cloneable{
|
||||
public BulletType bullet = Bullets.placeholder;
|
||||
/** shell ejection effect */
|
||||
public Effect ejectEffect = Fx.none;
|
||||
/** whether weapon should appear in the stats of a unit with this weapon */
|
||||
public boolean display = true;
|
||||
/** whether to consume ammo when ammo is enabled in rules */
|
||||
public boolean useAmmo = true;
|
||||
/** whether to create a flipped copy of this weapon upon initialization. default: true */
|
||||
@@ -147,7 +149,7 @@ public class Weapon implements Cloneable{
|
||||
}
|
||||
|
||||
public boolean hasStats(UnitType u){
|
||||
return true;
|
||||
return display;
|
||||
}
|
||||
|
||||
public void addStats(UnitType u, Table t){
|
||||
|
||||
@@ -37,6 +37,7 @@ public class MissileUnitType extends UnitType{
|
||||
fogRadius = 2f;
|
||||
loopSound = Sounds.missileTrail;
|
||||
loopSoundVolume = 0.05f;
|
||||
drawMinimap = false;
|
||||
//TODO weapon configs, etc?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,10 @@ public class BuildWeapon extends Weapon{
|
||||
rotate = true;
|
||||
noAttack = true;
|
||||
predictTarget = false;
|
||||
display = false;
|
||||
bullet = new BulletType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStats(UnitType u){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit, WeaponMount mount){
|
||||
mount.shoot = false;
|
||||
|
||||
@@ -21,7 +21,7 @@ public class LiquidDisplay extends Table{
|
||||
this.perSecond = perSecond;
|
||||
|
||||
add(new Stack(){{
|
||||
add(new Image(liquid.uiIcon));
|
||||
add(new Image(liquid.uiIcon).setScaling(Scaling.fit));
|
||||
|
||||
if(amount != 0){
|
||||
Table t = new Table().left().bottom();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.assets.loaders.TextureLoader.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.graphics.gl.*;
|
||||
import arc.input.*;
|
||||
@@ -69,6 +71,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
public Table sectorTop = new Table(), notifs = new Table(), expandTable = new Table();
|
||||
public Label hoverLabel = new Label("");
|
||||
|
||||
private Texture[] planetTextures;
|
||||
|
||||
public PlanetDialog(){
|
||||
super("", Styles.fullDialog);
|
||||
|
||||
@@ -87,8 +91,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
//clear all except first, which is the last sector.
|
||||
newPresets.truncate(1);
|
||||
}else if(selected != null){
|
||||
selected = null;
|
||||
updateSelected();
|
||||
selectSector(null);
|
||||
}else{
|
||||
Core.app.post(() -> hide());
|
||||
}
|
||||
@@ -160,6 +163,54 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
});
|
||||
|
||||
shown(this::setup);
|
||||
|
||||
//show selection of Erekir/Serpulo campaign if the user has no bases, and hasn't selected yet (essentially a "have they played campaign before" check)
|
||||
shown(() -> {
|
||||
if(!settings.getBool("campaignselect") && !content.planets().contains(p -> p.sectors.contains(s -> s.hasBase()))){
|
||||
var diag = new BaseDialog("@campaign.select");
|
||||
|
||||
Planet[] selected = {null};
|
||||
var group = new ButtonGroup<>();
|
||||
group.setMinCheckCount(0);
|
||||
state.planet = Planets.sun;
|
||||
Planet[] choices = {Planets.serpulo, Planets.erekir};
|
||||
int i = 0;
|
||||
for(var planet : choices){
|
||||
TextureRegion tex = new TextureRegion(planetTextures[i]);
|
||||
|
||||
diag.cont.button(b -> {
|
||||
b.top();
|
||||
b.add(planet.localizedName).color(Pal.accent).style(Styles.outlineLabel);
|
||||
b.row();
|
||||
b.image(new TextureRegionDrawable(tex)).grow().scaling(Scaling.fit);
|
||||
}, Styles.togglet, () -> selected[0] = planet).size(Core.app.isMobile() ? 220f : 320f).group(group);
|
||||
i ++;
|
||||
}
|
||||
|
||||
diag.cont.row();
|
||||
diag.cont.label(() -> selected[0] == null ? "@campaign.none" : "@campaign." + selected[0].name).labelAlign(Align.center).style(Styles.outlineLabel).width(440f).wrap().colspan(2);
|
||||
|
||||
diag.buttons.button("@ok", Icon.ok, () -> {
|
||||
state.planet = selected[0];
|
||||
lookAt(state.planet.getStartSector());
|
||||
selectSector(state.planet.getStartSector());
|
||||
settings.put("campaignselect", true);
|
||||
diag.hide();
|
||||
}).size(300f, 64f).disabled(b -> selected[0] == null);
|
||||
|
||||
app.post(() -> diag.show());
|
||||
}
|
||||
});
|
||||
|
||||
planetTextures = new Texture[2];
|
||||
String[] names = {"sprites/planets/serpulo.png", "sprites/planets/erekir.png"};
|
||||
for(int i = 0; i < names.length; i++){
|
||||
int fi = i;
|
||||
assets.load(names[i], Texture.class, new TextureParameter(){{
|
||||
minFilter = magFilter = TextureFilter.linear;
|
||||
}}).loaded = t -> planetTextures[fi] = t;
|
||||
assets.finishLoadingAsset(names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** show with no limitations, just as a map. */
|
||||
@@ -551,17 +602,14 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
buttons,
|
||||
//planet selection
|
||||
new Table(t -> {
|
||||
t.right();
|
||||
t.top().left();
|
||||
if(content.planets().count(this::selectable) > 1){
|
||||
t.table(Styles.black6, pt -> {
|
||||
pt.add("@planets").color(Pal.accent);
|
||||
pt.row();
|
||||
pt.image().growX().height(4f).pad(6f).color(Pal.accent);
|
||||
pt.row();
|
||||
t.table(Tex.pane, pt -> {
|
||||
pt.margin(4f);
|
||||
for(int i = 0; i < content.planets().size; i++){
|
||||
Planet planet = content.planets().get(i);
|
||||
if(selectable(planet)){
|
||||
pt.button(planet.localizedName, Styles.flatTogglet, () -> {
|
||||
pt.button(planet.localizedName, Icon.icons.get(planet.icon + "Small", Icon.icons.get(planet.icon, Icon.commandRallySmall)), Styles.flatTogglet, () -> {
|
||||
selected = null;
|
||||
launchSector = null;
|
||||
if(state.planet != planet){
|
||||
@@ -570,7 +618,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
rebuildExpand();
|
||||
}
|
||||
settings.put("lastplanet", planet.name);
|
||||
}).width(200).height(40).growX().update(bb -> bb.setChecked(state.planet == planet));
|
||||
}).width(190).height(40).growX().update(bb -> bb.setChecked(state.planet == planet)).with(w -> w.marginLeft(10f)).get().getChildren().get(1).setColor(planet.iconColor);
|
||||
pt.row();
|
||||
}
|
||||
}
|
||||
@@ -943,6 +991,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
void selectSector(Sector sector){
|
||||
selected = sector;
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
void updateSelected(){
|
||||
Sector sector = selected;
|
||||
Table stable = sectorTop;
|
||||
@@ -1128,8 +1181,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
|
||||
dialog.hide();
|
||||
lookAt(attacked);
|
||||
selected = attacked;
|
||||
updateSelected();
|
||||
selectSector(attacked);
|
||||
});
|
||||
dialog.show();
|
||||
|
||||
|
||||
@@ -116,60 +116,7 @@ public class ResearchDialog extends BaseDialog{
|
||||
if(currPlanet != null && currPlanet.techTree != null){
|
||||
switchTree(currPlanet.techTree);
|
||||
}
|
||||
|
||||
items = new ItemSeq(){
|
||||
//store sector item amounts for modifications
|
||||
ObjectMap<Sector, ItemSeq> cache = new ObjectMap<>();
|
||||
|
||||
{
|
||||
//add global counts of each sector
|
||||
for(Planet planet : content.planets()){
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase()){
|
||||
ItemSeq cached = sector.items();
|
||||
cache.put(sector, cached);
|
||||
cached.each((item, amount) -> {
|
||||
values[item.id] += Math.max(amount, 0);
|
||||
total += Math.max(amount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this is the only method that actually modifies the sequence itself.
|
||||
@Override
|
||||
public void add(Item item, int amount){
|
||||
//only have custom removal logic for when the sequence gets items taken out of it (e.g. research)
|
||||
if(amount < 0){
|
||||
//remove items from each sector's storage, one by one
|
||||
|
||||
//negate amount since it's being *removed* - this makes it positive
|
||||
amount = -amount;
|
||||
|
||||
//% that gets removed from each sector
|
||||
double percentage = (double)amount / get(item);
|
||||
int[] counter = {amount};
|
||||
cache.each((sector, seq) -> {
|
||||
if(counter[0] == 0) return;
|
||||
|
||||
//amount that will be removed
|
||||
int toRemove = Math.min((int)Math.ceil(percentage * seq.get(item)), counter[0]);
|
||||
|
||||
//actually remove it from the sector
|
||||
sector.removeItem(item, toRemove);
|
||||
seq.remove(item, toRemove);
|
||||
|
||||
counter[0] -= toRemove;
|
||||
});
|
||||
|
||||
//negate again to display correct number
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
super.add(item, amount);
|
||||
}
|
||||
};
|
||||
rebuildItems();
|
||||
|
||||
checkNodes(root);
|
||||
treeLayout();
|
||||
@@ -240,6 +187,68 @@ public class ResearchDialog extends BaseDialog{
|
||||
});
|
||||
}
|
||||
|
||||
public void rebuildItems(){
|
||||
items = new ItemSeq(){
|
||||
//store sector item amounts for modifications
|
||||
ObjectMap<Sector, ItemSeq> cache = new ObjectMap<>();
|
||||
|
||||
{
|
||||
//first, find a planet associated with the current tech tree
|
||||
Planet rootPlanet = lastNode.planet != null ? lastNode.planet : content.planets().find(p -> p.techTree == lastNode);
|
||||
|
||||
//if there is no root, fall back to serpulo
|
||||
if(rootPlanet == null) rootPlanet = Planets.serpulo;
|
||||
|
||||
//add global counts of each sector
|
||||
for(Sector sector : rootPlanet.sectors){
|
||||
if(sector.hasBase()){
|
||||
ItemSeq cached = sector.items();
|
||||
cache.put(sector, cached);
|
||||
cached.each((item, amount) -> {
|
||||
values[item.id] += Math.max(amount, 0);
|
||||
total += Math.max(amount, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this is the only method that actually modifies the sequence itself.
|
||||
@Override
|
||||
public void add(Item item, int amount){
|
||||
//only have custom removal logic for when the sequence gets items taken out of it (e.g. research)
|
||||
if(amount < 0){
|
||||
//remove items from each sector's storage, one by one
|
||||
|
||||
//negate amount since it's being *removed* - this makes it positive
|
||||
amount = -amount;
|
||||
|
||||
//% that gets removed from each sector
|
||||
double percentage = (double)amount / get(item);
|
||||
int[] counter = {amount};
|
||||
cache.each((sector, seq) -> {
|
||||
if(counter[0] == 0) return;
|
||||
|
||||
//amount that will be removed
|
||||
int toRemove = Math.min((int)Math.ceil(percentage * seq.get(item)), counter[0]);
|
||||
|
||||
//actually remove it from the sector
|
||||
sector.removeItem(item, toRemove);
|
||||
seq.remove(item, toRemove);
|
||||
|
||||
counter[0] -= toRemove;
|
||||
});
|
||||
|
||||
//negate again to display correct number
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
super.add(item, amount);
|
||||
}
|
||||
};
|
||||
|
||||
itemDisplay.rebuild(items);
|
||||
}
|
||||
|
||||
public @Nullable TechNode getPrefRoot(){
|
||||
Planet currPlanet = ui.planet.isShown() ?
|
||||
ui.planet.state.planet :
|
||||
@@ -253,6 +262,8 @@ public class ResearchDialog extends BaseDialog{
|
||||
root = new TechTreeNode(node, null);
|
||||
lastNode = node;
|
||||
view.rebuildAll();
|
||||
|
||||
rebuildItems();
|
||||
}
|
||||
|
||||
public void rebuildTree(TechNode node){
|
||||
|
||||
@@ -12,6 +12,7 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
@@ -26,6 +27,7 @@ public class MenuFragment{
|
||||
private Table container, submenu;
|
||||
private Button currentMenu;
|
||||
private MenuRenderer renderer;
|
||||
private Seq<MenuButton> customButtons = new Seq<>();
|
||||
|
||||
public void build(Group parent){
|
||||
renderer = new MenuRenderer();
|
||||
@@ -40,16 +42,21 @@ public class MenuFragment{
|
||||
parent.fill((x, y, w, h) -> renderer.render());
|
||||
|
||||
parent.fill(c -> {
|
||||
container = c;
|
||||
c.name = "menu container";
|
||||
c.pane(Styles.noBarPane, cont -> {
|
||||
container = cont;
|
||||
cont.name = "menu container";
|
||||
|
||||
if(!mobile){
|
||||
buildDesktop();
|
||||
Events.on(ResizeEvent.class, event -> buildDesktop());
|
||||
}else{
|
||||
buildMobile();
|
||||
Events.on(ResizeEvent.class, event -> buildMobile());
|
||||
}
|
||||
if(!mobile){
|
||||
c.left();
|
||||
buildDesktop();
|
||||
Events.on(ResizeEvent.class, event -> buildDesktop());
|
||||
}else{
|
||||
buildMobile();
|
||||
Events.on(ResizeEvent.class, event -> buildMobile());
|
||||
}
|
||||
}).with(pane -> {
|
||||
pane.setOverscroll(false, false);
|
||||
}).grow();
|
||||
});
|
||||
|
||||
parent.fill(c -> c.bottom().right().button(Icon.discord, new ImageButtonStyle(){{
|
||||
@@ -63,8 +70,6 @@ public class MenuFragment{
|
||||
fontColor = Color.white;
|
||||
up = infoBanner;
|
||||
}}, ui.about::show).size(84, 45).name("info"));
|
||||
|
||||
|
||||
}else if(becontrol.active()){
|
||||
parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> {
|
||||
ui.loadfrag.show();
|
||||
@@ -83,7 +88,7 @@ public class MenuFragment{
|
||||
parent.fill((x, y, w, h) -> {
|
||||
TextureRegion logo = Core.atlas.find("logo");
|
||||
float width = Core.graphics.getWidth(), height = Core.graphics.getHeight() - Core.scene.marginTop;
|
||||
float logoscl = Scl.scl(1);
|
||||
float logoscl = Scl.scl(1) * logo.scale;
|
||||
float logow = Math.min(logo.width * logoscl, Core.graphics.getWidth() - Scl.scl(20));
|
||||
float logoh = logow * (float)logo.height / logo.width;
|
||||
|
||||
@@ -116,23 +121,28 @@ public class MenuFragment{
|
||||
mods = new MobileButton(Icon.book, "@mods", ui.mods::show),
|
||||
exit = new MobileButton(Icon.exit, "@quit", () -> Core.app.exit());
|
||||
|
||||
Seq<MobileButton> customs = customButtons.map(b -> new MobileButton(b.icon, b.text, b.runnable == null ? () -> {} : b.runnable));
|
||||
|
||||
if(!Core.graphics.isPortrait()){
|
||||
container.marginTop(60f);
|
||||
container.add(play);
|
||||
container.add(join);
|
||||
container.add(custom);
|
||||
container.add(maps);
|
||||
// add odd custom buttons
|
||||
for(int i = 1; i < customs.size; i += 2){
|
||||
container.add(customs.get(i));
|
||||
}
|
||||
container.row();
|
||||
|
||||
container.table(table -> {
|
||||
table.defaults().set(container.defaults());
|
||||
|
||||
table.add(editor);
|
||||
table.add(tools);
|
||||
|
||||
table.add(mods);
|
||||
if(!ios) table.add(exit);
|
||||
}).colspan(4);
|
||||
container.add(editor);
|
||||
container.add(tools);
|
||||
container.add(mods);
|
||||
// add even custom buttons (before the exit button)
|
||||
for(int i = 0; i < customs.size; i += 2){
|
||||
container.add(customs.get(i));
|
||||
}
|
||||
if(!ios) container.add(exit);
|
||||
}else{
|
||||
container.marginTop(0f);
|
||||
container.add(play);
|
||||
@@ -144,13 +154,13 @@ public class MenuFragment{
|
||||
container.add(editor);
|
||||
container.add(tools);
|
||||
container.row();
|
||||
|
||||
container.table(table -> {
|
||||
table.defaults().set(container.defaults());
|
||||
|
||||
table.add(mods);
|
||||
if(!ios) table.add(exit);
|
||||
}).colspan(2);
|
||||
container.add(mods);
|
||||
// add custom buttons
|
||||
for(int i = 0; i < customs.size; i++){
|
||||
container.add(customs.get(i));
|
||||
if(i % 2 == 0) container.row();
|
||||
}
|
||||
if(!ios) container.add(exit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,23 +178,23 @@ public class MenuFragment{
|
||||
t.name = "buttons";
|
||||
|
||||
buttons(t,
|
||||
new Buttoni("@play", Icon.play,
|
||||
new Buttoni("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new Buttoni("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new Buttoni("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new Buttoni("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
new MenuButton("@play", Icon.play,
|
||||
new MenuButton("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new MenuButton("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new MenuButton("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new MenuButton("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
),
|
||||
new Buttoni("@database.button", Icon.menu,
|
||||
new Buttoni("@schematics", Icon.paste, ui.schematics::show),
|
||||
new Buttoni("@database", Icon.book, ui.database::show),
|
||||
new Buttoni("@about.button", Icon.info, ui.about::show)
|
||||
new MenuButton("@database.button", Icon.menu,
|
||||
new MenuButton("@schematics", Icon.paste, ui.schematics::show),
|
||||
new MenuButton("@database", Icon.book, ui.database::show),
|
||||
new MenuButton("@about.button", Icon.info, ui.about::show)
|
||||
),
|
||||
new Buttoni("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("@workshop", Icon.steam, platform::openWorkshop) : null,
|
||||
new Buttoni("@mods", Icon.book, ui.mods::show),
|
||||
new Buttoni("@settings", Icon.settings, ui.settings::show),
|
||||
new Buttoni("@quit", Icon.exit, Core.app::exit)
|
||||
new MenuButton("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new MenuButton("@workshop", Icon.steam, platform::openWorkshop) : null,
|
||||
new MenuButton("@mods", Icon.book, ui.mods::show),
|
||||
new MenuButton("@settings", Icon.settings, ui.settings::show)
|
||||
);
|
||||
|
||||
buttons(t, customButtons.toArray(MenuButton.class));
|
||||
buttons(t, new MenuButton("@quit", Icon.exit, Core.app::exit));
|
||||
}).width(width).growY();
|
||||
|
||||
container.table(background, t -> {
|
||||
@@ -199,7 +209,6 @@ public class MenuFragment{
|
||||
}
|
||||
|
||||
private void checkPlay(Runnable run){
|
||||
|
||||
if(!mods.hasContentErrors()){
|
||||
run.run();
|
||||
}else{
|
||||
@@ -222,8 +231,8 @@ public class MenuFragment{
|
||||
submenu.actions(Actions.alpha(1f), Actions.alpha(0f, 0.2f, Interp.fade), Actions.run(() -> submenu.clearChildren()));
|
||||
}
|
||||
|
||||
private void buttons(Table t, Buttoni... buttons){
|
||||
for(Buttoni b : buttons){
|
||||
private void buttons(Table t, MenuButton... buttons){
|
||||
for(MenuButton b : buttons){
|
||||
if(b == null) continue;
|
||||
Button[] out = {null};
|
||||
out[0] = t.button(b.text, b.icon, Styles.flatToggleMenut, () -> {
|
||||
@@ -251,24 +260,56 @@ public class MenuFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private static class Buttoni{
|
||||
final Drawable icon;
|
||||
final String text;
|
||||
final Runnable runnable;
|
||||
final Buttoni[] submenu;
|
||||
/** Adds a custom button to the menu. */
|
||||
public void addButton(String text, Drawable icon, Runnable callback){
|
||||
addButton(new MenuButton(text, icon, callback));
|
||||
}
|
||||
|
||||
public Buttoni(String text, Drawable icon, Runnable runnable){
|
||||
/** Adds a custom button to the menu. */
|
||||
public void addButton(String text, Runnable callback){
|
||||
addButton(text, Styles.none, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom button to the menu.
|
||||
* If {@link MenuButton#submenu} is null or the player is on mobile, {@link MenuButton#runnable} is invoked on click.
|
||||
* Otherwise, {@link MenuButton#submenu} is shown.
|
||||
*/
|
||||
public void addButton(MenuButton button){
|
||||
customButtons.add(button);
|
||||
}
|
||||
|
||||
/** Represents a menu button definition. */
|
||||
public static class MenuButton{
|
||||
public final Drawable icon;
|
||||
public final String text;
|
||||
/** Runnable ran when the button is clicked. Ignored on desktop if {@link #submenu} is not null. */
|
||||
public final Runnable runnable;
|
||||
/** Submenu shown when this button is clicked. Used instead of {@link #runnable} on desktop. */
|
||||
public final @Nullable MenuButton[] submenu;
|
||||
|
||||
/** Constructs a simple menu button, which behaves the same way on desktop and mobile. */
|
||||
public MenuButton(String text, Drawable icon, Runnable runnable){
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = runnable;
|
||||
this.submenu = null;
|
||||
}
|
||||
|
||||
public Buttoni(String text, Drawable icon, Buttoni... buttons){
|
||||
/** Constructs a button that runs the runnable when clicked on mobile or shows the submenu on desktop. */
|
||||
public MenuButton(String text, Drawable icon, Runnable runnable, MenuButton... submenu){
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = runnable;
|
||||
this.submenu = submenu;
|
||||
}
|
||||
|
||||
/** Comstructs a desktop-only button; used internally. */
|
||||
MenuButton(String text, Drawable icon, MenuButton... submenu){
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = () -> {};
|
||||
this.submenu = buttons;
|
||||
this.submenu = submenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,8 @@ public class LaunchPad extends Block{
|
||||
Draw.z(Layer.weather - 1);
|
||||
|
||||
TextureRegion region = blockOn() instanceof mindustry.world.blocks.campaign.LaunchPad p ? p.podRegion : Core.atlas.find("launchpod");
|
||||
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
|
||||
scale *= region.scl();
|
||||
float rw = region.width * scale, rh = region.height * scale;
|
||||
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(region, cx, cy, rw, rh, rotation);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class Conveyor extends Block implements Autotiler{
|
||||
if(bits == null) return;
|
||||
|
||||
TextureRegion region = regions[bits[0]][0];
|
||||
Draw.rect(region, plan.drawx(), plan.drawy(), region.width * bits[1] * Draw.scl, region.height * bits[2] * Draw.scl, plan.rotation * 90);
|
||||
Draw.rect(region, plan.drawx(), plan.drawy(), region.width * bits[1] * region.scl(), region.height * bits[2] * region.scl(), plan.rotation * 90);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -134,16 +134,16 @@ public class DirectionBridge extends Block{
|
||||
cy = (y1 + y2)/2f,
|
||||
len = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2)) - size * tilesize;
|
||||
|
||||
Draw.rect(bridgeRegion, cx, cy, len, bridgeRegion.height * Draw.scl, angle);
|
||||
Draw.rect(bridgeRegion, cx, cy, len, bridgeRegion.height * bridgeRegion.scl(), angle);
|
||||
if(liquidColor != null){
|
||||
Draw.color(liquidColor, liquidColor.a * Renderer.bridgeOpacity);
|
||||
Draw.rect(bridgeLiquidRegion, cx, cy, len, bridgeLiquidRegion.height * Draw.scl, angle);
|
||||
Draw.rect(bridgeLiquidRegion, cx, cy, len, bridgeLiquidRegion.height * bridgeLiquidRegion.scl(), angle);
|
||||
Draw.color();
|
||||
Draw.alpha(Renderer.bridgeOpacity);
|
||||
}
|
||||
if(bridgeBotRegion.found()){
|
||||
Draw.color(0.4f, 0.4f, 0.4f, 0.4f * Renderer.bridgeOpacity);
|
||||
Draw.rect(bridgeBotRegion, cx, cy, len, bridgeBotRegion.height * Draw.scl, angle);
|
||||
Draw.rect(bridgeBotRegion, cx, cy, len, bridgeBotRegion.height * bridgeBotRegion.scl(), angle);
|
||||
Draw.reset();
|
||||
}
|
||||
Draw.alpha(Renderer.bridgeOpacity);
|
||||
|
||||
@@ -179,7 +179,7 @@ public class Duct extends Block implements Autotiler{
|
||||
((source.block.rotate && source.front() == this && source.block.hasItems && source.block.isDuct) ||
|
||||
Edges.getFacingEdge(source.tile(), tile).relativeTo(tile) == rotation) :
|
||||
//standard acceptance - do not accept from front
|
||||
!(source.block.rotate && next == source) && Math.abs(Edges.getFacingEdge(source.tile, tile).relativeTo(tile.x, tile.y) - rotation) != 2
|
||||
!(source.block.rotate && next == source) && Edges.getFacingEdge(source.tile, tile) != null && Math.abs(Edges.getFacingEdge(source.tile, tile).relativeTo(tile.x, tile.y) - rotation) != 2
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.world.blocks.distribution;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
@@ -49,6 +50,11 @@ public class Sorter extends Block{
|
||||
return build == null || build.sortItem == null ? 0 : build.sortItem.color.rgba();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextureRegion[] icons(){
|
||||
return new TextureRegion[]{Core.atlas.find("source-bottom"), region};
|
||||
}
|
||||
|
||||
public class SorterBuild extends Building{
|
||||
public @Nullable Item sortItem;
|
||||
|
||||
@@ -63,15 +69,16 @@ public class Sorter extends Block{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
|
||||
if(sortItem == null){
|
||||
Draw.rect("cross", x, y);
|
||||
Draw.rect("cross-full", x, y);
|
||||
}else{
|
||||
Draw.color(sortItem.color);
|
||||
Draw.rect("center", x, y);
|
||||
Fill.square(x, y, tilesize/2f - 0.00001f);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,7 @@ public class SeaBush extends Prop{
|
||||
int lobes = rand.random(lobesMin, lobesMax);
|
||||
for(int i = 0; i < lobes; i++){
|
||||
float ba = i / (float)lobes * 360f + offset + rand.range(spread), angle = ba + Mathf.sin(Time.time + rand.random(0, timeRange), rand.random(sclMin, sclMax), rand.random(magMin, magMax));
|
||||
float w = region.width * Draw.scl, h = region.height * Draw.scl;
|
||||
float w = region.width * region.scl(), h = region.height * region.scl();
|
||||
var region = Angles.angleDist(ba, 225f) <= botAngle ? botRegion : this.region;
|
||||
|
||||
Draw.rect(region,
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Seaweed extends Prop{
|
||||
x = tile.worldx(), y = tile.worldy(),
|
||||
rotmag = 3f, rotscl = 0.5f,
|
||||
rot = Mathf.randomSeedRange(tile.pos(), 20f) - 45 + Mathf.sin(Time.time + x, 50f * rotscl, 0.5f * rotmag) + Mathf.sin(Time.time - y, 65f * rotscl, 0.9f* rotmag) + Mathf.sin(Time.time + y - x, 85f * rotscl, 0.9f* rotmag),
|
||||
w = region.width * Draw.scl, h = region.height * Draw.scl,
|
||||
w = region.width * region.scl(), h = region.height * region.scl(),
|
||||
scl = 30f, mag = 0.3f;
|
||||
|
||||
Draw.rectv(region, x, y, w, h, rot, vec -> vec.add(
|
||||
|
||||
@@ -23,7 +23,7 @@ public class TreeBlock extends Block{
|
||||
float
|
||||
x = tile.worldx(), y = tile.worldy(),
|
||||
rot = Mathf.randomSeed(tile.pos(), 0, 4) * 90 + Mathf.sin(Time.time + x, 50f, 0.5f) + Mathf.sin(Time.time - y, 65f, 0.9f) + Mathf.sin(Time.time + y - x, 85f, 0.9f),
|
||||
w = region.width * Draw.scl, h = region.height * Draw.scl,
|
||||
w = region.width * region.scl(), h = region.height * region.scl(),
|
||||
scl = 30f, mag = 0.2f;
|
||||
|
||||
if(shadow.found()){
|
||||
|
||||
@@ -18,7 +18,7 @@ public class WobbleProp extends Prop{
|
||||
public void drawBase(Tile tile){
|
||||
var region = variants > 0 ? variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))] : this.region;
|
||||
|
||||
Draw.rectv(region, tile.worldx(), tile.worldy(), region.width * Draw.scl, region.height * Draw.scl, 0, vec -> vec.add(
|
||||
Draw.rectv(region, tile.worldx(), tile.worldy(), region.width * region.scl(), region.height * region.scl(), 0, vec -> vec.add(
|
||||
Mathf.sin(vec.y*3 + Time.time, wscl, wmag) + Mathf.sin(vec.x*3 - Time.time, 70 * wtscl, 0.8f * wmag2),
|
||||
Mathf.cos(vec.x*3 + Time.time + 8, wscl + 6f, wmag * 1.1f) + Mathf.sin(vec.y*3 - Time.time, 50 * wtscl, 0.2f * wmag2)
|
||||
));
|
||||
|
||||
@@ -9,9 +9,7 @@ public class LiquidRouter extends LiquidBlock{
|
||||
|
||||
public LiquidRouter(String name){
|
||||
super(name);
|
||||
|
||||
underBullets = true;
|
||||
solid = false;
|
||||
solid = true;
|
||||
noUpdateDisabled = true;
|
||||
canOverdrive = false;
|
||||
floating = true;
|
||||
|
||||
@@ -21,7 +21,7 @@ import static mindustry.Vars.*;
|
||||
public class CanvasBlock extends Block{
|
||||
public float padding = 0f;
|
||||
public int canvasSize = 8;
|
||||
public int[] palette = {0x634b7dff, 0xc45d9f_ff, 0xe39aac_ff, 0xf0dab1_ff, 0x6461c2_ff, 0x2ba9b4_ff, 0x93d4b5_ff, 0xf0f6e8_ff};
|
||||
public int[] palette = {0x362944_ff, 0xc45d9f_ff, 0xe39aac_ff, 0xf0dab1_ff, 0x6461c2_ff, 0x2ba9b4_ff, 0x93d4b5_ff, 0xf0f6e8_ff};
|
||||
public int bitsPerPixel;
|
||||
public IntIntMap colorToIndex = new IntIntMap();
|
||||
|
||||
|
||||
@@ -197,26 +197,28 @@ public class PayloadConveyor extends Block{
|
||||
float glow = Math.max((dst - (Math.abs(fract() - 0.5f) * 2)) / dst, 0);
|
||||
Draw.mixcol(team.color, glow);
|
||||
|
||||
float trnext = fract() * size * tilesize, trprev = size * tilesize * (fract() - 1), rot = rotdeg();
|
||||
|
||||
TextureRegion clipped = clipRegion(tile.getHitbox(Tmp.r1), tile.getHitbox(Tmp.r2).move(trnext, 0), topRegion);
|
||||
float s = tilesize * size;
|
||||
float trnext = s * fract(), trprev = s * (fract() - 1), rot = rotdeg();
|
||||
|
||||
//next
|
||||
Tmp.v1.set((s- clipped.width *Draw.scl) + clipped.width /2f*Draw.scl - s/2f, s- clipped.height *Draw.scl + clipped.height /2f*Draw.scl - s/2f).rotate(rot);
|
||||
TextureRegion clipped = clipRegion(tile.getHitbox(Tmp.r1), tile.getHitbox(Tmp.r2).move(trnext, 0), topRegion);
|
||||
float widthNext = (s - clipped.width * clipped.scl()) * 0.5f;
|
||||
float heightNext = (s - clipped.height * clipped.scl()) * 0.5f;
|
||||
Tmp.v1.set(widthNext, heightNext).rotate(rot);
|
||||
Draw.rect(clipped, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
|
||||
clipped = clipRegion(tile.getHitbox(Tmp.r1), tile.getHitbox(Tmp.r2).move(trprev, 0), topRegion);
|
||||
|
||||
//prev
|
||||
Tmp.v1.set(- s/2f + clipped.width /2f*Draw.scl, - s/2f + clipped.height /2f*Draw.scl).rotate(rot);
|
||||
clipped = clipRegion(tile.getHitbox(Tmp.r1), tile.getHitbox(Tmp.r2).move(trprev, 0), topRegion);
|
||||
float widthPrev = (clipped.width * clipped.scl() - s) * 0.5f;
|
||||
float heightPrev = (clipped.height * clipped.scl() - s) * 0.5f;
|
||||
Tmp.v1.set(widthPrev, heightPrev).rotate(rot);
|
||||
Draw.rect(clipped, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
if(blends(i) && i != rotation){
|
||||
Draw.alpha(1f - Interp.pow5In.apply(fract()));
|
||||
//prev from back
|
||||
Tmp.v1.set(- s/2f + clipped.width /2f*Draw.scl, - s/2f + clipped.height /2f*Draw.scl).rotate(i * 90 + 180);
|
||||
Tmp.v1.set(widthPrev, heightPrev).rotate(i * 90 + 180);
|
||||
Draw.rect(clipped, x + Tmp.v1.x, y + Tmp.v1.y, i * 90 + 180);
|
||||
}
|
||||
}
|
||||
@@ -331,6 +333,7 @@ public class PayloadConveyor extends Block{
|
||||
|
||||
TextureRegion out = Tmp.tr1;
|
||||
out.set(region.texture);
|
||||
out.scale = region.scale;
|
||||
|
||||
if(overlaps){
|
||||
float w = region.u2 - region.u;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class VariableReactor extends PowerGenerator{
|
||||
public void updateTile(){
|
||||
heat = calculateHeat(sideHeat);
|
||||
|
||||
productionEfficiency = Mathf.clamp(heat / maxHeat) * efficiency;
|
||||
productionEfficiency = efficiency;
|
||||
warmup = Mathf.lerpDelta(warmup, productionEfficiency > 0 ? 1f : 0f, warmupSpeed);
|
||||
|
||||
if(instability >= 1f){
|
||||
|
||||
@@ -39,9 +39,9 @@ public class AttributeCrafter extends GenericCrafter{
|
||||
|
||||
addBar("efficiency", (AttributeCrafterBuild entity) ->
|
||||
new Bar(
|
||||
() -> Core.bundle.format("bar.efficiency", (int)(entity.efficiencyScale() * 100 * displayEfficiencyScale)),
|
||||
() -> Core.bundle.format("bar.efficiency", (int)(entity.efficiencyMultiplier() * 100 * displayEfficiencyScale)),
|
||||
() -> Pal.lightOrange,
|
||||
entity::efficiencyScale));
|
||||
entity::efficiencyMultiplier));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,10 +62,10 @@ public class AttributeCrafter extends GenericCrafter{
|
||||
|
||||
@Override
|
||||
public float getProgressIncrease(float base){
|
||||
return super.getProgressIncrease(base) * efficiencyScale();
|
||||
return super.getProgressIncrease(base) * efficiencyMultiplier();
|
||||
}
|
||||
|
||||
public float efficiencyScale(){
|
||||
public float efficiencyMultiplier(){
|
||||
return baseEfficiency + Math.min(maxBoost, boostScale * attrsum) + attribute.env();
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,19 @@ public class GenericCrafter extends Block{
|
||||
dumpOutputs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getProgressIncrease(float baseTime){
|
||||
//limit progress increase by maximum amount of liquid it can produce
|
||||
float scaling = 1f;
|
||||
if(outputLiquids != null){
|
||||
for(var s : outputLiquids){
|
||||
scaling = Math.min(scaling, (liquidCapacity - liquids.get(s.liquid)) / (s.amount * edelta()));
|
||||
}
|
||||
}
|
||||
|
||||
return super.getProgressIncrease(baseTime) * scaling;
|
||||
}
|
||||
|
||||
public float warmupTarget(){
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package mindustry.world.blocks.production;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.draw.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
/**
|
||||
@@ -23,9 +22,7 @@ public class Separator extends Block{
|
||||
public ItemStack[] results;
|
||||
public float craftTime;
|
||||
|
||||
public @Load("@-liquid") TextureRegion liquidRegion;
|
||||
public @Load("@-spinner") TextureRegion spinnerRegion;
|
||||
public float spinnerSpeed = 3f;
|
||||
public DrawBlock drawer = new DrawDefault();
|
||||
|
||||
public Separator(String name){
|
||||
super(name);
|
||||
@@ -51,6 +48,23 @@ public class Separator extends Block{
|
||||
consItems = findConsumer(c -> c instanceof ConsumeItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
super.load();
|
||||
|
||||
drawer.load(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPlanRegion(BuildPlan plan, Eachable<BuildPlan> list){
|
||||
drawer.drawPlan(this, plan, list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureRegion[] icons(){
|
||||
return drawer.finalIcons(this);
|
||||
}
|
||||
|
||||
public class SeparatorBuild extends Building{
|
||||
public float progress;
|
||||
public float totalProgress;
|
||||
@@ -81,13 +95,28 @@ public class Separator extends Block{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
drawer.draw(this);
|
||||
}
|
||||
|
||||
Drawf.liquid(liquidRegion, x, y, liquids.currentAmount() / liquidCapacity, liquids.current().color);
|
||||
@Override
|
||||
public void drawLight(){
|
||||
super.drawLight();
|
||||
drawer.drawLight(this);
|
||||
}
|
||||
|
||||
if(Core.atlas.isFound(spinnerRegion)){
|
||||
Draw.rect(spinnerRegion, x, y, totalProgress * spinnerSpeed);
|
||||
}
|
||||
@Override
|
||||
public float warmup(){
|
||||
return warmup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float progress(){
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float totalProgress(){
|
||||
return totalProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.world.blocks.sandbox;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
@@ -45,6 +46,11 @@ public class ItemSource extends Block{
|
||||
stats.add(Stat.output, itemsPerSecond, StatUnit.itemsSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextureRegion[] icons(){
|
||||
return new TextureRegion[]{Core.atlas.find("source-bottom"), region};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPlanConfig(BuildPlan plan, Eachable<BuildPlan> list){
|
||||
drawPlanConfigCenter(plan, plan.config, "center", true);
|
||||
@@ -61,15 +67,15 @@ public class ItemSource extends Block{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
|
||||
if(outputItem == null){
|
||||
Draw.rect("cross", x, y);
|
||||
Draw.rect("cross-full", x, y);
|
||||
}else{
|
||||
Draw.color(outputItem.color);
|
||||
Draw.rect("center", x, y);
|
||||
Fill.square(x, y, tilesize/2f - 0.00001f);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -89,11 +89,11 @@ public class Unloader extends Block{
|
||||
//sort so it gives priority for blocks that can only either receive or give (not both), and then by load, and then by last use
|
||||
//highest = unload from, lowest = unload to
|
||||
int unloadPriority = Boolean.compare(x.canUnload && !x.canLoad, y.canUnload && !y.canLoad); //priority to receive if it cannot give
|
||||
if (unloadPriority != 0) return unloadPriority;
|
||||
if(unloadPriority != 0) return unloadPriority;
|
||||
int loadPriority = Boolean.compare(x.canUnload || !x.canLoad, y.canUnload || !y.canLoad); //priority to give if it cannot receive
|
||||
if (loadPriority != 0) return loadPriority;
|
||||
if(loadPriority != 0) return loadPriority;
|
||||
int loadFactor = Float.compare(x.loadFactor, y.loadFactor);
|
||||
if (loadFactor != 0) return loadFactor;
|
||||
if(loadFactor != 0) return loadFactor;
|
||||
return Integer.compare(y.lastUsed, x.lastUsed); //inverted
|
||||
};
|
||||
|
||||
|
||||
@@ -12,10 +12,15 @@ import mindustry.world.blocks.production.HeatCrafter.*;
|
||||
public class DrawHeatRegion extends DrawBlock{
|
||||
public Color color = new Color(1f, 0.22f, 0.22f, 0.8f);
|
||||
public float pulse = 0.3f, pulseScl = 10f;
|
||||
public float layer = Layer.blockAdditive;
|
||||
|
||||
public TextureRegion heat;
|
||||
public String suffix = "-glow";
|
||||
|
||||
public DrawHeatRegion(float layer){
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
public DrawHeatRegion(String suffix){
|
||||
this.suffix = suffix;
|
||||
}
|
||||
@@ -27,13 +32,16 @@ public class DrawHeatRegion extends DrawBlock{
|
||||
public void draw(Building build){
|
||||
Draw.z(Layer.blockAdditive);
|
||||
if(build instanceof HeatCrafterBuild hc && hc.heat > 0){
|
||||
|
||||
float z = Draw.z();
|
||||
if(layer > 0) Draw.z(layer);
|
||||
Draw.blend(Blending.additive);
|
||||
Draw.color(color, Mathf.clamp(hc.heat / hc.heatRequirement()) * (color.a * (1f - pulse + Mathf.absin(pulseScl, pulse))));
|
||||
Draw.rect(heat, build.x, build.y);
|
||||
Draw.blend();
|
||||
Draw.color();
|
||||
Draw.z(z);
|
||||
}
|
||||
Draw.z(Layer.block);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,7 +33,7 @@ public class DrawPlasma extends DrawFlame{
|
||||
public void draw(Building build){
|
||||
Draw.blend(Blending.additive);
|
||||
for(int i = 0; i < regions.length; i++){
|
||||
float r = ((float)regions[i].width * Draw.scl - 3f + Mathf.absin(Time.time, 2f + i * 1f, 5f - i * 0.5f));
|
||||
float r = ((float)regions[i].width * regions[i].scl() - 3f + Mathf.absin(Time.time, 2f + i * 1f, 5f - i * 0.5f));
|
||||
|
||||
Draw.color(plasma1, plasma2, (float)i / regions.length);
|
||||
Draw.alpha((0.3f + Mathf.absin(Time.time, 2f + i * 2f, 0.3f + i * 0.05f)) * build.warmup());
|
||||
|
||||
@@ -21,6 +21,17 @@ public class DrawRegion extends DrawBlock{
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public DrawRegion(String suffix, float rotateSpeed){
|
||||
this.suffix = suffix;
|
||||
this.rotateSpeed = rotateSpeed;
|
||||
}
|
||||
|
||||
public DrawRegion(String suffix, float rotateSpeed, boolean spinSprite){
|
||||
this.suffix = suffix;
|
||||
this.spinSprite = spinSprite;
|
||||
this.rotateSpeed = rotateSpeed;
|
||||
}
|
||||
|
||||
public DrawRegion(){
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,19 @@ package mindustry.world.meta;
|
||||
|
||||
import arc.func.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
|
||||
public enum BuildVisibility{
|
||||
hidden(() -> false),
|
||||
shown(() -> true),
|
||||
debugOnly(() -> false),
|
||||
editorOnly(() -> Vars.state.rules.editor),
|
||||
sandboxOnly(() -> Vars.state == null || Vars.state.rules.infiniteResources),
|
||||
campaignOnly(() -> Vars.state == null || Vars.state.isCampaign()),
|
||||
lightingOnly(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()),
|
||||
berylliumOnly(() -> !Vars.state.rules.hiddenBuildItems.contains(Items.beryllium)),
|
||||
ammoOnly(() -> Vars.state == null || Vars.state.rules.unitAmmo),
|
||||
fogOnly(() -> Vars.state == null || Vars.state.rules.fog || Vars.state.rules.editor);
|
||||
public class BuildVisibility{
|
||||
public static final BuildVisibility
|
||||
|
||||
hidden = new BuildVisibility(() -> false),
|
||||
shown = new BuildVisibility(() -> true),
|
||||
debugOnly = new BuildVisibility(() -> false),
|
||||
editorOnly = new BuildVisibility(() -> Vars.state.rules.editor),
|
||||
sandboxOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.infiniteResources),
|
||||
campaignOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.isCampaign()),
|
||||
lightingOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()),
|
||||
ammoOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.unitAmmo),
|
||||
fogOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.fog || Vars.state.rules.editor);
|
||||
|
||||
private final Boolp visible;
|
||||
|
||||
@@ -22,7 +22,7 @@ public enum BuildVisibility{
|
||||
return visible.get();
|
||||
}
|
||||
|
||||
BuildVisibility(Boolp visible){
|
||||
public BuildVisibility(Boolp visible){
|
||||
this.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user