feat(ui): Added hurt animation

This commit is contained in:
Jakub Žitník 2025-03-26 13:01:46 +01:00
parent 8b09d71a44
commit b1483c138a
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
29 changed files with 270 additions and 40 deletions

View File

@ -48,7 +48,7 @@ public class Main {
// Yeah, yeah I know. Deal with it
}
try {
customLogicProvider.update(game);
customLogicProvider.update(game, screenRenderer);
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -3,13 +3,14 @@ package cz.jzitnik.game;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.GameStates;
import cz.jzitnik.game.entities.Player;
import cz.jzitnik.game.entities.SteveData;
import cz.jzitnik.game.generation.Generation;
import cz.jzitnik.game.entities.items.Item;
import cz.jzitnik.game.entities.items.ItemType;
import cz.jzitnik.game.handlers.place.CustomPlaceHandler;
import cz.jzitnik.game.mobs.EntitySpawnProvider;
import cz.jzitnik.game.sprites.Breaking;
import cz.jzitnik.game.sprites.Steve;
import cz.jzitnik.game.sprites.Steve.SteveState;
import cz.jzitnik.game.annotations.AutoTransient;
import cz.jzitnik.game.annotations.BreaksByPlace;
import cz.jzitnik.game.blocks.Chest;
@ -56,9 +57,14 @@ public class Game extends AutoTransientSupport {
for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) {
for (Block block : world[i][j]) {
if (block.getBlockId().equals("steve") && block.getSpriteState().isPresent()
&& block.getSpriteState().get() == Steve.SteveState.SECOND) {
return new int[] { j, i };
if (block.getBlockId().equals("steve")) {
var steveData = (SteveData) block.getData();
if (steveData.isTop()) {
return new int[] { j, i + 1 };
} else {
return new int[] { j, i };
}
}
}
}
@ -340,7 +346,7 @@ public class Game extends AutoTransientSupport {
ArrayList<Block> combinedList = new ArrayList<>();
combinedList.addAll(world[cords2[1]][cords2[0]]);
combinedList.addAll(world[cords2[1] + 1][cords2[0]]);
player.fell(combinedList);
player.fell(combinedList, this, screenRenderer);
screenRenderer.render(this);
break;
}
@ -434,4 +440,21 @@ public class Game extends AutoTransientSupport {
public boolean isSolid(List<Block> blocks) {
return !blocks.stream().allMatch(Block::isGhost);
}
public void playerHit(ScreenRenderer screenRenderer) {
player.getPlayerBlock1().setSpriteState(SteveState.FIRST_HURT);
player.getPlayerBlock2().setSpriteState(SteveState.SECOND_HURT);
new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
player.getPlayerBlock1().setSpriteState(SteveState.FIRST);
player.getPlayerBlock2().setSpriteState(SteveState.SECOND);
screenRenderer.render(this);
}).start();
}
}

View File

@ -7,8 +7,10 @@ import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.ReduceFallDamage;
import cz.jzitnik.game.core.reducefalldamage.Reducer;
import cz.jzitnik.tui.ScreenRenderer;
@Getter
@Setter
@ -38,7 +40,7 @@ public class Player implements Serializable {
fallDistance++;
}
public void fell(List<Block> fallblock) {
public void fell(List<Block> fallblock, Game game, ScreenRenderer screenRenderer) {
var block = fallblock.stream().filter(b -> b.getClass().isAnnotationPresent(ReduceFallDamage.class)).findFirst();
int damage = Math.max(fallDistance - 3, 0);
if (block.isPresent()) {
@ -52,19 +54,22 @@ public class Player implements Serializable {
System.exit(0);
}
}
dealDamage(damage);
dealDamage(damage, game, screenRenderer);
fallDistance = 0;
}
public synchronized void dealDamage(int amount) {
public synchronized void dealDamage(int amount, Game game, ScreenRenderer screenRenderer) {
health = Math.max(0, health - amount);
if (amount != 0) {
game.playerHit(screenRenderer);
}
if (health == 0) {
// TODO: Implement dead
}
}
public synchronized void dealDamage() {
dealDamage(1);
public synchronized void dealDamage(Game game, ScreenRenderer screenRenderer) {
dealDamage(1, game, screenRenderer);
}
}

View File

@ -0,0 +1,14 @@
package cz.jzitnik.game.entities;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class SteveData implements Serializable {
private boolean top = false;
}

View File

@ -1,6 +1,8 @@
package cz.jzitnik.game.generation;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.SteveData;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.SpriteLoader;
import cz.jzitnik.game.sprites.Steve;
@ -16,10 +18,12 @@ public class Generation {
Block steveBlock = new Block("steve", SpriteLoader.SPRITES.STEVE);
steveBlock.setSpriteState(Steve.SteveState.FIRST);
steveBlock.setGhost(true);
steveBlock.setData(new SteveData(true));
Block steveBlock2 = new Block("steve", SpriteLoader.SPRITES.STEVE);
steveBlock2.setSpriteState(Steve.SteveState.SECOND);
steveBlock2.setGhost(true);
steveBlock2.setMob(true);
steveBlock2.setData(new SteveData(false));
int[] terrainHeight = PopulateWorld.generateTerrain();
@ -32,6 +36,9 @@ public class Generation {
// Spawn player at a valid starting point
world[terrainHeight[256] - 1][256].add(steveBlock2);
world[terrainHeight[256] - 2][256].add(steveBlock);
game.getInventory().addItem(ItemBlockSupplier.getItem("sand"));
game.getInventory().addItem(ItemBlockSupplier.getItem("sand"));
}
private static void initializeWorld(List<Block>[][] world) {

View File

@ -1,7 +1,8 @@
package cz.jzitnik.game.logic;
import cz.jzitnik.game.Game;
import cz.jzitnik.tui.ScreenRenderer;
public interface CustomLogicInterface {
void nextIteration(Game game);
void nextIteration(Game game, ScreenRenderer screenRenderer);
}

View File

@ -6,6 +6,7 @@ import java.util.Set;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
@ -14,10 +15,10 @@ import org.reflections.Reflections;
public class CustomLogicProvider {
private final List<CustomLogicInterface> logicList = new ArrayList<>();
public void update(Game game) {
public void update(Game game, ScreenRenderer screenRenderer) {
for (CustomLogicInterface logicInterface : logicList) {
log.debug("Running logic {}.", logicInterface.getClass().getSimpleName());
logicInterface.nextIteration(game);
logicInterface.nextIteration(game, screenRenderer);
}
}

View File

@ -4,11 +4,12 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Player;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class Burning implements CustomLogicInterface {
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer screenRenderer) {
var world = game.getWorld();
int[] data = game.getPlayerCords();
int x = data[0];
@ -31,7 +32,7 @@ public class Burning implements CustomLogicInterface {
}
if (player.isBurning() || player.getBurningTimeout() != 0) {
player.dealDamage();
player.dealDamage(game, screenRenderer);
player.setBurningState(true);
} else {
player.setBurningState(false);

View File

@ -3,11 +3,12 @@ package cz.jzitnik.game.logic.services.daytime;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class DayTimeLogic implements CustomLogicInterface {
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int time = game.getDaytime();
if (time >= 600) {

View File

@ -6,13 +6,14 @@ import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.annotations.FallingBlock;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class FallingLogic implements CustomLogicInterface {
private static final int RADIUS = 30;
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
var world = game.getWorld();
int[] data = game.getPlayerCords();
int playerX = data[0];

View File

@ -4,6 +4,7 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.annotations.Farmable;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class FarmableLogic implements CustomLogicInterface {
@ -11,7 +12,7 @@ public class FarmableLogic implements CustomLogicInterface {
private static int GROW_LENGTH = 600;
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -6,6 +6,7 @@ import cz.jzitnik.game.annotations.Farmable;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.game.sprites.Farmland.FarmlandState;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class FarmlandLogic implements CustomLogicInterface {
@ -15,7 +16,7 @@ public class FarmlandLogic implements CustomLogicInterface {
private static final int AGE_THRESHOLD = 5;
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -8,6 +8,7 @@ import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.annotations.Flamable;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class FireSpreadingLogic implements CustomLogicInterface {
@ -15,7 +16,7 @@ public class FireSpreadingLogic implements CustomLogicInterface {
private Random random = new Random();
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
var world = game.getWorld();
int[] data = game.getPlayerCords();
int playerX = data[0];

View File

@ -7,6 +7,7 @@ import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.game.sprites.Water;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -29,7 +30,7 @@ public class FlowingLogic implements CustomLogicInterface {
}
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
processFlow(game, "water");
processFlow(game, "lava");
}

View File

@ -7,6 +7,7 @@ import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.annotations.Flamable;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class LavaFireLogic implements CustomLogicInterface {
@ -14,7 +15,7 @@ public class LavaFireLogic implements CustomLogicInterface {
private Random random = new Random();
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -4,13 +4,14 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class LavaWaterLogic implements CustomLogicInterface {
private static final int RADIUS = 20;
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -5,6 +5,7 @@ import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
import java.util.*;
@ -13,7 +14,7 @@ public class GrassGrowingLogic implements CustomLogicInterface {
private static final int RADIUS = 35;
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -4,6 +4,7 @@ import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
import java.util.*;
@ -14,7 +15,7 @@ public class LeavesFallingLogic implements CustomLogicInterface {
private final Random random = new Random();
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -7,6 +7,8 @@ import cz.jzitnik.game.annotations.Sapling;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.generation.Trees;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
import org.reflections.Reflections;
import java.util.HashSet;
@ -32,7 +34,7 @@ public class SaplingLogic implements CustomLogicInterface {
}
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer ignored) {
int[] data = game.getPlayerCords();
var world = game.getWorld();
int playerX = data[0];

View File

@ -3,11 +3,12 @@ package cz.jzitnik.game.logic.services.suffocating;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.CustomLogic;
import cz.jzitnik.game.logic.CustomLogicInterface;
import cz.jzitnik.tui.ScreenRenderer;
@CustomLogic
public class Suffocating implements CustomLogicInterface {
@Override
public void nextIteration(Game game) {
public void nextIteration(Game game, ScreenRenderer screenRenderer) {
var world = game.getWorld();
int[] data = game.getPlayerCords();
int x = data[0];
@ -17,7 +18,7 @@ public class Suffocating implements CustomLogicInterface {
if (blocks.stream().anyMatch(i -> !i.isGhost())) {
// Deal damage when solid block
game.getPlayer().dealDamage();
game.getPlayer().dealDamage(game, screenRenderer);
}
}
}

View File

@ -6,7 +6,7 @@ import java.util.HashMap;
public class Steve extends Sprite<Steve.SteveState> {
public enum SteveState {
FIRST, SECOND,
FIRST, SECOND, FIRST_HURT, SECOND_HURT
}
public Steve() {
@ -14,6 +14,8 @@ public class Steve extends Sprite<Steve.SteveState> {
{
put(SteveState.FIRST, "steve1.ans");
put(SteveState.SECOND, "steve2.ans");
put(SteveState.FIRST_HURT, "steve1_hurt.ans");
put(SteveState.SECOND_HURT, "steve2_hurt.ans");
}
}, SteveState.class);
}

View File

@ -1,13 +1,17 @@
package cz.jzitnik.game.threads.list;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.ThreadRegistry;
import cz.jzitnik.game.entities.Player;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.AllArgsConstructor;
@AllArgsConstructor
@ThreadRegistry
public class NoHungerThread extends Thread {
private final Player player;
private final Game game;
private final ScreenRenderer screenRenderer;
@Override
public void run() {
@ -15,7 +19,7 @@ public class NoHungerThread extends Thread {
try {
Thread.sleep(3000);
if (player.getHunger() == 0) {
player.dealDamage();
player.dealDamage(game, screenRenderer);
}
} catch (InterruptedException e) {
break;

View File

@ -1,6 +1,7 @@
package cz.jzitnik.tui;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.SteveData;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.sprites.Air;
import cz.jzitnik.game.sprites.SimpleSprite;
@ -37,9 +38,15 @@ public class ScreenRenderer {
private int[] getPlayerCords(List<Block>[][] world) {
for (int i = 0; i < world.length; i++) {
for (int j = 0; j < world[i].length; j++) {
if (world[i][j].stream().anyMatch(x -> x.getBlockId().equals("steve") && x.getSpriteState().isPresent()
&& x.getSpriteState().get() == Steve.SteveState.SECOND)) {
return new int[] { j, i };
var steve = world[i][j].stream().filter(x -> x.getBlockId().equals("steve")).findFirst();
if (steve.isPresent()) {
var steveData = (SteveData) steve.get().getData();
if (steveData.isTop()) {
return new int[] { j, i + 1 };
} else {
return new int[] { j, i };
}
}
}
}
@ -127,12 +134,14 @@ public class ScreenRenderer {
sprites.add(stringBuilder.toString());
}
if (blocks.stream()
.anyMatch(block -> block.getBlockId().equals("steve")
&& block.getSpriteState().get() == Steve.SteveState.SECOND)
&& game.getPlayer().isBurningState()) {
SimpleSprite fire = new SimpleSprite("fire.ans");
sprites.add(fire.getSprite());
var steve = blocks.stream().filter(block -> block.getBlockId().equals("steve")).findFirst();
if (steve.isPresent() && game.getPlayer().isBurning()) {
var steveData = (SteveData) steve.get().getData();
if (!steveData.isTop()) {
SimpleSprite fire = new SimpleSprite("fire.ans");
sprites.add(fire.getSprite());
}
}
var burningBlocks = blocks.stream().filter(Block::isOnFire).toList();

View File

@ -0,0 +1,25 @@
             
             
             
          
                 
              
          
              
                         
                          
                             
                            
                           
                           
                         
                              
                           
                             
                           
                         
                         
                         
                         
                         
                         

View File

@ -0,0 +1,25 @@
            
                
           
            
                
           
           
          
                         
                           
                              
                         
                               
                           
                              
                             
                           
                             
                         
                               
                         
                         
                         
                         
                         

View File

@ -0,0 +1,25 @@
                                  
                                  
                                
                                     
                                   
                                 
                                  
                               
                               
                                 
                                
                                
                                 
                                
                
                 
               
             
               
                
         
             
              
                 
                 

View File

@ -0,0 +1,25 @@
                                  
                                  
                                
                                    
                                   
                                   
                                  
                                 
                                 
                                   
                                
                               
                                
                                 
                
               
             
          
            
            
            
            
                
                 
                   

View File

@ -0,0 +1,25 @@
                                  
                                
                                   
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                    
                                     
                        
                         
                        
                        
                      
                     
                        
                       
                        
                        
                         
                         
                        

View File

@ -0,0 +1,25 @@