Files
Mindustry/core/src/mindustry/entities/comp/BulletComp.java
2022-05-02 22:24:45 -04:00

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();
}
}