232 lines
8.8 KiB
Java
232 lines
8.8 KiB
Java
package mindustry.entities.comp;
|
|
|
|
import arc.math.*;
|
|
import arc.math.geom.*;
|
|
import arc.util.*;
|
|
import mindustry.annotations.Annotations.*;
|
|
import mindustry.audio.*;
|
|
import mindustry.entities.*;
|
|
import mindustry.entities.bullet.*;
|
|
import mindustry.entities.units.*;
|
|
import mindustry.gen.*;
|
|
import mindustry.type.*;
|
|
|
|
import static mindustry.Vars.*;
|
|
|
|
@Component
|
|
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
|
|
@Import float x, y, rotation, reloadMultiplier;
|
|
@Import Vec2 vel;
|
|
@Import UnitType type;
|
|
|
|
/** temporary weapon sequence number */
|
|
static int sequenceNum = 0;
|
|
|
|
/** weapon mount array, never null */
|
|
@SyncLocal WeaponMount[] mounts = {};
|
|
@ReadOnly transient float aimX, aimY;
|
|
@ReadOnly transient boolean isRotate;
|
|
boolean isShooting;
|
|
float ammo;
|
|
|
|
float ammof(){
|
|
return ammo / type.ammoCapacity;
|
|
}
|
|
|
|
void setWeaponRotation(float rotation){
|
|
for(WeaponMount mount : mounts){
|
|
mount.rotation = rotation;
|
|
}
|
|
}
|
|
|
|
void setupWeapons(UnitType def){
|
|
mounts = new WeaponMount[def.weapons.size];
|
|
for(int i = 0; i < mounts.length; i++){
|
|
mounts[i] = new WeaponMount(def.weapons.get(i));
|
|
}
|
|
}
|
|
|
|
void controlWeapons(boolean rotateShoot){
|
|
controlWeapons(rotateShoot, rotateShoot);
|
|
}
|
|
|
|
void controlWeapons(boolean rotate, boolean shoot){
|
|
for(WeaponMount mount : mounts){
|
|
mount.rotate = rotate;
|
|
mount.shoot = shoot;
|
|
}
|
|
isRotate = rotate;
|
|
isShooting = shoot;
|
|
}
|
|
|
|
void aim(Position pos){
|
|
aim(pos.getX(), pos.getY());
|
|
}
|
|
|
|
/** Aim at something. This will make all mounts point at it. */
|
|
void aim(float x, float y){
|
|
Tmp.v1.set(x, y).sub(this.x, this.y);
|
|
if(Tmp.v1.len() < type.aimDst) Tmp.v1.setLength(type.aimDst);
|
|
|
|
x = Tmp.v1.x + this.x;
|
|
y = Tmp.v1.y + this.y;
|
|
|
|
for(WeaponMount mount : mounts){
|
|
mount.aimX = x;
|
|
mount.aimY = y;
|
|
}
|
|
|
|
aimX = x;
|
|
aimY = y;
|
|
}
|
|
|
|
boolean canShoot(){
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void remove(){
|
|
for(WeaponMount mount : mounts){
|
|
if(mount.bullet != null){
|
|
mount.bullet.time = mount.bullet.lifetime - 10f;
|
|
mount.bullet = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Update shooting and rotation for this unit. */
|
|
@Override
|
|
public void update(){
|
|
boolean can = canShoot();
|
|
|
|
for(WeaponMount mount : mounts){
|
|
Weapon weapon = mount.weapon;
|
|
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
|
|
|
|
float weaponRotation = this.rotation - 90 + (weapon.rotate ? mount.rotation : 0);
|
|
float mountX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
|
|
mountY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
|
|
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
|
|
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
|
|
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
|
|
|
|
//update continuous state
|
|
if(weapon.continuous && mount.bullet != null){
|
|
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime || mount.bullet.type != weapon.bullet){
|
|
mount.bullet = null;
|
|
}else{
|
|
mount.bullet.rotation(weaponRotation + 90);
|
|
mount.bullet.set(shootX, shootY);
|
|
vel.add(Tmp.v1.trns(rotation + 180f, mount.bullet.type.recoil));
|
|
if(weapon.shootSound != Sounds.none && !headless){
|
|
if(mount.sound == null) mount.sound = new SoundLoop(weapon.shootSound, 1f);
|
|
mount.sound.update(x, y, true);
|
|
}
|
|
}
|
|
}else{
|
|
//heat decreases when not firing
|
|
mount.heat = Math.max(mount.heat - Time.delta * reloadMultiplier / mount.weapon.cooldownTime, 0);
|
|
|
|
if(mount.sound != null){
|
|
mount.sound.update(x, y, false);
|
|
}
|
|
}
|
|
|
|
//flip weapon shoot side for alternating weapons at half reload
|
|
if(weapon.otherSide != -1 && weapon.alternate && mount.side == weapon.flipSprite &&
|
|
mount.reload + Time.delta > weapon.reload/2f && mount.reload <= weapon.reload/2f){
|
|
mounts[weapon.otherSide].side = !mounts[weapon.otherSide].side;
|
|
mount.side = !mount.side;
|
|
}
|
|
|
|
//rotate if applicable
|
|
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
|
|
float axisX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
|
|
axisY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
|
|
|
|
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - this.rotation;
|
|
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
|
|
}else if(!weapon.rotate){
|
|
mount.rotation = 0;
|
|
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
|
|
}
|
|
|
|
//shoot if applicable
|
|
if(mount.shoot && //must be shooting
|
|
can && //must be able to shoot
|
|
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
|
|
(!weapon.alternate || mount.side == weapon.flipSprite) &&
|
|
//TODO checking for velocity this way isn't entirely correct
|
|
(vel.len() >= mount.weapon.minShootVelocity || (net.active() && !isLocal())) && //check velocity requirements
|
|
mount.reload <= 0.0001f && //reload has to be 0
|
|
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
|
|
){
|
|
shoot(mount, shootX, shootY, mount.aimX, mount.aimY, mountX, mountY, shootAngle, Mathf.sign(weapon.x));
|
|
|
|
mount.reload = weapon.reload;
|
|
|
|
ammo--;
|
|
if(ammo < 0) ammo = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void shoot(WeaponMount mount, float x, float y, float aimX, float aimY, float mountX, float mountY, float rotation, int side){
|
|
Weapon weapon = mount.weapon;
|
|
|
|
float baseX = this.x, baseY = this.y;
|
|
boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f;
|
|
|
|
(delay ? weapon.chargeSound : weapon.continuous ? Sounds.none : weapon.shootSound).at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
|
|
|
|
BulletType ammo = weapon.bullet;
|
|
float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(x, y, aimX, aimY) / ammo.range()) : 1f;
|
|
|
|
sequenceNum = 0;
|
|
if(delay){
|
|
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
|
|
Time.run(sequenceNum * weapon.shotDelay + weapon.firstShotDelay, () -> {
|
|
if(!isAdded()) return;
|
|
mount.bullet = bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl);
|
|
});
|
|
sequenceNum++;
|
|
});
|
|
}else{
|
|
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> mount.bullet = bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
|
|
}
|
|
|
|
boolean parentize = ammo.keepVelocity;
|
|
|
|
if(delay){
|
|
Time.run(weapon.firstShotDelay, () -> {
|
|
if(!isAdded()) return;
|
|
|
|
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
|
|
Effect.shake(weapon.shake, weapon.shake, x, y);
|
|
mount.heat = 1f;
|
|
if(!weapon.continuous){
|
|
weapon.shootSound.at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
|
|
}
|
|
});
|
|
}else{
|
|
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
|
|
Effect.shake(weapon.shake, weapon.shake, x, y);
|
|
mount.heat = 1f;
|
|
}
|
|
|
|
weapon.ejectEffect.at(mountX, mountY, rotation * side);
|
|
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
|
|
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
|
|
apply(weapon.shootStatus, weapon.shootStatusDuration);
|
|
}
|
|
|
|
private Bullet bullet(Weapon weapon, float x, float y, float angle, float lifescl){
|
|
float xr = Mathf.range(weapon.xRand);
|
|
|
|
return weapon.bullet.create(this, team(),
|
|
x + Angles.trnsx(angle, 0, xr),
|
|
y + Angles.trnsy(angle, 0, xr),
|
|
angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
|
|
}
|
|
}
|