Auto-rebuilding enemy drones / GC improvements
This commit is contained in:
Binary file not shown.
@@ -792,8 +792,9 @@ public class Fx implements ContentList{
|
|||||||
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
|
Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin());
|
||||||
|
|
||||||
for(int i : Mathf.signs){
|
for(int i : Mathf.signs){
|
||||||
|
float ex = e.x, ey = e.y, fout = e.fout();
|
||||||
Angles.randLenVectors(e.id, 4, 1f + e.finpow() * 11f, e.rotation + 90f * i, 20f, (x, y) -> {
|
Angles.randLenVectors(e.id, 4, 1f + e.finpow() * 11f, e.rotation + 90f * i, 20f, (x, y) -> {
|
||||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f);
|
Fill.circle(ex + x, ey + y, fout * 1.5f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,8 +817,9 @@ public class Fx implements ContentList{
|
|||||||
Draw.color(Color.LIGHT_GRAY);
|
Draw.color(Color.LIGHT_GRAY);
|
||||||
|
|
||||||
for(int i : Mathf.signs){
|
for(int i : Mathf.signs){
|
||||||
|
float ex = e.x, ey = e.y, fout = e.fout();
|
||||||
Angles.randLenVectors(e.id, 4, -e.finpow() * 15f, e.rotation + 90f * i, 25f, (x, y) -> {
|
Angles.randLenVectors(e.id, 4, -e.finpow() * 15f, e.rotation + 90f * i, 25f, (x, y) -> {
|
||||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
|
Fill.circle(ex + x, ey + y, fout * 2f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class Logic implements ApplicationListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
TeamData data = state.teams.get(tile.getTeam());
|
TeamData data = state.teams.get(tile.getTeam());
|
||||||
data.brokenBlocks.addFirst(BrokenBlock.get(tile.x, tile.y, block.id, tile.rotation()));
|
data.brokenBlocks.addFirst(BrokenBlock.get(tile.x, tile.y, tile.rotation(), block.id));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -360,11 +360,11 @@ public class NetClient implements ApplicationListener{
|
|||||||
if(timer.get(0, playerSyncTime)){
|
if(timer.get(0, playerSyncTime)){
|
||||||
BuildRequest[] requests;
|
BuildRequest[] requests;
|
||||||
//limit to 10 to prevent buffer overflows
|
//limit to 10 to prevent buffer overflows
|
||||||
int usedRequests = Math.min(player.getPlaceQueue().size, 10);
|
int usedRequests = Math.min(player.buildQueue().size, 10);
|
||||||
|
|
||||||
requests = new BuildRequest[usedRequests];
|
requests = new BuildRequest[usedRequests];
|
||||||
for(int i = 0; i < usedRequests; i++){
|
for(int i = 0; i < usedRequests; i++){
|
||||||
requests[i] = player.getPlaceQueue().get(i);
|
requests[i] = player.buildQueue().get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Call.onClientShapshot(lastSent++, player.x, player.y,
|
Call.onClientShapshot(lastSent++, player.x, player.y,
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ public class NetServer implements ApplicationListener{
|
|||||||
player.isTyping = chatting;
|
player.isTyping = chatting;
|
||||||
player.isBoosting = boosting;
|
player.isBoosting = boosting;
|
||||||
player.isShooting = shooting;
|
player.isShooting = shooting;
|
||||||
player.getPlaceQueue().clear();
|
player.buildQueue().clear();
|
||||||
for(BuildRequest req : requests){
|
for(BuildRequest req : requests){
|
||||||
Tile tile = world.tile(req.x, req.y);
|
Tile tile = world.tile(req.x, req.y);
|
||||||
if(tile == null) continue;
|
if(tile == null) continue;
|
||||||
@@ -292,7 +292,7 @@ public class NetServer implements ApplicationListener{
|
|||||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
|
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
player.getPlaceQueue().addLast(req);
|
player.buildQueue().addLast(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector.set(x - player.getInterpolator().target.x, y - player.getInterpolator().target.y);
|
vector.set(x - player.getInterpolator().target.x, y - player.getInterpolator().target.y);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import io.anuke.mindustry.entities.traits.Entity;
|
|||||||
import static io.anuke.mindustry.Vars.collisions;
|
import static io.anuke.mindustry.Vars.collisions;
|
||||||
|
|
||||||
public class Entities{
|
public class Entities{
|
||||||
public static final int maxLeafObjects = 4;
|
|
||||||
private static final Array<EntityGroup<?>> groupArray = new Array<>();
|
private static final Array<EntityGroup<?>> groupArray = new Array<>();
|
||||||
private static final IntMap<EntityGroup<?>> groups = new IntMap<>();
|
private static final IntMap<EntityGroup<?>> groups = new IntMap<>();
|
||||||
private static final Rectangle viewport = new Rectangle();
|
private static final Rectangle viewport = new Rectangle();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class EntityGroup<T extends Entity>{
|
|||||||
private final Array<T> entitiesToRemove = new Array<>(false, 16);
|
private final Array<T> entitiesToRemove = new Array<>(false, 16);
|
||||||
private final Array<T> entitiesToAdd = new Array<>(false, 16);
|
private final Array<T> entitiesToAdd = new Array<>(false, 16);
|
||||||
private IntMap<T> map;
|
private IntMap<T> map;
|
||||||
private QuadTree<T> tree;
|
private QuadTree tree;
|
||||||
private Consumer<T> removeListener;
|
private Consumer<T> removeListener;
|
||||||
private Consumer<T> addListener;
|
private Consumer<T> addListener;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ public class EntityGroup<T extends Entity>{
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
if(useTree){
|
if(useTree){
|
||||||
tree = new QuadTree<>(Entities.maxLeafObjects, new Rectangle(0, 0, 0, 0));
|
tree = new QuadTree<>(new Rectangle(0, 0, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ public class EntityGroup<T extends Entity>{
|
|||||||
/** Resizes the internal quadtree, if it is enabled.*/
|
/** Resizes the internal quadtree, if it is enabled.*/
|
||||||
public void resize(float x, float y, float w, float h){
|
public void resize(float x, float y, float w, float h){
|
||||||
if(useTree){
|
if(useTree){
|
||||||
tree = new QuadTree<>(Entities.maxLeafObjects, new Rectangle(x, y, w, h));
|
tree = new QuadTree<>(new Rectangle(x, y, w, h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public interface BuilderMinerTrait extends MinerTrait, BuilderTrait{
|
|||||||
updateBuilding();
|
updateBuilding();
|
||||||
|
|
||||||
//mine only when not building
|
//mine only when not building
|
||||||
if(getCurrentRequest() == null){
|
if(buildRequest() == null){
|
||||||
updateMining();
|
updateMining();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,21 +35,21 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
Unit unit = (Unit)this;
|
Unit unit = (Unit)this;
|
||||||
//remove already completed build requests
|
//remove already completed build requests
|
||||||
removal.clear();
|
removal.clear();
|
||||||
for(BuildRequest req : getPlaceQueue()){
|
for(BuildRequest req : buildQueue()){
|
||||||
removal.add(req);
|
removal.add(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaceQueue().clear();
|
buildQueue().clear();
|
||||||
|
|
||||||
for(BuildRequest request : removal){
|
for(BuildRequest request : removal){
|
||||||
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
||||||
(!request.breaking && (world.tile(request.x, request.y).rotation() == request.rotation || !request.block.rotate)
|
(!request.breaking && (world.tile(request.x, request.y).rotation() == request.rotation || !request.block.rotate)
|
||||||
&& world.tile(request.x, request.y).block() == request.block))){
|
&& world.tile(request.x, request.y).block() == request.block))){
|
||||||
getPlaceQueue().addLast(request);
|
buildQueue().addLast(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildRequest current = getCurrentRequest();
|
BuildRequest current = buildRequest();
|
||||||
|
|
||||||
if(current == null){
|
if(current == null){
|
||||||
return;
|
return;
|
||||||
@@ -58,9 +58,9 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
Tile tile = world.tile(current.x, current.y);
|
Tile tile = world.tile(current.x, current.y);
|
||||||
|
|
||||||
if(dst(tile) > finalPlaceDst){
|
if(dst(tile) > finalPlaceDst){
|
||||||
if(getPlaceQueue().size > 1){
|
if(buildQueue().size > 1){
|
||||||
getPlaceQueue().removeFirst();
|
buildQueue().removeFirst();
|
||||||
getPlaceQueue().addLast(current);
|
buildQueue().addLast(current);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
||||||
Call.beginBreak(getTeam(), current.x, current.y);
|
Call.beginBreak(getTeam(), current.x, current.y);
|
||||||
}else{
|
}else{
|
||||||
getPlaceQueue().removeFirst();
|
buildQueue().removeFirst();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the queue for storing build requests. */
|
/** Returns the queue for storing build requests. */
|
||||||
Queue<BuildRequest> getPlaceQueue();
|
Queue<BuildRequest> buildQueue();
|
||||||
|
|
||||||
/** Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. */
|
/** Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. */
|
||||||
float getBuildPower(Tile tile);
|
float getBuildPower(Tile tile);
|
||||||
@@ -126,7 +126,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
}
|
}
|
||||||
|
|
||||||
default void writeBuilding(DataOutput output) throws IOException{
|
default void writeBuilding(DataOutput output) throws IOException{
|
||||||
BuildRequest request = getCurrentRequest();
|
BuildRequest request = buildRequest();
|
||||||
|
|
||||||
if(request != null){
|
if(request != null){
|
||||||
output.writeByte(request.breaking ? 1 : 0);
|
output.writeByte(request.breaking ? 1 : 0);
|
||||||
@@ -146,7 +146,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
}
|
}
|
||||||
|
|
||||||
default void readBuilding(DataInput input, boolean applyChanges) throws IOException{
|
default void readBuilding(DataInput input, boolean applyChanges) throws IOException{
|
||||||
if(applyChanges) getPlaceQueue().clear();
|
if(applyChanges) buildQueue().clear();
|
||||||
|
|
||||||
byte type = input.readByte();
|
byte type = input.readByte();
|
||||||
if(type != -1){
|
if(type != -1){
|
||||||
@@ -165,26 +165,26 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
request.progress = progress;
|
request.progress = progress;
|
||||||
|
|
||||||
if(applyChanges){
|
if(applyChanges){
|
||||||
getPlaceQueue().addLast(request);
|
buildQueue().addLast(request);
|
||||||
}else if(isBuilding()){
|
}else if(isBuilding()){
|
||||||
getCurrentRequest().progress = progress;
|
buildRequest().progress = progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return whether this builder's place queue contains items. */
|
/** Return whether this builder's place queue contains items. */
|
||||||
default boolean isBuilding(){
|
default boolean isBuilding(){
|
||||||
return getPlaceQueue().size != 0;
|
return buildQueue().size != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clears the placement queue. */
|
/** Clears the placement queue. */
|
||||||
default void clearBuilding(){
|
default void clearBuilding(){
|
||||||
getPlaceQueue().clear();
|
buildQueue().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
|
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
|
||||||
default void addBuildRequest(BuildRequest place){
|
default void addBuildRequest(BuildRequest place){
|
||||||
for(BuildRequest request : getPlaceQueue()){
|
for(BuildRequest request : buildQueue()){
|
||||||
if(request.x == place.x && request.y == place.y){
|
if(request.x == place.x && request.y == place.y){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,15 +193,15 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
if(tile != null && tile.entity instanceof BuildEntity){
|
if(tile != null && tile.entity instanceof BuildEntity){
|
||||||
place.progress = tile.<BuildEntity>entity().progress;
|
place.progress = tile.<BuildEntity>entity().progress;
|
||||||
}
|
}
|
||||||
getPlaceQueue().addLast(place);
|
buildQueue().addLast(place);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the build requests currently active, or the one at the top of the queue.
|
* Return the build requests currently active, or the one at the top of the queue.
|
||||||
* May return null.
|
* May return null.
|
||||||
*/
|
*/
|
||||||
default BuildRequest getCurrentRequest(){
|
default BuildRequest buildRequest(){
|
||||||
return getPlaceQueue().size == 0 ? null : getPlaceQueue().first();
|
return buildQueue().size == 0 ? null : buildQueue().first();
|
||||||
}
|
}
|
||||||
|
|
||||||
//due to iOS weirdness, this is apparently required
|
//due to iOS weirdness, this is apparently required
|
||||||
@@ -215,7 +215,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
|||||||
if(!isBuilding()) return;
|
if(!isBuilding()) return;
|
||||||
|
|
||||||
Unit unit = (Unit)this;
|
Unit unit = (Unit)this;
|
||||||
BuildRequest request = getCurrentRequest();
|
BuildRequest request = buildRequest();
|
||||||
Tile tile = world.tile(request.x, request.y);
|
Tile tile = world.tile(request.x, request.y);
|
||||||
|
|
||||||
if(dst(tile) > placeDistance && !state.isEditor()){
|
if(dst(tile) > placeDistance && !state.isEditor()){
|
||||||
|
|||||||
@@ -112,10 +112,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
|||||||
this.state.set(state);
|
this.state.set(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void retarget(Runnable run){
|
public boolean retarget(){
|
||||||
if(timer.get(timerTarget, 20)){
|
return timer.get(timerTarget, 20);
|
||||||
run.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Only runs when the unit has a target. */
|
/** Only runs when the unit has a target. */
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public abstract class FlyingUnit extends BaseUnit{
|
|||||||
target = null;
|
target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
retarget(() -> {
|
if(retarget()){
|
||||||
targetClosest();
|
targetClosest();
|
||||||
|
|
||||||
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
|
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
|
||||||
@@ -41,7 +41,7 @@ public abstract class FlyingUnit extends BaseUnit{
|
|||||||
if(target == null){
|
if(target == null){
|
||||||
setState(patrol);
|
setState(patrol);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
attack(type.attackLength);
|
attack(type.attackLength);
|
||||||
@@ -71,7 +71,7 @@ public abstract class FlyingUnit extends BaseUnit{
|
|||||||
},
|
},
|
||||||
patrol = new UnitState(){
|
patrol = new UnitState(){
|
||||||
public void update(){
|
public void update(){
|
||||||
retarget(() -> {
|
if(retarget()){
|
||||||
targetClosest();
|
targetClosest();
|
||||||
targetClosestEnemyFlag(BlockFlag.target);
|
targetClosestEnemyFlag(BlockFlag.target);
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ public abstract class FlyingUnit extends BaseUnit{
|
|||||||
}
|
}
|
||||||
|
|
||||||
target = getClosestCore();
|
target = getClosestCore();
|
||||||
});
|
};
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
circle(60f + Mathf.absin(Time.time() + Mathf.randomSeed(id) * 1200f, 70f, 1200f));
|
circle(60f + Mathf.absin(Time.time() + Mathf.randomSeed(id) * 1200f, 70f, 1200f));
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ public abstract class GroundUnit extends BaseUnit{
|
|||||||
target = null;
|
target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
retarget(this::targetClosest);
|
if(retarget()){
|
||||||
|
targetClosest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void patrol(){
|
protected void patrol(){
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Queue<BuildRequest> getPlaceQueue(){
|
public Queue<BuildRequest> buildQueue(){
|
||||||
return placeQueue;
|
return placeQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,8 +428,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
|
|||||||
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
|
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
|
||||||
public void drawBuildRequests(){
|
public void drawBuildRequests(){
|
||||||
BuildRequest last = null;
|
BuildRequest last = null;
|
||||||
for(BuildRequest request : getPlaceQueue()){
|
for(BuildRequest request : buildQueue()){
|
||||||
if(request.progress > 0.01f || (getCurrentRequest() == request && (dst(request.x * tilesize, request.y * tilesize) <= placeDistance || state.isEditor()))) continue;
|
if(request.progress > 0.01f || (buildRequest() == request && (dst(request.x * tilesize, request.y * tilesize) <= placeDistance || state.isEditor()))) continue;
|
||||||
|
|
||||||
if(request.breaking){
|
if(request.breaking){
|
||||||
Block block = world.ltile(request.x, request.y).block();
|
Block block = world.ltile(request.x, request.y).block();
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ public abstract class BaseDrone extends FlyingUnit{
|
|||||||
if(health >= maxHealth()){
|
if(health >= maxHealth()){
|
||||||
state.set(attack);
|
state.set(attack);
|
||||||
}else if(!targetHasFlag(BlockFlag.repair)){
|
}else if(!targetHasFlag(BlockFlag.repair)){
|
||||||
retarget(() -> {
|
if(retarget()){
|
||||||
Tile repairPoint = Geometry.findClosest(x, y, world.indexer.getAllied(team, BlockFlag.repair));
|
Tile repairPoint = Geometry.findClosest(x, y, world.indexer.getAllied(team, BlockFlag.repair));
|
||||||
if(repairPoint != null){
|
if(repairPoint != null){
|
||||||
target = repairPoint;
|
target = repairPoint;
|
||||||
}else{
|
}else{
|
||||||
setState(getStartState());
|
setState(getStartState());
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}else{
|
}else{
|
||||||
circle(40f);
|
circle(40f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import io.anuke.arc.collection.IntIntMap;
|
|||||||
import io.anuke.arc.collection.Queue;
|
import io.anuke.arc.collection.Queue;
|
||||||
import io.anuke.arc.math.Mathf;
|
import io.anuke.arc.math.Mathf;
|
||||||
import io.anuke.arc.util.*;
|
import io.anuke.arc.util.*;
|
||||||
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.entities.EntityGroup;
|
import io.anuke.mindustry.entities.EntityGroup;
|
||||||
import io.anuke.mindustry.entities.traits.BuilderTrait;
|
import io.anuke.mindustry.entities.traits.BuilderTrait;
|
||||||
import io.anuke.mindustry.entities.traits.TargetTrait;
|
import io.anuke.mindustry.entities.traits.TargetTrait;
|
||||||
import io.anuke.mindustry.entities.type.*;
|
import io.anuke.mindustry.entities.type.*;
|
||||||
import io.anuke.mindustry.entities.units.UnitState;
|
import io.anuke.mindustry.entities.units.UnitState;
|
||||||
import io.anuke.mindustry.game.EventType.BuildSelectEvent;
|
import io.anuke.mindustry.game.EventType.BuildSelectEvent;
|
||||||
|
import io.anuke.mindustry.game.Teams.TeamData;
|
||||||
|
import io.anuke.mindustry.gen.BrokenBlock;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
import io.anuke.mindustry.world.blocks.BuildBlock;
|
import io.anuke.mindustry.world.blocks.BuildBlock;
|
||||||
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
|
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
|
||||||
@@ -20,7 +23,6 @@ import java.io.*;
|
|||||||
|
|
||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
//TODO follow players
|
|
||||||
public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
||||||
private static final StaticReset reset = new StaticReset();
|
private static final StaticReset reset = new StaticReset();
|
||||||
private static final IntIntMap totals = new IntIntMap();
|
private static final IntIntMap totals = new IntIntMap();
|
||||||
@@ -43,12 +45,22 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
BuildEntity entity = (BuildEntity)target;
|
BuildEntity entity = (BuildEntity)target;
|
||||||
TileEntity core = getClosestCore();
|
TileEntity core = getClosestCore();
|
||||||
|
|
||||||
if(entity != null && core != null && (entity.progress < 1f || entity.progress > 0f) && entity.tile.block() instanceof BuildBlock){ //building is valid
|
if(isBuilding() && entity == null && isRebuild()){
|
||||||
|
target = world.tile(buildRequest().x, buildRequest().y);
|
||||||
|
circle(placeDistance * 0.7f);
|
||||||
|
target = null;
|
||||||
|
|
||||||
|
BuildRequest request = buildRequest();
|
||||||
|
|
||||||
|
if(world.tile(request.x, request.y).entity instanceof BuildEntity){
|
||||||
|
target = world.tile(request.x, request.y).entity;
|
||||||
|
}
|
||||||
|
}else if(entity != null && core != null && (entity.progress < 1f || entity.progress > 0f) && entity.tile.block() instanceof BuildBlock){ //building is valid
|
||||||
if(!isBuilding() && dst(target) < placeDistance * 0.9f){ //within distance, begin placing
|
if(!isBuilding() && dst(target) < placeDistance * 0.9f){ //within distance, begin placing
|
||||||
if(isBreaking){
|
if(isBreaking){
|
||||||
getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y));
|
buildQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y));
|
||||||
}else{
|
}else{
|
||||||
getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.rotation(), entity.cblock));
|
buildQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.rotation(), entity.cblock));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +70,7 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
if(playerTarget == null || playerTarget.getTeam() != team || !playerTarget.isValid()){
|
if(playerTarget == null || playerTarget.getTeam() != team || !playerTarget.isValid()){
|
||||||
playerTarget = null;
|
playerTarget = null;
|
||||||
|
|
||||||
retarget(() -> {
|
if(retarget()){
|
||||||
float minDst = Float.POSITIVE_INFINITY;
|
float minDst = Float.POSITIVE_INFINITY;
|
||||||
int minDrones = Integer.MAX_VALUE;
|
int minDrones = Integer.MAX_VALUE;
|
||||||
|
|
||||||
@@ -75,7 +87,13 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if(getSpawner() != null){
|
||||||
|
target = getSpawner();
|
||||||
|
circle(40f);
|
||||||
|
target = null;
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
incDrones(playerTarget);
|
incDrones(playerTarget);
|
||||||
TargetTrait prev = target;
|
TargetTrait prev = target;
|
||||||
@@ -103,7 +121,7 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
BuilderDrone drone = (BuilderDrone)unit;
|
BuilderDrone drone = (BuilderDrone)unit;
|
||||||
if(drone.isBuilding()){
|
if(drone.isBuilding()){
|
||||||
//stop building if opposite building begins.
|
//stop building if opposite building begins.
|
||||||
BuildRequest req = drone.getCurrentRequest();
|
BuildRequest req = drone.buildRequest();
|
||||||
if(req.breaking != event.breaking && req.x == event.tile.x && req.y == event.tile.y){
|
if(req.breaking != event.breaking && req.x == event.tile.x && req.y == event.tile.y){
|
||||||
drone.clearBuilding();
|
drone.clearBuilding();
|
||||||
drone.target = null;
|
drone.target = null;
|
||||||
@@ -131,13 +149,17 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isRebuild(){
|
||||||
|
return Vars.state.rules.enemyCheat && team == waveTeam;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getBuildPower(Tile tile){
|
public float getBuildPower(Tile tile){
|
||||||
return type.buildPower;
|
return type.buildPower;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Queue<BuildRequest> getPlaceQueue(){
|
public Queue<BuildRequest> buildQueue(){
|
||||||
return placeQueue;
|
return placeQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +169,8 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
|
|
||||||
if(!isBuilding() && timer.get(timerTarget2, 15)){
|
if(!isBuilding() && timer.get(timerTarget2, 15)){
|
||||||
for(Player player : playerGroup.all()){
|
for(Player player : playerGroup.all()){
|
||||||
if(player.getTeam() == team && player.getCurrentRequest() != null){
|
if(player.getTeam() == team && player.buildRequest() != null){
|
||||||
BuildRequest req = player.getCurrentRequest();
|
BuildRequest req = player.buildRequest();
|
||||||
Tile tile = world.tile(req.x, req.y);
|
Tile tile = world.tile(req.x, req.y);
|
||||||
if(tile != null && tile.entity instanceof BuildEntity){
|
if(tile != null && tile.entity instanceof BuildEntity){
|
||||||
BuildEntity b = tile.entity();
|
BuildEntity b = tile.entity();
|
||||||
@@ -162,6 +184,16 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isRebuild()){
|
||||||
|
TeamData data = Vars.state.teams.get(team);
|
||||||
|
if(!data.brokenBlocks.isEmpty()){
|
||||||
|
long block = data.brokenBlocks.removeLast();
|
||||||
|
|
||||||
|
placeQueue.addFirst(new BuildRequest(BrokenBlock.x(block), BrokenBlock.y(block), BrokenBlock.rotation(block), content.block(BrokenBlock.block(block))));
|
||||||
|
setState(build);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBuilding();
|
updateBuilding();
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ public class MinerDrone extends BaseDrone implements MinerTrait{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
retarget(() -> {
|
if(retarget()){
|
||||||
findItem();
|
findItem();
|
||||||
|
|
||||||
if(targetItem == null) return;
|
if(targetItem == null) return;
|
||||||
|
|
||||||
target = world.indexer.findClosestOre(x, y, targetItem);
|
target = world.indexer.findClosestOre(x, y, targetItem);
|
||||||
});
|
};
|
||||||
|
|
||||||
if(target instanceof Tile){
|
if(target instanceof Tile){
|
||||||
moveTo(type.range / 1.5f);
|
moveTo(type.range / 1.5f);
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ public class RepairDrone extends BaseDrone{
|
|||||||
|
|
||||||
public void update(){
|
public void update(){
|
||||||
|
|
||||||
retarget(() -> target = Units.findDamagedTile(team, x, y));
|
if(retarget()){
|
||||||
|
target = Units.findDamagedTile(team, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
if(target.dst(RepairDrone.this) > type.range){
|
if(target.dst(RepairDrone.this) > type.range){
|
||||||
|
|||||||
@@ -42,11 +42,17 @@ public class Teams{
|
|||||||
return enemiesOf(team).contains(other);
|
return enemiesOf(team).contains(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TeamData{
|
/** Allocates a new array with the active teams.
|
||||||
|
* Never call in the main game loop.*/
|
||||||
|
public Array<TeamData> getActive(){
|
||||||
|
return Array.select(map, t -> t != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TeamData{
|
||||||
public final ObjectSet<Tile> cores = new ObjectSet<>();
|
public final ObjectSet<Tile> cores = new ObjectSet<>();
|
||||||
public final LongQueue brokenBlocks = new LongQueue();
|
|
||||||
public final EnumSet<Team> enemies;
|
public final EnumSet<Team> enemies;
|
||||||
public final Team team;
|
public final Team team;
|
||||||
|
public LongQueue brokenBlocks = new LongQueue();
|
||||||
|
|
||||||
public TeamData(Team team, EnumSet<Team> enemies){
|
public TeamData(Team team, EnumSet<Team> enemies){
|
||||||
this.team = team;
|
this.team = team;
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
mode = placing;
|
mode = placing;
|
||||||
}else if(selected != null){
|
}else if(selected != null){
|
||||||
//only begin shooting if there's no cursor event
|
//only begin shooting if there's no cursor event
|
||||||
if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && player.getPlaceQueue().size == 0 && !droppingItem &&
|
if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && player.buildQueue().size == 0 && !droppingItem &&
|
||||||
!tryBeginMine(selected) && player.getMineTile() == null && !ui.chatfrag.chatOpen()){
|
!tryBeginMine(selected) && player.getMineTile() == null && !ui.chatfrag.chatOpen()){
|
||||||
player.isShooting = true;
|
player.isShooting = true;
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
player.isShooting = true;
|
player.isShooting = true;
|
||||||
}
|
}
|
||||||
}else if(Core.input.keyTap(Binding.deselect) && (block != null || mode != none || player.isBuilding()) &&
|
}else if(Core.input.keyTap(Binding.deselect) && (block != null || mode != none || player.isBuilding()) &&
|
||||||
!(player.getCurrentRequest() != null && player.getCurrentRequest().breaking && Core.keybinds.get(Binding.deselect) == Core.keybinds.get(Binding.break_block))){
|
!(player.buildRequest() != null && player.buildRequest().breaking && Core.keybinds.get(Binding.deselect) == Core.keybinds.get(Binding.break_block))){
|
||||||
if(block == null){
|
if(block == null){
|
||||||
player.clearBuilding();
|
player.clearBuilding();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package io.anuke.mindustry.io;
|
package io.anuke.mindustry.io;
|
||||||
|
|
||||||
|
import io.anuke.arc.collection.EnumSet;
|
||||||
|
import io.anuke.arc.collection.LongQueue;
|
||||||
import io.anuke.arc.util.serialization.Json;
|
import io.anuke.arc.util.serialization.Json;
|
||||||
import io.anuke.arc.util.serialization.JsonValue;
|
import io.anuke.arc.util.serialization.JsonValue;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.game.Rules;
|
import io.anuke.mindustry.game.*;
|
||||||
import io.anuke.mindustry.game.SpawnGroup;
|
import io.anuke.mindustry.game.Teams.TeamData;
|
||||||
import io.anuke.mindustry.type.*;
|
import io.anuke.mindustry.type.*;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -36,6 +38,25 @@ public class JsonIO{
|
|||||||
return Vars.content.getByName(ContentType.item, jsonData.asString());
|
return Vars.content.getByName(ContentType.item, jsonData.asString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setSerializer(TeamData.class, new Serializer<TeamData>(){
|
||||||
|
@Override
|
||||||
|
public void write(Json json, TeamData object, Class knownType){
|
||||||
|
json.writeObjectStart();
|
||||||
|
json.writeValue("brokenBlocks", object.brokenBlocks.toArray());
|
||||||
|
json.writeValue("team", object.team.ordinal());
|
||||||
|
json.writeObjectEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TeamData read(Json json, JsonValue jsonData, Class type){
|
||||||
|
long[] blocks = jsonData.get("brokenBlocks").asLongArray();
|
||||||
|
Team team = Team.all[jsonData.getInt("team", 0)];
|
||||||
|
TeamData out = new TeamData(team, EnumSet.of(new Team[]{}));
|
||||||
|
out.brokenBlocks = new LongQueue(blocks);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
});
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public static String write(Object object){
|
public static String write(Object object){
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package io.anuke.mindustry.io;
|
package io.anuke.mindustry.io;
|
||||||
|
|
||||||
import io.anuke.arc.collection.Array;
|
import io.anuke.arc.collection.*;
|
||||||
import io.anuke.arc.collection.StringMap;
|
|
||||||
import io.anuke.arc.util.Time;
|
import io.anuke.arc.util.Time;
|
||||||
import io.anuke.arc.util.io.CounterInputStream;
|
import io.anuke.arc.util.io.CounterInputStream;
|
||||||
import io.anuke.mindustry.entities.Entities;
|
import io.anuke.mindustry.entities.Entities;
|
||||||
import io.anuke.mindustry.entities.EntityGroup;
|
import io.anuke.mindustry.entities.EntityGroup;
|
||||||
import io.anuke.mindustry.entities.traits.*;
|
import io.anuke.mindustry.entities.traits.*;
|
||||||
import io.anuke.mindustry.game.*;
|
import io.anuke.mindustry.game.*;
|
||||||
|
import io.anuke.mindustry.game.Teams.TeamData;
|
||||||
|
import io.anuke.mindustry.gen.BrokenBlock;
|
||||||
import io.anuke.mindustry.maps.Map;
|
import io.anuke.mindustry.maps.Map;
|
||||||
import io.anuke.mindustry.type.ContentType;
|
import io.anuke.mindustry.type.ContentType;
|
||||||
import io.anuke.mindustry.world.*;
|
import io.anuke.mindustry.world.*;
|
||||||
@@ -64,6 +65,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
"wavetime", state.wavetime,
|
"wavetime", state.wavetime,
|
||||||
"stats", JsonIO.write(state.stats),
|
"stats", JsonIO.write(state.stats),
|
||||||
"rules", JsonIO.write(state.rules),
|
"rules", JsonIO.write(state.rules),
|
||||||
|
"teamdata", JsonIO.write(state.teams.getActive().toArray(TeamData.class)),
|
||||||
"width", world.width(),
|
"width", world.width(),
|
||||||
"height", world.height()
|
"height", world.height()
|
||||||
).merge(tags));
|
).merge(tags));
|
||||||
@@ -77,6 +79,13 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
||||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||||
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
||||||
|
|
||||||
|
//only broken blocks are transferred over right now; nothing else
|
||||||
|
TeamData[] teams = JsonIO.read(TeamData[].class, map.get("teamdata", "[]"));
|
||||||
|
for(TeamData data : teams){
|
||||||
|
state.teams.get(data.team).brokenBlocks = data.brokenBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\"));
|
Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\"));
|
||||||
world.setMap(worldmap == null ? new Map(StringMap.of(
|
world.setMap(worldmap == null ? new Map(StringMap.of(
|
||||||
"name", map.get("mapname", "Unknown"),
|
"name", map.get("mapname", "Unknown"),
|
||||||
@@ -92,13 +101,13 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
|
|
||||||
//floor + overlay
|
//floor + overlay
|
||||||
for(int i = 0; i < world.width() * world.height(); i++){
|
for(int i = 0; i < world.width() * world.height(); i++){
|
||||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
Tile tile = world.rawTile(i % world.width(), i / world.width());
|
||||||
stream.writeShort(tile.floorID());
|
stream.writeShort(tile.floorID());
|
||||||
stream.writeShort(tile.overlayID());
|
stream.writeShort(tile.overlayID());
|
||||||
int consecutives = 0;
|
int consecutives = 0;
|
||||||
|
|
||||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
Tile nextTile = world.rawTile(j % world.width(), j / world.width());
|
||||||
|
|
||||||
if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){
|
if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){
|
||||||
break;
|
break;
|
||||||
@@ -113,7 +122,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
|
|
||||||
//blocks
|
//blocks
|
||||||
for(int i = 0; i < world.width() * world.height(); i++){
|
for(int i = 0; i < world.width() * world.height(); i++){
|
||||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
Tile tile = world.rawTile(i % world.width(), i / world.width());
|
||||||
stream.writeShort(tile.blockID());
|
stream.writeShort(tile.blockID());
|
||||||
|
|
||||||
if(tile.entity != null){
|
if(tile.entity != null){
|
||||||
@@ -126,7 +135,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
int consecutives = 0;
|
int consecutives = 0;
|
||||||
|
|
||||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
Tile nextTile = world.rawTile(j % world.width(), j / world.width());
|
||||||
|
|
||||||
if(nextTile.blockID() != tile.blockID()){
|
if(nextTile.blockID() != tile.blockID()){
|
||||||
break;
|
break;
|
||||||
@@ -264,6 +273,8 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
}
|
}
|
||||||
|
|
||||||
content.setTemporaryMapper(map);
|
content.setTemporaryMapper(map);
|
||||||
|
|
||||||
|
remapContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeContentHeader(DataOutput stream) throws IOException{
|
public void writeContentHeader(DataOutput stream) throws IOException{
|
||||||
@@ -287,4 +298,17 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** sometimes it's necessary to remap IDs after the content header is read.*/
|
||||||
|
public void remapContent(){
|
||||||
|
for(Team team : Team.all){
|
||||||
|
if(state.teams.isActive(team)){
|
||||||
|
LongQueue queue = state.teams.get(team).brokenBlocks;
|
||||||
|
for(int i = 0; i < queue.size; i++){
|
||||||
|
//remap broken block IDs
|
||||||
|
queue.set(i, BrokenBlock.block(queue.get(i), content.block(BrokenBlock.block(queue.get(i))).id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ public class CustomRulesDialog extends FloatingDialog{
|
|||||||
|
|
||||||
title("$rules.title.enemy");
|
title("$rules.title.enemy");
|
||||||
check("$rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
|
check("$rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
|
||||||
|
check("$rules.enemyCheat", b -> rules.enemyCheat = b, () -> rules.enemyCheat);
|
||||||
number("$rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200));
|
number("$rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ public class SettingsMenuDialog extends SettingsDialog{
|
|||||||
sound.add("[LIGHT_GRAY]there is no sound implemented in v4 yet");
|
sound.add("[LIGHT_GRAY]there is no sound implemented in v4 yet");
|
||||||
|
|
||||||
game.screenshakePref();
|
game.screenshakePref();
|
||||||
game.checkPref("effects", true);
|
|
||||||
if(mobile){
|
if(mobile){
|
||||||
game.checkPref("autotarget", true);
|
game.checkPref("autotarget", true);
|
||||||
}
|
}
|
||||||
@@ -212,6 +211,7 @@ public class SettingsMenuDialog extends SettingsDialog{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graphics.checkPref("effects", true);
|
||||||
graphics.checkPref("playerchat", true);
|
graphics.checkPref("playerchat", true);
|
||||||
graphics.checkPref("minimap", !mobile);
|
graphics.checkPref("minimap", !mobile);
|
||||||
graphics.checkPref("fps", false);
|
graphics.checkPref("fps", false);
|
||||||
|
|||||||
Reference in New Issue
Block a user