Files
Mindustry/core/src/mindustry/entities/comp/LegsComp.java
2022-07-14 21:56:29 -04:00

258 lines
8.8 KiB
Java

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.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
private static final Vec2 straightVec = new Vec2();
@Import float x, y, rotation, speedMultiplier;
@Import UnitType type;
@Import Team team;
transient Leg[] legs = {};
transient float totalLength;
transient float moveSpace;
transient float baseRotation;
transient Floor lastDeepFloor;
transient Vec2 curMoveOffset = new Vec2();
@Replace
@Override
public SolidPred solidity(){
return type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
}
@Override
@Replace
public int pathType(){
return type.allowLegStep ? Pathfinder.costLegs : Pathfinder.costGround;
}
@Override
@Replace
public Floor drownFloor(){
return lastDeepFloor;
}
@Override
public void add(){
resetLegs();
}
@Override
public void unloaded(){
resetLegs(1f);
}
@MethodPriority(-1)
@Override
public void destroy(){
if(!isAdded() || Vars.headless) return;
float legExplodeRad = type.legRegion.height / 4f / 1.45f;
//create effects for legs being destroyed
for(int i = 0; i < legs.length; i++){
Leg l = legs[i];
Vec2 base = legOffset(Tmp.v1, i).add(x, y);
Tmp.v2.set(l.base).sub(l.joint).inv().setLength(type.legExtension);
for(Vec2 vec : new Vec2[]{base, l.joint, l.base}){
Damage.dynamicExplosion(vec.x, vec.y, 0f, 0f, 0f, legExplodeRad, state.rules.damageExplosions, false, team, type.deathExplosionEffect);
}
Fx.legDestroy.at(base.x, base.y, 0f, new LegDestroyData(base.cpy(), l.joint, type.legRegion));
Fx.legDestroy.at(l.joint.x, l.joint.y, 0f, new LegDestroyData(l.joint.cpy().add(Tmp.v2), l.base, type.legBaseRegion));
}
}
public void resetLegs(){
resetLegs(type.legLength);
}
public void resetLegs(float legLength){
int count = type.legCount;
this.legs = new Leg[count];
if(type.lockLegBase){
baseRotation = rotation;
}
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
float dstRot = legAngle(i);
Vec2 baseOffset = legOffset(Tmp.v5, i).add(x, y);
l.joint.trns(dstRot, legLength/2f).add(baseOffset);
l.base.trns(dstRot, legLength).add(baseOffset);
legs[i] = l;
}
}
@Override
public void update(){
if(Mathf.dst(deltaX(), deltaY()) > 0.001f){
baseRotation = Angles.moveToward(baseRotation, Mathf.angle(deltaX(), deltaY()), type.rotateSpeed);
}
if(type.lockLegBase){
baseRotation = rotation;
}
float legLength = type.legLength;
//set up initial leg positions
if(legs.length != type.legCount){
resetLegs();
}
float moveSpeed = type.legSpeed;
int div = Math.max(legs.length / type.legGroupSize, 2);
moveSpace = legLength / 1.6f / (div / 2f) * type.legMoveSpace;
//TODO should move legs even when still, based on speed. also, to prevent "slipping", make sure legs move when they are too far from their destination
totalLength += type.legContinuousMove ? type.speed * speedMultiplier * Time.delta : Mathf.dst(deltaX(), deltaY());
float trns = moveSpace * 0.85f * type.legForwardScl;
//rotation + offset vector
boolean moving = moving();
Vec2 moveOffset = !moving ? Tmp.v4.setZero() : Tmp.v4.trns(Angles.angle(deltaX(), deltaY()), trns);
//make it smooth, not jumpy
moveOffset = curMoveOffset.lerpDelta(moveOffset, 0.1f);
lastDeepFloor = null;
int deeps = 0;
for(int i = 0; i < legs.length; i++){
float dstRot = legAngle(i);
Vec2 baseOffset = legOffset(Tmp.v5, i).add(x, y);
Leg l = legs[i];
//TODO is limiting twice necessary?
l.joint.sub(baseOffset).clampLength(type.legMinLength * legLength/2f, type.legMaxLength * legLength/2f).add(baseOffset);
l.base.sub(baseOffset).clampLength(type.legMinLength * legLength, type.legMaxLength * legLength).add(baseOffset);
float stageF = (totalLength + i*type.legPairOffset) / moveSpace;
int stage = (int)stageF;
int group = stage % div;
boolean move = i % div == group;
boolean side = i < legs.length/2;
//back legs have reversed directions
boolean backLeg = Math.abs((i + 0.5f) - legs.length/2f) <= 0.501f;
if(backLeg && type.flipBackLegs) side = !side;
if(type.flipLegSide) side = !side;
l.moving = move;
l.stage = moving ? stageF % 1f : Mathf.lerpDelta(l.stage, 0f, 0.1f);
Floor floor = Vars.world.floorWorld(l.base.x, l.base.y);
if(floor.isDeep()){
deeps ++;
lastDeepFloor = floor;
}
if(l.group != group){
//create effect when transitioning to a group it can't move in
if(!move && (moving || !type.legContinuousMove) && i % div == l.group){
if(!headless && !inFogTo(player.team())){
if(floor.isLiquid){
floor.walkEffect.at(l.base.x, l.base.y, type.rippleScale, floor.mapColor);
floor.walkSound.at(x, y, 1f, floor.walkSoundVolume);
}else{
Fx.unitLandSmall.at(l.base.x, l.base.y, type.rippleScale, floor.mapColor);
}
//shake when legs contact ground
if(type.stepShake > 0){
Effect.shake(type.stepShake, type.stepShake, l.base);
}
}
if(type.legSplashDamage > 0){
Damage.damage(team, l.base.x, l.base.y, type.legSplashRange, type.legSplashDamage, false, true);
}
}
l.group = group;
}
//leg destination
Vec2 legDest = Tmp.v1.trns(dstRot, legLength * type.legLengthScl).add(baseOffset).add(moveOffset);
//join destination
Vec2 jointDest = Tmp.v2;//.trns(rot2, legLength / 2f + type.legBaseOffset).add(moveOffset);
InverseKinematics.solve(legLength/2f, legLength/2f, Tmp.v6.set(l.base).sub(baseOffset), side, jointDest);
jointDest.add(baseOffset);
Tmp.v6.set(baseOffset).lerp(l.base, 0.5f);
//lerp between kinematic and linear?
//jointDest.lerp(Tmp.v6.set(baseOffset).lerp(l.base, 0.5f), 1f - type.kinematicScl);
if(move){
float moveFract = stageF % 1f;
l.base.lerpDelta(legDest, moveFract);
l.joint.lerpDelta(jointDest, moveFract / 2f);
}
l.joint.lerpDelta(jointDest, moveSpeed / 4f);
//limit again after updating
l.joint.sub(baseOffset).clampLength(type.legMinLength * legLength/2f, type.legMaxLength * legLength/2f).add(baseOffset);
l.base.sub(baseOffset).clampLength(type.legMinLength * legLength, type.legMaxLength * legLength).add(baseOffset);
}
//when at least 1 leg is touching land, it can't drown
if(deeps != legs.length){
lastDeepFloor = null;
}
}
Vec2 legOffset(Vec2 out, int index){
out.trns(defaultLegAngle(index), type.legBaseOffset);
if(type.legStraightness > 0){
straightVec.trns(defaultLegAngle(index) - baseRotation, type.legBaseOffset);
straightVec.y = Mathf.sign(straightVec.y) * type.legBaseOffset * type.legStraightLength;
straightVec.rotate(baseRotation);
out.lerp(straightVec, type.baseLegStraightness);
}
return out;
}
/** @return outwards facing angle of leg at the specified index. */
float legAngle(int index){
if(type.legStraightness > 0){
return Mathf.slerp(defaultLegAngle(index), (index >= legs.length/2 ? -90 : 90f) + baseRotation, type.legStraightness);
}
return defaultLegAngle(index);
}
float defaultLegAngle(int index){
return baseRotation + 360f / legs.length * index + (360f / legs.length / 2f);
}
}