270 lines
7.7 KiB
Java
270 lines
7.7 KiB
Java
package mindustry.entities.comp;
|
|
|
|
import arc.func.*;
|
|
import arc.graphics.g2d.*;
|
|
import arc.math.*;
|
|
import arc.math.geom.*;
|
|
import arc.struct.*;
|
|
import arc.util.*;
|
|
import mindustry.annotations.Annotations.*;
|
|
import mindustry.content.*;
|
|
import mindustry.core.*;
|
|
import mindustry.entities.*;
|
|
import mindustry.entities.bullet.*;
|
|
import mindustry.game.*;
|
|
import mindustry.game.Teams.*;
|
|
import mindustry.gen.*;
|
|
import mindustry.graphics.*;
|
|
import mindustry.world.*;
|
|
import mindustry.world.blocks.environment.*;
|
|
|
|
import static mindustry.Vars.*;
|
|
|
|
@EntityDef(value = {Bulletc.class}, pooled = true, serialize = false)
|
|
@Component(base = true)
|
|
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, damage, lastX, lastY, time, lifetime;
|
|
@Import Vec2 vel;
|
|
|
|
IntSeq collided = new IntSeq(6);
|
|
BulletType type;
|
|
|
|
Object data;
|
|
float fdata;
|
|
|
|
@ReadOnly
|
|
private float rotation;
|
|
|
|
//setting this variable to true prevents lifetime from decreasing for a frame.
|
|
transient boolean keepAlive;
|
|
transient @Nullable Tile aimTile;
|
|
transient float aimX, aimY;
|
|
transient float originX, originY;
|
|
transient @Nullable Mover mover;
|
|
transient boolean absorbed, hit;
|
|
transient @Nullable Trail trail;
|
|
|
|
@Override
|
|
public void getCollisions(Cons<QuadTree> consumer){
|
|
Seq<TeamData> data = state.teams.present;
|
|
for(int i = 0; i < data.size; i++){
|
|
if(data.items[i].team != team){
|
|
consumer.get(data.items[i].tree());
|
|
}
|
|
}
|
|
}
|
|
|
|
//bullets always considered local
|
|
@Override
|
|
@Replace
|
|
public boolean isLocal(){
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void add(){
|
|
type.init(self());
|
|
}
|
|
|
|
@Override
|
|
public void remove(){
|
|
if(Groups.isClearing) return;
|
|
|
|
//'despawned' only counts when the bullet is killed externally or reaches the end of life
|
|
if(!hit){
|
|
type.despawned(self());
|
|
}
|
|
type.removed(self());
|
|
collided.clear();
|
|
}
|
|
|
|
@Override
|
|
public float damageMultiplier(){
|
|
if(owner instanceof Unit u) return u.damageMultiplier() * state.rules.unitDamage(team);
|
|
if(owner instanceof Building) return state.rules.blockDamage(team);
|
|
|
|
return 1f;
|
|
}
|
|
|
|
@Override
|
|
public void absorb(){
|
|
absorbed = true;
|
|
remove();
|
|
}
|
|
|
|
public boolean hasCollided(int id){
|
|
return collided.size != 0 && collided.contains(id);
|
|
}
|
|
|
|
@Replace
|
|
public float clipSize(){
|
|
return type.drawSize;
|
|
}
|
|
|
|
@Replace
|
|
@Override
|
|
public boolean collides(Hitboxc other){
|
|
return type.collides && (other instanceof Teamc t && t.team() != team)
|
|
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
|
|
&& !(type.pierce && hasCollided(other.id())); //prevent multiple collisions
|
|
}
|
|
|
|
@MethodPriority(100)
|
|
@Override
|
|
public void collision(Hitboxc other, float x, float y){
|
|
type.hit(self(), x, y);
|
|
|
|
//must be last.
|
|
if(!type.pierce){
|
|
hit = true;
|
|
remove();
|
|
}else{
|
|
collided.add(other.id());
|
|
}
|
|
|
|
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
|
|
}
|
|
|
|
@Override
|
|
public void update(){
|
|
if(mover != null){
|
|
mover.move(self());
|
|
}
|
|
|
|
type.update(self());
|
|
|
|
if(type.collidesTiles && type.collides && type.collidesGround){
|
|
tileRaycast(World.toTile(lastX), World.toTile(lastY), tileX(), tileY());
|
|
}
|
|
|
|
if(type.removeAfterPierce && type.pierceCap != -1 && collided.size >= type.pierceCap){
|
|
hit = true;
|
|
remove();
|
|
}
|
|
|
|
if(keepAlive){
|
|
time -= Time.delta;
|
|
keepAlive = false;
|
|
}
|
|
}
|
|
|
|
public void moveRelative(float x, float y){
|
|
float rot = rotation();
|
|
this.x += Angles.trnsx(rot, x * Time.delta, y * Time.delta);
|
|
this.y += Angles.trnsy(rot, x * Time.delta, y * Time.delta);
|
|
}
|
|
|
|
public void turn(float x, float y){
|
|
float ang = vel.angle();
|
|
vel.add(Angles.trnsx(ang, x * Time.delta, y * Time.delta), Angles.trnsy(ang, x * Time.delta, y * Time.delta)).limit(type.speed);
|
|
}
|
|
|
|
public boolean checkUnderBuild(Building build, float x, float y){
|
|
return
|
|
(!build.block.underBullets ||
|
|
//direct hit on correct tile
|
|
(aimTile != null && aimTile.build == build) ||
|
|
//a piercing bullet overshot the aim tile, it's fine to hit things now
|
|
(type.pierce && aimTile != null && Mathf.dst(x, y, originX, originY) > aimTile.dst(originX, originY) + 2f));
|
|
}
|
|
|
|
//copy-paste of World#raycastEach, inlined for lambda capture performance.
|
|
@Override
|
|
public void tileRaycast(int x1, int y1, int x2, int y2){
|
|
int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1;
|
|
int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1;
|
|
int e2, err = dx - dy;
|
|
int ww = world.width(), wh = world.height();
|
|
|
|
while(x >= 0 && y >= 0 && x < ww && y < wh){
|
|
Building build = world.build(x, y);
|
|
|
|
if(type.collideFloor || type.collideTerrain){
|
|
Tile tile = world.tile(x, y);
|
|
if(
|
|
type.collideFloor && (tile == null || tile.floor().hasSurface() || tile.block() != Blocks.air) ||
|
|
type.collideTerrain && tile != null && tile.block() instanceof StaticWall
|
|
){
|
|
remove();
|
|
hit = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(build != null && isAdded()
|
|
&& checkUnderBuild(build, x, y)
|
|
&& build.collide(self()) && type.testCollision(self(), build)
|
|
&& !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
|
|
|
|
boolean remove = false;
|
|
float health = build.health;
|
|
|
|
if(build.team != team){
|
|
remove = build.collision(self());
|
|
}
|
|
|
|
if(remove || type.collidesTeam){
|
|
if(Mathf.dst2(lastX, lastY, x * tilesize, y * tilesize) < Mathf.dst2(lastX, lastY, this.x, this.y)){
|
|
this.x = x * tilesize;
|
|
this.y = y * tilesize;
|
|
}
|
|
|
|
if(!type.pierceBuilding){
|
|
hit = true;
|
|
remove();
|
|
}else{
|
|
collided.add(build.id);
|
|
}
|
|
}
|
|
|
|
type.hitTile(self(), build, x * tilesize, y * tilesize, health, true);
|
|
|
|
//stop raycasting when building is hit
|
|
if(type.pierceBuilding) return;
|
|
}
|
|
|
|
if(x == x2 && y == y2) break;
|
|
|
|
e2 = 2 * err;
|
|
if(e2 > -dy){
|
|
err -= dy;
|
|
x += sx;
|
|
}
|
|
|
|
if(e2 < dx){
|
|
err += dx;
|
|
y += sy;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void draw(){
|
|
Draw.z(type.layer);
|
|
|
|
type.draw(self());
|
|
type.drawLight(self());
|
|
|
|
Draw.reset();
|
|
}
|
|
|
|
public void initVel(float angle, float amount){
|
|
vel.trns(angle, amount);
|
|
rotation = angle;
|
|
}
|
|
|
|
/** Sets the bullet's rotation in degrees. */
|
|
@Override
|
|
public void rotation(float angle){
|
|
vel.setAngle(rotation = angle);
|
|
}
|
|
|
|
/** @return the bullet's rotation. */
|
|
@Override
|
|
public float rotation(){
|
|
return vel.isZero(0.001f) ? rotation : vel.angle();
|
|
}
|
|
}
|