Merging changes from private branch
This commit is contained in:
@@ -63,6 +63,11 @@ abstract class BlockUnitComp implements Unitc{
|
||||
return tile != null && tile.isValid();
|
||||
}
|
||||
|
||||
@Replace
|
||||
public boolean isAdded(){
|
||||
return tile != null && tile.isValid();
|
||||
}
|
||||
|
||||
@Replace
|
||||
public void team(Team team){
|
||||
if(tile != null && this.team != team){
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
|
||||
static final float warpDst = 30f;
|
||||
|
||||
@Import UnitType type;
|
||||
@Import float x, y;
|
||||
@Import Team team;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(!type.bounded) return;
|
||||
|
||||
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
|
||||
|
||||
//TODO hidden map rules only apply to player teams? should they?
|
||||
if(state.rules.limitMapArea && !team.isAI()){
|
||||
bot = state.rules.limitY * tilesize;
|
||||
left = state.rules.limitX * tilesize;
|
||||
top = state.rules.limitHeight * tilesize + bot;
|
||||
right = state.rules.limitWidth * tilesize + left;
|
||||
}
|
||||
|
||||
if(!net.client() || isLocal()){
|
||||
|
||||
float dx = 0f, dy = 0f;
|
||||
|
||||
//repel unit out of bounds
|
||||
if(x < left) dx += (-(x - left)/warpDst);
|
||||
if(y < bot) dy += (-(y - bot)/warpDst);
|
||||
if(x > right) dx -= (x - right)/warpDst;
|
||||
if(y > top) dy -= (y - top)/warpDst;
|
||||
|
||||
velAddNet(dx * Time.delta, dy * Time.delta);
|
||||
}
|
||||
|
||||
//clamp position if not flying
|
||||
if(isGrounded()){
|
||||
x = Mathf.clamp(x, left, right - tilesize);
|
||||
y = Mathf.clamp(y, bot, top - tilesize);
|
||||
}
|
||||
|
||||
//kill when out of bounds
|
||||
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
}
|
||||
|
||||
if(!(tile.build instanceof ConstructBuild cb)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true, true)){
|
||||
if(Build.checkNoUnitOverlap(current.block, current.x, current.y)){
|
||||
boolean hasAll = infinite || current.isRotation(team) ||
|
||||
//derelict repair
|
||||
|
||||
@@ -16,7 +16,6 @@ import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
@@ -47,7 +46,7 @@ import java.util.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
|
||||
@Component(base = true)
|
||||
@Component(base = true, genInterface = false)
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Sized, Senseable, Controllable, Settable{
|
||||
//region vars and initialization
|
||||
static final float timeToSleep = 60f * 1, recentDamageTime = 60f * 5f;
|
||||
@@ -63,7 +62,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
transient Tile tile;
|
||||
transient Block block;
|
||||
transient Seq<Building> proximity = new Seq<>(6);
|
||||
transient Seq<Building> proximity = new Seq<>(true, 6, Building.class);
|
||||
transient int cdump;
|
||||
transient int rotation;
|
||||
transient float payloadRotation;
|
||||
@@ -99,8 +98,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
private transient float timeScale = 1f, timeScaleDuration;
|
||||
private transient float dumpAccum;
|
||||
|
||||
private transient @Nullable SoundLoop sound;
|
||||
|
||||
private transient boolean sleeping;
|
||||
private transient float sleepTime;
|
||||
private transient boolean initialized;
|
||||
@@ -126,6 +123,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
add();
|
||||
}
|
||||
|
||||
checkAllowUpdate();
|
||||
created();
|
||||
|
||||
return self();
|
||||
@@ -136,10 +134,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
this.block = block;
|
||||
this.team = team;
|
||||
|
||||
if(block.loopSound != Sounds.none){
|
||||
sound = new SoundLoop(block.loopSound, block.loopSoundVolume);
|
||||
}
|
||||
|
||||
health = block.health;
|
||||
maxHealth(block.health);
|
||||
timer(new Interval(block.timers));
|
||||
@@ -342,13 +336,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public @Nullable Tile findClosestEdge(Position to, Boolf<Tile> solid){
|
||||
if(to == null) return null;
|
||||
Tile best = null;
|
||||
float mindst = 0f;
|
||||
for(var point : Edges.getEdges(block.size)){
|
||||
Tile other = Vars.world.tile(tile.x + point.x, tile.y + point.y);
|
||||
if(other != null && !solid.get(other) && (best == null || to.dst2(other) < mindst)){
|
||||
best = other;
|
||||
mindst = other.dst2(other);
|
||||
mindst = other.dst2(to);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
@@ -473,6 +468,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return lastDamageTime + recentDamageTime >= Time.time;
|
||||
}
|
||||
|
||||
public void eachEdge(Cons<Tile> cons){
|
||||
for(var edge : block.getEdges()){
|
||||
Tile other = world.tile(tile.x + edge.x, tile.y + edge.y);
|
||||
if(other != null){
|
||||
cons.get(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Building nearby(int dx, int dy){
|
||||
return world.build(tile.x + dx, tile.y + dy);
|
||||
}
|
||||
@@ -809,6 +813,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBeReplaced(Block other){
|
||||
return other.canReplace(block);
|
||||
}
|
||||
|
||||
public void handleItem(Building source, Item item){
|
||||
items.add(item, 1);
|
||||
}
|
||||
@@ -1066,7 +1074,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void incrementDump(int prox){
|
||||
cdump = ((cdump + 1) % prox);
|
||||
//this is possible if transferring an item changed a block
|
||||
if(prox != 0){
|
||||
cdump = ((cdump + 1) % prox);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used for dumping items. */
|
||||
@@ -1092,6 +1103,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** Called after this building is created in the world. May be called multiple times, or when adjacent buildings change. */
|
||||
//TODO ??? this is just onProximityUpdate ?
|
||||
public void onProximityAdded(){
|
||||
if(power != null){
|
||||
updatePowerGraph();
|
||||
@@ -1156,16 +1168,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return getProgressIncrease(1f) / edelta();
|
||||
}
|
||||
|
||||
/** @return whether this block should play its active sound.*/
|
||||
public boolean shouldActiveSound(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return volume cale of active sound. */
|
||||
public float activeSoundVolume(){
|
||||
return 1f;
|
||||
}
|
||||
|
||||
/** @return whether this block should play its idle sound.*/
|
||||
public boolean shouldAmbientSound(){
|
||||
return shouldConsume();
|
||||
@@ -1315,14 +1317,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void onRemoved(){
|
||||
}
|
||||
|
||||
/** Called every frame a unit is on this */
|
||||
/** Called every frame a unit is on this. Hovering/flying/steppy units do not apply. */
|
||||
public void unitOn(Unit unit){
|
||||
}
|
||||
|
||||
/** Called every frame a unit is on this. Applies to any unit. */
|
||||
public void unitOnAny(Unit unit){
|
||||
}
|
||||
|
||||
/** Called when a unit that spawned at this tile is removed. */
|
||||
public void unitRemoved(Unit unit){
|
||||
}
|
||||
|
||||
/** Called when a puddle is on this building. Only called at an interval (40 ticks). */
|
||||
public void puddleOn(Puddle puddle){
|
||||
|
||||
}
|
||||
|
||||
/** Called when arbitrary configuration is applied to a tile. */
|
||||
public void configured(@Nullable Unit builder, @Nullable Object value){
|
||||
//null is of type void.class; anonymous classes use their superclass.
|
||||
@@ -1372,6 +1383,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return block.itemCapacity;
|
||||
}
|
||||
|
||||
public void splashLiquid(Liquid liquid, float amount){
|
||||
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
|
||||
|
||||
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> {
|
||||
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
|
||||
if(other != null){
|
||||
Puddles.deposit(other, liquid, splash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when a block begins (not finishes!) deconstruction. The building is still present at this point. */
|
||||
public void onDeconstructed(@Nullable Unit builder){
|
||||
//deposit non-incinerable liquid on ground
|
||||
@@ -1383,9 +1407,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Called when the block is destroyed. The tile is still intact at this stage. */
|
||||
public void onDestroyed(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
|
||||
float explosiveness = block.baseExplosiveness;
|
||||
float flammability = 0f;
|
||||
@@ -1411,22 +1432,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
if(block.hasLiquids && state.rules.damageExplosions){
|
||||
|
||||
liquids.each((liquid, amount) -> {
|
||||
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
|
||||
|
||||
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> {
|
||||
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
|
||||
if(other != null){
|
||||
Puddles.deposit(other, liquid, splash);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
liquids.each(this::splashLiquid);
|
||||
}
|
||||
|
||||
//cap explosiveness so fluid tanks/vaults don't instakill units
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect);
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect, block.baseShake);
|
||||
|
||||
if(block.createRubble && !floor().solid && !floor().isLiquid){
|
||||
Effect.rubble(x, y, block.size);
|
||||
@@ -1657,8 +1667,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
damage = Damage.applyArmor(damage, block.armor);
|
||||
}
|
||||
|
||||
damage(other.team, damage);
|
||||
Events.fire(bulletDamageEvent.set(self(), other));
|
||||
damage(other, other.team, damage);
|
||||
|
||||
if(health <= 0 && !wasDead){
|
||||
Events.fire(new BuildingBulletDestroyEvent(self(), other));
|
||||
@@ -1681,6 +1690,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
/** Changes this building's team in a safe manner. */
|
||||
public void changeTeam(Team next){
|
||||
if(this.team == next) return;
|
||||
if(block.forceTeam != null) team = block.forceTeam;
|
||||
|
||||
Team last = this.team;
|
||||
boolean was = isValid();
|
||||
@@ -1693,10 +1703,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
indexer.addIndex(tile);
|
||||
Events.fire(teamChangeEvent.set(last, self()));
|
||||
}
|
||||
|
||||
checkAllowUpdate();
|
||||
}
|
||||
|
||||
public boolean canPickup(){
|
||||
return true;
|
||||
return block.canPickup;
|
||||
}
|
||||
|
||||
/** Called right before this building is picked up. */
|
||||
@@ -1764,6 +1776,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
public void onNearbyBuildAdded(Building other){}
|
||||
|
||||
public void consume(){
|
||||
for(Consume cons : block.consumers){
|
||||
cons.trigger(self());
|
||||
@@ -2094,22 +2108,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
stopSound();
|
||||
}
|
||||
|
||||
public void stopSound(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killed(){
|
||||
dead = true;
|
||||
Events.fire(new BlockDestroyEvent(tile));
|
||||
block.destroySound.at(tile);
|
||||
block.destroySound.at(tile, Mathf.random(block.destroyPitchMin, block.destroyPitchMax));
|
||||
onDestroyed();
|
||||
if(tile != emptyTile){
|
||||
tile.remove();
|
||||
@@ -2118,48 +2121,44 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
afterDestroyed();
|
||||
}
|
||||
|
||||
public void checkAllowUpdate(){
|
||||
if(!allowUpdate()){
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Final
|
||||
@Replace
|
||||
@Override
|
||||
public void update(){
|
||||
//TODO should just avoid updating buildings instead
|
||||
if(state.isEditor()) return;
|
||||
|
||||
//TODO refactor to timestamp-based system?
|
||||
if((timeScaleDuration -= Time.delta) <= 0f || !block.canOverdrive){
|
||||
timeScale = 1f;
|
||||
}
|
||||
|
||||
if(!allowUpdate()){
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if(!headless && !wasVisible && state.rules.fog && !inFogTo(player.team())){
|
||||
visibleFlags |= (1L << player.team().id);
|
||||
wasVisible = true;
|
||||
renderer.blocks.updateShadow(self());
|
||||
renderer.minimap.update(tile);
|
||||
}
|
||||
|
||||
//TODO separate system for sound? AudioSource, etc
|
||||
if(!headless){
|
||||
if(sound != null){
|
||||
sound.update(x, y, shouldActiveSound(), activeSoundVolume());
|
||||
}
|
||||
|
||||
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
|
||||
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
|
||||
}
|
||||
//TODO separate multithreaded system for sound? AudioSource, etc
|
||||
if(!headless && block.ambientSound != Sounds.none && shouldAmbientSound()){
|
||||
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
|
||||
}
|
||||
|
||||
updateConsumption();
|
||||
|
||||
//TODO just handle per-block instead
|
||||
if(enabled || !block.noUpdateDisabled){
|
||||
updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
/** When a block is newly revealed outside of camera view range, it is updated on the minimap. */
|
||||
public void updateFogVisibility(){
|
||||
if(!wasVisible && !inFogTo(player.team())){
|
||||
visibleFlags |= (1L << player.team().id);
|
||||
wasVisible = true;
|
||||
renderer.blocks.updateShadow(self());
|
||||
renderer.minimap.update(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Rect out){
|
||||
out.setCentered(x, y, block.size * tilesize, block.size * tilesize);
|
||||
|
||||
@@ -39,6 +39,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
|
||||
//setting this variable to true prevents lifetime from decreasing for a frame.
|
||||
transient boolean keepAlive;
|
||||
/** Unlike the owner, the shooter is the original entity that created this bullet. For a second-stage missile, the shooter would be the turret, but the owner would be the last missile stage.*/
|
||||
transient Entityc shooter;
|
||||
transient @Nullable Tile aimTile;
|
||||
transient float aimX, aimY;
|
||||
@@ -48,6 +49,9 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
transient @Nullable Trail trail;
|
||||
transient int frags;
|
||||
|
||||
transient Posc stickyTarget;
|
||||
transient float stickyX, stickyY, stickyRotation, stickyRotationOffset;
|
||||
|
||||
@Override
|
||||
public void getCollisions(Cons<QuadTree> consumer){
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
@@ -106,24 +110,43 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
@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
|
||||
&& !(other instanceof Unit f && !f.checkTarget(type.collidesAir, type.collidesGround))
|
||||
&& !(type.pierce && hasCollided(other.id())) && stickyTarget == null; //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();
|
||||
if(type.sticky){
|
||||
if(stickyTarget == null){
|
||||
//tunnel into the target a bit for better visuals
|
||||
this.x = x + vel.x;
|
||||
this.y = y + vel.y;
|
||||
stickTo(other);
|
||||
}
|
||||
}else{
|
||||
collided.add(other.id());
|
||||
}
|
||||
type.hit(self(), x, y);
|
||||
|
||||
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
public void stickTo(Posc other){
|
||||
lifetime += type.stickyExtraLifetime;
|
||||
//sticky bullets don't actually hit anything.
|
||||
stickyX = this.x - other.x();
|
||||
stickyY = this.y - other.y();
|
||||
stickyTarget = other;
|
||||
stickyRotationOffset = rotation;
|
||||
stickyRotation = (other instanceof Rotc rot ? rot.rotation() : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,9 +155,21 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
mover.move(self());
|
||||
}
|
||||
|
||||
if(type.accel != 0){
|
||||
vel.setLength(vel.len() + type.accel * Time.delta);
|
||||
}
|
||||
|
||||
type.update(self());
|
||||
|
||||
if(type.collidesTiles && type.collides && type.collidesGround){
|
||||
if(stickyTarget != null){
|
||||
//only stick to things that still exist in the world
|
||||
if(stickyTarget instanceof Healthc h && h.isValid()){
|
||||
float rotate = (stickyTarget instanceof Rotc rot ? rot.rotation() - stickyRotation : 0f);
|
||||
set(Tmp.v1.set(stickyX, stickyY).rotate(rotate).add(stickyTarget));
|
||||
this.rotation = rotate + stickyRotationOffset;
|
||||
vel.setAngle(this.rotation);
|
||||
}
|
||||
}else if(type.collidesTiles && type.collides && type.collidesGround){
|
||||
tileRaycast(World.toTile(lastX), World.toTile(lastY), tileX(), tileY());
|
||||
}
|
||||
|
||||
@@ -165,6 +200,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
(!build.block.underBullets ||
|
||||
//direct hit on correct tile
|
||||
(aimTile != null && aimTile.build == build) ||
|
||||
//bullet type allows hitting under bullets
|
||||
type.hitUnder ||
|
||||
//same team has no 'under build' mechanics
|
||||
(build.team == team) ||
|
||||
//a piercing bullet overshot the aim tile, it's fine to hit things now
|
||||
@@ -201,31 +238,46 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
&& 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(type.sticky){
|
||||
if(build.team != team){
|
||||
//stick to edge of block
|
||||
Vec2 hit = Geometry.raycastRect(lastX, lastY, x, y, Tmp.r1.setCentered(x * tilesize, y * tilesize, tilesize, tilesize));
|
||||
if(hit != null){
|
||||
this.x = hit.x;
|
||||
this.y = hit.y;
|
||||
}
|
||||
|
||||
if(build.team != team){
|
||||
remove = build.collision(self());
|
||||
}
|
||||
stickTo(build);
|
||||
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
boolean remove = false;
|
||||
float health = build.health;
|
||||
|
||||
if(build.team != team){
|
||||
remove = build.collision(self());
|
||||
}
|
||||
|
||||
if(!type.pierceBuilding){
|
||||
hit = true;
|
||||
remove();
|
||||
}else{
|
||||
collided.add(build.id);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -247,7 +299,11 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
public void draw(){
|
||||
Draw.z(type.layer);
|
||||
|
||||
type.draw(self());
|
||||
if(type.underwater){
|
||||
Drawf.underwater(() -> type.draw(self()));
|
||||
}else{
|
||||
type.draw(self());
|
||||
}
|
||||
type.drawLight(self());
|
||||
|
||||
Draw.reset();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -22,7 +20,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
@Import float x, y, speedMultiplier, rotation, hitSize;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
@Import Vec2 vel;
|
||||
|
||||
transient Floor lastDeepFloor;
|
||||
transient float lastCrawlSlowdown = 1f;
|
||||
@@ -31,13 +28,7 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return EntityCollisions::legsSolid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return Pathfinder.costLegs;
|
||||
return ignoreSolids() ? null : EntityCollisions::legsSolid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,6 +101,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
segmentRot = Angles.clampRange(segmentRot, rotation, type.segmentMaxRot);
|
||||
|
||||
crawlTime += vel.len() * Time.delta;
|
||||
crawlTime += deltaLen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import mindustry.entities.EntityCollisions.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@Component
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Hitboxc, Unitc{
|
||||
@Import float x, y;
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return isFlying() ? null : EntityCollisions::solid;
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,12 +59,16 @@ abstract class EntityComp{
|
||||
|
||||
}
|
||||
|
||||
void beforeWrite(){
|
||||
|
||||
}
|
||||
|
||||
void afterRead(){
|
||||
|
||||
}
|
||||
|
||||
/** Called after *all* entities are read. */
|
||||
void afterAllRead(){
|
||||
//called after all entities have been read (useful for ID resolution)
|
||||
void afterReadAll(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
|
||||
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
|
||||
|
||||
@Import float x, y, speedMultiplier, hitSize;
|
||||
@Import Vec2 vel;
|
||||
@Import UnitType type;
|
||||
|
||||
@SyncLocal float elevation;
|
||||
private transient boolean wasFlying;
|
||||
transient boolean hovering;
|
||||
transient float drownTime;
|
||||
transient float splashTimer;
|
||||
transient @Nullable Floor lastDrownFloor;
|
||||
|
||||
boolean checkTarget(boolean targetAir, boolean targetGround){
|
||||
return (isGrounded() && targetGround) || (isFlying() && targetAir);
|
||||
}
|
||||
|
||||
boolean isGrounded(){
|
||||
return elevation < 0.001f;
|
||||
}
|
||||
|
||||
boolean isFlying(){
|
||||
return elevation >= 0.09f;
|
||||
}
|
||||
|
||||
boolean canDrown(){
|
||||
return isGrounded() && !hovering;
|
||||
}
|
||||
|
||||
@Nullable Floor drownFloor(){
|
||||
return canDrown() ? floorOn() : null;
|
||||
}
|
||||
|
||||
boolean emitWalkSound(){
|
||||
return true;
|
||||
}
|
||||
|
||||
void landed(){
|
||||
|
||||
}
|
||||
|
||||
void wobble(){
|
||||
x += Mathf.sin(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
y += Mathf.cos(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
}
|
||||
|
||||
void moveAt(Vec2 vector, float acceleration){
|
||||
Vec2 t = tmp1.set(vector); //target vector
|
||||
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
|
||||
vel.add(tmp2);
|
||||
}
|
||||
|
||||
float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
|
||||
return on.speedMultiplier * speedMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
Floor floor = floorOn();
|
||||
|
||||
if(isFlying() != wasFlying){
|
||||
if(wasFlying){
|
||||
if(tileOn() != null){
|
||||
Fx.unitLand.at(x, y, floorOn().isLiquid ? 1f : 0.5f, tileOn().floor().mapColor);
|
||||
}
|
||||
}
|
||||
|
||||
wasFlying = isFlying();
|
||||
}
|
||||
|
||||
if(!hovering && isGrounded()){
|
||||
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
|
||||
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
|
||||
splashTimer = 0f;
|
||||
|
||||
if(emitWalkSound()){
|
||||
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDrowning();
|
||||
}
|
||||
|
||||
public void updateDrowning(){
|
||||
Floor floor = drownFloor();
|
||||
|
||||
if(floor != null && floor.isLiquid && floor.drownTime > 0){
|
||||
lastDrownFloor = floor;
|
||||
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
|
||||
if(Mathf.chanceDelta(0.05f)){
|
||||
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
|
||||
}
|
||||
|
||||
if(drownTime >= 0.999f && !net.client()){
|
||||
kill();
|
||||
Events.fire(new UnitDrownEvent(self()));
|
||||
}
|
||||
}else{
|
||||
drownTime -= Time.delta / 50f;
|
||||
}
|
||||
|
||||
drownTime = Mathf.clamp(drownTime);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -19,7 +18,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
abstract class LegsComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
private static final Vec2 straightVec = new Vec2();
|
||||
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@@ -37,13 +36,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return type.allowLegStep ? Pathfinder.costLegs : Pathfinder.costGround;
|
||||
return ignoreSolids() ? null : type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,6 +104,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
|
||||
legs[i] = l;
|
||||
}
|
||||
totalLength = Mathf.random(100f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, ElevationMovec{
|
||||
abstract class MechComp implements Posc, Hitboxc, Unitc, Mechc, ElevationMovec{
|
||||
@Import float x, y, hitSize;
|
||||
@Import UnitType type;
|
||||
|
||||
@@ -68,7 +68,7 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
|
||||
}
|
||||
}
|
||||
}
|
||||
return canDrown() ? floorOn() : null;
|
||||
return floorOn();
|
||||
}
|
||||
|
||||
public float walkExtend(boolean scaled){
|
||||
|
||||
@@ -10,7 +10,7 @@ import mindustry.gen.*;
|
||||
* Will bounce off of other objects that are at similar elevations.
|
||||
* Has mass.*/
|
||||
@Component
|
||||
abstract class PhysicsComp implements Velc, Hitboxc, Flyingc{
|
||||
abstract class PhysicsComp implements Velc, Hitboxc{
|
||||
@Import float hitSize, x, y;
|
||||
@Import Vec2 vel;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
|
||||
private static Puddle paramPuddle;
|
||||
private static Cons<Unit> unitCons = unit -> {
|
||||
if(unit.isGrounded() && !unit.hovering){
|
||||
if(unit.isGrounded() && !unit.type.hovering){
|
||||
unit.hitbox(rect2);
|
||||
if(rect.overlaps(rect2)){
|
||||
unit.apply(paramPuddle.liquid.effect, 60 * 2);
|
||||
@@ -104,6 +104,10 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
}
|
||||
|
||||
updateTime = 40f;
|
||||
|
||||
if(tile.build != null){
|
||||
tile.build.puddleOn(self());
|
||||
}
|
||||
}
|
||||
|
||||
if(!headless && liquid.particleEffect != Fx.none){
|
||||
|
||||
158
core/src/mindustry/entities/comp/SegmentComp.java
Normal file
158
core/src/mindustry/entities/comp/SegmentComp.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
@Component
|
||||
abstract class SegmentComp implements Posc, Rotc, Hitboxc, Unitc, Segmentc{
|
||||
@Import float x, y, rotation;
|
||||
@Import UnitType type;
|
||||
@Import Vec2 vel;
|
||||
|
||||
transient @Nullable Segmentc parentSegment, childSegment, headSegment;
|
||||
transient int segmentIndex;
|
||||
|
||||
int parentId;
|
||||
|
||||
public boolean isHead(){
|
||||
return parentSegment == null;
|
||||
}
|
||||
|
||||
public void addChild(Unit other){
|
||||
if(other == self()) return;
|
||||
|
||||
if(childSegment != null){
|
||||
childSegment.parentSegment(null);
|
||||
}
|
||||
|
||||
if(other instanceof Segmentc seg){
|
||||
if(seg.parentSegment() != null){
|
||||
seg.parentSegment().childSegment(null);
|
||||
}
|
||||
|
||||
childSegment = seg;
|
||||
seg.parentSegment(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean ignoreSolids(){
|
||||
return isFlying() || parentSegment != null;
|
||||
}
|
||||
|
||||
//TODO make it phase through things.
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(childSegment != null && !childSegment.isValid()){
|
||||
childSegment = null;
|
||||
}
|
||||
|
||||
if(parentSegment != null && !parentSegment.isValid()){
|
||||
parentSegment = null;
|
||||
}
|
||||
|
||||
if(parentSegment == null){
|
||||
segmentIndex = 0;
|
||||
|
||||
if(childSegment != null){
|
||||
headSegment = this;
|
||||
childSegment.updateSegment(this, this, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public boolean playerControllable(){
|
||||
return type.playerControllable && isHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean shouldUpdateController(){
|
||||
return isHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean moving(){
|
||||
if(isHead()){
|
||||
return !vel.isZero(0.01f);
|
||||
}else{
|
||||
return deltaLen() / Time.delta >= 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int collisionLayer(){
|
||||
if(parentSegment != null) return -1;
|
||||
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public boolean isCommandable(){
|
||||
return parentSegment == null && controller() instanceof CommandAI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSync(){
|
||||
checkParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReadAll(){
|
||||
checkParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeWrite(){
|
||||
parentId = parentSegment == null ? -1 : parentSegment.id();
|
||||
}
|
||||
|
||||
public void checkParent(){
|
||||
if(parentId != -1){
|
||||
var parent = Groups.unit.getByID(parentId);
|
||||
if(parent instanceof Segmentc seg){
|
||||
parentSegment = seg;
|
||||
seg.childSegment(this);
|
||||
return;
|
||||
}
|
||||
parentId = -1;
|
||||
}
|
||||
//TODO should this unassign the parent's child too?
|
||||
parentSegment = null;
|
||||
}
|
||||
|
||||
public void updateSegment(Segmentc head, Segmentc parent, int index){
|
||||
rotation = Angles.clampRange(rotation, parent.rotation(), type.segmentRotationRange);
|
||||
segmentIndex = index;
|
||||
headSegment = head;
|
||||
|
||||
float headDelta = head.deltaLen();
|
||||
|
||||
//TODO should depend on the head's speed.
|
||||
if(headDelta > 0.001f){
|
||||
rotation = Mathf.slerpDelta(rotation, parent.rotation(), type.baseRotateSpeed * Mathf.clamp(headDelta / type().speed / Time.delta));
|
||||
}
|
||||
|
||||
Vec2 moveVec = Tmp.v1.trns(rotation + 180f, type.segmentSpacing).add(parent).sub(x, y);
|
||||
float prefSpeed = type.speed * Time.delta * 9999f;
|
||||
move(moveVec.limit(prefSpeed)); //TODO other segments are left behind
|
||||
|
||||
if(childSegment != null){
|
||||
childSegment.updateSegment(head, this, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class StatusComp implements Posc, Flyingc{
|
||||
abstract class StatusComp implements Posc{
|
||||
private Seq<StatusEntry> statuses = new Seq<>(4);
|
||||
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
|
||||
|
||||
@@ -28,12 +28,12 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
@Import float maxHealth;
|
||||
|
||||
/** Apply a status effect for 1 tick (for permanent effects) **/
|
||||
void apply(StatusEffect effect){
|
||||
public void apply(StatusEffect effect){
|
||||
apply(effect, 1);
|
||||
}
|
||||
|
||||
/** Adds a status effect to this unit. */
|
||||
void apply(StatusEffect effect, float duration){
|
||||
public void apply(StatusEffect effect, float duration){
|
||||
if(effect == StatusEffects.none || effect == null || isImmune(effect)) return; //don't apply empty or immune effects
|
||||
|
||||
//unlock status effects regardless of whether they were applied to friendly units
|
||||
@@ -67,18 +67,18 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
}
|
||||
}
|
||||
|
||||
float getDuration(StatusEffect effect){
|
||||
public float getDuration(StatusEffect effect){
|
||||
var entry = statuses.find(e -> e.effect == effect);
|
||||
return entry == null ? 0 : entry.time;
|
||||
}
|
||||
|
||||
void clearStatuses(){
|
||||
public void clearStatuses(){
|
||||
statuses.each(e -> e.effect.onRemoved(self()));
|
||||
statuses.clear();
|
||||
}
|
||||
|
||||
/** Removes a status effect. */
|
||||
void unapply(StatusEffect effect){
|
||||
public void unapply(StatusEffect effect){
|
||||
statuses.remove(e -> {
|
||||
if(e.effect == effect){
|
||||
e.effect.onRemoved(self());
|
||||
@@ -89,13 +89,15 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
});
|
||||
}
|
||||
|
||||
boolean isBoss(){
|
||||
public boolean isBoss(){
|
||||
return hasEffect(StatusEffects.boss);
|
||||
}
|
||||
|
||||
abstract boolean isImmune(StatusEffect effect);
|
||||
public boolean isImmune(StatusEffect effect){
|
||||
return type.immunities.contains(effect);
|
||||
}
|
||||
|
||||
Color statusColor(){
|
||||
public Color statusColor(){
|
||||
if(statuses.size == 0){
|
||||
return Tmp.c1.set(Color.white);
|
||||
}
|
||||
@@ -168,6 +170,8 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
applyDynamicStatus().armorOverride = armor;
|
||||
}
|
||||
|
||||
public abstract boolean isGrounded();
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
Floor floor = floorOn();
|
||||
@@ -237,7 +241,7 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasEffect(StatusEffect effect){
|
||||
public boolean hasEffect(StatusEffect effect){
|
||||
return applied.get(effect.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec{
|
||||
abstract class TankComp implements Posc, Hitboxc, Unitc, ElevationMovec{
|
||||
@Import float x, y, hitSize, rotation, speedMultiplier;
|
||||
@Import boolean hovering, disarmed;
|
||||
@Import boolean disarmed;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
|
||||
@@ -27,6 +27,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
|
||||
transient float treadTime;
|
||||
transient boolean walked;
|
||||
transient Floor lastDeepFloor;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -51,6 +52,9 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
}
|
||||
}
|
||||
|
||||
lastDeepFloor = null;
|
||||
boolean anyNonDeep = false;
|
||||
|
||||
//calculate overlapping tiles so it slows down when going "over" walls
|
||||
int r = Math.max((int)(hitSize * 0.6f / tilesize), 0);
|
||||
|
||||
@@ -62,6 +66,12 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
solids ++;
|
||||
}
|
||||
|
||||
if(t.floor().isDeep()){
|
||||
lastDeepFloor = t.floor();
|
||||
}else{
|
||||
anyNonDeep = true;
|
||||
}
|
||||
|
||||
//TODO should this apply to the player team(s)? currently PvE due to balancing
|
||||
if(type.crushDamage > 0 && !disarmed && (walked || deltaLen() >= 0.01f) && t != null
|
||||
//damage radius is 1 tile smaller to prevent it from just touching walls as it passes
|
||||
@@ -76,6 +86,10 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
}
|
||||
}
|
||||
|
||||
if(anyNonDeep){
|
||||
lastDeepFloor = null;
|
||||
}
|
||||
|
||||
lastSlowdown = Mathf.lerp(1f, type.crawlSlowdown, Mathf.clamp((float)solids / total / type.crawlSlowdownFrac));
|
||||
|
||||
//trigger animation only when walking manually
|
||||
@@ -89,7 +103,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
@Override
|
||||
@Replace
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
|
||||
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
|
||||
//TODO take into account extra blocks
|
||||
return on.speedMultiplier * speedMultiplier * lastSlowdown;
|
||||
}
|
||||
@@ -97,17 +111,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
@Replace
|
||||
@Override
|
||||
public @Nullable Floor drownFloor(){
|
||||
//tanks can only drown when all the nearby floors are deep
|
||||
//TODO implement properly
|
||||
if(hitSize >= 12 && canDrown()){
|
||||
for(Point2 p : Geometry.d8){
|
||||
Floor f = world.floorWorld(x + p.x * tilesize, y + p.y * tilesize);
|
||||
if(!f.isDeep()){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canDrown() ? floorOn() : null;
|
||||
return canDrown() ? lastDeepFloor : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
39
core/src/mindustry/entities/comp/UnderwaterMoveComp.java
Normal file
39
core/src/mindustry/entities/comp/UnderwaterMoveComp.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
@Component
|
||||
abstract class UnderwaterMoveComp implements WaterMovec{
|
||||
@Import UnitType type;
|
||||
|
||||
@MethodPriority(10f)
|
||||
@Replace
|
||||
public void draw(){
|
||||
//TODO draw status effects?
|
||||
|
||||
Drawf.underwater(() -> {
|
||||
type.draw(self());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int collisionLayer(){
|
||||
return PhysicsProcess.layerUnderwater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hittable(){
|
||||
return false && type.hittable(self());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean targetable(Team targeter){
|
||||
return false && type.targetable(self(), targeter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
@@ -34,10 +33,12 @@ import static mindustry.Vars.*;
|
||||
import static mindustry.logic.GlobalVars.*;
|
||||
|
||||
@Component(base = true)
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
|
||||
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
|
||||
static final float warpDst = 30f;
|
||||
|
||||
@Import boolean hovering, dead, disarmed;
|
||||
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
|
||||
@Import boolean dead, disarmed;
|
||||
@Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
|
||||
@Import Team team;
|
||||
@Import int id;
|
||||
@Import @Nullable Tile mineTile;
|
||||
@@ -62,6 +63,48 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
private transient boolean wasPlayer;
|
||||
private transient boolean wasHealed;
|
||||
|
||||
@SyncLocal float elevation;
|
||||
private transient boolean wasFlying;
|
||||
transient float drownTime;
|
||||
transient float splashTimer;
|
||||
transient @Nullable Floor lastDrownFloor;
|
||||
|
||||
public boolean checkTarget(boolean targetAir, boolean targetGround){
|
||||
return (isGrounded() && targetGround) || (isFlying() && targetAir);
|
||||
}
|
||||
|
||||
public boolean isGrounded(){
|
||||
return elevation < 0.001f;
|
||||
}
|
||||
|
||||
public boolean isFlying(){
|
||||
return elevation >= 0.09f;
|
||||
}
|
||||
|
||||
public boolean canDrown(){
|
||||
return isGrounded() && type.canDrown;
|
||||
}
|
||||
|
||||
public @Nullable Floor drownFloor(){
|
||||
return floorOn();
|
||||
}
|
||||
|
||||
public void wobble(){
|
||||
x += Mathf.sin(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
y += Mathf.cos(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
}
|
||||
|
||||
public void moveAt(Vec2 vector, float acceleration){
|
||||
Vec2 t = tmp1.set(vector); //target vector
|
||||
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
|
||||
vel.add(tmp2);
|
||||
}
|
||||
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
|
||||
return on.speedMultiplier * speedMultiplier;
|
||||
}
|
||||
|
||||
/** Called when this unit was unloaded from a factory or spawn point. */
|
||||
public void unloaded(){
|
||||
|
||||
@@ -352,12 +395,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean canDrown(){
|
||||
return isGrounded() && !hovering && type.canDrown;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean canShoot(){
|
||||
@@ -420,11 +457,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
|
||||
}
|
||||
|
||||
/** @return pathfinder path type for calculating costs. This is used for wave AI only. (TODO: remove) */
|
||||
public int pathType(){
|
||||
return Pathfinder.costGround;
|
||||
}
|
||||
|
||||
public void lookAt(float angle){
|
||||
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta * speedMultiplier());
|
||||
}
|
||||
@@ -445,8 +477,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
return controller instanceof CommandAI;
|
||||
}
|
||||
|
||||
public boolean canTarget(Unit other){
|
||||
return other != null && other.checkTarget(type.targetAir, type.targetGround);
|
||||
public boolean canTarget(Teamc other){
|
||||
return other != null && (other instanceof Unit u ? u.checkTarget(type.targetAir, type.targetGround) : (other instanceof Building b && type.targetGround));
|
||||
}
|
||||
|
||||
public CommandAI command(){
|
||||
@@ -475,9 +507,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
this.drag = type.drag;
|
||||
this.armor = type.armor;
|
||||
this.hitSize = type.hitSize;
|
||||
this.hovering = type.hovering;
|
||||
|
||||
if(controller == null) controller(type.createController(self()));
|
||||
if(mounts().length != type.weapons.size) setupWeapons(type);
|
||||
if(abilities.length != type.abilities.size){
|
||||
abilities = new Ability[type.abilities.size];
|
||||
@@ -485,6 +515,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
abilities[i] = type.abilities.get(i).copy();
|
||||
}
|
||||
}
|
||||
if(controller == null) controller(type.createController(self()));
|
||||
}
|
||||
|
||||
public boolean playerControllable(){
|
||||
return type.playerControllable;
|
||||
}
|
||||
|
||||
public boolean targetable(Team targeter){
|
||||
@@ -504,7 +539,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
|
||||
@Override
|
||||
public void afterRead(){
|
||||
afterSync();
|
||||
setType(this.type);
|
||||
controller.unit(self());
|
||||
//reset controller state
|
||||
if(!(controller instanceof AIController ai && ai.keepState())){
|
||||
controller(type.createController(self()));
|
||||
@@ -512,7 +548,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAllRead(){
|
||||
public void afterReadAll(){
|
||||
controller.afterRead(self());
|
||||
}
|
||||
|
||||
@@ -555,11 +591,98 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDrowning(){
|
||||
Floor floor = drownFloor();
|
||||
|
||||
if(floor != null && floor.isLiquid && floor.drownTime > 0 && canDrown()){
|
||||
lastDrownFloor = floor;
|
||||
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
|
||||
if(Mathf.chanceDelta(0.05f)){
|
||||
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
|
||||
}
|
||||
|
||||
if(drownTime >= 0.999f && !net.client()){
|
||||
kill();
|
||||
Events.fire(new UnitDrownEvent(self()));
|
||||
}
|
||||
}else{
|
||||
drownTime -= Time.delta / 50f;
|
||||
}
|
||||
|
||||
drownTime = Mathf.clamp(drownTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
||||
type.update(self());
|
||||
|
||||
//update bounds
|
||||
|
||||
if(type.bounded){
|
||||
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
|
||||
|
||||
//TODO hidden map rules only apply to player teams? should they?
|
||||
if(state.rules.limitMapArea && !team.isAI()){
|
||||
bot = state.rules.limitY * tilesize;
|
||||
left = state.rules.limitX * tilesize;
|
||||
top = state.rules.limitHeight * tilesize + bot;
|
||||
right = state.rules.limitWidth * tilesize + left;
|
||||
}
|
||||
|
||||
if(!net.client() || isLocal()){
|
||||
|
||||
float dx = 0f, dy = 0f;
|
||||
|
||||
//repel unit out of bounds
|
||||
if(x < left) dx += (-(x - left)/warpDst);
|
||||
if(y < bot) dy += (-(y - bot)/warpDst);
|
||||
if(x > right) dx -= (x - right)/warpDst;
|
||||
if(y > top) dy -= (y - top)/warpDst;
|
||||
|
||||
velAddNet(dx * Time.delta, dy * Time.delta);
|
||||
}
|
||||
|
||||
//clamp position if not flying
|
||||
if(isGrounded()){
|
||||
x = Mathf.clamp(x, left, right - tilesize);
|
||||
y = Mathf.clamp(y, bot, top - tilesize);
|
||||
}
|
||||
|
||||
//kill when out of bounds
|
||||
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
//update drown/flying state
|
||||
|
||||
Floor floor = floorOn();
|
||||
Tile tile = tileOn();
|
||||
|
||||
if(isFlying() != wasFlying){
|
||||
if(wasFlying){
|
||||
if(tile != null){
|
||||
Fx.unitLand.at(x, y, floor.isLiquid ? 1f : 0.5f, tile.floor().mapColor);
|
||||
}
|
||||
}
|
||||
|
||||
wasFlying = isFlying();
|
||||
}
|
||||
|
||||
if(!type.hovering && isGrounded() && type.emitWalkEffect){
|
||||
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
|
||||
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
|
||||
splashTimer = 0f;
|
||||
|
||||
if(type.emitWalkSound){
|
||||
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDrowning();
|
||||
|
||||
if(wasHealed && healTime <= -1f){
|
||||
healTime = 1f;
|
||||
}
|
||||
@@ -647,8 +770,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
Tile tile = tileOn();
|
||||
Floor floor = floorOn();
|
||||
if(tile != null && tile.build != null){
|
||||
tile.build.unitOnAny(self());
|
||||
}
|
||||
|
||||
if(tile != null && isGrounded() && !type.hovering){
|
||||
//unit block update
|
||||
@@ -673,7 +797,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
|
||||
//AI only updates on the server
|
||||
if(!net.client() && !dead){
|
||||
if(!net.client() && !dead && shouldUpdateController()){
|
||||
controller.updateUnit();
|
||||
}
|
||||
|
||||
@@ -688,6 +812,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldUpdateController(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return a preview UI icon for this unit. */
|
||||
public TextureRegion icon(){
|
||||
return type.uiIcon;
|
||||
@@ -770,11 +898,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
type.display(self(), table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmune(StatusEffect effect){
|
||||
return type.immunities.contains(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
type.draw(self());
|
||||
|
||||
@@ -39,6 +39,10 @@ abstract class VelComp implements Posc{
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean ignoreSolids(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return whether this entity can move through a location*/
|
||||
boolean canPass(int tileX, int tileY){
|
||||
SolidPred s = solidity();
|
||||
|
||||
38
core/src/mindustry/entities/comp/WaterCrawlComp.java
Normal file
38
core/src/mindustry/entities/comp/WaterCrawlComp.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.EntityCollisions.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
@Component
|
||||
abstract class WaterCrawlComp implements Posc, Velc, Hitboxc, Unitc, Crawlc{
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@Import UnitType type;
|
||||
|
||||
@Replace
|
||||
public SolidPred solidity(){
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
|
||||
}
|
||||
|
||||
@Replace
|
||||
public boolean onSolid(){
|
||||
return EntityCollisions.waterSolid(tileX(), tileY());
|
||||
}
|
||||
|
||||
@Replace
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
|
||||
return (on.shallow ? 1f : 1.3f) * speedMultiplier;
|
||||
}
|
||||
|
||||
public boolean onLiquid(){
|
||||
Tile tile = tileOn();
|
||||
return tile != null && tile.floor().isLiquid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -18,7 +17,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Unitc{
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@Import UnitType type;
|
||||
|
||||
@@ -38,19 +37,6 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return Pathfinder.costNaval;
|
||||
}
|
||||
|
||||
//don't want obnoxious splashing
|
||||
@Override
|
||||
@Replace
|
||||
public boolean emitWalkSound(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(){
|
||||
tleft.clear();
|
||||
@@ -59,6 +45,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
//TODO: move to UnitType
|
||||
float z = Draw.z();
|
||||
|
||||
Draw.z(Layer.debris);
|
||||
@@ -76,7 +63,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return isFlying() ? null : EntityCollisions::waterSolid;
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
|
||||
}
|
||||
|
||||
@Replace
|
||||
|
||||
Reference in New Issue
Block a user