Merging changes from private branch

This commit is contained in:
Anuken
2025-04-04 11:47:35 -04:00
parent cf5c6d0905
commit b7dbe54d76
161 changed files with 2484 additions and 1137 deletions

View File

@@ -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){

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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(){
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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){

View File

@@ -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;

View File

@@ -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){

View 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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

View 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);
}
}

View File

@@ -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());

View File

@@ -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();

View 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;
}
}

View File

@@ -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