Generate tool bugfixes / Map edits / New blocks

This commit is contained in:
Anuken
2019-03-17 18:30:33 -04:00
parent 6dcf889ffe
commit 783f453da3
21 changed files with 1176 additions and 1056 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -250,6 +250,7 @@ filter.rivernoise = River Noise
filter.scatter = Scatter filter.scatter = Scatter
filter.terrain = Terrain filter.terrain = Terrain
filter.option.scale = Scale filter.option.scale = Scale
filter.option.chance = Chance
filter.option.mag = Magnitude filter.option.mag = Magnitude
filter.option.threshold = Threshold filter.option.threshold = Threshold
filter.option.circle-scale = Circle Scale filter.option.circle-scale = Circle Scale
@@ -258,6 +259,8 @@ filter.option.falloff = Falloff
filter.option.floor = Floor filter.option.floor = Floor
filter.option.wall = Wall filter.option.wall = Wall
filter.option.ore = Ore filter.option.ore = Ore
filter.option.floor2 = Secondary Floor
filter.option.threshold2 = Secondary Threshold
width = Width: width = Width:
height = Height: height = Height:
menu = Menu menu = Menu
@@ -340,6 +343,7 @@ blocks.poweroutput = Power Output: {0}
blocks.powercapacity = Power Capacity blocks.powercapacity = Power Capacity
blocks.powershot = Power/Shot blocks.powershot = Power/Shot
blocks.targetsair = Targets Air blocks.targetsair = Targets Air
blocks.targetsground = Targets Ground
blocks.items = Items: {0} blocks.items = Items: {0}
blocks.itemsmoved = Move Speed blocks.itemsmoved = Move Speed
blocks.shootrange = Range blocks.shootrange = Range
@@ -563,6 +567,8 @@ block.grass.name = Grass
block.salt.name = Salt block.salt.name = Salt
block.sandrocks.name = Sand Rocks block.sandrocks.name = Sand Rocks
block.spore-pine.name = Spore Pine block.spore-pine.name = Spore Pine
block.sporerocks.name = Spore Rocks
block.rock.name = Rock
block.shale.name = Shale block.shale.name = Shale
block.shale-boulder.name = Shale Boulder block.shale-boulder.name = Shale Boulder
block.moss.name = Moss block.moss.name = Moss

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 500 B

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -66,6 +66,8 @@ public class Vars{
public static final float finalWorldBounds = worldBounds + 500; public static final float finalWorldBounds = worldBounds + 500;
/**ticks spent out of bound until self destruct.*/ /**ticks spent out of bound until self destruct.*/
public static final float boundsCountdown = 60*7; public static final float boundsCountdown = 60*7;
/**for map generator dialog*/
public static boolean updateEditorOnChange = false;
/**size of tiles in units*/ /**size of tiles in units*/
public static final int tilesize = 8; public static final int tilesize = 8;
/**all choosable player colors in join/host dialog*/ /**all choosable player colors in join/host dialog*/

View File

@@ -36,9 +36,9 @@ public class Blocks implements ContentList{
//environment //environment
air, part, spawn, deepwater, water, tar, stone, craters, charr, sand, ice, snow, air, part, spawn, deepwater, water, tar, stone, craters, charr, sand, ice, snow,
holostone, rocks, icerocks, cliffs, sporePine, pine, whiteTree, whiteTreeDead, sporeCluster, holostone, rocks, sporerocks, icerocks, cliffs, sporePine, pine, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, grass, salt, iceSnow, sandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock,
//ores //ores
oreCopper, oreLead, oreScrap, oreCoal, oreTitanium, oreThorium, oreCopper, oreLead, oreScrap, oreCoal, oreTitanium, oreThorium,
@@ -214,6 +214,14 @@ public class Blocks implements ContentList{
variants = 2; variants = 2;
}}; }};
sporerocks = new StaticWall("sporerocks"){{
variants = 2;
}};
rock = new Rock("rock"){{
variants = 2;
}};
icerocks = new StaticWall("icerocks"){{ icerocks = new StaticWall("icerocks"){{
variants = 2; variants = 2;
}}; }};
@@ -694,7 +702,7 @@ public class Blocks implements ContentList{
}}; }};
junction = new Junction("junction"){{ junction = new Junction("junction"){{
requirements(Category.distribution, ItemStack.with(Items.copper, 3)); requirements(Category.distribution, ItemStack.with(Items.copper, 3), true);
speed = 26; speed = 26;
capacity = 32; capacity = 32;
health = 25; health = 25;
@@ -1067,20 +1075,21 @@ public class Blocks implements ContentList{
}}; }};
scatter = new BurstTurret("scatter"){{ scatter = new BurstTurret("scatter"){{
requirements(Category.turret, ItemStack.with(Items.copper, 170, Items.lead, 90), true); requirements(Category.turret, ItemStack.with(Items.copper, 170, Items.lead, 90));
ammo( ammo(
Items.scrap, Bullets.flakScrap, Items.scrap, Bullets.flakScrap,
Items.lead, Bullets.flakLead Items.lead, Bullets.flakLead
); );
reload = 45f; reload = 43f;
range = 160f; range = 160f;
size = 2; size = 2;
burstSpacing = 5f; burstSpacing = 5f;
shots = 2; shots = 2;
targetGround = false;
recoil = 2f; recoil = 2f;
rotatespeed = 10f; rotatespeed = 10f;
inaccuracy = 18f; inaccuracy = 17f;
shootCone = 35f; shootCone = 35f;
health = 220*size*size; health = 220*size*size;

View File

@@ -6,10 +6,12 @@ import io.anuke.arc.function.Supplier;
import io.anuke.arc.graphics.Pixmap; import io.anuke.arc.graphics.Pixmap;
import io.anuke.arc.graphics.Pixmap.Format; import io.anuke.arc.graphics.Pixmap.Format;
import io.anuke.arc.graphics.Texture; import io.anuke.arc.graphics.Texture;
import io.anuke.arc.scene.ui.Image;
import io.anuke.arc.scene.ui.layout.Stack;
import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Scaling;
import io.anuke.arc.util.async.AsyncExecutor; import io.anuke.arc.util.async.AsyncExecutor;
import io.anuke.arc.util.async.AsyncResult; import io.anuke.arc.util.async.AsyncResult;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.editor.generation.*; import io.anuke.mindustry.editor.generation.*;
import io.anuke.mindustry.editor.generation.GenerateFilter.GenerateInput; import io.anuke.mindustry.editor.generation.GenerateFilter.GenerateInput;
import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Team;
@@ -21,7 +23,7 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor; import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.mobile; import static io.anuke.mindustry.Vars.*;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class MapGenerateDialog extends FloatingDialog{ public class MapGenerateDialog extends FloatingDialog{
@@ -48,8 +50,10 @@ public class MapGenerateDialog extends FloatingDialog{
shown(this::setup); shown(this::setup);
addCloseButton(); addCloseButton();
buttons.addButton("$editor.apply", () -> { buttons.addButton("$editor.apply", () -> {
apply(); ui.loadAnd(() -> {
hide(); apply();
hide();
});
}).size(160f, 64f); }).size(160f, 64f);
buttons.addButton("$editor.randomize", () -> { buttons.addButton("$editor.randomize", () -> {
for(GenerateFilter filter : filters){ for(GenerateFilter filter : filters){
@@ -75,15 +79,20 @@ public class MapGenerateDialog extends FloatingDialog{
cont.clear(); cont.clear();
cont.table("flat", t -> { cont.table("flat", t -> {
t.margin(8f); t.margin(8f);
t.add(new BorderImage(texture)).size(400f).padRight(6); t.stack(new BorderImage(texture), new Stack(){{
add(new Image("loadDim"));
add(new Image("icon-refresh"){{
setScaling(Scaling.none);
}});
visible(() -> generating);
}}).size(mobile ? 300f : 400f).padRight(6);
t.pane(p -> filterTable = p).width(300f).get().setScrollingDisabled(true, false); t.pane(p -> filterTable = p).width(300f).get().setScrollingDisabled(true, false);
}).grow(); }).grow();
update();
buffer1 = create(); buffer1 = create();
buffer2 = create(); buffer2 = create();
update();
rebuildFilters(); rebuildFilters();
} }
@@ -186,6 +195,16 @@ public class MapGenerateDialog extends FloatingDialog{
result.get(); result.get();
} }
buffer1 = null;
buffer2 = null;
generating = false;
if(pixmap != null){
pixmap.dispose();
texture.dispose();
pixmap = null;
texture = null;
}
//writeback buffer //writeback buffer
DummyTile[][] writeTiles = new DummyTile[editor.width()][editor.height()]; DummyTile[][] writeTiles = new DummyTile[editor.width()][editor.height()];
@@ -196,7 +215,7 @@ public class MapGenerateDialog extends FloatingDialog{
} }
for(GenerateFilter filter : filters){ for(GenerateFilter filter : filters){
input.setFilter(filter, (x, y) -> dset(editor.tile(x, y))); input.setFilter(filter, editor.width(), editor.height(), 1, (x, y) -> dset(editor.tile(x, y)));
//write to buffer //write to buffer
for(int x = 0; x < editor.width(); x++){ for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){ for(int y = 0; y < editor.height(); y++){
@@ -215,10 +234,10 @@ public class MapGenerateDialog extends FloatingDialog{
DummyTile write = writeTiles[x][y]; DummyTile write = writeTiles[x][y];
tile.setRotation((byte)write.rotation); tile.setRotation((byte)write.rotation);
tile.setFloor((Floor)write.floor); tile.setFloor((Floor)content.block(write.floor));
tile.setBlock(write.block); tile.setBlock(content.block(write.block));
tile.setTeam(write.team); tile.setTeam(Team.all[write.team]);
tile.setOre(write.ore); tile.setOre(content.block(write.ore));
} }
} }
}); });
@@ -239,74 +258,81 @@ public class MapGenerateDialog extends FloatingDialog{
Array<GenerateFilter> copy = new Array<>(filters); Array<GenerateFilter> copy = new Array<>(filters);
result = executor.submit(() -> { result = executor.submit(() -> {
generating = true; try{
generating = true;
if(!filters.isEmpty()){
//write to buffer1 for reading
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(editor.tile(px * scaling, py * scaling));
}
}
}
for(GenerateFilter filter : copy){
input.setFilter(filter, pixmap.getWidth(), pixmap.getHeight(), scaling, (x, y) -> buffer1[x][y]);
//read from buffer1 and write to buffer2
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int x = px * scaling, y = py * scaling;
DummyTile tile = buffer1[px][py];
input.begin(editor, x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, Team.all[tile.team], tile.rotation);
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(buffer2[px][py]);
}
}
}
if(!filters.isEmpty()){
//write to buffer1 for reading
for(int px = 0; px < pixmap.getWidth(); px++){ for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){ for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(editor.tile(px * scaling, py * scaling)); int color;
//get result from buffer1 if there's filters left, otherwise get from editor directly
if(filters.isEmpty()){
Tile tile = editor.tile(px * scaling, py * scaling);
color = MapIO.colorFor(tile.floor(), tile.block(), tile.ore(), Team.none);
}else{
DummyTile tile = buffer1[px][py];
color = MapIO.colorFor(content.block(tile.floor), content.block(tile.block), content.block(tile.ore), Team.none);
}
pixmap.drawPixel(px, pixmap.getHeight() - 1 - py, color);
} }
} }
}
for(GenerateFilter filter : copy){ Core.app.post(() -> {
input.setFilter(filter, (x, y) -> buffer1[x][y]); texture.draw(pixmap, 0, 0);
//read from buffer1 and write to buffer2 generating = false;
for(int px = 0; px < pixmap.getWidth(); px++){ });
for(int py = 0; py < pixmap.getHeight(); py++){ }catch(Exception e){
int x = px*scaling, y = py*scaling;
DummyTile tile = buffer1[px][py];
input.begin(editor, x, y, tile.floor, tile.block, tile.ore);
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, tile.team, tile.rotation);
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(buffer2[px][py]);
}
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int color;
//get result from buffer1 if there's filters left, otherwise get from editor directly
if(filters.isEmpty()){
Tile tile = editor.tile(px * scaling, py * scaling);
color = MapIO.colorFor(tile.floor(), tile.block(), tile.ore(), Team.none);
}else{
DummyTile tile = buffer1[px][py];
color = MapIO.colorFor(tile.floor, tile.block, tile.ore, Team.none);
}
pixmap.drawPixel(px, pixmap.getHeight() - 1 - py, color);
}
}
Core.app.post(() -> {
texture.draw(pixmap, 0, 0);
generating = false; generating = false;
}); e.printStackTrace();
}
return null; return null;
}); });
} }
public static class DummyTile{ public static class DummyTile{
public Block block = Blocks.air, ore = Blocks.air, floor = Blocks.air; public byte block, floor, ore, team, rotation;
public Team team = Team.none;
public int rotation;
void set(Block floor, Block wall, Block ore, Team team, int rotation){ void set(Block floor, Block wall, Block ore, Team team, int rotation){
this.floor = floor; this.floor = floor.id;
this.block = wall; this.block = wall.id;
this.ore = ore; this.ore = ore.id;
this.team = team; this.team = (byte)team.ordinal();
this.rotation = rotation; this.rotation = (byte)rotation;
} }
void set(DummyTile other){ void set(DummyTile other){
set(other.floor, other.block, other.ore, other.team, other.rotation); this.floor = other.floor;
this.block = other.block;
this.ore = other.ore;
this.team = other.team;
this.rotation = other.rotation;
} }
void set(Tile other){ void set(Tile other){

View File

@@ -267,7 +267,9 @@ public class MapView extends Element implements GestureListener{
Draw.color(Pal.remove); Draw.color(Pal.remove);
Lines.stroke(2f); Lines.stroke(2f);
Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2); Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2);
editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight); if(Core.scene.getKeyboardFocus() != null && isDescendantOf(Core.scene.getKeyboardFocus())){
editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
}
Draw.reset(); Draw.reset();
if(!ScissorStack.pushScissors(rect.set(x, y, width, height))){ if(!ScissorStack.pushScissors(rect.set(x, y, width, height))){

View File

@@ -4,6 +4,8 @@ import io.anuke.mindustry.editor.MapGenerateDialog.DummyTile;
import io.anuke.mindustry.editor.generation.FilterOption.SliderOption; import io.anuke.mindustry.editor.generation.FilterOption.SliderOption;
import io.anuke.mindustry.world.blocks.Floor; import io.anuke.mindustry.world.blocks.Floor;
import static io.anuke.mindustry.Vars.content;
public class DistortFilter extends GenerateFilter{ public class DistortFilter extends GenerateFilter{
float scl = 40, mag = 5; float scl = 40, mag = 5;
@@ -16,10 +18,10 @@ public class DistortFilter extends GenerateFilter{
@Override @Override
public void apply(){ public void apply(){
DummyTile tile = in.tile(in.x + noise(in.x, in.y, scl, mag)-mag/2f, in.y + noise(in.x, in.y+o, scl, mag)-mag/2f); DummyTile tile = in.tile(in.x / (in.scaling) + (noise(in.x, in.y, scl, mag)-mag/2f)/in.scaling, in.y / (in.scaling) + (noise(in.x, in.y+o, scl, mag)-mag/2f)/in.scaling);
in.floor = tile.floor; in.floor = content.block(tile.floor);
if(!tile.block.synthetic() && !in.block.synthetic()) in.block = tile.block; if(!content.block(tile.block).synthetic() && !in.block.synthetic()) in.block = content.block(tile.block);
if(!((Floor)in.floor).isLiquid) in.ore = tile.ore; if(!((Floor)in.floor).isLiquid) in.ore = content.block(tile.ore);
} }
} }

View File

@@ -12,6 +12,8 @@ import io.anuke.mindustry.world.Block.Icon;
import io.anuke.mindustry.world.blocks.Floor; import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.OreBlock; import io.anuke.mindustry.world.blocks.OreBlock;
import static io.anuke.mindustry.Vars.updateEditorOnChange;
public abstract class FilterOption{ public abstract class FilterOption{
public static final Predicate<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OreBlock)) && Core.atlas.isFound(b.icon(Icon.full)); public static final Predicate<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OreBlock)) && Core.atlas.isFound(b.icon(Icon.full));
public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full)); public static final Predicate<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(Icon.full));
@@ -40,7 +42,11 @@ public abstract class FilterOption{
table.row(); table.row();
Slider slider = table.addSlider(min, max, (max-min)/200f, setter).growX().get(); Slider slider = table.addSlider(min, max, (max-min)/200f, setter).growX().get();
slider.setValue(getter.get()); slider.setValue(getter.get());
slider.changed(changed); if(updateEditorOnChange){
slider.changed(changed);
}else{
slider.released(changed);
}
} }
} }

View File

@@ -56,7 +56,7 @@ public abstract class GenerateFilter{
public Floor srcfloor; public Floor srcfloor;
public Block srcblock; public Block srcblock;
public Block srcore; public Block srcore;
public int x, y; public int x, y, width, height, scaling;
public MapEditor editor; public MapEditor editor;
public Block floor, block, ore; public Block floor, block, ore;
@@ -74,14 +74,17 @@ public abstract class GenerateFilter{
this.y = y; this.y = y;
} }
public void setFilter(GenerateFilter filter, TileProvider buffer){ public void setFilter(GenerateFilter filter, int width, int height, int scaling, TileProvider buffer){
this.buffer = buffer; this.buffer = buffer;
this.width = width;
this.height = height;
this.scaling = scaling;
noise.setSeed(filter.seed); noise.setSeed(filter.seed);
pnoise.setSeed((int)(filter.seed + 1)); pnoise.setSeed((int)(filter.seed + 1));
} }
DummyTile tile(float x, float y){ DummyTile tile(float x, float y){
return buffer.get(Mathf.clamp((int)x, 0, editor.width() - 1), Mathf.clamp((int)y, 0, editor.height() - 1)); return buffer.get(Mathf.clamp((int)x, 0, width - 1), Mathf.clamp((int)y, 0, height - 1));
} }
public interface TileProvider{ public interface TileProvider{

View File

@@ -32,20 +32,18 @@ public abstract class FlyingUnit extends BaseUnit{
target = null; target = null;
} }
if(target == null){ retarget(() -> {
targetClosest();
retarget(() -> { if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
targetClosest(); if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
if(target == null) targetClosestEnemyFlag(BlockFlag.producer); if(target == null){
if(target == null) targetClosestEnemyFlag(BlockFlag.turret); setState(patrol);
}
});
if(target == null){ if(target != null){
setState(patrol);
}
});
}else{
attack(type.attackLength); attack(type.attackLength);
if((Angles.near(angleTo(target), rotation, type.shootCone) || getWeapon().ignoreRotation) //bombers and such don't care about rotation if((Angles.near(angleTo(target), rotation, type.shootCone) || getWeapon().ignoreRotation) //bombers and such don't care about rotation

View File

@@ -58,6 +58,7 @@ public abstract class Turret extends Block{
protected float shootShake = 0f; protected float shootShake = 0f;
protected float xRand = 0f; protected float xRand = 0f;
protected boolean targetAir = true; protected boolean targetAir = true;
protected boolean targetGround = true;
protected Vector2 tr = new Vector2(); protected Vector2 tr = new Vector2();
protected Vector2 tr2 = new Vector2(); protected Vector2 tr2 = new Vector2();
@@ -108,6 +109,7 @@ public abstract class Turret extends Block{
stats.add(BlockStat.reload, 60f / reload, StatUnit.none); stats.add(BlockStat.reload, 60f / reload, StatUnit.none);
stats.add(BlockStat.shots, shots, StatUnit.none); stats.add(BlockStat.shots, shots, StatUnit.none);
stats.add(BlockStat.targetsAir, targetAir); stats.add(BlockStat.targetsAir, targetAir);
stats.add(BlockStat.targetsGround, targetGround);
} }
@Override @Override
@@ -201,7 +203,7 @@ public abstract class Turret extends Block{
TurretEntity entity = tile.entity(); TurretEntity entity = tile.entity();
entity.target = Units.getClosestTarget(tile.getTeam(), entity.target = Units.getClosestTarget(tile.getTeam(),
tile.drawx(), tile.drawy(), range, e -> !e.isDead() && (!e.isFlying() || targetAir)); tile.drawx(), tile.drawy(), range, e -> !e.isDead() && (!e.isFlying() || targetAir) && (e.isFlying() || targetGround));
} }
protected void turnToTarget(Tile tile, float targetRot){ protected void turnToTarget(Tile tile, float targetRot){

View File

@@ -47,6 +47,7 @@ public enum BlockStat{
reload(StatCategory.shooting), reload(StatCategory.shooting),
powerShot(StatCategory.shooting), powerShot(StatCategory.shooting),
targetsAir(StatCategory.shooting), targetsAir(StatCategory.shooting),
targetsGround(StatCategory.shooting),
boostItem(StatCategory.optional), boostItem(StatCategory.optional),
boostLiquid(StatCategory.optional),; boostLiquid(StatCategory.optional),;