More intelligent suicide units / Bugfixes
This commit is contained in:
@@ -25,13 +25,11 @@ public class GroundAI extends AIController{
|
|||||||
Tilec core = unit.closestEnemyCore();
|
Tilec core = unit.closestEnemyCore();
|
||||||
|
|
||||||
if(core != null){
|
if(core != null){
|
||||||
float dst = unit.dst(core);
|
if(unit.within(core,unit.range() / 1.1f)){
|
||||||
|
|
||||||
if(dst < unit.range() / 1.1f){
|
|
||||||
target = core;
|
target = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dst > unit.range() * 0.5f){
|
if(!unit.within(core, unit.range() * 0.5f)){
|
||||||
moveToCore(FlagTarget.enemyCores);
|
moveToCore(FlagTarget.enemyCores);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
core/src/mindustry/ai/types/SuicideAI.java
Normal file
70
core/src/mindustry/ai/types/SuicideAI.java
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package mindustry.ai.types;
|
||||||
|
|
||||||
|
import mindustry.*;
|
||||||
|
import mindustry.ai.Pathfinder.*;
|
||||||
|
import mindustry.entities.*;
|
||||||
|
import mindustry.gen.*;
|
||||||
|
import mindustry.world.*;
|
||||||
|
|
||||||
|
public class SuicideAI extends GroundAI{
|
||||||
|
static boolean blockedByBlock;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(){
|
||||||
|
|
||||||
|
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
|
||||||
|
target = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(retarget()){
|
||||||
|
targetClosest();
|
||||||
|
}
|
||||||
|
|
||||||
|
Tilec core = unit.closestEnemyCore();
|
||||||
|
|
||||||
|
boolean rotate = false, shoot = false;
|
||||||
|
|
||||||
|
if(!Units.invalidateTarget(target, unit, unit.range())){
|
||||||
|
rotate = true;
|
||||||
|
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
|
||||||
|
(target instanceof Tilec ? ((Tilec)target).block().size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||||
|
|
||||||
|
if(unit.type().hasWeapons()){
|
||||||
|
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedByBlock = false;
|
||||||
|
|
||||||
|
//raycast for target
|
||||||
|
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
|
||||||
|
Tile tile = Vars.world.tile(x, y);
|
||||||
|
if(tile != null && tile.entity == target) return false;
|
||||||
|
if(tile != null && tile.entity != null && tile.entity.team() != unit.team()){
|
||||||
|
blockedByBlock = true;
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
return tile == null || tile.solid();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//shoot when there's an enemy block in the way
|
||||||
|
if(blockedByBlock){
|
||||||
|
shoot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!blocked){
|
||||||
|
//move towards target directly
|
||||||
|
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
if(core != null){
|
||||||
|
moveToCore(FlagTarget.enemyCores);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(unit.moving()) unit.lookAt(unit.vel().angle());
|
||||||
|
}
|
||||||
|
|
||||||
|
unit.controlWeapons(rotate, shoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ public class Bullets implements ContentList{
|
|||||||
waterShot, cryoShot, slagShot, oilShot,
|
waterShot, cryoShot, slagShot, oilShot,
|
||||||
|
|
||||||
//environment, misc.
|
//environment, misc.
|
||||||
fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag, eruptorShot,
|
fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag,
|
||||||
|
|
||||||
//bombs
|
//bombs
|
||||||
bombExplosive, bombIncendiary, bombOil;
|
bombExplosive, bombIncendiary, bombOil;
|
||||||
@@ -544,13 +544,6 @@ public class Bullets implements ContentList{
|
|||||||
drag = 0.03f;
|
drag = 0.03f;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
eruptorShot = new LiquidBulletType(Liquids.slag){{
|
|
||||||
damage = 2;
|
|
||||||
speed = 2.1f;
|
|
||||||
drag = 0.02f;
|
|
||||||
shootEffect = Fx.shootSmall;
|
|
||||||
}};
|
|
||||||
|
|
||||||
oilShot = new LiquidBulletType(Liquids.oil){{
|
oilShot = new LiquidBulletType(Liquids.oil){{
|
||||||
drag = 0.03f;
|
drag = 0.03f;
|
||||||
}};
|
}};
|
||||||
|
|||||||
@@ -125,10 +125,10 @@ public class Fx{
|
|||||||
|
|
||||||
stroke(3f * e.fout());
|
stroke(3f * e.fout());
|
||||||
color(e.color, Color.white, e.fin());
|
color(e.color, Color.white, e.fin());
|
||||||
beginLine();
|
|
||||||
|
|
||||||
linePoint(e.x, e.y);
|
beginLine();
|
||||||
lines.each(Lines::linePoint);
|
lines.each(Lines::linePoint);
|
||||||
|
linePoint(e.x, e.y);
|
||||||
endLine();
|
endLine();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|||||||
@@ -119,23 +119,28 @@ public class UnitTypes implements ContentList{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
crawler = new UnitType("crawler"){{
|
crawler = new UnitType("crawler"){{
|
||||||
|
defaultController = SuicideAI::new;
|
||||||
|
|
||||||
speed = 0.8f;
|
speed = 0.8f;
|
||||||
hitsize = 8f;
|
hitsize = 8f;
|
||||||
health = 120;
|
health = 140;
|
||||||
sway = 0.25f;
|
sway = 0.25f;
|
||||||
|
range = 40f;
|
||||||
|
|
||||||
weapons.add(new Weapon(){{
|
weapons.add(new Weapon(){{
|
||||||
reload = 12f;
|
reload = 12f;
|
||||||
shootCone = 180f;
|
shootCone = 180f;
|
||||||
ejectEffect = Fx.none;
|
ejectEffect = Fx.none;
|
||||||
shootSound = Sounds.explosion;
|
shootSound = Sounds.explosion;
|
||||||
bullet = new BombBulletType(2f, 3f, "clear"){{
|
bullet = new BombBulletType(0f, 0f, "clear"){{
|
||||||
hitEffect = Fx.pulverize;
|
hitEffect = Fx.pulverize;
|
||||||
lifetime = 30f;
|
lifetime = 10f;
|
||||||
speed = 1.1f;
|
speed = 1f;
|
||||||
splashDamageRadius = 55f;
|
splashDamageRadius = 55f;
|
||||||
instantDisappear = true;
|
instantDisappear = true;
|
||||||
splashDamage = 30f;
|
splashDamage = 30f;
|
||||||
killShooter = true;
|
killShooter = true;
|
||||||
|
hittable = false;
|
||||||
}};
|
}};
|
||||||
}});
|
}});
|
||||||
}};
|
}};
|
||||||
@@ -233,10 +238,16 @@ public class UnitTypes implements ContentList{
|
|||||||
reload = 10f;
|
reload = 10f;
|
||||||
alternate = true;
|
alternate = true;
|
||||||
ejectEffect = Fx.none;
|
ejectEffect = Fx.none;
|
||||||
bullet = Bullets.eruptorShot;
|
|
||||||
recoil = 1f;
|
recoil = 1f;
|
||||||
x = 7f;
|
x = 7f;
|
||||||
shootSound = Sounds.flame;
|
shootSound = Sounds.flame;
|
||||||
|
|
||||||
|
bullet = new LiquidBulletType(Liquids.slag){{
|
||||||
|
damage = 11;
|
||||||
|
speed = 2.3f;
|
||||||
|
drag = 0.02f;
|
||||||
|
shootEffect = Fx.shootSmall;
|
||||||
|
}};
|
||||||
}});
|
}});
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
@@ -207,47 +207,53 @@ public class Damage{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void tileDamage(Team team, int startx, int starty, int radius, float baseDamage){
|
public static void tileDamage(Team team, int startx, int starty, int baseRadius, float baseDamage){
|
||||||
bits.clear();
|
//tile damage is posted, so that destroying a block that causes a chain explosion will run in the next frame
|
||||||
propagation.clear();
|
//this prevents recursive damage calls from messing up temporary variables
|
||||||
int bitOffset = bits.width() / 2;
|
Core.app.post(() -> {
|
||||||
|
|
||||||
propagation.addFirst(PropCell.get((byte)0, (byte)0, (short)baseDamage));
|
bits.clear();
|
||||||
//clamp radius to fit bits
|
propagation.clear();
|
||||||
radius = Math.min(radius, bits.width() / 2);
|
int bitOffset = bits.width() / 2;
|
||||||
|
|
||||||
while(!propagation.isEmpty()){
|
propagation.addFirst(PropCell.get((byte)0, (byte)0, (short)baseDamage));
|
||||||
int prop = propagation.removeLast();
|
//clamp radius to fit bits
|
||||||
int x = PropCell.x(prop);
|
int radius = Math.min(baseRadius, bits.width() / 2);
|
||||||
int y = PropCell.y(prop);
|
|
||||||
int damage = PropCell.damage(prop);
|
|
||||||
//manhattan distance used for calculating falloff, results in a diamond pattern
|
|
||||||
int dst = Math.abs(x) + Math.abs(y);
|
|
||||||
|
|
||||||
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
|
while(!propagation.isEmpty()){
|
||||||
|
int prop = propagation.removeLast();
|
||||||
|
int x = PropCell.x(prop);
|
||||||
|
int y = PropCell.y(prop);
|
||||||
|
int damage = PropCell.damage(prop);
|
||||||
|
//manhattan distance used for calculating falloff, results in a diamond pattern
|
||||||
|
int dst = Math.abs(x) + Math.abs(y);
|
||||||
|
|
||||||
bits.set(bitOffset + x, bitOffset + y);
|
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
|
||||||
Tilec tile = world.ent(startx + x, starty + y);
|
|
||||||
|
|
||||||
if(scaledDamage <= 0 || tile == null) continue;
|
bits.set(bitOffset + x, bitOffset + y);
|
||||||
|
Tile tile = world.tile(startx + x, starty + y);
|
||||||
|
|
||||||
//apply damage to entity if needed
|
if(scaledDamage <= 0 || tile == null) continue;
|
||||||
if(tile.team() != team){
|
|
||||||
int health = (int)tile.health();
|
|
||||||
if(tile.health() > 0){
|
|
||||||
tile.damage(scaledDamage);
|
|
||||||
scaledDamage -= health;
|
|
||||||
|
|
||||||
if(scaledDamage <= 0) continue;
|
//apply damage to entity if needed
|
||||||
|
if(tile.entity != null && tile.team() != team){
|
||||||
|
int health = (int)tile.entity.health();
|
||||||
|
if(tile.entity.health() > 0){
|
||||||
|
tile.entity.damage(scaledDamage);
|
||||||
|
scaledDamage -= health;
|
||||||
|
|
||||||
|
if(scaledDamage <= 0) continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Point2 p : Geometry.d4){
|
||||||
|
if(!bits.get(bitOffset + x + p.x, bitOffset + y + p.y)){
|
||||||
|
propagation.addFirst(PropCell.get((byte)(x + p.x), (byte)(y + p.y), (short)scaledDamage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for(Point2 p : Geometry.d4){
|
|
||||||
if(!bits.get(bitOffset + x + p.x, bitOffset + y + p.y)){
|
|
||||||
propagation.addFirst(PropCell.get((byte)(x + p.x), (byte)(y + p.y), (short)scaledDamage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void completeDamage(Team team, float x, float y, float radius, float damage){
|
private static void completeDamage(Team team, float x, float y, float radius, float damage){
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class LiquidBulletType extends BulletType{
|
|||||||
hitEffect = Fx.hitLiquid;
|
hitEffect = Fx.hitLiquid;
|
||||||
smokeEffect = Fx.none;
|
smokeEffect = Fx.none;
|
||||||
shootEffect = Fx.none;
|
shootEffect = Fx.none;
|
||||||
drag = 0.009f;
|
drag = 0.001f;
|
||||||
knockback = 0.55f;
|
knockback = 0.55f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,14 @@ public class LiquidBulletType extends BulletType{
|
|||||||
public void draw(Bulletc b){
|
public void draw(Bulletc b){
|
||||||
Draw.color(liquid.color, Color.white, b.fout() / 100f);
|
Draw.color(liquid.color, Color.white, b.fout() / 100f);
|
||||||
|
|
||||||
Fill.circle(b.x(), b.y(), 0.5f + b.fout() * 2.5f);
|
Fill.circle(b.x(), b.y(), 3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void despawned(Bulletc b){
|
||||||
|
super.despawned(b);
|
||||||
|
|
||||||
|
hit(b, b.x(), b.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc{
|
|||||||
|
|
||||||
void setupWeapons(UnitType def){
|
void setupWeapons(UnitType def){
|
||||||
mounts = new WeaponMount[def.weapons.size];
|
mounts = new WeaponMount[def.weapons.size];
|
||||||
range = 0f;
|
range = def.range;
|
||||||
for(int i = 0; i < mounts.length; i++){
|
for(int i = 0; i < mounts.length; i++){
|
||||||
mounts[i] = new WeaponMount(def.weapons.get(i));
|
mounts[i] = new WeaponMount(def.weapons.get(i));
|
||||||
range = Math.max(range, def.weapons.get(i).bullet.range());
|
range = Math.max(range, def.weapons.get(i).bullet.range());
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
"height", world.height(),
|
"height", world.height(),
|
||||||
"viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(),
|
"viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(),
|
||||||
"controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name,
|
"controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name,
|
||||||
"nocores", state.rules.defaultTeam.cores().isEmpty()
|
"nocores", state.rules.defaultTeam.cores().isEmpty(),
|
||||||
|
"playerteam", player == null ? state.rules.defaultTeam.uid : player.team().uid
|
||||||
).merge(tags));
|
).merge(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +112,10 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
player.set(Tmp.v1);
|
player.set(Tmp.v1);
|
||||||
|
|
||||||
control.input.controlledType = content.getByName(ContentType.unit, map.get("controlledType", "<none>"));
|
control.input.controlledType = content.getByName(ContentType.unit, map.get("controlledType", "<none>"));
|
||||||
|
Team team = Team.get(map.getInt("playerteam", state.rules.defaultTeam.uid));
|
||||||
|
if(!net.client() && team != Team.derelict){
|
||||||
|
player.team(team);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
|
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
|
||||||
|
|||||||
Reference in New Issue
Block a user