258 lines
8.8 KiB
Java
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);
|
|
}
|
|
|
|
}
|