Improved vtol effects, added state machines for behavior

This commit is contained in:
Anuken
2018-04-29 15:31:13 -04:00
parent 37fcd81e87
commit 9641a388e2
19 changed files with 334 additions and 83 deletions
@@ -56,6 +56,7 @@ public abstract class Unit extends SyncEntity implements SerializableEntity {
stream.writeShort((short)health);
stream.writeByte(status.current().id);
stream.writeFloat(status.getTime());
inventory.write(stream);
}
@Override
@@ -67,6 +68,7 @@ public abstract class Unit extends SyncEntity implements SerializableEntity {
byte effect = stream.readByte();
float etime = stream.readFloat();
this.inventory.read(stream);
this.team = Team.values()[team];
this.health = health;
this.x = x;
@@ -125,7 +127,10 @@ public abstract class Unit extends SyncEntity implements SerializableEntity {
damage(health + 1, false);
}
float px = x, py = y;
move(velocity.x / getMass() * floor.speedMultiplier * Timers.delta(), velocity.y / getMass() * floor.speedMultiplier * Timers.delta());
if(Math.abs(px - x) <= 0.0001f) velocity.x = 0f;
if(Math.abs(py - y) <= 0.0001f) velocity.y = 0f;
}
velocity.scl(Mathf.clamp(1f-drag* floor.dragMultiplier* Timers.delta()));
@@ -1,7 +1,14 @@
package io.anuke.mindustry.entities;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.resource.*;
import io.anuke.mindustry.resource.AmmoEntry;
import io.anuke.mindustry.resource.AmmoType;
import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.resource.ItemStack;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class UnitInventory {
private Array<AmmoEntry> ammos = new Array<>();
@@ -14,6 +21,33 @@ public class UnitInventory {
this.ammoCapacity = ammoCapacity;
}
public void write(DataOutputStream stream) throws IOException {
stream.writeInt(item == null ? 0 : item.amount);
stream.writeByte(item == null ? 0 : item.item.id);
stream.writeInt(totalAmmo);
stream.writeByte(ammos.size);
for(int i = 0; i < ammos.size; i ++){
stream.writeByte(ammos.get(i).type.id);
stream.writeInt(ammos.get(i).amount);
}
}
public void read(DataInputStream stream) throws IOException {
int iamount = stream.readInt();
byte iid = stream.readByte();
this.totalAmmo = stream.readInt();
byte ammoa = stream.readByte();
for(int i = 0; i < ammoa; i ++){
byte aid = stream.readByte();
int am = stream.readInt();
ammos.add(new AmmoEntry(AmmoType.getByID(aid), am));
}
if(iamount != 0){
item = new ItemStack(Item.getByID(iid), iamount);
}
}
public AmmoType getAmmo() {
return ammos.size == 0 ? null : ammos.peek().type;
}
@@ -4,9 +4,11 @@ import io.anuke.mindustry.ai.OptimizedPathFinder;
import io.anuke.mindustry.ai.SmoothGraphPath;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.resource.Item;
import io.anuke.mindustry.world.flags.BlockFlag;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Effects.Effect;
import io.anuke.ucore.entities.Entity;
@@ -25,6 +27,7 @@ public class BaseUnit extends Unit{
public UnitType type;
public Timer timer = new Timer(5);
public float walkTime = 0f;
public StateMachine state = new StateMachine();
public Entity target;
protected OptimizedPathFinder finder;
@@ -46,7 +49,12 @@ public class BaseUnit extends Unit{
public void effectAt(Effect effect, float rotation, float dx, float dy){
Effects.effect(effect,
x + Angles.trnsx(rotation, dx, dy),
y + Angles.trnsy(rotation, dx, dy), Mathf.atan2(dx, dy));
y + Angles.trnsy(rotation, dx, dy), Mathf.atan2(dx, dy) + rotation);
}
public boolean targetHasFlag(BlockFlag flag){
return target instanceof TileEntity &&
((TileEntity)target).tile.block().flags.contains(flag);
}
@Override
@@ -129,6 +137,7 @@ public class BaseUnit extends Unit{
hitbox.solid = true;
hitbox.setSize(type.hitsize);
hitboxTile.setSize(type.hitsizeTile);
state.set(this, type.getStartState());
if(!isFlying()){
finder = new OptimizedPathFinder();
@@ -1,16 +1,9 @@
package io.anuke.mindustry.entities.units;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.game.TeamInfo.TeamData;
import io.anuke.mindustry.resource.AmmoType;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.graphics.Draw;
import static io.anuke.mindustry.Vars.state;
public class FlyingUnitType extends UnitType {
protected static Vector2 vec = new Vector2();
@@ -29,11 +22,7 @@ public class FlyingUnitType extends UnitType {
super.update(unit);
unit.rotation = unit.velocity.angle();
if(unit.velocity.len() > 0.2f && unit.timer.get(timerBoost, 2f)){
//Effects.effect(UnitFx.vtolHover, unit.x + Angles.trnsx(unit.rotation + 180f, boosterLength),
// unit.y + Angles.trnsy(unit.rotation + 180f, boosterLength));
}
unit.state.update(unit);
}
@Override
@@ -47,51 +36,6 @@ public class FlyingUnitType extends UnitType {
@Override
public void behavior(BaseUnit unit) {
vec.set(unit.target.x - unit.x, unit.target.y - unit.y);
float len = vec.len();
float circleLength = 40f;
if(vec.len() < circleLength){
vec.rotate((circleLength-vec.len())/circleLength * 180f);
}
vec.setLength(speed * Timers.delta());
unit.velocity.add(vec); //TODO clamp it so it doesn't glitch out at low fps
if(unit.inventory.hasAmmo() && unit.timer.get(timerReload, reload) && len < range){
AmmoType ammo = unit.inventory.getAmmo();
unit.inventory.useAmmo();
shoot(unit, ammo, unit.rotation, 4f);
}
}
@Override
public void updateTargeting(BaseUnit unit) {
if(!unit.timer.get(timerTarget, 20)) return;
ObjectSet<TeamData> teams = state.teams.enemyDataOf(unit.team);
Tile closest = null;
float cdist = 0f;
for(TeamData data : teams){
for(Tile tile : data.cores){
float dist = Vector2.dst(unit.x, unit.y, tile.drawx(), tile.drawy());
if(closest == null || dist < cdist){
closest = tile;
cdist = dist;
}
}
}
if(closest != null){
unit.target = closest.entity;
}else{
unit.target = null;
}
}
}
@@ -0,0 +1,15 @@
package io.anuke.mindustry.entities.units;
public class StateMachine {
private UnitState state;
public void update(BaseUnit unit){
if(state != null) state.update(unit);
}
public void set(BaseUnit unit, UnitState next){
if(state != null) state.exited(unit);
this.state = next;
if(next != null) next.entered(unit);
}
}
@@ -0,0 +1,7 @@
package io.anuke.mindustry.entities.units;
public interface UnitState {
default void entered(BaseUnit unit){}
default void exited(BaseUnit unit){}
default void update(BaseUnit unit){}
}
@@ -5,7 +5,6 @@ import com.badlogic.gdx.utils.ObjectMap;
import io.anuke.mindustry.content.fx.ExplosionFx;
import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetEvents;
import io.anuke.mindustry.resource.AmmoType;
@@ -59,6 +58,10 @@ public abstract class UnitType {
public abstract void draw(BaseUnit unit);
public UnitState getStartState(){
return null;
}
public void drawUnder(BaseUnit unit){}
public boolean isFlying(){
@@ -94,10 +97,6 @@ public abstract class UnitType {
if(unit.target == null || (unit.target instanceof Unit && ((Unit)unit.target).isDead())){
unit.target = null;
}
if(unit.timer.get(timerTarget, 20)){
unit.target = Units.getClosestEnemy(unit.team, unit.x, unit.y, range, e -> true);
}
}
public void shoot(BaseUnit unit, AmmoType type, float rotation, float translation){
@@ -1,26 +1,49 @@
package io.anuke.mindustry.entities.units.types;
import io.anuke.mindustry.content.AmmoTypes;
import io.anuke.mindustry.content.fx.UnitFx;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.entities.units.FlyingUnitType;
import io.anuke.mindustry.entities.units.UnitState;
import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.resource.AmmoType;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.flags.BlockFlag;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.world;
public class Vtol extends FlyingUnitType {
public Vtol(){
super("vtol");
setAmmo(AmmoTypes.basicIron);
speed = 0.3f;
maxVelocity = 2f;
}
@Override
public void drawUnder(BaseUnit unit) {
float rotation = unit.rotation - 90;
float scl = 0.6f + Mathf.absin(Timers.time(), 1f, 0.3f);
float dy = -6f*scl;
Draw.color(Palette.lighterOrange, Palette.lightFlame, Mathf.absin(Timers.time(), 3f, 0.7f));
Draw.rect("vtol-flame",
unit.x + Angles.trnsx(rotation, 0, dy),
unit.y + Angles.trnsy(rotation, 0, dy), Mathf.atan2(0, dy) + rotation);
Draw.color();
/*
for(int i : Mathf.signs) {
float rotation = unit.rotation - 90;
@@ -32,16 +55,14 @@ public class Vtol extends FlyingUnitType {
Draw.rect("vtol-flame",
unit.x + Angles.trnsx(rotation, dx, dy),
unit.y + Angles.trnsy(rotation, dx, dy), Mathf.atan2(dx, dy));
unit.y + Angles.trnsy(rotation, dx, dy), Mathf.atan2(dx, dy) + rotation);
Draw.rect("vtol-flame",
unit.x + Angles.trnsx(rotation, dx2, dy2),
unit.y + Angles.trnsy(rotation, dx2, dy2), Mathf.atan2(dx2, dy2));
unit.y + Angles.trnsy(rotation, dx2, dy2), Mathf.atan2(dx2, dy2) + rotation);
Draw.color();
}
}*/
}
@Override
@@ -50,8 +71,8 @@ public class Vtol extends FlyingUnitType {
Draw.rect(name, unit.x, unit.y, unit.rotation - 90);
for(int i : Mathf.signs){
Draw.rect(name + "-booster-1", unit.x + i, unit.y, 12*i, 12, unit.rotation - 90);
Draw.rect(name + "-booster-2", unit.x + i, unit.y, 12*i, 12, unit.rotation - 90);
Draw.rect(name + "-booster-1", unit.x, unit.y, 12*i, 12, unit.rotation - 90);
Draw.rect(name + "-booster-2", unit.x, unit.y, 12*i, 12, unit.rotation - 90);
}
Draw.alpha(1f);
@@ -61,14 +82,103 @@ public class Vtol extends FlyingUnitType {
public void update(BaseUnit unit) {
super.update(unit);
unit.x += Mathf.sin(Timers.time() + unit.id*999, 25f, 0.07f);
unit.y += Mathf.cos(Timers.time() + unit.id*999, 25f, 0.07f);
unit.rotation += Mathf.sin(Timers.time() + unit.id*99, 10f, 8f);
unit.x += Mathf.sin(Timers.time() + unit.id * 999, 25f, 0.07f);
unit.y += Mathf.cos(Timers.time() + unit.id * 999, 25f, 0.07f);
if(unit.velocity.len() <= 0.2f){
unit.rotation += Mathf.sin(Timers.time() + unit.id * 99, 10f, 8f);
}
if(unit.timer.get(timerBoost, 2)){
unit.effectAt(UnitFx.vtolHover, unit.rotation + 180f, 4f, 0);
}
}
@Override
public void behavior(BaseUnit unit) {
//super.behavior(unit);
public UnitState getStartState(){
return resupply;
}
void circle(BaseUnit unit, float circleLength){
vec.set(unit.target.x - unit.x, unit.target.y - unit.y);
if(vec.len() < circleLength){
vec.rotate((circleLength-vec.len())/circleLength * 180f);
}
vec.setLength(speed * Timers.delta());
unit.velocity.add(vec);
}
void attack(BaseUnit unit, float circleLength){
vec.set(unit.target.x - unit.x, unit.target.y - unit.y);
float ang = unit.angleTo(unit.target);
float diff = Angles.angleDist(ang, unit.rotation);
if(diff > 100f && vec.len() < circleLength){
vec.setAngle(unit.velocity.angle());
}else{
vec.setAngle(Mathf.slerpDelta(unit.velocity.angle(), vec.angle(), 0.4f));
}
vec.setLength(speed*Timers.delta());
unit.velocity.add(vec);
}
public final UnitState
resupply = new UnitState(){
public void entered(BaseUnit unit) {
unit.target = null;
}
public void update(BaseUnit unit) {
if(unit.inventory.totalAmmo() + 10 >= unit.inventory.ammoCapacity()){
unit.state.set(unit, attack);
}else if(!unit.targetHasFlag(BlockFlag.resupplyPoint)){
if(unit.timer.get(timerTarget, 20)) {
Tile target = Geometry.findClosest(unit.x, unit.y, world.indexer().getAllied(unit.team, BlockFlag.resupplyPoint));
if (target != null) unit.target = target.entity;
}
}else{
circle(unit, 20f);
}
}
},
attack = new UnitState(){
public void entered(BaseUnit unit) {
unit.target = null;
}
public void update(BaseUnit unit) {
if(!unit.inventory.hasAmmo()) {
unit.state.set(unit, resupply);
}else if (unit.target == null){
if(unit.timer.get(timerTarget, 20)) {
Unit closest = Units.getClosestEnemy(unit.team, unit.x, unit.y,
unit.inventory.getAmmo().getRange(), other -> true);
if(closest != null){
unit.target = closest;
}else {
Tile target = Geometry.findClosest(unit.x, unit.y, world.indexer().getEnemy(unit.team, BlockFlag.resupplyPoint));
if (target != null) unit.target = target.entity;
}
}
}else{
attack(unit, 150f);
if (unit.timer.get(timerReload, 7) && Mathf.angNear(unit.angleTo(unit.target), unit.rotation, 16f)
&& unit.distanceTo(unit.target) < unit.inventory.getAmmo().getRange()) {
AmmoType ammo = unit.inventory.getAmmo();
unit.inventory.useAmmo();
shoot(unit, ammo, unit.rotation, 4f);
}
}
}
};
}