Merge branch 'master' into patch-4

This commit is contained in:
BasedUser 2020-10-13 00:58:31 +03:00 committed by GitHub
commit 56ca782cae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
144 changed files with 8415 additions and 3840 deletions

View file

@ -10,7 +10,7 @@ script:
- git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
- cd ../MindustryBuilds
- echo ${TRAVIS_TAG}
- if [ -n "$TRAVIS_TAG" ]; then echo versionName=5-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi
- if [ -n "$TRAVIS_TAG" ]; then echo versionName=6-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi
- git tag ${TRAVIS_BUILD_NUMBER}
- git config --global user.name "Build Uploader"
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds ${TRAVIS_BUILD_NUMBER}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds; fi

View file

@ -76,7 +76,7 @@ android{
targetSdkVersion 29
versionName versionNameResult
versionCode vcode
versionCode = (System.getenv("TRAVIS_BUILD_ID") != null ? System.getenv("TRAVIS_BUILD_ID").toInteger() : vcode)
if(project.hasProperty("release")){
props['androidBuildCode'] = (vcode + 1).toString()
@ -98,15 +98,20 @@ android{
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}else if(System.getenv("CI") == "true"){
storeFile = file("../../bekeystore.jks")
storePassword = System.getenv("keystore_password")
keyAlias = System.getenv("keystore_alias")
keyPassword = System.getenv("keystore_alias_password")
}else{
println("No keystore property found. Releases will be unsigned.")
}
}
}
if(project.hasProperty("RELEASE_STORE_FILE")) {
buildTypes {
release {
if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){
buildTypes{
release{
signingConfig signingConfigs.release
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

After

Width:  |  Height:  |  Size: 389 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 B

After

Width:  |  Height:  |  Size: 377 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

View file

@ -20,7 +20,7 @@ gameover = Game Over
gameover.pvp = The[accent] {0}[] team is victorious!
highscore = [accent]New highscore!
copied = Copied.
indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- Content is missing\n - Most [scarlet]Unit AI[] does not work properly\n- Many units are unfinished\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[].
indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- SFX and music are unfinished/missing\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[].
indev.notready = This part of the game isn't ready yet
load.sound = Sounds
@ -291,6 +291,8 @@ waiting = [lightgray]Waiting...
waiting.players = Waiting for players...
wave.enemies = [lightgray]{0} Enemies Remaining
wave.enemy = [lightgray]{0} Enemy Remaining
wave.guardianwarn = Guardian approaching in [accent]{0}[] waves.
wave.guardianwarn.one = Guardian approaching in [accent]{0}[] wave.
loadimage = Load Image
saveimage = Save Image
unknown = Unknown
@ -329,6 +331,7 @@ editor.generation = Generation:
editor.ingame = Edit In-Game
editor.publish.workshop = Publish On Workshop
editor.newmap = New Map
editor.center = Center
workshop = Workshop
waves.title = Waves
waves.remove = Remove
@ -572,6 +575,7 @@ info.title = Info
error.title = [scarlet]An error has occured
error.crashtitle = An error has occured
unit.nobuild = [scarlet]Unit can't build
lastaccessed = [lightgray]Last Accessed: {0}
blocks.input = Input
blocks.output = Output
blocks.booster = Booster
@ -627,6 +631,7 @@ bar.powerbalance = Power: {0}/s
bar.powerstored = Stored: {0}/{1}
bar.poweramount = Power: {0}
bar.poweroutput = Power Output: {0}
bar.powerlines = Connections: {0}/{1}
bar.items = Items: {0}
bar.capacity = Capacity: {0}
bar.unitcap = {0} {1}/{2}
@ -943,6 +948,7 @@ block.cliff.name = Cliff
block.sand-boulder.name = Sand Boulder
block.grass.name = Grass
block.slag.name = Slag
block.space.name = Space
block.salt.name = Salt
block.salt-wall.name = Salt Wall
block.pebbles.name = Pebbles
@ -988,6 +994,7 @@ block.darksand-water.name = Dark Sand Water
block.char.name = Char
block.dacite.name = Dacite
block.dacite-wall.name = Dacite Wall
block.dacite-boulder.name = Dacite Boulder
block.ice-snow.name = Ice Snow
block.stone-wall.name = Stone Wall
block.ice-wall.name = Ice Wall
@ -1261,7 +1268,7 @@ block.rotary-pump.description = An advanced pump. Pumps more liquid, but require
block.thermal-pump.description = The ultimate pump.
block.conduit.description = Basic liquid transport block. Moves liquids forward. Used in conjunction with pumps and other conduits.
block.pulse-conduit.description = An advanced liquid transport block. Transports liquids faster and stores more than standard conduits.
block.plated-conduit.description = Moves liquids at the same rate as pulse conduits, but possesses more armor. Does not accept fluids from the sides by anything other than conduits.\nLeaks less.
block.plated-conduit.description = Moves liquids at the same rate as pulse conduits, but possesses more armor. Does not accept fluids from the sides by anything other than conduits.\nDoes not leak.
block.liquid-router.description = Accepts liquids from one direction and outputs them to up to 3 other directions equally. Can also store a certain amount of liquid. Useful for splitting the liquids from one source to multiple targets.
block.liquid-tank.description = Stores a large amount of liquids. Use for creating buffers in situations with non-constant demand of materials or as a safeguard for cooling vital blocks.
block.liquid-junction.description = Acts as a bridge for two crossing conduits. Useful in situations with two different conduits carrying different liquids to different locations.

View file

@ -313,3 +313,4 @@
63423=memory-bank|block-memory-bank-medium
63422=foreshadow|block-foreshadow-medium
63421=tsunami|block-tsunami-medium
63420=space|block-space-medium

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
core/assets/shaders/space.frag Executable file
View file

@ -0,0 +1,23 @@
#define HIGHP
#define NSCALE 2700.0
#define CAMSCALE (NSCALE*5.0)
uniform sampler2D u_texture;
uniform sampler2D u_stars;
uniform vec2 u_campos;
uniform vec2 u_ccampos;
uniform vec2 u_resolution;
uniform float u_time;
varying vec2 v_texCoords;
void main(){
vec2 c = v_texCoords.xy;
vec2 coords = vec2(c.x * u_resolution.x, c.y * u_resolution.y);
vec4 color = texture2D(u_texture, c);
color.rgb = texture2D(u_stars, coords/NSCALE + vec2(-0.1, -0.1) + u_ccampos / CAMSCALE).rgb;
gl_FragColor = color;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

After

Width:  |  Height:  |  Size: 825 B

Before After
Before After

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 562 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 429 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 354 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 426 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

@ -85,9 +85,9 @@ public class Vars implements Loadable{
/** range for moving items for logic units */
public static final float logicItemTransferRange = 45f;
/** duration of time between turns in ticks */
public static final float turnDuration = 20 * Time.toMinutes;
public static final float turnDuration = 2 * Time.toMinutes;
/** turns needed to destroy a sector completely */
public static final float sectorDestructionTurns = 3f;
public static final float sectorDestructionTurns = 2f;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.1f;
/** launch animation duration */
@ -184,7 +184,7 @@ public class Vars implements Loadable{
public static GameState state;
public static EntityCollisions collisions;
public static DefaultWaves defaultWaves;
public static mindustry.audio.LoopControl loops;
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Mods mods;
public static Schematics schematics;

View file

@ -9,6 +9,7 @@ import mindustry.game.*;
import mindustry.game.Schematic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
import mindustry.world.blocks.storage.*;
@ -19,9 +20,13 @@ import java.io.*;
import static mindustry.Vars.*;
public class BaseRegistry{
/** cores, sorted by tier */
public Seq<BasePart> cores = new Seq<>();
/** parts with no requirement */
public Seq<BasePart> parts = new Seq<>();
public ObjectMap<Content, Seq<BasePart>> reqParts = new ObjectMap<>();
public ObjectMap<Item, OreBlock> ores = new ObjectMap<>();
public ObjectMap<Item, Floor> oreFloors = new ObjectMap<>();
public Seq<BasePart> forResource(Content item){
return reqParts.get(item, Seq::new);
@ -32,6 +37,15 @@ public class BaseRegistry{
parts.clear();
reqParts.clear();
//load ore types and corresponding items
for(Block block : content.blocks()){
if(block instanceof OreBlock && block.asFloor().itemDrop != null){
ores.put(block.asFloor().itemDrop, (OreBlock)block);
}else if(block.isFloor() && block.asFloor().itemDrop != null && !oreFloors.containsKey(block.asFloor().itemDrop)){
oreFloors.put(block.asFloor().itemDrop, block.asFloor());
}
}
String[] names = Core.files.internal("basepartnames").readString().split("\n");
for(String name : names){

View file

@ -77,14 +77,18 @@ public class WaveSpawner{
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
if(doShockwave){
Time.run(20f, () -> Fx.spawnShockwave.at(spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
doShockwave(spawnX, spawnY);
}
});
Time.runTask(121f, () -> spawning = false);
}
public void doShockwave(float x, float y){
Time.run(20f, () -> Fx.spawnShockwave.at(x, y, state.rules.dropZoneRadius));
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true));
}
private void eachGroundSpawn(SpawnConsumer cons){
for(Tile spawn : spawns){
cons.accept(spawn.worldx(), spawn.worldy(), true);

View file

@ -15,7 +15,7 @@ public class CircleFormation extends FormationPattern{
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
}else{
outLocation.set(0, 0, 360f * slotNumber);
outLocation.set(0, spacing * 1.1f, 360f * slotNumber);
}
outLocation.z += angleOffset;

View file

@ -17,13 +17,17 @@ public class BuilderAI extends AIController{
@Nullable Builderc following;
@Override
public void updateUnit(){
public void updateMovement(){
Builderc builder = (Builderc)unit;
if(builder.moving()){
builder.lookAt(builder.vel().angle());
}
if(target != null && shouldShoot()){
unit.lookAt(target);
}
builder.updateBuilding(true);
if(following != null){
@ -95,13 +99,29 @@ public class BuilderAI extends AIController{
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
//add build request.
builder.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.
blocks.addLast(blocks.removeFirst());
}else{
//shift head of queue to tail, try something else next time
blocks.removeFirst();
blocks.addLast(block);
}
}
}
}
@Override
public AIController fallback(){
return unit.type().flying ? new FlyingAI() : new GroundAI();
}
@Override
public boolean useFallback(){
return state.rules.waves && unit.team == state.rules.waveTeam && !unit.team.rules().ai;
}
@Override
public boolean shouldShoot(){
return !((Builderc)unit).isBuilding();
}
}

View file

@ -21,6 +21,7 @@ public class LogicAI extends AIController{
public LUnitControl control = LUnitControl.stop;
public float moveX, moveY, moveRad;
public float itemTimer, payTimer, controlTimer = logicControlTimeout, targetTimer;
@Nullable
public Building controller;
public BuildPlan plan = new BuildPlan();

View file

@ -2,27 +2,33 @@ package mindustry.ai.types;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.blocks.ConstructBlock.*;
//note that repair AI doesn't attack anything even if it theoretically can
public class RepairAI extends AIController{
@Override
protected void updateMovement(){
boolean shoot = false;
if(target != null){
if(!target.within(unit, unit.type().range * 0.8f)){
moveTo(target, unit.type().range * 0.8f);
}
if(target instanceof Building){
boolean shoot = false;
if(target.within(unit, unit.type().range)){
unit.aim(target);
shoot = true;
}
unit.controlWeapons(shoot);
}else if(target == null){
unit.controlWeapons(false);
}
unit.controlWeapons(shoot);
if(target != null){
if(!target.within(unit, unit.type().range * 0.65f)){
moveTo(target, unit.type().range * 0.65f);
}
unit.lookAt(target);
}
}
@Override
@ -30,5 +36,10 @@ public class RepairAI extends AIController{
target = Units.findDamagedTile(unit.team, unit.x, unit.y);
if(target instanceof ConstructBuild) target = null;
if(target == null){
super.updateTargeting();
}
}
}

View file

@ -6,6 +6,8 @@ import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.meta.*;
public class SuicideAI extends GroundAI{
@ -14,7 +16,7 @@ public class SuicideAI extends GroundAI{
@Override
public void updateUnit(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y, Float.MAX_VALUE)){
target = null;
}
@ -26,7 +28,7 @@ public class SuicideAI extends GroundAI{
boolean rotate = false, shoot = false, moveToTarget = false;
if(!Units.invalidateTarget(target, unit, unit.range())){
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
rotate = true;
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
@ -35,29 +37,36 @@ public class SuicideAI extends GroundAI{
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
blockedByBlock = false;
//do not move toward walls or transport blocks
if(!(target instanceof Building build && (
build.block.group == BlockGroup.walls ||
build.block.group == BlockGroup.liquids ||
build.block.group == BlockGroup.transportation
))){
blockedByBlock = false;
//raycast for target
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{
return tile == null || tile.solid();
//raycast for target
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{
return tile == null || tile.solid();
}
});
//shoot when there's an enemy block in the way
if(blockedByBlock){
shoot = true;
}
});
//shoot when there's an enemy block in the way
if(blockedByBlock){
shoot = true;
}
if(!blocked){
moveToTarget = true;
//move towards target directly
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
if(!blocked){
moveToTarget = true;
//move towards target directly
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
}
}
}
@ -78,4 +87,10 @@ public class SuicideAI extends GroundAI{
unit.controlWeapons(rotate, shoot);
}
@Override
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground &&
!(t.block instanceof Conveyor || t.block instanceof Conduit)); //do not target conveyors/conduits
}
}

View file

@ -35,7 +35,7 @@ public class Blocks implements ContentList{
public static Block
//environment
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater,
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space,
dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneWall, sandWall, moss, sporeMoss, shale, shaleWall, shaleBoulder, sandBoulder, daciteBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, basalt, magmarock, hotrock, snowWall, boulder, snowBoulder, saltWall,
@ -216,6 +216,13 @@ public class Blocks implements ContentList{
lightColor = Color.orange.cpy().a(0.38f);
}};
space = new Floor("space"){{
cacheLayer = CacheLayer.space;
placeableOn = false;
solid = true;
variants = 0;
}};
stone = new Floor("stone");
craters = new Floor("craters"){{
@ -695,7 +702,7 @@ public class Blocks implements ContentList{
drawer = new DrawAnimation();
consumes.item(Items.sporePod, 1);
consumes.power(0.60f);
consumes.power(0.7f);
}};
pulverizer = new GenericCrafter("pulverizer"){{
@ -720,7 +727,7 @@ public class Blocks implements ContentList{
hasPower = hasItems = hasLiquids = true;
consumes.liquid(Liquids.oil, 0.1f);
consumes.power(0.5f);
consumes.power(0.7f);
}};
incinerator = new Incinerator("incinerator"){{
@ -822,27 +829,27 @@ public class Blocks implements ContentList{
}};
scrapWall = new Wall("scrap-wall"){{
requirements(Category.defense, BuildVisibility.sandboxOnly, with());
requirements(Category.defense, BuildVisibility.sandboxOnly, with(Items.scrap, 6));
health = 60 * wallHealthMultiplier;
variants = 5;
}};
scrapWallLarge = new Wall("scrap-wall-large"){{
requirements(Category.defense, BuildVisibility.sandboxOnly, with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 4));
health = 60 * 4 * wallHealthMultiplier;
size = 2;
variants = 4;
}};
scrapWallHuge = new Wall("scrap-wall-huge"){{
requirements(Category.defense, BuildVisibility.sandboxOnly, with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 9));
health = 60 * 9 * wallHealthMultiplier;
size = 3;
variants = 3;
}};
scrapWallGigantic = new Wall("scrap-wall-gigantic"){{
requirements(Category.defense, BuildVisibility.sandboxOnly, with());
requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 16));
health = 60 * 16 * wallHealthMultiplier;
size = 4;
}};
@ -987,7 +994,7 @@ public class Blocks implements ContentList{
router = new Router("router"){{
requirements(Category.distribution, with(Items.copper, 3));
buildCostMultiplier = 2f;
buildCostMultiplier = 4f;
}};
distributor = new Router("distributor"){{
@ -1280,7 +1287,7 @@ public class Blocks implements ContentList{
}};
cultivator = new Cultivator("cultivator"){{
requirements(Category.production, with(Items.copper, 10, Items.lead, 25, Items.silicon, 10));
requirements(Category.production, with(Items.copper, 25, Items.lead, 25, Items.silicon, 10));
outputItem = new ItemStack(Items.sporePod, 1);
craftTime = 140;
size = 2;
@ -1288,7 +1295,7 @@ public class Blocks implements ContentList{
hasPower = true;
hasItems = true;
consumes.power(0.80f);
consumes.power(0.9f);
consumes.liquid(Liquids.water, 0.2f);
}};
@ -1334,6 +1341,7 @@ public class Blocks implements ContentList{
size = 4;
unitCapModifier = 14;
researchCostMultiplier = 0.04f;
}};
coreNucleus = new CoreBlock("core-nucleus"){{
@ -1345,20 +1353,19 @@ public class Blocks implements ContentList{
size = 5;
unitCapModifier = 20;
researchCostMultiplier = 0.06f;
}};
vault = new StorageBlock("vault"){{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
group = BlockGroup.storage;
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
group = BlockGroup.storage;
}};
unloader = new Unloader("unloader"){{
@ -1527,9 +1534,9 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 4.5f;
force = 5f;
scaledForce = 5.5f;
range = 110f;
range = 160f;
damage = 0.4f;
health = 160 * size * size;
rotateSpeed = 10;

View file

@ -354,6 +354,8 @@ public class Bullets implements ContentList{
width = 16f;
height = 23f;
shootEffect = Fx.shootBig;
pierceCap = 2;
pierceBuilding = true;
}};
standardIncendiaryBig = new BasicBulletType(7f, 60, "bullet"){{
@ -363,6 +365,8 @@ public class Bullets implements ContentList{
backColor = Pal.lightOrange;
status = StatusEffects.burning;
shootEffect = Fx.shootBig;
pierceCap = 2;
pierceBuilding = true;
}};
damageLightning = new BulletType(0.0001f, 0f){{

View file

@ -33,7 +33,7 @@ public class StatusEffects implements ContentList{
freezing = new StatusEffect("freezing"){{
speedMultiplier = 0.6f;
armorMultiplier = 0.8f;
healthMultiplier = 0.8f;
effect = Fx.freezing;
init(() -> {
@ -81,7 +81,7 @@ public class StatusEffects implements ContentList{
melting = new StatusEffect("melting"){{
speedMultiplier = 0.8f;
armorMultiplier = 0.8f;
healthMultiplier = 0.8f;
damage = 0.3f;
effect = Fx.melting;
@ -93,7 +93,7 @@ public class StatusEffects implements ContentList{
sapped = new StatusEffect("sapped"){{
speedMultiplier = 0.7f;
armorMultiplier = 0.8f;
healthMultiplier = 0.8f;
effect = Fx.sapped;
effectChance = 0.1f;
}};
@ -115,7 +115,7 @@ public class StatusEffects implements ContentList{
}};
overdrive = new StatusEffect("overdrive"){{
armorMultiplier = 0.95f;
healthMultiplier = 0.95f;
speedMultiplier = 1.15f;
damageMultiplier = 1.4f;
damage = -0.01f;
@ -132,13 +132,13 @@ public class StatusEffects implements ContentList{
}};
shielded = new StatusEffect("shielded"){{
armorMultiplier = 3f;
healthMultiplier = 3f;
}};
boss = new StatusEffect("boss"){{
permanent = true;
damageMultiplier = 1.5f;
armorMultiplier = 1.5f;
damageMultiplier = 2f;
healthMultiplier = 2f;
}};
shocked = new StatusEffect("shocked");

View file

@ -110,8 +110,6 @@ public class TechTree implements ContentList{
node(Items.coal, with(Items.lead, 3000), () -> {
node(Items.graphite, with(Items.coal, 1000), () -> {
node(illuminator, () -> {
});
node(graphitePress, () -> {
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
@ -230,6 +228,9 @@ public class TechTree implements ContentList{
});
});
});
node(illuminator, () -> {
});
});
});

View file

@ -26,10 +26,7 @@ public class UnitTypes implements ContentList{
public static @EntityDef({Unitc.class, Mechc.class}) UnitType vela;
//legs
public static @EntityDef({Unitc.class, Legsc.class}) UnitType corvus;
//legs
public static @EntityDef({Unitc.class, Legsc.class}) UnitType atrax;
public static @EntityDef({Unitc.class, Legsc.class}) UnitType corvus, atrax;
//legs + building
public static @EntityDef({Unitc.class, Legsc.class, Builderc.class}) UnitType spiroct, arkyid, toxopid;
@ -164,7 +161,7 @@ public class UnitTypes implements ContentList{
y = 1f;
x = 16f;
shootY = 8f;
reload = 50f;
reload = 45f;
recoil = 5f;
shake = 2f;
ejectEffect = Fx.shellEjectBig;
@ -173,7 +170,7 @@ public class UnitTypes implements ContentList{
inaccuracy = 3f;
shotDelay = 4f;
bullet = new BasicBulletType(7f, 45){{
bullet = new BasicBulletType(7f, 50){{
width = 11f;
height = 20f;
lifetime = 25f;
@ -182,7 +179,7 @@ public class UnitTypes implements ContentList{
lightningLength = 6;
lightningColor = Pal.surge;
//standard bullet damage is far too much for lightning
lightningDamage = 25;
lightningDamage = 30;
}};
}},
@ -230,8 +227,9 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.artillery;
bullet = new BasicBulletType(13f, 55){{
bullet = new BasicBulletType(13f, 60){{
pierce = true;
pierceCap = 10;
width = 14f;
height = 33f;
lifetime = 15f;
@ -250,6 +248,8 @@ public class UnitTypes implements ContentList{
width = 10f;
height = 10f;
pierce = true;
pierceBuilding = true;
pierceCap = 3;
lifetime = 20f;
hitEffect = Fx.flakExplosion;
@ -514,7 +514,7 @@ public class UnitTypes implements ContentList{
crawler = new UnitType("crawler"){{
defaultController = SuicideAI::new;
speed = 0.92f;
speed = 1f;
hitSize = 8f;
health = 180;
mechSideSway = 0.25f;
@ -529,9 +529,9 @@ public class UnitTypes implements ContentList{
hitEffect = Fx.pulverize;
lifetime = 10f;
speed = 1f;
splashDamageRadius = 55f;
splashDamageRadius = 70f;
instantDisappear = true;
splashDamage = 60f;
splashDamage = 80f;
killShooter = true;
hittable = false;
collidesAir = true;
@ -1145,7 +1145,7 @@ public class UnitTypes implements ContentList{
speed = 1.9f;
rotateSpeed = 15f;
accel = 0.1f;
range = 70f;
range = 130f;
health = 400;
buildSpeed = 0.5f;
engineOffset = 6.5f;
@ -1187,8 +1187,6 @@ public class UnitTypes implements ContentList{
healPercent = 5.5f;
collidesTeam = true;
backColor = Pal.heal;
frontColor = Color.white;
backColor = Pal.heal;
trailColor = Pal.heal;
}};
}});
@ -1233,7 +1231,7 @@ public class UnitTypes implements ContentList{
}};
quad = new UnitType("quad"){{
armor = 4f;
armor = 8f;
health = 6000;
speed = 1.2f;
rotateSpeed = 2f;
@ -1256,7 +1254,7 @@ public class UnitTypes implements ContentList{
new Weapon(){{
x = y = 0f;
mirror = false;
reload = 60f;
reload = 55f;
minShootVelocity = 0.01f;
bullet = new BasicBulletType(){{
@ -1289,9 +1287,9 @@ public class UnitTypes implements ContentList{
speed = 0.001f;
collides = false;
healPercent = 10f;
splashDamage = 240f;
splashDamageRadius = 115f;
healPercent = 15f;
splashDamage = 320f;
splashDamageRadius = 120f;
}};
}});
}};
@ -1546,7 +1544,7 @@ public class UnitTypes implements ContentList{
xRand = 8f;
shotDelay = 1f;
bullet = new MissileBulletType(4.2f, 25){{
bullet = new MissileBulletType(4.2f, 30){{
homingPower = 0.12f;
width = 8f;
height = 8f;
@ -1554,8 +1552,8 @@ public class UnitTypes implements ContentList{
drag = -0.003f;
homingRange = 80f;
keepVelocity = false;
splashDamageRadius = 25f;
splashDamage = 25f;
splashDamageRadius = 30f;
splashDamage = 35f;
lifetime = 56f;
trailColor = Pal.bulletYellowBack;
backColor = Pal.bulletYellowBack;

View file

@ -152,7 +152,7 @@ public class Weathers implements ContentList{
if(tile != null && tile.floor().liquidDrop == Liquids.water){
Draw.color(Tmp.c1.set(tile.floor().mapColor).mul(1.5f).a(state.opacity()));
Draw.rect(splashes[(int)(life * (splashes.length - 1))], x, y);
}else{
}else if(tile != null && tile.floor().liquidDrop == null && !tile.floor().solid){
Draw.color(Color.royal, Color.white, 0.3f);
Draw.alpha(Mathf.slope(life) * state.opacity());

View file

@ -317,6 +317,7 @@ public class Control implements ApplicationListener, Loadable{
}else{
net.reset();
logic.reset();
sector.setSecondsPassed(0);
world.loadSector(sector);
state.rules.sector = sector;
//assign origin when launching

View file

@ -9,7 +9,6 @@ import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
@ -89,13 +88,16 @@ public class Logic implements ApplicationListener{
if(state.isCampaign()){
long seconds = state.rules.sector.getSecondsPassed();
CoreBuild core = state.rules.defaultTeam.core();
//THE WAVES NEVER END
state.rules.waves = true;
//apply fractional damage based on how many turns have passed for this sector
float turnsPassed = seconds / (turnDuration / 60f);
//float turnsPassed = seconds / (turnDuration / 60f);
if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
SectorDamage.apply(turnsPassed / sectorDestructionTurns);
}
//TODO sector damage disabled for now
//if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
// SectorDamage.apply(turnsPassed / sectorDestructionTurns);
//}
//add resources based on turns passed
if(state.rules.sector.save != null && core != null){
@ -197,6 +199,8 @@ public class Logic implements ApplicationListener{
state.rules.waves = false;
}
//TODO capturing is disabled
/*
//if there's a "win" wave and no enemies are present, win automatically
if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){
//the sector has been conquered - waves get disabled
@ -209,7 +213,7 @@ public class Logic implements ApplicationListener{
if(!headless){
control.saves.saveSector(state.rules.sector);
}
}
}*/
}else{
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
state.gameOver = true;

View file

@ -329,8 +329,8 @@ public class NetServer implements ApplicationListener{
votes += d;
voted.addAll(player.uuid(), admins.getInfo(player.uuid()).lastIP);
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
target.name, votes, votesRequired()));
Call.sendMessage(Strings.format("[lightgray]@[lightgray] has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
player.name, target.name, votes, votesRequired()));
checkPass();
}

View file

@ -65,6 +65,11 @@ public class World{
return tile == null || tile.block().solid;
}
public boolean wallSolidFull(int x, int y){
Tile tile = tile(x, y);
return tile == null || (tile.block().solid && tile.block().fillsTile);
}
public boolean isAccessible(int x, int y){
return !wallSolid(x, y - 1) || !wallSolid(x, y + 1) || !wallSolid(x - 1, y) || !wallSolid(x + 1, y);
}

View file

@ -22,7 +22,7 @@ public abstract class UnlockableContent extends MappableContent{
/** Whether this content is always unlocked in the tech tree. */
public boolean alwaysUnlocked = false;
/** Icons by Cicon ID.*/
protected TextureRegion[] cicons = new TextureRegion[mindustry.ui.Cicon.all.length];
protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length];
/** Unlock state. Loaded from settings. Do not modify outside of the constructor. */
protected boolean unlocked;

View file

@ -26,7 +26,7 @@ public class EditorTile extends Tile{
if(type instanceof OverlayFloor){
//don't place on liquids
if(!floor.isLiquid){
if(floor.hasSurface()){
setOverlayID(type.id);
}
return;
@ -96,7 +96,14 @@ public class EditorTile extends Tile{
super.recache();
}
}
@Override
protected void changed(){
if(state.isGame()){
super.changed();
}
}
@Override
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
if(skip()){

View file

@ -118,7 +118,7 @@ public enum EditorTool{
if(editor.drawBlock.isOverlay()){
Block dest = tile.overlay();
if(dest == editor.drawBlock) return;
tester = t -> t.overlay() == dest && !t.floor().isLiquid;
tester = t -> t.overlay() == dest && t.floor().hasSurface();
setter = t -> t.setOverlay(editor.drawBlock);
}else if(editor.drawBlock.isFloor()){
Block dest = tile.floor();

View file

@ -557,6 +557,12 @@ public class MapEditorDialog extends Dialog implements Disposable{
t.add(slider).width(size * 3f - 20).padTop(4f);
}).padTop(5).growX().top();
mid.row();
mid.table(t -> {
t.button("@editor.center", Icon.move, Styles.cleart, () -> view.center()).growX().margin(9f);
}).growX().top();
}).margin(0).left().growY();

View file

@ -417,7 +417,7 @@ public class MapGenerateDialog extends BaseDialog{
public void set(Block floor, Block wall, Block ore, Team team){
this.floor = floor.id;
this.block = wall.id;
this.ore = floor.asFloor().isLiquid ? 0 : ore.id;
this.ore = !floor.asFloor().hasSurface() ? 0 : ore.id;
this.team = (byte)team.id;
}

View file

@ -171,6 +171,10 @@ public class MapView extends Element implements GestureListener{
this.grid = grid;
}
public void center(){
offsetx = offsety = 0;
}
@Override
public void act(float delta){
super.act(delta);

View file

@ -146,7 +146,7 @@ public class Effect{
if(headless || region == null || !Core.atlas.isFound(region)) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
if(tile == null || !tile.floor().hasSurface()) return;
Decal decal = Decal.create();
decal.set(x, y);

View file

@ -127,7 +127,7 @@ public class EntityCollisions{
public static boolean legsSolid(int x, int y){
Tile tile = world.tile(x, y);
return tile == null || tile.staticDarkness() >= 2;
return tile == null || tile.staticDarkness() >= 2 || tile.floor().solid;
}
public static boolean waterSolid(int x, int y){

View file

@ -45,6 +45,10 @@ public class Puddles{
return;
}
if(tile.floor().solid){
return;
}
Puddle p = map.get(tile.pos());
if(p == null){
Puddle puddle = Puddle.create();

View file

@ -25,6 +25,7 @@ public abstract class BulletType extends Content{
public float drawSize = 40f;
public float drag = 0f;
public boolean pierce, pierceBuilding;
public int pierceCap = -1;
public Effect hitEffect, despawnEffect;
/** Effect created when shooting. */
@ -235,6 +236,11 @@ public abstract class BulletType extends Content{
}
public void init(Bullet b){
if(pierceCap >= 1) {
pierce = true;
//pierceBuilding is not enabled by default, because a bullet may want to *not* pierce buildings
}
if(killShooter && b.owner() instanceof Healthc){
((Healthc)b.owner()).kill();
}
@ -311,7 +317,7 @@ public abstract class BulletType extends Content{
bullet.data = data;
bullet.drag = drag;
bullet.hitSize = hitSize;
bullet.damage = damage < 0 ? this.damage : damage;
bullet.damage = (damage < 0 ? this.damage : damage) * bullet.damageMultiplier();
bullet.add();
if(keepVelocity && owner instanceof Velc) bullet.vel.add(((Velc)owner).vel().x, ((Velc)owner).vel().y);

View file

@ -116,7 +116,6 @@ abstract class BuilderComp implements Unitc{
current.progress = entity.progress;
}
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
void drawBuildRequests(){

View file

@ -53,10 +53,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient Block block;
transient Seq<Building> proximity = new Seq<>(8);
transient boolean updateFlow;
transient byte dump;
transient byte cdump;
transient int rotation;
transient boolean enabled = true;
transient float enabledControlTime;
transient String lastAccessed;
PowerModule power;
ItemModule items;
@ -348,6 +349,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** Called when an unloader takes an item. */
public void itemTaken(Item item){
}
/** Called when this block is dropped as a payload. */
public void dropped(){
@ -443,7 +449,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public boolean dumpPayload(Payload todump){
if(proximity.size == 0) return false;
int dump = this.dump;
int dump = this.cdump;
for(int i = 0; i < proximity.size; i++){
Building other = proximity.get((i + dump) % proximity.size);
@ -477,7 +483,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void dumpLiquid(Liquid liquid){
int dump = this.dump;
int dump = this.cdump;
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
@ -507,15 +513,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public float moveLiquidForward(float leakResistance, Liquid liquid){
public float moveLiquidForward(boolean leaks, Liquid liquid){
Tile next = tile.getNearby(rotation);
if(next == null) return 0;
if(next.build != null){
return moveLiquid(next.build, liquid);
}else if(leakResistance != 100f && !next.block().solid && !next.block().hasLiquids){
float leakAmount = liquids.get(liquid) / leakResistance;
}else if(leaks && !next.block().solid && !next.block().hasLiquids){
float leakAmount = liquids.get(liquid) / 1.5f;
Puddles.deposit(next, tile, liquid, leakAmount);
liquids.remove(liquid, leakAmount);
}
@ -577,7 +583,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* containers, it gets added to the block's inventory.
*/
public void offload(Item item){
int dump = this.dump;
int dump = this.cdump;
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
@ -595,7 +601,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* Tries to put this item into a nearby container. Returns success. Unlike #offload(), this method does not change the block inventory.
*/
public boolean put(Item item){
int dump = this.dump;
int dump = this.cdump;
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
@ -621,7 +627,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public boolean dump(Item todump){
if(!block.hasItems || items.total() == 0 || (todump != null && !items.has(todump))) return false;
int dump = this.dump;
int dump = this.cdump;
if(proximity.size == 0) return false;
@ -656,7 +662,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void incrementDump(int prox){
dump = (byte)((dump + 1) % prox);
cdump = (byte)((cdump + 1) % prox);
}
/** Used for dumping items. */
@ -874,6 +880,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//null is of type void.class; anonymous classes use their superclass.
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
if(builder != null && builder.isPlayer()){
lastAccessed = builder.getPlayer().name;
}
if(block.configurations.containsKey(type)){
block.configurations.get(type).get(this, value);
}
@ -1038,6 +1048,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
if(net.active() && lastAccessed != null){
table.row();
table.add(Core.bundle.format("lastaccessed", lastAccessed)).growX().wrap().left();
}
table.marginBottom(-5);
}
}
@ -1165,10 +1180,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
proximity.add(tile);
}
for(Building other : tmpTiles){
other.onProximityUpdate();
}
onProximityAdded();
onProximityUpdate();

View file

@ -19,12 +19,11 @@ import static mindustry.Vars.*;
abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Drawc, Shielderc, Ownerc, Velc, Bulletc, Timerc{
@Import Team team;
@Import Entityc owner;
@Import float x,y;
@Import float x, y, damage;
IntSeq collided = new IntSeq(6);
Object data;
BulletType type;
float damage;
float fdata;
@Override
@ -76,11 +75,6 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
return type.drawSize;
}
@Override
public float damage(){
return damage * damageMultiplier();
}
@Replace
@Override
public boolean collides(Hitboxc other){
@ -150,6 +144,10 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
return false;
});
}
if(type.pierceCap != -1 && collided.size >= type.pierceCap) {
remove();
}
}
@Override

View file

@ -4,5 +4,5 @@ import mindustry.annotations.Annotations.*;
@Component
abstract class DamageComp{
abstract float damage();
float damage;
}

View file

@ -86,10 +86,10 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
return true;
}
if(payload instanceof BuildPayload){
return dropBlock((BuildPayload)payload);
}else if(payload instanceof UnitPayload){
return dropUnit((UnitPayload)payload);
if(payload instanceof BuildPayload b){
return dropBlock(b);
}else if(payload instanceof UnitPayload p){
return dropUnit(p);
}
return false;
}
@ -126,6 +126,8 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
int rot = (int)((rotation + 45f) / 90f) % 4;
payload.place(on, rot);
if(isPlayer()) payload.build.lastAccessed = getPlayer().name;
Fx.unitDrop.at(tile);
Fx.placeBlock.at(on.drawx(), on.drawy(), on.block().size);
return true;

View file

@ -188,6 +188,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
if(unit.isRemote()){
unit.snapInterpolation();
}
//reset selected block when switching units
if(!headless && isLocal()){
control.input.block = null;
}
}
Events.fire(new UnitChangeEvent(self(), unit));

View file

@ -9,7 +9,7 @@ import static mindustry.Vars.*;
@Component
abstract class ShieldComp implements Healthc, Posc{
@Import float health, hitTime, x, y;
@Import float health, hitTime, x, y, healthMultiplier;
@Import boolean dead;
/** Absorbs health damage. */
@ -22,6 +22,7 @@ abstract class ShieldComp implements Healthc, Posc{
@Replace
@Override
public void damage(float amount){
amount /= healthMultiplier;
//apply armor
amount = Math.max(amount - armor, minArmorDamage * amount);

View file

@ -19,7 +19,7 @@ abstract class StatusComp implements Posc, Flyingc{
private Seq<StatusEntry> statuses = new Seq<>();
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, armorMultiplier = 1, reloadMultiplier = 1;
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1;
@Import UnitType type;
@ -104,7 +104,7 @@ abstract class StatusComp implements Posc, Flyingc{
}
applied.clear();
speedMultiplier = damageMultiplier = armorMultiplier = reloadMultiplier = 1f;
speedMultiplier = damageMultiplier = healthMultiplier = reloadMultiplier = 1f;
if(statuses.isEmpty()) return;
@ -122,7 +122,7 @@ abstract class StatusComp implements Posc, Flyingc{
statuses.remove(index);
}else{
speedMultiplier *= entry.effect.speedMultiplier;
armorMultiplier *= entry.effect.armorMultiplier;
healthMultiplier *= entry.effect.healthMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
reloadMultiplier *= entry.effect.reloadMultiplier;
entry.effect.update(self(), entry.time);

View file

@ -111,7 +111,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
//update continuous state
if(weapon.continuous && mount.bullet != null){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime || mount.bullet.type != weapon.bullet){
mount.bullet = null;
}else{
mount.bullet.rotation(weaponRotation + 90);

View file

@ -21,6 +21,7 @@ public class AIController implements UnitController{
protected Unit unit;
protected Interval timer = new Interval(4);
protected AIController fallback;
/** main target that is being faced */
protected Teamc target;
@ -34,11 +35,27 @@ public class AIController implements UnitController{
@Override
public void updateUnit(){
//use fallback AI when possible
if(useFallback() && (fallback != null || (fallback = fallback()) != null)){
if(fallback.unit != unit) fallback.unit(unit);
fallback.updateUnit();
return;
}
updateVisuals();
updateTargeting();
updateMovement();
}
@Nullable
protected AIController fallback(){
return null;
}
protected boolean useFallback(){
return false;
}
protected UnitCommand command(){
return unit.team.data().command;
}

View file

@ -1,5 +1,6 @@
package mindustry.game;
import arc.func.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
@ -37,6 +38,7 @@ public class DefaultWaves{
unitScaling = 1.7f;
spacing = 2;
max = 4;
shieldScaling = 15f;
}},
new SpawnGroup(pulsar){{
@ -54,10 +56,12 @@ public class DefaultWaves{
}},
new SpawnGroup(dagger){{
begin = 8;
begin = 12;
unitScaling = 1;
unitAmount = 4;
spacing = 2;
shieldScaling = 10f;
max = 20;
}},
new SpawnGroup(mace){{
@ -65,12 +69,15 @@ public class DefaultWaves{
spacing = 3;
unitScaling = 1;
end = 40;
shieldScaling = 20f;
}},
new SpawnGroup(mace){{
new SpawnGroup(spiroct){{
begin = 45;
spacing = 3;
unitScaling = 2;
unitScaling = 1;
max = 10;
shieldScaling = 10f;
effect = StatusEffects.overdrive;
}},
@ -86,17 +93,19 @@ public class DefaultWaves{
begin = 16;
unitScaling = 1;
spacing = 2;
shieldScaling = 20f;
}},
new SpawnGroup(dagger){{
new SpawnGroup(quasar){{
begin = 82;
spacing = 3;
unitAmount = 4;
unitScaling = 3;
shieldScaling = 30f;
effect = StatusEffects.overdrive;
}},
new SpawnGroup(dagger){{
new SpawnGroup(pulsar){{
begin = 41;
spacing = 5;
unitAmount = 1;
@ -110,6 +119,7 @@ public class DefaultWaves{
unitAmount = 2;
unitScaling = 2;
max = 20;
shieldScaling = 30;
}},
new SpawnGroup(dagger){{
@ -135,6 +145,7 @@ public class DefaultWaves{
unitAmount = 2;
spacing = 2;
unitScaling = 2;
shieldScaling = 20;
}},
new SpawnGroup(flare){{
@ -142,6 +153,8 @@ public class DefaultWaves{
unitAmount = 4;
unitScaling = 3;
spacing = 5;
shields = 100f;
shieldScaling = 10f;
effect = StatusEffects.overdrive;
}},
@ -151,13 +164,15 @@ public class DefaultWaves{
unitScaling = 3;
spacing = 5;
max = 16;
shieldScaling = 30;
}},
new SpawnGroup(horizon){{
new SpawnGroup(nova){{
begin = 53;
unitAmount = 2;
unitScaling = 3;
spacing = 4;
shieldScaling = 20;
}},
new SpawnGroup(atrax){{
@ -165,6 +180,7 @@ public class DefaultWaves{
unitAmount = 4;
unitScaling = 1;
spacing = 3;
shieldScaling = 5f;
}},
new SpawnGroup(scepter){{
@ -172,6 +188,7 @@ public class DefaultWaves{
unitAmount = 1;
unitScaling = 1;
spacing = 30;
shieldScaling = 10f;
}},
new SpawnGroup(reign){{
@ -179,13 +196,32 @@ public class DefaultWaves{
unitAmount = 1;
unitScaling = 1;
spacing = 40;
shieldScaling = 20f;
}},
new SpawnGroup(antumbra){{
begin = 131;
begin = 120;
unitAmount = 1;
unitScaling = 1;
spacing = 40;
shieldScaling = 20f;
}},
new SpawnGroup(vela){{
begin = 100;
unitAmount = 1;
unitScaling = 1;
spacing = 30;
shieldScaling = 20f;
}},
new SpawnGroup(corvus){{
begin = 145;
unitAmount = 1;
unitScaling = 1;
spacing = 35;
shieldScaling = 30f;
shields = 100;
}},
new SpawnGroup(horizon){{
@ -193,6 +229,17 @@ public class DefaultWaves{
unitAmount = 2;
unitScaling = 3;
spacing = 4;
shields = 40f;
shieldScaling = 20f;
}},
new SpawnGroup(atrax){{
begin = 210;
unitAmount = 1;
unitScaling = 1;
spacing = 35;
shields = 1000;
shieldScaling = 35f;
}}
);
}
@ -200,7 +247,7 @@ public class DefaultWaves{
}
//TODO move elsewhere
public static Seq<SpawnGroup> generate(){
public static Seq<SpawnGroup> generate(float difficulty){
UnitType[][] species = {
{dagger, mace, fortress, scepter, reign},
{nova, pulsar, quasar, vela, corvus},
@ -216,58 +263,72 @@ public class DefaultWaves{
Seq<SpawnGroup> out = new Seq<>();
//max reasonable wave, after which everything gets boring
int cap = 400;
int cap = 200;
//main sequence
float shieldStart = 30, shieldsPerWave = 12;
UnitType[] curSpecies = Structs.random(species);
int curTier = 0;
float shieldStart = 30, shieldsPerWave = 20 + difficulty*30f;
for(int i = 0; i < cap;){
int f = i;
int next = Mathf.random(15, 25);
Intc createProgression = start -> {
//main sequence
UnitType[] curSpecies = Structs.random(species);
int curTier = 0;
float shieldAmount = Math.max((i - shieldStart) * shieldsPerWave, 0);
for(int i = start; i < cap;){
int f = i;
int next = Mathf.random(8, 16);
//main progression
out.add(new SpawnGroup(curSpecies[Math.min(curTier, curSpecies.length - 1)]){{
unitAmount = f == 0 ? 1 : 10;
begin = f;
end = f + next >= cap ? never : f + next;
max = 16;
unitScaling = Mathf.random(1f, 2f);
shields = shieldAmount;
shieldScaling = shieldsPerWave;
}});
float shieldAmount = Math.max((i - shieldStart) * shieldsPerWave, 0);
int space = start == 0 ? 1 : Mathf.random(1, 2);
//extra progression that tails out, blends in
out.add(new SpawnGroup(curSpecies[Math.min(curTier, curSpecies.length - 1)]){{
unitAmount = 6;
begin = f + next;
end = f + next + Mathf.random(8, 12);
max = 10;
unitScaling = Mathf.random(2f);
spacing = Mathf.random(2, 3);
shields = shieldAmount;
shieldScaling = shieldsPerWave;
}});
//main progression
out.add(new SpawnGroup(curSpecies[Math.min(curTier, curSpecies.length - 1)]){{
unitAmount = f == 0 ? 1 : 10;
begin = f;
end = f + next >= cap ? never : f + next;
max = 20;
unitScaling = Mathf.random(1f, 2f);
shields = shieldAmount;
shieldScaling = shieldsPerWave;
spacing = space;
}});
i += next;
if(curTier < 3 || Mathf.chance(0.2)){
curTier ++;
//extra progression that tails out, blends in
out.add(new SpawnGroup(curSpecies[Math.min(curTier, curSpecies.length - 1)]){{
unitAmount = 6;
begin = f + next;
end = f + next + Mathf.random(8, 12);
max = 14;
unitScaling = Mathf.random(2f);
spacing = Mathf.random(2, 3);
shields = shieldAmount;
shieldScaling = shieldsPerWave;
}});
i += next;
if(curTier < 3 || Mathf.chance(0.2)){
curTier ++;
}
//do not spawn bosses
curTier = Math.min(curTier, 3);
//small chance to switch species
if(Mathf.chance(0.3)){
curSpecies = Structs.random(species);
}
}
};
//do not spawn bosses
curTier = Math.min(curTier, 3);
createProgression.get(0);
//small chance to switch species
if(Mathf.chance(0.3)){
curSpecies = Structs.random(species);
}
int step = 5 + Mathf.random(3);
while(step <= cap){
createProgression.get(step);
step += (int)(Mathf.random(12, 25) * Mathf.lerp(1f, 0.4f, difficulty));
}
int bossWave = Mathf.random(30, 60);
int bossSpacing = Mathf.random(30, 50);
int bossWave = (int)(Mathf.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty));
int bossSpacing = (int)(Mathf.random(25, 40) * Mathf.lerp(1f, 0.6f, difficulty));
//main boss progression
out.add(new SpawnGroup(Structs.random(species)[4]){{
@ -283,7 +344,7 @@ public class DefaultWaves{
//alt boss progression
out.add(new SpawnGroup(Structs.random(species)[4]){{
unitAmount = 1;
begin = bossWave + Mathf.random(4, 6) * bossSpacing;
begin = bossWave + Mathf.random(3, 5) * bossSpacing;
spacing = bossSpacing;
end = never;
max = 16;
@ -291,6 +352,14 @@ public class DefaultWaves{
shieldScaling = shieldsPerWave;
}});
//shift back waves on higher difficulty for a harder start
int shift = Math.max((int)(difficulty * 15 - 5), 0);
for(SpawnGroup group : out){
group.begin -= shift;
group.end -= shift;
}
return out;
}
}

View file

@ -28,6 +28,7 @@ public class Objectives{
}
}
//TODO fix
public static class SectorComplete extends SectorObjective{
public SectorComplete(SectorPreset zone){
@ -38,12 +39,12 @@ public class Objectives{
@Override
public boolean complete(){
return preset.sector.isCaptured();
return preset.sector.save != null && preset.sector.save.meta.wave >= preset.sector.save.meta.rules.winWave;
}
@Override
public String display(){
return Core.bundle.format("requirement.capture", preset.localizedName);
return Core.bundle.format("requirement.wave", preset.sector.save == null ? "<unknown>" : preset.sector.save.meta.rules.winWave, preset.localizedName);
}
}

View file

@ -62,6 +62,10 @@ public class Schematic implements Publishable, Comparable<Schematic>{
return tags.get("name", "unknown");
}
public String description(){
return tags.get("description", "");
}
public void save(){
schematics.saveChanges(this);
}
@ -90,7 +94,7 @@ public class Schematic implements Publishable, Comparable<Schematic>{
@Override
public String steamDescription(){
return null;
return description();
}
@Override

View file

@ -10,6 +10,8 @@ import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.modules.*;
import java.util.*;
import static mindustry.Vars.*;
public class SectorInfo{
@ -42,7 +44,15 @@ public class SectorInfo{
/** Counter refresh state. */
private transient Interval time = new Interval();
/** Core item storage to prevent spoofing. */
private transient int[] lastCoreItems;
private transient int[] coreItemCounts;
/** Handles core item changes. */
public void handleCoreItem(Item item, int amount){
if(coreItemCounts == null){
coreItemCounts = new int[content.items().size];
}
coreItemCounts[item.id] += amount;
}
/** @return the real location items go when launched on this sector */
public Sector getRealDestination(){
@ -105,12 +115,6 @@ public class SectorInfo{
universe.runTurn();
}
//create last stored core items
if(lastCoreItems == null){
lastCoreItems = new int[content.items().size];
updateCoreDeltas();
}
CoreBuild ent = state.rules.defaultTeam.core();
//refresh throughput
@ -124,15 +128,16 @@ public class SectorInfo{
stat.loaded = true;
}
//how the resources changed - only interested in negative deltas, since that's what happens during spoofing
int coreDelta = Math.min(ent == null ? 0 : ent.items.get(item) - lastCoreItems[item.id], 0);
//add counter, subtract how many items were taken from the core during this time
stat.means.add(Math.max(stat.counter + coreDelta, 0));
stat.means.add(Math.max(stat.counter, 0));
stat.counter = 0;
stat.mean = stat.means.rawMean();
});
if(coreItemCounts == null){
coreItemCounts = new int[content.items().size];
}
//refresh core items
for(Item item : content.items()){
ExportStat stat = production.get(item, ExportStat::new);
@ -143,21 +148,14 @@ public class SectorInfo{
//get item delta
//TODO is preventing negative production a good idea?
int delta = Math.max((ent == null ? 0 : ent.items.get(item)) - lastCoreItems[item.id], 0);
int delta = Math.max(ent == null ? 0 : coreItemCounts[item.id], 0);
//store means
stat.means.add(delta);
stat.mean = stat.means.rawMean();
}
updateCoreDeltas();
}
}
private void updateCoreDeltas(){
CoreBuild ent = state.rules.defaultTeam.core();
for(int i = 0; i < lastCoreItems.length; i++){
lastCoreItems[i] = ent == null ? 0 : ent.items.get(i);
Arrays.fill(coreItemCounts, 0);
}
}

View file

@ -1,5 +1,6 @@
package mindustry.game;
import arc.util.*;
import arc.util.serialization.*;
import arc.util.serialization.Json.*;
import mindustry.content.*;
@ -27,7 +28,7 @@ public class SpawnGroup implements Serializable{
/** The spacing, in waves, of spawns. For example, 2 = spawns every other wave */
public int spacing = 1;
/** Maximum amount of units that spawn */
public int max = 100;
public int max = 40;
/** How many waves need to pass before the amount of units spawned increases by 1 */
public float unitScaling = never;
/** Shield points that this unit has. */
@ -37,8 +38,10 @@ public class SpawnGroup implements Serializable{
/** Amount of enemies spawned initially, with no scaling */
public int unitAmount = 1;
/** Status effect applied to the spawned unit. Null to disable. */
@Nullable
public StatusEffect effect;
/** Items this unit spawns with. Null to disable. */
@Nullable
public ItemStack items;
public SpawnGroup(UnitType type){
@ -51,6 +54,7 @@ public class SpawnGroup implements Serializable{
/** Returns the amount of units spawned on a specific wave. */
public int getUnitsSpawned(int wave){
if(spacing == 0) spacing = 1;
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
return 0;
}
@ -84,12 +88,12 @@ public class SpawnGroup implements Serializable{
if(begin != 0) json.writeValue("begin", begin);
if(end != never) json.writeValue("end", end);
if(spacing != 1) json.writeValue("spacing", spacing);
//if(max != 40) json.writeValue("max", max);
if(max != 40) json.writeValue("max", max);
if(unitScaling != never) json.writeValue("scaling", unitScaling);
if(shields != 0) json.writeValue("shields", shields);
if(shieldScaling != 0) json.writeValue("shieldScaling", shieldScaling);
if(unitAmount != 1) json.writeValue("amount", unitAmount);
if(effect != null) json.writeValue("effect", effect.id);
if(effect != null) json.writeValue("effect", effect.name);
}
@Override
@ -101,12 +105,18 @@ public class SpawnGroup implements Serializable{
begin = data.getInt("begin", 0);
end = data.getInt("end", never);
spacing = data.getInt("spacing", 1);
//max = data.getInt("max", 40);
max = data.getInt("max", 40);
unitScaling = data.getFloat("scaling", never);
shields = data.getFloat("shields", 0);
shieldScaling = data.getFloat("shieldScaling", 0);
unitAmount = data.getInt("amount", 1);
effect = content.getByID(ContentType.status, data.getInt("effect", -1));
//old boss effect ID
if(data.has("effect") && data.get("effect").isNumber() && data.getInt("effect", -1) == 8){
effect = StatusEffects.boss;
}else{
effect = content.getByName(ContentType.status, data.has("effect") && data.get("effect").isString() ? data.getString("effect", "none") : "none");
}
}
@Override

View file

@ -140,8 +140,9 @@ public class Universe{
if(!sector.isBeingPlayed()){
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
//TODO sector damage disabled for now
//check if the sector has been attacked too many times...
if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
/*if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
//fire event for losing the sector
Events.fire(new SectorLoseEvent(sector));
@ -151,17 +152,17 @@ public class Universe{
//clear recieved
sector.setExtraItems(new ItemSeq());
sector.save = null;
}
}*/
}
//export to another sector
if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){
Sector to = sector.save.meta.secinfo.destination;
if(to.save != null){
ItemSeq items = to.getExtraItems();
ItemSeq items = new ItemSeq();
//calculated exported items to this sector
sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed)));
to.setExtraItems(items);
to.addItems(items);
}
}

View file

@ -51,6 +51,17 @@ public enum CacheLayer{
endShader(Shaders.slag);
}
},
space{
@Override
public void begin(){
beginShader();
}
@Override
public void end(){
endShader(Shaders.space);
}
},
normal(5),
walls(3);

View file

@ -7,6 +7,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.types.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.ui.*;
@ -151,6 +152,13 @@ public class OverlayRenderer{
input.drawOverSelect();
if(ui.hudfrag.blockfrag.hover() instanceof Unit unit && unit.controller() instanceof LogicAI ai && ai.controller instanceof Building build){
Drawf.square(build.x, build.y, build.block.size * tilesize/2f + 2f);
if(!unit.within(build, unit.hitSize * 2f)){
Drawf.arrow(unit.x, unit.y, build.x, build.y, unit.hitSize *2f, 4f);
}
}
//draw selection overlay when dropping item
if(input.isDroppingItem()){
Vec2 v = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());

View file

@ -19,7 +19,7 @@ public class Shaders{
public static UnitBuild build;
public static DarknessShader darkness;
public static LightShader light;
public static SurfaceShader water, mud, tar, slag;
public static SurfaceShader water, mud, tar, slag, space;
public static PlanetShader planet;
public static PlanetGridShader planetGrid;
public static AtmosphereShader atmosphere;
@ -44,6 +44,7 @@ public class Shaders{
mud = new SurfaceShader("mud");
tar = new SurfaceShader("tar");
slag = new SurfaceShader("slag");
space = new SpaceShader("space");
planet = new PlanetShader();
planetGrid = new PlanetGridShader();
atmosphere = new AtmosphereShader();
@ -196,6 +197,34 @@ public class Shaders{
}
}
//seed: 8kmfuix03fw
public static class SpaceShader extends SurfaceShader{
Texture texture;
public SpaceShader(String frag){
super(frag);
Core.assets.load("sprites/space.png", Texture.class).loaded = t -> {
texture = (Texture)t;
texture.setFilter(TextureFilter.linear);
texture.setWrap(TextureWrap.mirroredRepeat);
};
}
@Override
public void apply(){
setUniformf("u_campos", Core.camera.position.x, Core.camera.position.y);
setUniformf("u_ccampos", Core.camera.position);
setUniformf("u_resolution", Core.graphics.getWidth(), Core.graphics.getHeight());
setUniformf("u_time", Time.time());
texture.bind(1);
renderer.effectBuffer.getTexture().bind(0);
setUniformi("u_stars", 1);
}
}
public static class SurfaceShader extends LoadShader{
public SurfaceShader(String frag){
@ -225,7 +254,7 @@ public class Shaders{
public static class LoadShader extends Shader{
public LoadShader(String frag, String vert){
super(Core.files.internal("shaders/" + vert + ".vert").readString(), Core.files.internal("shaders/" + frag + ".frag").readString());
super(Core.files.internal("shaders/" + vert + ".vert"), Core.files.internal("shaders/" + frag + ".frag"));
}
}
}

View file

@ -23,10 +23,6 @@ public class PlanetRenderer implements Disposable{
shadowColor = new Color(0, 0, 0, 0.7f);
private static final Seq<Vec3> points = new Seq<>();
private static final PlanetInterfaceRenderer emptyRenderer = new PlanetInterfaceRenderer(){
@Override public void renderSectors(Planet planet){}
@Override public void renderProjections(){}
};
/** Camera direction relative to the planet. Length is determined by zoom. */
public final Vec3 camPos = new Vec3();

View file

@ -176,7 +176,7 @@ public class DesktopInput extends InputHandler{
public void update(){
super.update();
if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content))){
if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content) || scene.getKeyboardFocus().isDescendantOf(ui.minimapfrag.elem))){
ui.listfrag.toggle();
}

View file

@ -124,7 +124,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building tile, Item item, int amount){
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange)) return;
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange) || player.dead()) return;
amount = Math.min(player.unit().maxAccepted(item), amount);
int fa = amount;
@ -256,7 +256,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
public static void rotateBlock(Player player, Building tile, boolean direction){
public static void rotateBlock(@Nullable Player player, Building tile, boolean direction){
if(tile == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
@ -264,6 +264,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
throw new ValidateException(player, "Player cannot rotate a block.");
}
if(player != null) tile.lastAccessed = player.name;
tile.rotation = Mathf.mod(tile.rotation + Mathf.sign(direction), 4);
tile.updateProximity();
tile.noSleep();
@ -271,7 +272,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Building tile){
if(player == null || tile == null || !player.within(tile, buildingRange) || tile.items == null) return;
if(player == null || tile == null || !player.within(tile, buildingRange) || tile.items == null || player.dead()) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.depositItem, tile.tile, action -> {
@ -531,6 +532,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
});
}else{
lastSchematic.tags.put("name", text);
lastSchematic.tags.put("description", "");
schematics.add(lastSchematic);
ui.showInfoFade("@schematic.saved");
ui.schematics.showInfo(lastSchematic);

View file

@ -290,12 +290,15 @@ public class TypeIO{
public static void writeController(Writes write, UnitController control){
//no real unit controller state is written, only the type
if(control instanceof Player){
if(control instanceof Player p){
write.b(0);
write.i(((Player)control).id);
}else if(control instanceof FormationAI){
write.i(p.id);
}else if(control instanceof FormationAI form){
write.b(1);
write.i(((FormationAI)control).leader.id);
write.i(form.leader.id);
}else if(control instanceof LogicAI logic && logic.controller != null){
write.b(3);
write.i(logic.controller.pos());
}else{
write.b(2);
}
@ -312,6 +315,19 @@ public class TypeIO{
}else if(type == 1){ //formation controller
int id = read.i();
return prev instanceof FormationAI ? prev : new FormationAI(Groups.unit.getByID(id), null);
}else if(type == 3){
int pos = read.i();
if(prev instanceof LogicAI pai){
pai.controller = world.build(pos);
return pai;
}else{
//create new AI for assignment
LogicAI out = new LogicAI();
//instantly time out when updated.
out.controlTimer = LogicAI.logicControlTimeout;
out.controller = world.build(pos);
return out;
}
}else{
//there are two cases here:
//1: prev controller was not a player, carry on

View file

@ -52,6 +52,9 @@ public class LAssembler{
}
}
//used as a special value for any environmental solid block
putConst("@solid", Blocks.stoneWall);
putConst("@air", Blocks.air);
for(UnitType type : Vars.content.units()){

View file

@ -6,6 +6,7 @@ import arc.util.*;
import arc.util.noise.*;
import mindustry.*;
import mindustry.ai.types.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
@ -251,6 +252,11 @@ public class LExecutor{
case spawn -> {
res = Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
case damaged -> {
Building b = Units.findDamagedTile(unit.team, unit.x, unit.y);
res = b == null ? null : b.tile;
build = true;
}
}
if(res != null && (!build || res.build != null)){
@ -390,7 +396,9 @@ public class LExecutor{
if(exec.bool(p1)){
Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type().hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
Call.pickedUnitPayload(unit, result);
if(result != null){
Call.pickedUnitPayload(unit, result);
}
}else{ //buildings
Building tile = world.buildWorld(unit.x, unit.y);
@ -433,12 +441,16 @@ public class LExecutor{
}
case getBlock -> {
float x = exec.numf(p1), y = exec.numf(p2);
if(unit.within(x, y, unit.range())){
float range = Math.max(unit.range(), buildingRange);
if(!unit.within(x, y, range)){
exec.setobj(p3, null);
exec.setnum(p4, 0);
}else{
Tile tile = world.tileWorld(x, y);
Block block = tile == null || !tile.synthetic() ? null : tile.block();
//any environmental solid block is returned as StoneWall, aka "@solid"
Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block();
exec.setobj(p3, block);
exec.setnum(p4, tile != null && tile.build != null ? tile.build.rotation : 0);
}
}
case itemDrop -> {

View file

@ -3,7 +3,8 @@ package mindustry.logic;
public enum LLocate{
ore,
building,
spawn;
spawn,
damaged;
public static final LLocate[] all = values();
}

View file

@ -889,7 +889,7 @@ public class LStatements{
table.row();
}
case spawn -> {
case spawn, damaged -> {
table.row();
}
}

View file

@ -15,7 +15,7 @@ public enum LUnitControl{
mine("x", "y"),
flag("value"),
build("x", "y", "block", "rotation"),
getBlock("x", "y", "result"),
getBlock("x", "y", "result", "resRot"),
within("x", "y", "radius", "result");
public final String[] params;

View file

@ -98,7 +98,9 @@ public class Map implements Comparable<Map>, Publishable{
public Rules rules(Rules base){
try{
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}"));
//this replacement is a MASSIVE hack but it fixes some incorrect overwriting of team-specific rules.
//may need to be tweaked later
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}").replace("teams:{2:{infiniteAmmo:true}},", ""));
if(result.spawns.isEmpty()) result.spawns = Vars.defaultWaves.get();
return result;
}catch(Exception e){

Some files were not shown because too many files have changed in this diff Show more