package Roguelike.Entity.Tasks; import Roguelike.Entity.Entity; import Roguelike.Entity.EnvironmentEntity; import Roguelike.Entity.GameEntity; import Roguelike.GameEvent.GameEventHandler; import Roguelike.Global; import Roguelike.Global.Direction; import Roguelike.Global.Passability; import Roguelike.Items.Item; import Roguelike.Items.Item.EquipmentSlot; import Roguelike.Sound.SoundInstance; import Roguelike.Sprite.Sprite; import Roguelike.Sprite.SpriteAction; import Roguelike.Sprite.SpriteAnimation.BumpAnimation; import Roguelike.Sprite.SpriteAnimation.MoveAnimation; import Roguelike.Sprite.SpriteAnimation.MoveAnimation.MoveEquation; import Roguelike.Sprite.SpriteEffect; import Roguelike.Tiles.GameTile; import Roguelike.Tiles.Point; import Roguelike.Util.EnumBitflag; import com.badlogic.gdx.Game; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; public class TaskAttack extends AbstractTask { private static final EnumBitflag<Passability> WeaponPassability = new EnumBitflag<Passability>( Passability.LIGHT ); public Direction dir; public TaskAttack( Direction dir ) { if ( !Global.CanMoveDiagonal && !dir.isCardinal() ) { throw new RuntimeException( "Invalid attack direction: " + dir.toString() ); } this.dir = dir; } public static Array<Point> buildAllDirectionHitTiles( GameEntity entity ) { Array<Point> points = new Array<Point>( ); Item weapon = entity.getInventory().getEquip( EquipmentSlot.WEAPON ); for ( Direction dir : Direction.values() ) { if ( Global.CanMoveDiagonal || dir.isCardinal() ) { int xstep = 0; int ystep = 0; int sx = 0; int sy = 0; if ( dir == Direction.NORTH ) { sx = 0; sy = entity.size - 1; xstep = 1; ystep = 0; } else if ( dir == Direction.SOUTH ) { sx = 0; sy = 0; xstep = 1; ystep = 0; } else if ( dir == Direction.EAST ) { sx = entity.size - 1; sy = 0; xstep = 0; ystep = 1; } else if ( dir == Direction.WEST ) { sx = 0; sy = 0; xstep = 0; ystep = 1; } for (int i = 0; i < entity.size; i++) { GameTile attackerTile = entity.tile[sx + xstep * i][sy + ystep * i]; if (weapon != null && weapon.wepDef != null) { Matrix3 mat = new Matrix3( ); mat.setToRotation( dir.getAngle() ); Vector3 vec = new Vector3(); for (Point point : weapon.wepDef.hitPoints) { vec.set( point.x, point.y, 0 ); vec.mul( mat ); int dx = Math.round( vec.x ); int dy = Math.round( vec.y ); Point pos = Global.PointPool.obtain().set( attackerTile.x + dx, attackerTile.y + dy ); points.add( pos ); } } else { Point pos = Global.PointPool.obtain().set( attackerTile.x + dir.getX(), attackerTile.y + dir.getY() ); points.add( pos ); } } } } // restrict by visibility and remove duplicates Array<Point> visibleTiles = entity.visibilityCache.getCurrentShadowCast(); Iterator<Point> itr = points.iterator(); while (itr.hasNext()) { Point pos = itr.next(); boolean matchFound = false; // Remove not visible for (Point point : visibleTiles) { if (point.x == pos.x && point.y == pos.y) { matchFound = true; break; } } // Remove duplicates for (int i = 0; i < points.size; i++) { Point opos = points.get( i ); if (opos != pos && opos.x == pos.x && opos.y == pos.y) { matchFound = false; break; } } if (!matchFound) { itr.remove(); Global.PointPool.free( pos ); } } return points; } public static Array<GameTile> buildHitTileArray( GameEntity attacker, Direction dir ) { Array<GameTile> tiles = new Array<GameTile>( ); Item weapon = attacker.getInventory().getEquip( EquipmentSlot.WEAPON ); int xstep = 0; int ystep = 0; int sx = 0; int sy = 0; if ( dir == Direction.NORTH ) { sx = 0; sy = attacker.size - 1; xstep = 1; ystep = 0; } else if ( dir == Direction.SOUTH ) { sx = 0; sy = 0; xstep = 1; ystep = 0; } else if ( dir == Direction.EAST ) { sx = attacker.size - 1; sy = 0; xstep = 0; ystep = 1; } else if ( dir == Direction.WEST ) { sx = 0; sy = 0; xstep = 0; ystep = 1; } for (int i = 0; i < attacker.size; i++) { GameTile attackerTile = attacker.tile[sx + xstep * i][sy + ystep * i]; if (weapon != null && weapon.wepDef != null) { Matrix3 mat = new Matrix3( ); mat.setToRotation( dir.getAngle() ); Vector3 vec = new Vector3(); for (Point point : weapon.wepDef.hitPoints) { vec.set( point.x, point.y, 0 ); vec.mul( mat ); int dx = Math.round( vec.x ); int dy = Math.round( vec.y ); GameTile tile = attackerTile.level.getGameTile( attackerTile.x + dx, attackerTile.y + dy ); if (tile != null) { tiles.add( tile ); } } } else { tiles.add( attackerTile.level.getGameTile( attackerTile.x + dir.getX(), attackerTile.y + dir.getY() ) ); } } // restrict by visibility and remove duplicates Array<Point> visibleTiles = attacker.visibilityCache.getCurrentShadowCast(); Iterator<GameTile> itr = tiles.iterator(); while (itr.hasNext()) { GameTile tile = itr.next(); boolean matchFound = false; // Remove not visible for (Point point : visibleTiles) { if (point.x == tile.x && point.y == tile.y) { matchFound = true; break; } } // Remove duplicates for (int i = 0; i < tiles.size; i++) { GameTile otile = tiles.get( i ); if (otile != tile && otile.x == tile.x && otile.y == tile.y) { matchFound = false; break; } } if (!matchFound) { itr.remove(); } } return tiles; } public boolean checkHitSomething( GameEntity obj ) { Array<GameTile> hitTiles = buildHitTileArray( obj, dir ); // Check if should attack something boolean hitSomething = false; for ( GameTile tile : hitTiles ) { if ( tile.entity != null && !tile.entity.isAllies( obj ) ) { hitSomething = true; break; } } return hitSomething; } public boolean canAttackTile( GameEntity obj, GameTile tile ) { Array<GameTile> hitTiles = buildHitTileArray( obj, dir ); for ( GameTile t : hitTiles ) { if ( t == tile ) { return true; } } return false; } @Override public void processTask( GameEntity obj ) { // Collect data GameTile oldTile = obj.tile[ 0 ][ 0 ]; int newX = oldTile.x + dir.getX(); int newY = oldTile.y + dir.getY(); GameTile newTile = oldTile.level.getGameTile( newX, newY ); Item wep = obj.getInventory().getEquip( EquipmentSlot.WEAPON ); Array<GameTile> hitTiles = buildHitTileArray( obj, dir ); // Check if should attack something boolean hitSomething = false; for ( GameTile tile : hitTiles ) { if ( tile.entity != null && !tile.entity.isAllies( obj ) ) { hitSomething = true; break; } if ( tile.environmentEntity != null && tile.environmentEntity.canTakeDamage && !tile.environmentEntity.passableBy.intersect( obj.getTravelType() ) ) { hitSomething = true; } } // Do attack if ( hitSomething ) { doAttack( hitTiles, obj, wep ); // do graphics stuff obj.sprite.spriteAnimation = new BumpAnimation( 0.1f, dir ); } } private void doAttack( Array<GameTile> hitTiles, final GameEntity entity, Item weapon ) { final GameTile source = entity.tile[0][0]; // Get all the attacked tiles Array<GameTile> attackedTiles = new Array<GameTile>( ); if (weapon == null || weapon.wepDef == null || weapon.wepDef.hitType == Item.WeaponDefinition.HitType.ALL) { attackedTiles.addAll( hitTiles ); } else if (weapon.wepDef.hitType == Item.WeaponDefinition.HitType.CLOSEST) { int num = weapon.wepDef.hitData != null ? Integer.parseInt( weapon.wepDef.hitData ) : 1; Array<GameTile> validEntityTiles = new Array<GameTile>( ); Array<GameTile> validEnvironmentTiles = new Array<GameTile>( ); // Get tiles valid to hit for ( GameTile tile : hitTiles ) { if ( tile.entity != null && !tile.entity.isAllies( entity ) ) { validEntityTiles.add( tile ); } else if ( tile.environmentEntity != null && tile.environmentEntity.canTakeDamage ) { validEnvironmentTiles.add( tile ); } } Comparator<GameTile> comp = new Comparator<GameTile>() { @Override public int compare( GameTile o1, GameTile o2 ) { int dist1 = Math.abs( o1.x - source.x ) + Math.abs( o1.y - source.y ); int dist2 = Math.abs( o2.x - source.x ) + Math.abs( o2.y - source.y ); return dist1 - dist2; } }; // sort by distance validEntityTiles.sort( comp ); validEnvironmentTiles.sort( comp ); for ( int i = 0; i < num && i < validEntityTiles.size; i++ ) { attackedTiles.add( validEntityTiles.get( i ) ); } for ( int i = 0; i < num - validEntityTiles.size && i < validEnvironmentTiles.size; i++ ) { attackedTiles.add( validEnvironmentTiles.get( i ) ); } } else if (weapon.wepDef.hitType == Item.WeaponDefinition.HitType.RANDOM) { int num = weapon.wepDef.hitData != null ? Integer.parseInt( weapon.wepDef.hitData ) : 1; Array<GameTile> validEntityTiles = new Array<GameTile>( ); Array<GameTile> validEnvironmentTiles = new Array<GameTile>( ); // Get tiles valid to hit for ( GameTile tile : hitTiles ) { if ( tile.entity != null && !tile.entity.isAllies( entity ) ) { validEntityTiles.add( tile ); } else if ( tile.environmentEntity != null && tile.environmentEntity.canTakeDamage ) { validEnvironmentTiles.add( tile ); } } if (validEntityTiles.size > 0) { for (int i = 0; i < num; i++) { attackedTiles.add( validEntityTiles.random() ); } } else if (validEnvironmentTiles.size > 0) { for (int i = 0; i < num; i++) { attackedTiles.add( validEnvironmentTiles.random() ); } } } Sprite hitEffect = null; if ( weapon == null ) { hitEffect = entity.defaultHitEffect; } else { hitEffect = weapon.getWeaponHitEffect(); } Point minPoint = Global.PointPool.obtain().set( Integer.MAX_VALUE, Integer.MAX_VALUE ); Point maxPoint = Global.PointPool.obtain().set( 0, 0 ); int hitCount = weapon != null && weapon.wepDef != null ? weapon.wepDef.hitCount : 1; float animdelay = 0; for (int i = 0; i < hitCount; i++) { // Do the attack for ( GameTile tile : attackedTiles ) { // do misses int hitPercent = weapon != null && weapon.wepDef != null ? weapon.wepDef.hitPercent : 100; if ( hitPercent < 100 ) { if ( MathUtils.random( 100 ) > hitPercent ) { // Argh! a miss! Hit a random surrounding tile Direction dir = Direction.values()[ MathUtils.random( Direction.values().length - 1 ) ]; GameTile newTile = tile.level.getGameTile( tile.x + dir.getX(), tile.y + dir.getY() ); if ( newTile != null ) { tile = newTile; } } } if ( weapon == null || weapon.wepDef == null || weapon.wepDef.hitType != Item.WeaponDefinition.HitType.ALL ) { int[] diff = tile.getPosDiff( source ); Sprite sprite = hitEffect.copy(); if ( sprite.spriteAnimation != null ) { int distMoved = ( Math.abs( diff[ 0 ] ) + Math.abs( diff[ 1 ] ) ) / Global.TileSize; sprite.spriteAnimation.set( 0.05f * distMoved, diff ); } Vector2 vec = new Vector2( diff[ 0 ] * -1, diff[ 1 ] * -1 ); vec.nor(); float x = vec.x; float y = vec.y; double dot = 0 * x + 1 * y; // dot product double det = 0 * y - 1 * x; // determinant float angle = (float) Math.atan2( det, dot ) * MathUtils.radiansToDegrees; sprite.rotation = angle; sprite.renderDelay = animdelay; animdelay += 0.1f; boolean isMoving = sprite.spriteAnimation != null && sprite.spriteAnimation instanceof MoveAnimation; final SoundInstance sound = hitEffect.sound; final GameTile hitTile = tile; final GameEntity hitEntity = hitTile.entity; final EnvironmentEntity hitEnvEntity = hitTile.environmentEntity; sprite.spriteAction = new SpriteAction( isMoving ? SpriteAction.FirePoint.End : SpriteAction.FirePoint.Start) { @Override public void evaluate() { // do on hit for ( GameEventHandler handler : entity.getAllHandlers() ) { handler.onHit( entity, hitTile ); } if ( hitEntity != null && !hitEntity.isAllies( entity ) ) { entity.attack( hitEntity, dir ); } else if ( hitEnvEntity != null && !hitEnvEntity.passableBy.intersect( entity.getTravelType() ) ) { entity.attack( hitEnvEntity, dir ); } if ( sound != null ) { sound.play( hitTile ); } } }; SpriteEffect effect = new SpriteEffect( sprite, Direction.CENTER, weapon != null && weapon.light != null ? weapon.light.copyNoFlag() : null ); tile.spriteEffects.add( effect ); } else { // do on hit for ( GameEventHandler handler : entity.getAllHandlers() ) { handler.onHit( entity, tile ); } if ( tile.entity != null && !tile.entity.isAllies( entity ) ) { entity.attack( tile.entity, dir ); } else if ( tile.environmentEntity != null && !tile.environmentEntity.passableBy.intersect( entity.getTravelType() ) ) { entity.attack( tile.environmentEntity, dir ); } if ( tile.x < minPoint.x ) { minPoint.x = tile.x; } if ( tile.x > maxPoint.x ) { maxPoint.x = tile.x; } if ( tile.y < minPoint.y ) { minPoint.y = tile.y; } if ( tile.y > maxPoint.y ) { maxPoint.y = tile.y; } } } } if ( weapon != null && weapon.wepDef != null && weapon.wepDef.hitType == Item.WeaponDefinition.HitType.ALL ) { // Use a joined sprite Sprite sprite = hitEffect.copy(); sprite.rotation = dir.getAngle(); sprite.baseScale[0] = ( maxPoint.x - minPoint.x ) + 1; sprite.baseScale[1] = ( maxPoint.y - minPoint.y ) + 1; if (dir == Direction.WEST || dir == Direction.EAST) { float temp = sprite.baseScale[0]; sprite.baseScale[0] = sprite.baseScale[1]; sprite.baseScale[1] = temp; } SpriteEffect effect = new SpriteEffect( sprite, Direction.CENTER, weapon != null && weapon.light != null ? weapon.light.copyNoFlag() : null ); int px = minPoint.x; int py = minPoint.y; float dx = ( maxPoint.x - minPoint.x ) / 2.0f; float dy = ( maxPoint.y - minPoint.y ) / 2.0f; px += dir.getX() < 0 ? Math.ceil( dx ) : Math.floor( dx ); py += dir.getY() < 0 ? Math.ceil( dy ) : Math.floor( dy ); GameTile tile = attackedTiles.first().level.getGameTile( px, py ); tile.spriteEffects.add( effect ); SoundInstance sound = hitEffect.sound; if ( sound != null ) { sound.play( tile ); } } } }