1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-11-13 07:15:28 +03:00

New tile edge algorithms / Struct value type generation

This commit is contained in:
Anuken 2019-01-28 22:11:23 -05:00
parent 5a3ec8f407
commit 54bade668e
19 changed files with 7275 additions and 7459 deletions

View File

@ -22,7 +22,7 @@ public class Annotations{
}
/**Marks a field of a struct. Optional.*/
@Target(ElementType.TYPE)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface StructField{
/**Size of a struct field in bits. Not valid on booleans or floating point numbers.*/

View File

@ -9,14 +9,17 @@ import io.anuke.annotations.Annotations.StructField;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**Generates ""value types"" classes that are packed into integer primitives of the most aproppriate size.
* It would be nice if Java didn't make crazy hacks like this necessary.*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({
"io.anuke.annotations.Annotations.Struct"
@ -44,31 +47,114 @@ public class StructAnnotationProcessor extends AbstractProcessor{
Set<TypeElement> elements = ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Struct.class));
for(TypeElement elem : elements){
TypeName type = TypeName.get(elem.asType());
if(!type.toString().endsWith("Struct")){
if(!elem.getSimpleName().toString().endsWith("Struct")){
Utils.messager.printMessage(Kind.ERROR, "All classes annotated with @Struct must have their class names end in 'Struct'.", elem);
continue;
}
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(packageName + "." + elem.getSimpleName().toString());
String structName = elem.getSimpleName().toString().substring(0, elem.getSimpleName().toString().length() - "Struct".length());
String structParam = structName.toLowerCase();
int offset = 0;
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(structName)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
for(VariableElement var : ElementFilter.fieldsIn(Collections.singletonList(elem))){
if(!var.asType().getKind().isPrimitive()){
Utils.messager.printMessage(Kind.ERROR, "All struct fields must be primitives.", var);
try{
List<VariableElement> variables = ElementFilter.fieldsIn(elem.getEnclosedElements());
int structSize = variables.stream().mapToInt(StructAnnotationProcessor::varSize).sum();
int structTotalSize = (structSize <= 8 ? 8 : structSize <= 16 ? 16 : structSize <= 32 ? 32 : 64);
if(variables.size() == 0){
Utils.messager.printMessage(Kind.ERROR, "making a struct with no fields is utterly pointles.", elem);
continue;
}
StructField an = var.getAnnotation(StructField.class);
int size = an == null ? typeSize(var.asType().getKind()) : an.value();
//obtain type which will be stored
Class<?> structType = typeForSize(structSize);
MethodSpec.Builder getter = MethodSpec.methodBuilder(var.getSimpleName().toString());
MethodSpec.Builder setter = MethodSpec.methodBuilder(var.getSimpleName().toString());
//[constructor] get(fields...) : structType
MethodSpec.Builder constructor = MethodSpec.methodBuilder("get") //TODO 'get'..?
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(structType);
StringBuilder cons = new StringBuilder();
StringBuilder doc = new StringBuilder();
doc.append("Bits used: ").append(structSize).append(" / ").append(structTotalSize).append("\n");
int offset = 0;
for(VariableElement var : variables){
int size = varSize(var);
TypeName varType = TypeName.get(var.asType());
String varName = var.getSimpleName().toString();
//add val param to constructor
constructor.addParameter(varType, varName);
//[get] field(structType) : fieldType
MethodSpec.Builder getter = MethodSpec.methodBuilder(var.getSimpleName().toString())
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(varType)
.addParameter(structType, structParam);
//[set] field(structType, fieldType) : structType
MethodSpec.Builder setter = MethodSpec.methodBuilder(var.getSimpleName().toString())
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(structType)
.addParameter(structType, structParam).addParameter(varType, "value");
//[getter]
if(varType == TypeName.BOOLEAN){
//bools: single bit, is simplified
getter.addStatement("return ($L & (1L << $L)) != 0", structParam, offset);
}else if(varType == TypeName.FLOAT){
//floats: need conversion
getter.addStatement("return Float.intBitsToFloat((int)(($L >> $L) & $L))", structParam, offset, bitString(size, structTotalSize));
}else{
//bytes, shorts, chars, ints
getter.addStatement("return ($T)(($L >> $L) & $L)", varType, structParam, offset, bitString(size, structTotalSize));
}
//[setter] + [constructor building]
if(varType == TypeName.BOOLEAN){
cons.append(" | (").append(varName).append(" ? ").append("1L << ").append(offset).append("L : 0)");
//bools: single bit, needs special case to clear things
setter.beginControlFlow("if(value)");
setter.addStatement("return ($T)(($L & ~(1L << $LL)))", structType, structParam, offset);
setter.nextControlFlow("else");
setter.addStatement("return ($T)(($L & ~(1L << $LL)) | (1L << $LL))", structType, structParam, offset, offset);
setter.endControlFlow();
}else if(varType == TypeName.FLOAT){
cons.append(" | (").append("(").append(structType).append(")").append("Float.floatToIntBits(").append(varName).append(") << ").append(offset).append("L)");
//floats: need conversion
setter.addStatement("return ($T)(($L & $L) | (($T)Float.floatToIntBits(value) << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset);
}else{
cons.append(" | (").append("(").append(structType).append(")").append(varName).append(" << ").append(offset).append("L)");
//bytes, shorts, chars, ints
setter.addStatement("return ($T)(($L & $L) | (($T)value << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset);
}
doc.append("<br> ").append(varName).append(" [").append(offset).append("..").append(size + offset).append("]\n");
//add finished methods
classBuilder.addMethod(getter.build());
classBuilder.addMethod(setter.build());
offset += size;
}
classBuilder.addJavadoc(doc.toString());
//add constructor final statement + add to class and build
constructor.addStatement("return ($T)($L)", structType, cons.toString().substring(3));
classBuilder.addMethod(constructor.build());
JavaFile.builder(packageName, classBuilder.build()).build().writeTo(Utils.filer);
}catch(IllegalArgumentException e){
e.printStackTrace();
Utils.messager.printMessage(Kind.ERROR, e.getMessage(), elem);
}
JavaFile.builder(packageName, classBuilder.build()).build().writeTo(Utils.filer);
}
return true;
@ -78,8 +164,53 @@ public class StructAnnotationProcessor extends AbstractProcessor{
}
}
static String bitString(int offset, int size, int totalSize){
StringBuilder builder = new StringBuilder();
for(int i = 0; i < offset; i++) builder.append('0');
for(int i = 0; i < size; i++) builder.append('1');
for(int i = 0; i < totalSize - size - offset; i++) builder.append('0');
return "0b" + builder.reverse().toString() + "L";
}
static String bitString(int size, int totalSize){
StringBuilder builder = new StringBuilder();
for(int i = 0; i < size; i++) builder.append('1');
for(int i = 0; i < totalSize - size; i++) builder.append('0');
return "0b" + builder.reverse().toString() + "L";
}
static int varSize(VariableElement var) throws IllegalArgumentException{
if(!var.asType().getKind().isPrimitive()){
throw new IllegalArgumentException("All struct fields must be primitives: " + var);
}
StructField an = var.getAnnotation(StructField.class);
if(var.asType().getKind() == TypeKind.BOOLEAN && an != null && an.value() != 1){
throw new IllegalArgumentException("Booleans can only be one bit long... why would you do this?");
}
if(var.asType().getKind() == TypeKind.FLOAT && an != null && an.value() != 32){
throw new IllegalArgumentException("Float size can't be changed. Very sad.");
}
return an == null ? typeSize(var.asType().getKind()) : an.value();
}
static Class<?> typeForSize(int size) throws IllegalArgumentException{
if(size <= 8){
return byte.class;
}else if(size <= 16){
return short.class;
}else if(size <= 32){
return int.class;
}else if(size <= 64){
return long.class;
}
throw new IllegalArgumentException("Too many fields, must fit in 64 bits. Curent size: " + size);
}
/**returns a type's element size in bits.*/
static int typeSize(TypeKind kind){
static int typeSize(TypeKind kind) throws IllegalArgumentException{
switch(kind){
case BOOLEAN:
return 1;
@ -91,11 +222,8 @@ public class StructAnnotationProcessor extends AbstractProcessor{
case CHAR:
case INT:
return 32;
case LONG:
case DOUBLE:
return 64;
default:
throw new IllegalArgumentException("Invalid type kind: " + kind);
throw new IllegalArgumentException("Invalid type kind: " + kind + ". Note that doubles and longs are not supported.");
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 KiB

After

Width:  |  Height:  |  Size: 1013 KiB

View File

@ -1,7 +1,9 @@
package io.anuke.mindustry.content;
import io.anuke.arc.Core;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.graphics.CacheLayer;
import io.anuke.mindustry.type.Category;
@ -32,8 +34,9 @@ public class Blocks implements ContentList{
public static Block
//environment
air, part, spawn, space, metalfloor, deepwater, water, tar, stone, craters, charr, blackstone, dirt, sand, ice, snow, iceSnow,
grass, holostone, holostoneSnow, shrub, rock, icerock, blackrock, rocks, icerocks, cliffs, pine, whiteTree, sporeCluster,
air, part, spawn, deepwater, water, tar, stone, craters, charr, sand, ice, snow,
grass, holostone, rocks, icerocks, cliffs, pine, whiteTree, whiteTreeDead, sporeCluster,
iceSnow,
//crafting
siliconSmelter, graphitePress, plastaniumCompressor, multiPress, phaseWeaver, surgeSmelter, pyratiteMixer, blastMixer, cryofluidMixer,
@ -83,6 +86,12 @@ public class Blocks implements ContentList{
public void draw(Tile tile){}
public void load(){}
public void init(){}
public TextureRegion[] variantRegions(){
if(variantRegions == null){
variantRegions = new TextureRegion[]{Core.atlas.find("clear")};
}
return variantRegions;
}
};
part = new BlockPart();
@ -97,18 +106,6 @@ public class Blocks implements ContentList{
new BuildBlock("build" + i);
}
space = new Floor("space"){{
placeableOn = false;
variants = 0;
cacheLayer = CacheLayer.space;
solid = true;
minimapColor = Color.valueOf("000001");
}};
metalfloor = new Floor("metalfloor"){{
variants = 6;
}};
deepwater = new Floor("deepwater"){{
liquidColor = Color.valueOf("546bb3");
speedMultiplier = 0.2f;
@ -161,16 +158,6 @@ public class Blocks implements ContentList{
minimapColor = Color.valueOf("323232");
}};
blackstone = new Floor("blackstone"){{
minimapColor = Color.valueOf("252525");
playerUnmineable = true;
hasOres = true;
}};
dirt = new Floor("dirt"){{
minimapColor = Color.valueOf("6e501e");
}};
sand = new Floor("sand"){{
itemDrop = Items.sand;
minimapColor = Color.valueOf("988a67");
@ -184,13 +171,12 @@ public class Blocks implements ContentList{
minimapColor = Color.valueOf("b8eef8");
}};
snow = new Floor("snow"){{
minimapColor = Color.valueOf("c2d1d2");
holostone = new Floor("holostone"){{
hasOres = true;
}};
iceSnow = new Floor("ice-snow"){{
snow = new Floor("snow"){{
minimapColor = Color.valueOf("c2d1d2");
variants = 3;
}};
grass = new Floor("grass"){{
@ -198,8 +184,6 @@ public class Blocks implements ContentList{
minimapColor = Color.valueOf("549d5b");
}};
shrub = new Rock("shrub");
cliffs = new StaticWall("cliffs"){{
variants = 1;
fillsTile = false;
@ -218,19 +202,18 @@ public class Blocks implements ContentList{
variants = 0;
}};
whiteTree = new TreeBlock("white-tree-dead"){{
whiteTreeDead = new TreeBlock("white-tree-dead"){{
}};
whiteTree = new TreeBlock("white-tree"){{
}};
sporeCluster = new Rock("spore-cluster"){{
variants = 3;
}};
holostone = new Floor("holostone"){{
hasOres = true;
}};
holostoneSnow = new Floor("holostone-snow"){{
iceSnow = new Floor("iceSnow"){{
variants = 3;
}};
//endregion

View File

@ -167,7 +167,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.beginEdit(data, meta.tags, false);
view.clearStack();
}catch(Exception e){
ui.showError(Core.bundle.format("editor.errormapload", Strings.parseException(e, false)));
ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false)));
Log.err(e);
}
}));

View File

@ -201,7 +201,7 @@ public class FloorRenderer{
int chunksx = Mathf.ceil((float) (world.width()) / chunksize),
chunksy = Mathf.ceil((float) (world.height()) / chunksize) ;
cache = new Chunk[chunksx][chunksy];
SpriteCache sprites = new SpriteCache(world.width() * world.height() * 3, (world.width() / chunksize) * (world.height() / chunksize) * 2, false);
SpriteCache sprites = new SpriteCache(world.width() * world.height() * 5, (world.width() / chunksize) * (world.height() / chunksize) * 2, false);
cbatch = new CacheBatch(sprites);
Time.mark();

View File

@ -13,6 +13,7 @@ import io.anuke.mindustry.maps.MapTileData.TileDataMarker;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.OreBlock;
import io.anuke.mindustry.world.blocks.StaticWall;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import static io.anuke.mindustry.Vars.*;
@ -89,7 +90,8 @@ public class MapGenerator extends Generator{
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, data.width()-1);
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, data.height()-1);
if(tiles[newX][newY].block() != Blocks.spawn && !tile.block().synthetic()&& !tiles[newX][newY].block().synthetic()){
if(tile.block() instanceof StaticWall
&& tiles[newX][newY].block() instanceof StaticWall){
tile.setBlock(tiles[newX][newY].block());
}

View File

@ -115,7 +115,7 @@ public class Block extends BlockStorage{
protected Array<Tile> tempTiles = new Array<>();
protected TextureRegion[] icons = new TextureRegion[Icon.values().length];
protected TextureRegion[] generatedIcons;
protected TextureRegion[] variants;
protected TextureRegion[] variantRegions;
protected TextureRegion region;
public Block(String name){
@ -503,10 +503,10 @@ public class Block extends BlockStorage{
}
public TextureRegion[] variantRegions(){
if(variants == null){
variants = new TextureRegion[]{icon(Icon.full)};
if(variantRegions == null){
variantRegions = new TextureRegion[]{icon(Icon.full)};
}
return variants;
return variantRegions;
}
public boolean hasEntity(){

View File

@ -20,7 +20,8 @@ public class LegacyColorMapper implements ContentList{
public void load(){
defaultValue = new LegacyBlock(Blocks.stone, Blocks.air);
map("ff0000", Blocks.dirt, 0);
//TODO remap colors later
// map("ff0000", Blocks.dirt, 0);
map("00ff00", Blocks.stone, 0);
map("323232", Blocks.stone, 0);
map("646464", Blocks.stone, 1);
@ -28,15 +29,15 @@ public class LegacyColorMapper implements ContentList{
map("5ab464", Blocks.grass, 1);
map("506eb4", Blocks.water, 0);
map("465a96", Blocks.deepwater, 0);
map("252525", Blocks.blackstone, 0);
map("575757", Blocks.blackstone, 1);
//map("252525", Blocks.blackstone, 0);
//map("575757", Blocks.blackstone, 1);
map("988a67", Blocks.sand, 0);
map("e5d8bb", Blocks.sand, 1);
map("c2d1d2", Blocks.snow, 0);
map("c4e3e7", Blocks.ice, 0);
map("f7feff", Blocks.snow, 1);
map("6e501e", Blocks.dirt, 0);
map("ed5334", Blocks.blackstone, 0);
//map("6e501e", Blocks.dirt, 0);
//map("ed5334", Blocks.blackstone, 0);
map("292929", Blocks.tar, 0);
map("c3a490", OreBlock.get(Blocks.stone, Items.copper), 0);
map("161616", OreBlock.get(Blocks.stone, Items.coal), 0);

View File

@ -6,6 +6,8 @@ import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.Draw;
import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.content.StatusEffects;
import io.anuke.mindustry.type.Item;
@ -51,8 +53,9 @@ public class Floor extends Block{
public float heat = 0f;
/** if true, this block cannot be mined by players. useful for annoying things like sand. */
public boolean playerUnmineable = false;
protected TextureRegion[] regions;
protected TextureRegion[][] edges;
protected byte eq = 0;
public Floor(String name){
super(name);
@ -65,24 +68,26 @@ public class Floor extends Block{
//load variant regions for drawing
if(variants > 0){
regions = new TextureRegion[variants];
variantRegions = new TextureRegion[variants];
for(int i = 0; i < variants; i++){
regions[i] = Core.atlas.find(name + (i + 1));
variantRegions[i] = Core.atlas.find(name + (i + 1));
}
}else{
regions = new TextureRegion[1];
regions[0] = Core.atlas.find(name);
variantRegions = new TextureRegion[1];
variantRegions[0] = Core.atlas.find(name);
}
int size = (int)(tilesize / Draw.scl);
edges = Core.atlas.find(name + "-edge").split(size, size);
region = regions[0];
if(Core.atlas.has(name + "-edge")){
edges = Core.atlas.find(name + "-edge").split(size, size);
}
region = variantRegions[0];
}
@Override
public TextureRegion[] variantRegions(){
return regions;
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(Core.atlas.has(name) ? name : name + "1")};
}
@Override
@ -98,12 +103,57 @@ public class Floor extends Block{
public void draw(Tile tile){
Mathf.random.setSeed(tile.pos());
Draw.rect(regions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, regions.length - 1))], tile.worldx(), tile.worldy());
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
drawEdges(tile);
}
@Override
public TextureRegion[] generateIcons(){
return new TextureRegion[]{Core.atlas.find(Core.atlas.has(name) ? name : name + "1")};
protected void drawEdges(Tile tile){
eq = 0;
Floor floor = tile.floor();
for(int i = 0; i < 8; i++){
Point2 point = Geometry.d8[i];
Tile other = tile.getNearby(point);
if(other != null && other.floor().id < floor.id && other.floor().edges != null){
eq |= (1 << i);
}
}
for(int i = 0; i < 8; i++){
if(eq(i)){
Point2 point = Geometry.d8[i];
Tile other = tile.getNearby(point);
TextureRegion region = edge(other.floor(), type(i), 2-(point.x + 1), 2-(point.y + 1));
Draw.rect(region, tile.worldx(), tile.worldy());
}
}
}
int type(int i){
if(!eq(i - 1) && !eq(i + 1)){
//case 0: touching
return 0;
}else if(eq(i - 1) && eq(i - 2) && eq(i + 1) && eq(i + 2)){
//case 2: surrounded
return 2;
}else if(eq(i - 1) && eq(i + 1)){
//case 1: flat
return 1;
}else{
//case 0 is rounded, so it's the safest choice, should work for most possibilities
return 0;
}
}
boolean eq(int i){
return (eq & (1 << Mathf.mod(i, 8))) != 0;
}
TextureRegion edge(Floor block, int type, int x, int y){
return block.edges[x + type*3][2-y];
}
}

View File

@ -31,7 +31,7 @@ public class OreBlock extends Floor{
@Override
public void draw(Tile tile){
Draw.rect(regions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, regions.length - 1))], tile.worldx(), tile.worldy());
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}
public static Block get(Block floor, Item item){

View File

@ -9,6 +9,7 @@ import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Mech;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock;
import static io.anuke.mindustry.Vars.content;
@ -143,6 +144,31 @@ public class Generators {
}
}
});
ImagePacker.generate("edges", () -> {
for(Block block : content.blocks()){
if(!(block instanceof Floor)) continue;
Floor floor = (Floor)block;
if(ImagePacker.has(floor.name + "-edge")){
continue;
}
try{
Image image = ImagePacker.get(floor.generateIcons()[0]);
Image edge = ImagePacker.get("edge-stencil");
for(int x = 0; x < edge.width(); x++){
for(int y = 0; y < edge.height(); y++){
edge.draw(x, y, edge.getColor(x, y).mul(image.getColor(x % image.width(), y % image.height())));
}
}
edge.save(floor.name + "-edge");
}catch(Exception ignored){}
}
});
}
}

View File

@ -111,6 +111,10 @@ public class ImagePacker{
return get(Core.atlas.find(name));
}
static boolean has(String name){
return Core.atlas.has(name);
}
static Image get(TextureRegion region){
GenRegion.validate(region);