1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-11 08:15:35 +03:00

Support for unit missiles

This commit is contained in:
Anuken 2021-12-07 16:58:11 -05:00
parent f5e9df1265
commit 0b7d8f371e
16 changed files with 276 additions and 77 deletions

View File

@ -26,6 +26,7 @@ mindustry.type.Weather.WeatherStateComp=14
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15
mindustry.world.blocks.campaign.PayloadLaunchPad.LargeLaunchPayloadComp=34
mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22
missile=39
mono=16
nova=17
oct=26
@ -36,5 +37,7 @@ quasar=32
risso=20
scuttler=35
spiroct=21
timed=38
timedDef=37
toxopid=33
vela=25

View File

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:lifetime,type:float},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:time,type:float},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,34 @@
package mindustry.ai.types;
import mindustry.entities.units.*;
public class MissileAI extends AIController{
//TODO UNPREDICTABLE TARGETING
@Override
public void updateMovement(){
unloadPayloads();
if(target != null){
unit.lookAt(target);
var build = unit.buildOn();
//kill instantly on building contact
//TODO kill on target unit contact too
if(build != null && build == target){
unit.kill();
}
}
//move forward forever
unit.moveAt(vec.trns(unit.rotation, unit.speed()));
}
@Override
public boolean retarget(){
//more frequent retarget. TODO lag?
return timer.get(timerTarget, 10f);
}
}

View File

@ -72,6 +72,9 @@ public class UnitTypes{
//endregion
//missile definition, needed for codegen
public static @EntityDef({Unitc.class, TimedKillc.class}) UnitType missile;
//region neoplasm
public static @EntityDef({Unitc.class, Crawlc.class}) UnitType scuttler;
@ -598,7 +601,11 @@ public class UnitTypes{
shootSound = Sounds.explosion;
x = shootY = 0f;
mirror = false;
bullet = new BombBulletType(0f, 0f, "clear"){{
bullet = new BulletType(){{
collidesTiles = false;
collides = false;
hitSound = Sounds.explosion;
hitEffect = Fx.pulverize;
lifetime = 10f;
speed = 1f;
@ -1461,7 +1468,6 @@ public class UnitTypes{
health = 280;
accel = 0.4f;
rotateSpeed = 3.3f;
trailLength = 20;
rotateShooting = false;
armor = 2f;
@ -2413,6 +2419,9 @@ public class UnitTypes{
}});
}};
//endregion
//region erekir - core
//TODO bad name
evoke = new UnitType("evoke"){{
defaultController = BuilderAI::new;
@ -2438,8 +2447,8 @@ public class UnitTypes{
engineSize = 0;
setEnginesMirror(
new UnitEngine(21 / 4f, 19 / 4f, 2.2f, 45f),
new UnitEngine(23 / 4f, -22 / 4f, 2.2f, 315f)
new UnitEngine(21 / 4f, 19 / 4f, 2.2f, 45f),
new UnitEngine(23 / 4f, -22 / 4f, 2.2f, 315f)
);
weapons.add(new Weapon(){{
@ -2470,6 +2479,30 @@ public class UnitTypes{
smokeEffect = Fx.shootBigSmoke;
buildingDamageMultiplier = 0.4f;
}};
//TODO REMOVE
/*
unitSpawned = new MissileUnitType("duo"){{
trailScl = 1.1f;
speed = 3f;
weapons.add(new Weapon(){{
shootOnDeath = true;
bullet = new BulletType(){{
collidesTiles = false;
collides = false;
hitSound = Sounds.explosion;
lifetime = 10f;
speed = 1f;
splashDamageRadius = 55f;
instantDisappear = true;
splashDamage = 90f;
killShooter = true;
hittable = false;
collidesAir = true;
}};
}});
}};*/
}});
}};
@ -2597,8 +2630,6 @@ public class UnitTypes{
}});
i ++;
}
}};
//endregion

View File

@ -69,7 +69,7 @@ abstract class CommanderComp implements Entityc, Posc{
units.clear();
Units.nearby(team, x, y, type.commandRadius, u -> {
if(u.isAI() && include.get(u) && u != self() && u.type.flying == type.flying && u.hitSize <= hitSize * 1.1f){
if(u.isAI() && include.get(u) && u != self() && u.type.flying == type.flying && u.hitSize <= hitSize * 1.1f && u.type.playerControllable){
units.add(u);
}
});

View File

@ -5,6 +5,7 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
@ -50,9 +51,9 @@ abstract class PosComp implements Position{
return tile == null ? Blocks.air : tile.block();
}
boolean onSolid(){
Tile tile = tileOn();
return tile == null || tile.solid();
@Nullable
Building buildOn(){
return world.buildWorld(x, y);
}
@Nullable
@ -60,6 +61,11 @@ abstract class PosComp implements Position{
return world.tileWorld(x, y);
}
boolean onSolid(){
Tile tile = tileOn();
return tile == null || tile.solid();
}
@Override
public float getX(){
return x;

View File

@ -0,0 +1,28 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
//basically just TimedComp but kills instead of removing.
@Component
abstract class TimedKillComp implements Entityc, Healthc, Scaled{
float time, lifetime;
//called last so pooling and removal happens then.
@MethodPriority(100)
@Override
public void update(){
time = Math.min(time + Time.delta, lifetime);
if(time >= lifetime){
kill();
}
}
@Override
public float fin(){
return time / lifetime;
}
}

View File

@ -1,26 +0,0 @@
package mindustry.entities.comp;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@Component
abstract class TrailComp implements Unitc{
@Import UnitType type;
@Import float x, y, rotation;
transient Trail trail = new Trail(6);
@Override
public void update(){
trail.length = type.trailLength;
float scale = elevation();
float offset = type.engineOffset/2f + type.engineOffset/2f*scale;
float cx = x + Angles.trnsx(rotation + 180, offset), cy = y + Angles.trnsy(rotation + 180, offset);
trail.update(cx, cy);
}
}

View File

@ -20,6 +20,7 @@ import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
@ -46,6 +47,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
boolean spawnedByCore;
double flag;
transient @Nullable Trail trail;
transient float shadowAlpha = -1f;
transient Seq<Ability> abilities = new Seq<>(0);
transient float healTime;
@ -339,6 +342,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public void remove(){
team.data().updateCount(type, -1);
controller.removed(self());
//make sure trail doesn't just go poof
if(trail != null && trail.size() > 0){
Fx.trailFade.at(x, y, trail.width(), team.color, trail.copy());
}
}
@Override
@ -390,6 +398,16 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
if(trail != null){
trail.length = type.trailLength;
float scale = elevation();
float offset = type.engineOffset/2f + type.engineOffset/2f*scale;
float cx = x + Angles.trnsx(rotation + 180, offset), cy = y + Angles.trnsy(rotation + 180, offset);
trail.update(cx, cy);
}
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f) * dragMultiplier * state.rules.dragMultiplier;
//apply knockback based on spawns

View File

@ -10,7 +10,7 @@ public class Trail{
public int length;
private final FloatSeq points;
private float lastX = -1, lastY = -1, lastAngle = -1, counter = 0f;
private float lastX = -1, lastY = -1, lastAngle = -1, counter = 0f, lastW = 0f;
public Trail(int length){
this.length = length;
@ -26,6 +26,10 @@ public class Trail{
return out;
}
public float width(){
return lastW;
}
public void clear(){
points.clear();
}
@ -49,23 +53,43 @@ public class Trail{
public void draw(Color color, float width){
Draw.color(color);
float[] items = points.items;
float lx = lastX, ly = lastY, lastAngle = this.lastAngle;
float lastAngle = this.lastAngle;
float size = width / (points.size / 3);
for(int i = 0; i < points.size - 3; i+= 3){
float x1 = items[i], y1 = items[i + 1], w1 = items[i + 2],
x2 = items[i + 3], y2 = items[i + 4], w2 = items[i + 5];
float size = width / (points.size/3);
float z1 = lastAngle;
float z2 = -Angles.angleRad(x2, y2, lx, ly);
for(int i = 0; i < points.size; i += 3){
float x1 = items[i], y1 = items[i + 1], w1 = items[i + 2];
float x2, y2, w2;
//last position is always lastX/Y/W
if(i < points.size - 3){
x2 = items[i + 3];
y2 = items[i + 4];
w2 = items[i + 5];
}else{
x2 = lastX;
y2 = lastY;
w2 = lastW;
}
float z2 = -Angles.angleRad(x1, y1, x2, y2);
//end of the trail (i = 0) has the same angle as the next.
float z1 = i == 0 ? z2 : lastAngle;
if(w1 <= 0.001f || w2 <= 0.001f) continue;
float cx = Mathf.sin(z1) * i/3f * size * w1, cy = Mathf.cos(z1) * i/3f * size * w1,
nx = Mathf.sin(z2) * (i/3f + 1) * size * w2, ny = Mathf.cos(z2) * (i/3f + 1) * size * w2;
Fill.quad(x1 - cx, y1 - cy, x1 + cx, y1 + cy, x2 + nx, y2 + ny, x2 - nx, y2 - ny);
float
cx = Mathf.sin(z1) * i/3f * size * w1,
cy = Mathf.cos(z1) * i/3f * size * w1,
nx = Mathf.sin(z2) * (i/3f + 1) * size * w2,
ny = Mathf.cos(z2) * (i/3f + 1) * size * w2;
Fill.quad(
x1 - cx, y1 - cy,
x1 + cx, y1 + cy,
x2 + nx, y2 + ny,
x2 - nx, y2 - ny
);
lastAngle = z2;
lx = x2;
ly = y2;
}
Draw.reset();
@ -89,19 +113,21 @@ public class Trail{
/** Adds a new point to the trail at intervals. */
public void update(float x, float y, float width){
if((counter += Time.delta) >= 0.99f){
//TODO fix longer trails at low FPS
if((counter += Time.delta) >= 1f){
if(points.size > length*3){
points.removeRange(0, 2);
}
lastAngle = -Angles.angleRad(x, y, lastX, lastY);
points.add(x, y, width);
lastX = x;
lastY = y;
counter = 0f;
counter %= 1f;
}
//update last position regardless, so it joins
lastAngle = -Angles.angleRad(x, y, lastX, lastY);
lastX = x;
lastY = y;
lastW = width;
}
}

View File

@ -0,0 +1,30 @@
package mindustry.type;
import mindustry.ai.types.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
/** Field template for unit types. No new functionality. */
public class MissileUnitType extends UnitType{
public MissileUnitType(String name){
super(name);
playerControllable = false;
createWreck = false;
logicControllable = false;
isCounted = false;
useUnitCap = false;
allowedInPayloads = false;
defaultController = MissileAI::new;
flying = true;
constructor = TimedKillUnit::create;
envEnabled = Env.any;
envDisabled = Env.none;
trailLength = 7;
hidden = true;
rotateSpeed = 2f;
range = 30f;
//TODO weapons, etc
}
}

View File

@ -43,7 +43,7 @@ public class UnitType extends UnlockableContent{
/** If true, the unit is always at elevation 1. */
public boolean flying;
/** If `flying` and this is true, the unit can appear on the title screen */
/** If `flying` and this is true, the unit can appear on the title screen. TODO remove with new menu*/
public boolean onTitleScreen = true;
/** Creates a new instance of this unit class. */
public Prov<? extends Unit> constructor;
@ -60,6 +60,7 @@ public class UnitType extends UnlockableContent{
public float speed = 1.1f, boostMultiplier = 1f, rotateSpeed = 5f, baseRotateSpeed = 5f;
public float drag = 0.3f, accel = 0.5f, landShake = 0f, rippleScale = 1f, riseSpeed = 0.08f, fallSpeed = 0.018f;
public float health = 200f, range = -1, miningRange = 70f, armor = 0f, maxRange = -1f, buildRange = Vars.buildingRange;
public float lifetime = 60f * 5f; //for missiles only
public float crashDamageMultiplier = 1f;
public boolean targetAir = true, targetGround = true;
public boolean faceTarget = true, rotateShooting = true, isCounted = true, lowAltitude = false, circleTarget = false;
@ -135,7 +136,7 @@ public class UnitType extends UnlockableContent{
public float lightRadius = -1f, lightOpacity = 0.6f;
public Color lightColor = Pal.powerLight;
public boolean drawCell = true, drawItems = true, drawShields = true, drawBody = true;
public int trailLength = 3;
public int trailLength = 0;
public float trailX = 4f, trailY = -3f, trailScl = 1f;
/** Whether the unit can heal blocks. Initialized in init() */
public boolean canHeal = false;
@ -182,6 +183,9 @@ public class UnitType extends UnlockableContent{
unit.ammo = ammoCapacity; //fill up on ammo upon creation
unit.elevation = flying ? 1f : 0;
unit.heal();
if(unit instanceof TimedKillc u){
u.lifetime(lifetime);
}
return unit;
}
@ -811,9 +815,8 @@ public class UnitType extends UnlockableContent{
float scale = unit.elevation;
float offset = engineOffset/2f + engineOffset/2f*scale;
if(unit instanceof Trailc){
Trail trail = ((Trailc)unit).trail();
trail.draw(unit.team.color, (engineSize + Mathf.absin(Time.time, 2f, engineSize / 4f) * scale) * trailScl);
if(trailLength > 0 && !naval){
drawTrail(unit);
}
Draw.color(engineColor == null ? unit.team.color : engineColor);
@ -831,6 +834,14 @@ public class UnitType extends UnlockableContent{
Draw.color();
}
public void drawTrail(Unit unit){
if(unit.trail == null){
unit.trail = new Trail(trailLength);
}
Trail trail = unit.trail;
trail.draw(unit.team.color, (engineSize + Mathf.absin(Time.time, 2f, engineSize / 4f) * unit.elevation) * trailScl);
}
public void drawEngines(Unit unit){
if(!unit.isFlying()) return;

View File

@ -29,6 +29,8 @@ public class Weapon implements Cloneable{
public String name;
/** bullet shot */
public BulletType bullet = Bullets.standardCopper;
/** unit, to spawn at bullet, e.g. missile. TODO prevent bullet creation */
public @Nullable UnitType unitSpawned;
/** shell ejection effect */
public Effect ejectEffect = Fx.none;
/** whether to consume ammo when ammo is enabled in rules */
@ -403,13 +405,24 @@ public class Weapon implements Cloneable{
unit.apply(shootStatus, shootStatusDuration);
}
protected Bullet bullet(Unit unit, float shootX, float shootY, float angle, float lifescl){
protected @Nullable Bullet bullet(Unit unit, float shootX, float shootY, float angle, float lifescl){
float xr = Mathf.range(xRand);
float
x = shootX + Angles.trnsx(angle, 0, xr),
y = shootY + Angles.trnsy(angle, 0, xr);
return bullet.create(unit, unit.team,
shootX + Angles.trnsx(angle, 0, xr),
shootY + Angles.trnsy(angle, 0, xr),
angle, (1f - velocityRnd) + Mathf.random(velocityRnd), lifescl);
if(unitSpawned == null){
return bullet.create(unit, unit.team, x, y, angle, (1f - velocityRnd) + Mathf.random(velocityRnd), lifescl);
}else{
Unit spawned = unitSpawned.create(unit.team);
spawned.set(x, y);
spawned.rotation = angle;
//immediately spawn at top speed, since it was launched
spawned.vel.trns(angle, unitSpawned.speed);
spawned.add();
//TODO assign AI target here?
return null;
}
}
public Weapon copy(){

View File

@ -1,4 +1,18 @@
package mindustry.world.blocks.defense;
public class DirectionalForceProjector{
public class DirectionalForceProjector extends ForceProjector{
public DirectionalForceProjector(String name){
super(name);
consumeCoolant = false;
}
public class DirectionalForceProjectorBuild extends ForceBuild{
@Override
public void deflectBullets(){
//TODO
}
}
}

View File

@ -32,13 +32,15 @@ public class ForceProjector extends Block{
public float cooldownNormal = 1.75f;
public float cooldownLiquid = 1.5f;
public float cooldownBrokenBase = 0.35f;
public float coolantConsumption = 0.1f;
public boolean consumeCoolant = true;
public Effect absorbEffect = Fx.absorb;
public Effect shieldBreakEffect = Fx.shieldBreak;
public @Load("@-top") TextureRegion topRegion;
static ForceBuild paramEntity;
static Effect paramEffect;
static final Cons<Bullet> shieldConsumer = trait -> {
protected static ForceBuild paramEntity;
protected static Effect paramEffect;
protected static final Cons<Bullet> shieldConsumer = trait -> {
if(trait.team != paramEntity.team && trait.type.absorbable && Intersector.isInsideHexagon(paramEntity.x, paramEntity.y, paramEntity.realRadius() * 2f, trait.x(), trait.y())){
trait.absorb();
paramEffect.at(trait);
@ -55,10 +57,13 @@ public class ForceProjector extends Block{
hasPower = true;
hasLiquids = true;
hasItems = true;
envEnabled |= Env.space;
ambientSound = Sounds.shield;
ambientSoundVolume = 0.08f;
consumes.add(new ConsumeCoolant(0.1f)).boost().update(false);
envEnabled |= Env.space;
if(consumeCoolant){
consumes.add(new ConsumeCoolant(coolantConsumption)).boost().update(false);
}
}
@Override
@ -80,12 +85,17 @@ public class ForceProjector extends Block{
@Override
public void setStats(){
stats.timePeriod = phaseUseTime;
boolean consItems = consumes.has(ConsumeType.item);
if(consItems) stats.timePeriod = phaseUseTime;
super.setStats();
stats.add(Stat.shieldHealth, shieldHealth, StatUnit.none);
stats.add(Stat.cooldownTime, (int) (shieldHealth / cooldownBrokenBase / 60f), StatUnit.seconds);
stats.add(Stat.boostEffect, phaseRadiusBoost / tilesize, StatUnit.blocks);
stats.add(Stat.boostEffect, phaseShieldBoost, StatUnit.shieldHealth);
if(consItems){
stats.add(Stat.boostEffect, phaseRadiusBoost / tilesize, StatUnit.blocks);
stats.add(Stat.boostEffect, phaseShieldBoost, StatUnit.shieldHealth);
}
}
@Override
@ -146,9 +156,9 @@ public class ForceProjector extends Block{
warmup = Mathf.lerpDelta(warmup, efficiency(), 0.1f);
if(buildup > 0){
if(buildup > 0 && consumes.has(ConsumeType.liquid)){
float scale = !broken ? cooldownNormal : cooldownBrokenBase;
ConsumeLiquidFilter cons = consumes.get(ConsumeType.liquid);
Consume cons = consumes.get(ConsumeType.liquid);
if(cons.valid(this)){
cons.update(this);
scale *= (cooldownLiquid * (1f + (liquids.current().heatCapacity - 0.4f) * 0.9f));