Reflecting engine changes, some init task improvements

master
Ondřej Hruška 10 years ago
parent e368877b6e
commit cf096bcce5
  1. 1
      .gitignore
  2. 10
      src/mightypork/rogue/Const.java
  3. 38
      src/mightypork/rogue/Launcher.java
  4. 81
      src/mightypork/rogue/RogueApp.java
  5. 18
      src/mightypork/rogue/RogueStateManager.java
  6. 12
      src/mightypork/rogue/events/LoadingOverlayRequest.java
  7. 14
      src/mightypork/rogue/events/RogueStateRequest.java
  8. 70
      src/mightypork/rogue/init/RogueAddResources.java
  9. 18
      src/mightypork/rogue/init/RogueInitUI.java
  10. 30
      src/mightypork/rogue/init/RogueSetupConfig.java
  11. 8
      src/mightypork/rogue/init/RogueSetupDisplay.java
  12. 4
      src/mightypork/rogue/init/RogueSetupGlobalKeys.java
  13. 10
      src/mightypork/rogue/init/RogueSetupWorkdir.java
  14. 28
      src/mightypork/rogue/screens/FpsOverlay.java
  15. 46
      src/mightypork/rogue/screens/LoadingOverlay.java
  16. 12
      src/mightypork/rogue/screens/RogueScreen.java
  17. 22
      src/mightypork/rogue/screens/game/HeartBar.java
  18. 36
      src/mightypork/rogue/screens/game/IngameNav.java
  19. 80
      src/mightypork/rogue/screens/game/InvSlot.java
  20. 64
      src/mightypork/rogue/screens/game/LayerAskSave.java
  21. 40
      src/mightypork/rogue/screens/game/LayerDeath.java
  22. 86
      src/mightypork/rogue/screens/game/LayerGameUi.java
  23. 146
      src/mightypork/rogue/screens/game/LayerInv.java
  24. 26
      src/mightypork/rogue/screens/game/LayerMapView.java
  25. 36
      src/mightypork/rogue/screens/game/LayerWin.java
  26. 18
      src/mightypork/rogue/screens/game/NavButton.java
  27. 156
      src/mightypork/rogue/screens/game/ScreenGame.java
  28. 52
      src/mightypork/rogue/screens/menu/ScreenMainMenu.java
  29. 44
      src/mightypork/rogue/screens/select_world/ScreenSelectWorld.java
  30. 88
      src/mightypork/rogue/screens/select_world/WorldSlot.java
  31. 111
      src/mightypork/rogue/screens/story/ScreenStory.java
  32. 74
      src/mightypork/rogue/world/Inventory.java
  33. 80
      src/mightypork/rogue/world/PlayerControl.java
  34. 64
      src/mightypork/rogue/world/PlayerData.java
  35. 218
      src/mightypork/rogue/world/PlayerFacade.java
  36. 132
      src/mightypork/rogue/world/World.java
  37. 136
      src/mightypork/rogue/world/WorldConsole.java
  38. 66
      src/mightypork/rogue/world/WorldProvider.java
  39. 90
      src/mightypork/rogue/world/WorldRenderer.java
  40. 18
      src/mightypork/rogue/world/entity/AiTimer.java
  41. 44
      src/mightypork/rogue/world/entity/Entities.java
  42. 150
      src/mightypork/rogue/world/entity/Entity.java
  43. 26
      src/mightypork/rogue/world/entity/EntityModel.java
  44. 22
      src/mightypork/rogue/world/entity/EntityModule.java
  45. 28
      src/mightypork/rogue/world/entity/EntityPathFinder.java
  46. 4
      src/mightypork/rogue/world/entity/EntityRenderer.java
  47. 28
      src/mightypork/rogue/world/entity/impl/BossRatAi.java
  48. 20
      src/mightypork/rogue/world/entity/impl/BrownRatAi.java
  49. 38
      src/mightypork/rogue/world/entity/impl/EntityBossRat.java
  50. 42
      src/mightypork/rogue/world/entity/impl/EntityBrownRat.java
  51. 44
      src/mightypork/rogue/world/entity/impl/EntityGrayRat.java
  52. 76
      src/mightypork/rogue/world/entity/impl/EntityPlayer.java
  53. 24
      src/mightypork/rogue/world/entity/impl/GrayRatAi.java
  54. 200
      src/mightypork/rogue/world/entity/impl/MonsterAi.java
  55. 72
      src/mightypork/rogue/world/entity/modules/EntityModuleHealth.java
  56. 134
      src/mightypork/rogue/world/entity/modules/EntityModulePosition.java
  57. 12
      src/mightypork/rogue/world/entity/modules/EntityMoveListener.java
  58. 98
      src/mightypork/rogue/world/entity/modules/EntityPos.java
  59. 38
      src/mightypork/rogue/world/entity/render/EntityRendererMobLR.java
  60. 2
      src/mightypork/rogue/world/events/GameWinEvent.java
  61. 4
      src/mightypork/rogue/world/events/GameWinHandler.java
  62. 2
      src/mightypork/rogue/world/events/PlayerDeathHandler.java
  63. 4
      src/mightypork/rogue/world/events/PlayerKilledEvent.java
  64. 12
      src/mightypork/rogue/world/events/PlayerStepEndEvent.java
  65. 2
      src/mightypork/rogue/world/events/PlayerStepEndListener.java
  66. 4
      src/mightypork/rogue/world/events/WorldAscendRequest.java
  67. 2
      src/mightypork/rogue/world/events/WorldAscendRequestListener.java
  68. 4
      src/mightypork/rogue/world/events/WorldDescendRequest.java
  69. 2
      src/mightypork/rogue/world/events/WorldDescendRequestListener.java
  70. 20
      src/mightypork/rogue/world/events/WorldPauseRequest.java
  71. 110
      src/mightypork/rogue/world/gen/LevelBuilder.java
  72. 30
      src/mightypork/rogue/world/gen/MapTheme.java
  73. 2
      src/mightypork/rogue/world/gen/RoomBuilder.java
  74. 22
      src/mightypork/rogue/world/gen/RoomEntry.java
  75. 10
      src/mightypork/rogue/world/gen/Rooms.java
  76. 352
      src/mightypork/rogue/world/gen/ScratchMap.java
  77. 78
      src/mightypork/rogue/world/gen/WorldCreator.java
  78. 16
      src/mightypork/rogue/world/gen/WorldGenError.java
  79. 52
      src/mightypork/rogue/world/gen/rooms/AbstractRectRoom.java
  80. 14
      src/mightypork/rogue/world/gen/rooms/BasicRoom.java
  81. 26
      src/mightypork/rogue/world/gen/rooms/BossRoom.java
  82. 14
      src/mightypork/rogue/world/gen/rooms/DeadEndRoom.java
  83. 20
      src/mightypork/rogue/world/gen/rooms/EntranceRoom.java
  84. 20
      src/mightypork/rogue/world/gen/rooms/ExitRoom.java
  85. 20
      src/mightypork/rogue/world/gen/rooms/ItemShrineRoom.java
  86. 14
      src/mightypork/rogue/world/gen/rooms/SecretRoom.java
  87. 18
      src/mightypork/rogue/world/gen/rooms/StorageRoom.java
  88. 10
      src/mightypork/rogue/world/gen/rooms/TreasureChestRoom.java
  89. 30
      src/mightypork/rogue/world/gen/themes/ThemeBrick.java
  90. 88
      src/mightypork/rogue/world/gui/MapView.java
  91. 32
      src/mightypork/rogue/world/gui/Minimap.java
  92. 50
      src/mightypork/rogue/world/gui/WorldConsoleRenderer.java
  93. 70
      src/mightypork/rogue/world/gui/interaction/MIPKeyboard.java
  94. 70
      src/mightypork/rogue/world/gui/interaction/MIPMouse.java
  95. 22
      src/mightypork/rogue/world/gui/interaction/MapInteractionPlugin.java
  96. 122
      src/mightypork/rogue/world/item/Item.java
  97. 28
      src/mightypork/rogue/world/item/ItemModel.java
  98. 12
      src/mightypork/rogue/world/item/ItemRenderer.java
  99. 44
      src/mightypork/rogue/world/item/Items.java
  100. 22
      src/mightypork/rogue/world/item/impl/ItemBaseFood.java
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -2,6 +2,7 @@
/target/
/~local/
/.rogue-save/
/rogue-workdir/
/build/out/*.jar
/build/in/*.jar
*.log

@ -7,20 +7,20 @@ package mightypork.rogue;
* @author Ondřej Hruška (MightyPork)
*/
public final class Const {
// STRINGS
public static final int VERSION = 6;
public static final String APP_NAME = "Rogue: Savage Rats";
public static final String TITLEBAR = APP_NAME + ", v" + VERSION;
// AUDIO
public static final int FPS_RENDER = 120; // max
// INITIAL WINDOW SIZE
public static final int WINDOW_W = 640;
public static final int WINDOW_H = 480;
/** Render dark in unknown area & skip invisible stuff */
public static boolean RENDER_UFOG = true;
}

@ -9,7 +9,7 @@ import mightypork.utils.files.OsUtils;
public class Launcher {
/**
* Launcher
*
@ -18,72 +18,72 @@ public class Launcher {
public static void main(String[] args)
{
// System.out.println("argv = " + Arrays.toString(args)+"\n");
Level llSyso = Level.FINER;
File workdir = null;
boolean logBus = false;
try {
boolean localWorkdir = false;
String lwdDir = null;
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
if (arg.equals("--workdir") || arg.equals("-w")) {
localWorkdir = true;
lwdDir = args[i + 1];
i++;
continue;
} else if (arg.equals("--silent") || arg.equals("-s")) {
llSyso = Level.OFF;
continue;
} else if (arg.equals("--warn") || arg.equals("-e")) {
llSyso = Level.WARNING;
continue;
} else if (arg.equals("--verbose") || arg.equals("-v")) {
llSyso = Level.ALL;
continue;
} else if (arg.equals("--help") || arg.equals("-h")) {
printHelp();
System.exit(0);
} else if (arg.equals("--debug-bus")) {
logBus = true;
} else {
System.err.println("Unknown argument: " + arg);
printHelp();
System.exit(1);
}
}
if (!localWorkdir) {
workdir = OsUtils.getHomeWorkDir(".rogue");
} else {
workdir = new File(lwdDir);
}
} catch (final Exception e) {
System.out.println("Error parsing arguments:");
e.printStackTrace();
printHelp();
System.exit(1);
}
final App app = new RogueApp(workdir, llSyso);
if (logBus) App.bus().detailedLogging = true;
final App app = new RogueApp(workdir, llSyso);
if (logBus) App.bus().detailedLogging = true;
app.start();
}
private static void printHelp()
{
//@formatter:off

@ -6,15 +6,10 @@ import java.util.logging.Level;
import mightypork.gamecore.backends.lwjgl.LwjglBackend;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.DeltaMainLoop;
import mightypork.gamecore.core.MainLoop;
import mightypork.gamecore.core.events.MainLoopRequest;
import mightypork.gamecore.core.init.InitTaskCrashHandler;
import mightypork.gamecore.core.init.InitTaskIonizables;
import mightypork.gamecore.core.init.InitTaskLog;
import mightypork.gamecore.core.init.InitTaskLogHeader;
import mightypork.gamecore.core.init.InitTaskMainLoop;
import mightypork.gamecore.core.init.InitTaskResourceLoaderAsync;
import mightypork.gamecore.core.plugins.screenshot.InitTaskPluginScreenshot;
import mightypork.gamecore.core.plugins.screenshot.ScreenshotRequestListener;
import mightypork.gamecore.gui.events.ViewportChangeEvent;
@ -22,12 +17,12 @@ import mightypork.gamecore.gui.events.ViewportChangeListener;
import mightypork.gamecore.resources.Res;
import mightypork.rogue.RogueStateManager.RogueState;
import mightypork.rogue.events.RogueStateRequest;
import mightypork.rogue.init.AddResources;
import mightypork.rogue.init.InitScreens;
import mightypork.rogue.init.SetupConfig;
import mightypork.rogue.init.SetupDisplay;
import mightypork.rogue.init.SetupGlobalKeys;
import mightypork.rogue.init.SetupWorkdir;
import mightypork.rogue.init.RogueAddResources;
import mightypork.rogue.init.RogueInitUI;
import mightypork.rogue.init.RogueSetupConfig;
import mightypork.rogue.init.RogueSetupDisplay;
import mightypork.rogue.init.RogueSetupGlobalKeys;
import mightypork.rogue.init.RogueSetupWorkdir;
import mightypork.rogue.world.Inventory;
import mightypork.utils.ion.Ion;
import mightypork.utils.logging.Log;
@ -39,13 +34,13 @@ import mightypork.utils.logging.Log;
* @author Ondřej Hruška (MightyPork)
*/
public final class RogueApp extends App implements ViewportChangeListener, ScreenshotRequestListener {
public RogueApp(File workdir, final Level logStdout)
{
super(new LwjglBackend());
addInitTask(new InitTaskLog() {
@Override
public void init()
{
@ -55,25 +50,24 @@ public final class RogueApp extends App implements ViewportChangeListener, Scree
setLogName("runtime");
}
});
addInitTask(new InitTaskLogHeader() {
@Override
public void before()
{
Log.i("## Starting Rogue v." + Const.VERSION + " ##");
}
});
addInitTask(new InitTaskCrashHandler());
addInitTask(new SetupWorkdir(workdir));
addInitTask(new SetupConfig());
addInitTask(new RogueSetupWorkdir(workdir));
addInitTask(new RogueSetupConfig());
addInitTask(new InitTaskPluginScreenshot("screenshots"));
addInitTask(new InitTaskIonizables() {
@Override
public void after()
{
@ -81,38 +75,27 @@ public final class RogueApp extends App implements ViewportChangeListener, Scree
Ion.register(Inventory.class);
}
});
addInitTask(new SetupDisplay());
addInitTask(new SetupGlobalKeys());
addInitTask(new InitTaskResourceLoaderAsync());
addInitTask(new AddResources());
addInitTask(new InitTaskMainLoop() {
@Override
protected MainLoop getLoopImpl()
{
return new DeltaMainLoop();
}
});
addInitTask(new InitScreens());
addInitTask(new RogueSetupDisplay());
addInitTask(new RogueSetupGlobalKeys());
addInitTask(new RogueAddResources());
addInitTask(new RogueInitUI());
}
@Override
public void onScreenshotRequest()
{
Res.sound("gui.shutter").play(0.8);
}
@Override
protected void postInit()
{
bus().send(new MainLoopRequest(new Runnable() {
@Override
public void run()
{
@ -125,16 +108,16 @@ public final class RogueApp extends App implements ViewportChangeListener, Scree
}
}, false));
}
@Override
public void onViewportChanged(ViewportChangeEvent event)
{
// save viewport size to config file
final boolean fs = gfx().isFullscreen();
cfg().setValue("display.fullscreen", fs);
if (!fs) {
cfg().setValue("display.width", gfx().getWidth());
cfg().setValue("display.height", gfx().getHeight());

@ -8,40 +8,40 @@ import mightypork.utils.logging.Log;
public class RogueStateManager extends BusNode {
public static enum RogueState
{
MAIN_MENU, SELECT_WORLD, PLAY_WORLD, EXIT, STORY
}
public void triggerAction(RogueState state, boolean fromDark)
{
switch (state) {
case MAIN_MENU:
App.bus().send(new CrossfadeRequest("main_menu", fromDark));
break;
case SELECT_WORLD:
App.bus().send(new CrossfadeRequest("select_world", fromDark));
break;
case PLAY_WORLD:
App.bus().send(new CrossfadeRequest("game", fromDark));
break;
case STORY:
App.bus().send(new CrossfadeRequest("story", fromDark));
break;
case EXIT:
App.bus().send(new CrossfadeRequest(null));
break;
default:
Log.w("Unknown action: " + state);
break;
}
}
}

@ -13,11 +13,11 @@ import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
*/
@SingleReceiverEvent
public class LoadingOverlayRequest extends BusEvent<LoadingOverlay> {
private final String msg;
private final Runnable task;
/**
* @param msg task description
* @param task task runnable
@ -27,12 +27,12 @@ public class LoadingOverlayRequest extends BusEvent<LoadingOverlay> {
this.task = task;
this.msg = msg;
}
@Override
protected void handleBy(LoadingOverlay handler)
{
handler.show(msg, task);
}
}

@ -12,25 +12,25 @@ import mightypork.utils.eventbus.BusEvent;
* @author Ondřej Hruška (MightyPork)
*/
public class RogueStateRequest extends BusEvent<RogueStateManager> {
final private RogueState requested;
private final boolean fromDark;
public RogueStateRequest(RogueState requested)
{
this.requested = requested;
this.fromDark = false;
}
public RogueStateRequest(RogueState requested, boolean fromDark)
{
this.requested = requested;
this.fromDark = fromDark;
}
@Override
protected void handleBy(RogueStateManager handler)
{

@ -15,29 +15,29 @@ import mightypork.gamecore.graphics.textures.WrapMode;
import mightypork.utils.math.constraints.rect.Rect;
public class AddResources extends InitTaskResources {
public class RogueAddResources extends InitTaskResources {
@Override
public void addFonts(FontRegistry fonts)
{
DeferredFont font;
//fonts.loadFont("polygon_pixel", new DeferredFont("/res/font/PolygonPixel5x7Standard.ttf", Glyphs.basic, 16));
fonts.addFont("press_start", font = App.gfx().createFont("/res/font/PressStart2P.ttf", Glyphs.basic, 16));
fonts.addFont("battlenet", font = App.gfx().createFont("/res/font/battlenet.ttf", Glyphs.basic, 16));
font.setDiscardRatio(3 / 16D, 2 / 16D);
fonts.addFont("tinyutf", font = App.gfx().createFont("/res/font/TinyUnicode2.ttf", Glyphs.basic, 16));
font.setDiscardRatio(5 / 16D, 3 / 16D);
// aliases
fonts.addAlias("thick", "press_start");
fonts.addAlias("thin", "battlenet");
fonts.addAlias("tiny", "tinyutf");
}
@Override
public void addSounds(SoundRegistry sounds)
{
@ -45,42 +45,42 @@ public class AddResources extends InitTaskResources {
sounds.addLoop("music.menu", "/res/audio/music/Home_Base_Groove.ogg", 1, 0.7, 3, 1.5);
sounds.addLoop("music.dungeon", "/res/audio/music/8bit_Dungeon_Level.ogg", 1, 0.6, 3, 1.5);
//sounds.addLoop("music.dungeon2.boss", "/res/audio/music/8bit_Dungeon_Boss.ogg", 1, 0.6, 3, 1.5);
sounds.addEffect("gui.shutter", "/res/audio/effects/shutter.ogg", 1, 1);
sounds.addEffect("gui.click", "/res/audio/effects/click.ogg", 1, 1);
sounds.addEffect("game.win", "/res/audio/effects/win.ogg", 1, 0.7);
sounds.addEffect("game.lose", "/res/audio/effects/lose.ogg", 1, 1);
sounds.addEffect("item.drop", "/res/audio/effects/drop.ogg", 1, 1);
sounds.addEffect("item.pickup", "/res/audio/effects/pickup.ogg", 1, 1);
sounds.addEffect("item.break", "/res/audio/effects/break.ogg", 1, 1);
sounds.addEffect("player.eat", "/res/audio/effects/eat.ogg", 1, 1);
sounds.addEffect("player.hurt", "/res/audio/effects/hurt.ogg", 1, 1);
sounds.addEffect("crate.open", "/res/audio/effects/crate.ogg", 1, 1);
sounds.addEffect("door.open", "/res/audio/effects/door.ogg", 1, 1);
sounds.addEffect("door.close", "/res/audio/effects/door.ogg", 0.8, 1);
sounds.addEffect("rat.sqeak1", "/res/audio/effects/mouse1.ogg", 1, 1);
sounds.addEffect("rat.sqeak2", "/res/audio/effects/mouse2.ogg", 1, 1);
sounds.addEffect("rat.sqeak3", "/res/audio/effects/mouse3.ogg", 1, 1);
sounds.addEffect("rat.sqeak4", "/res/audio/effects/mouse4.ogg", 1, 1);
sounds.addEffect("rat.sqeak5", "/res/audio/effects/mouse5.ogg", 1, 1);
}
@Override
public void addTextures(TextureRegistry textures)
{
ITexture texture;
QuadGrid grid;
// gui
texture = textures.loadTexture("/res/img/gui.png", FilterMode.NEAREST, WrapMode.CLAMP);
// small gui elements
grid = texture.grid(32, 32);
textures.add("hud.heart.on", grid.makeQuad(0, 0));
@ -88,14 +88,14 @@ public class AddResources extends InitTaskResources {
textures.add("hud.heart.half", grid.makeQuad(2, 0));
textures.add("hud.xp.on", grid.makeQuad(0, 1));
textures.add("hud.xp.off", grid.makeQuad(1, 1));
// nav
grid = texture.grid(8, 8);
textures.add("nav.bg", grid.makeQuad(0, 7, 4, 1));
textures.add("nav.button.bg.base", grid.makeQuad(4, 7));
textures.add("nav.button.bg.hover", grid.makeQuad(5, 7));
textures.add("nav.button.bg.down", grid.makeQuad(6, 7));
textures.add("nav.button.fg.eat", grid.makeQuad(0, 6));
textures.add("nav.button.fg.inventory", grid.makeQuad(1, 6));
textures.add("nav.button.fg.attack", grid.makeQuad(2, 6));
@ -106,10 +106,10 @@ public class AddResources extends InitTaskResources {
textures.add("nav.button.fg.magnify", grid.makeQuad(7, 6));
textures.add("nav.button.fg.save", grid.makeQuad(7, 5));
textures.add("nav.button.fg.load", grid.makeQuad(6, 5));
textures.add("inv.slot.base", grid.makeQuad(0, 5));
textures.add("inv.slot.selected", grid.makeQuad(1, 5));
// sprites
texture = textures.loadTexture("/res/img/sprites.png", FilterMode.NEAREST, WrapMode.CLAMP);
grid = texture.grid(8, 8);
@ -118,7 +118,7 @@ public class AddResources extends InitTaskResources {
textures.add("sprite.rat.brown", grid.makeSheet(0, 2, 4, 1));
textures.add("sprite.rat.boss", grid.makeSheet(0, 3, 4, 1));
textures.add("sprite.zzz", grid.makeQuad(0, 7)); // sleep thingy
// logo
texture = textures.loadTexture("/res/img/logo.png", FilterMode.NEAREST, WrapMode.CLAMP);
textures.add("logo", texture.makeQuad(Rect.make(0, 0, 0.543, 0.203)));
@ -126,52 +126,52 @@ public class AddResources extends InitTaskResources {
textures.add("death", grid.makeQuad(0, 2));
textures.add("death2", grid.makeQuad(1, 2, 1.5, 1));
textures.add("win", grid.makeQuad(2.5, 2, 1.5, 1));
grid = texture.grid(8, 4);
textures.add("story_1", grid.makeQuad(0, 2, 3, 1));
textures.add("story_2", grid.makeQuad(3, 2, 3, 1));
textures.add("story_3", grid.makeQuad(0, 3, 3, 1));
// tiles
texture = textures.addTexture("tiles", "/res/img/tiles.png", FilterMode.NEAREST, WrapMode.CLAMP);
grid = texture.grid(8, 8);
textures.add("tile.brick.floor", grid.makeSheet(0, 1, 5, 1));
textures.add("tile.brick.wall", grid.makeSheet(0, 0, 8, 1));
textures.add("tile.brick.door.closed", grid.makeQuad(1, 2));
textures.add("tile.brick.door.open", grid.makeQuad(2, 2));
textures.add("tile.brick.door.secret", grid.makeSheet(0, 3, 2, 1));
textures.add("tile.brick.passage", grid.makeSheet(3, 2, 4, 1));
textures.add("tile.brick.stairs.up", grid.makeQuad(0, 6));
textures.add("tile.brick.stairs.down", grid.makeQuad(1, 6));
textures.add("tile.extra.chest.closed", grid.makeQuad(0, 4));
textures.add("tile.extra.chest.open", grid.makeQuad(1, 4));
// shadows
textures.add("tile.shadow.n", grid.makeQuad(0, 7));
textures.add("tile.shadow.s", grid.makeQuad(0, 7).flipY());
textures.add("tile.shadow.w", grid.makeQuad(1, 7));
textures.add("tile.shadow.e", grid.makeQuad(1, 7).flipX());
textures.add("tile.shadow.nw", grid.makeQuad(2, 7));
textures.add("tile.shadow.ne", grid.makeQuad(2, 7).flipX());
textures.add("tile.shadow.sw", grid.makeQuad(2, 7).flipY());
textures.add("tile.shadow.se", grid.makeQuad(2, 7).flipY().flipX());
// unexplored fog
textures.add("tile.ufog.n", grid.makeQuad(3, 7));
textures.add("tile.ufog.s", grid.makeQuad(3, 7).flipY());
textures.add("tile.ufog.w", grid.makeQuad(4, 7));
textures.add("tile.ufog.e", grid.makeQuad(4, 7).flipX());
textures.add("tile.ufog.nw", grid.makeQuad(5, 7));
textures.add("tile.ufog.ne", grid.makeQuad(5, 7).flipX());
textures.add("tile.ufog.sw", grid.makeQuad(5, 7).flipY());
textures.add("tile.ufog.se", grid.makeQuad(5, 7).flipY().flipX());
textures.add("tile.ufog.full", grid.makeQuad(6, 7));
texture = textures.addTexture("items", "/res/img/items.png", FilterMode.NEAREST, WrapMode.CLAMP);
grid = texture.grid(8, 8);
textures.add("item.meat", grid.makeQuad(0, 0));
@ -186,5 +186,5 @@ public class AddResources extends InitTaskResources {
textures.add("item.knife", grid.makeQuad(1, 1));
textures.add("item.twig", grid.makeQuad(2, 1));
}
}

@ -2,7 +2,7 @@ package mightypork.rogue.init;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.init.InitTaskScreens;
import mightypork.gamecore.core.init.InitTaskUI;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay;
@ -16,28 +16,28 @@ import mightypork.rogue.screens.story.ScreenStory;
import mightypork.rogue.world.WorldProvider;
public class InitScreens extends InitTaskScreens {
public class RogueInitUI extends InitTaskUI {
@Override
protected Renderable getMainRenderableImpl()
protected Renderable createMainRenderable()
{
final ScreenRegistry screens = new ScreenRegistry();
app.addChildClient(screens);
/* game screen references world provider instance */
App.bus().subscribe(new RogueStateManager());
App.bus().subscribe(WorldProvider.get());
screens.addOverlay(new CrossfadeOverlay());
screens.addOverlay(new CrossfadeOverlay());
screens.addScreen("main_menu", new ScreenMainMenu());
screens.addScreen("select_world", new ScreenSelectWorld());
screens.addScreen("game", new ScreenGame());
screens.addScreen("story", new ScreenStory());
screens.addOverlay(new FpsOverlay());
screens.addOverlay(new LoadingOverlay());
return screens;
}
}

@ -2,37 +2,37 @@ package mightypork.rogue.init;
import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.core.config.InitTaskConfig;
import mightypork.gamecore.core.init.InitTaskConfig;
public class SetupConfig extends InitTaskConfig {
public class RogueSetupConfig extends InitTaskConfig {
@Override
protected Config buildConfig()
{
final Config cfg = new Config("config.ini", "Rogue config file");
cfg.addBoolean("display.fullscreen", false, "Start in fullscreen (remembers state at exit)");
cfg.addInteger("display.width", 1024, "Initial width (remembers from last time)");
cfg.addInteger("display.height", 768, "Initial height (remembers from last time)");
cfg.addBoolean("opt.show_story", true, "Show story on start-up.");
// keys
cfg.addBoolean("opt.show_story", true, "Show story on start-up.");
// keys
cfg.addKeyStroke("global.quit", "CTRL+Q", "Quit the game");
cfg.addKeyStroke("global.quit_force", "CTRL+SHIFT+Q", "Quit the game without asking, low-level");
cfg.addKeyStroke("global.screenshot", "F2", "Take screenshot (save into working directory)");
cfg.addKeyStroke("global.fullscreen", "F11", "Toggle fullscreen");
cfg.addKeyStroke("global.fps_meter", "F3", "Toggle FPS meter overlay");
cfg.addKeyStroke("general.close", "ESC", "Leave a dialog or screen");
cfg.addKeyStroke("general.cancel", "ESC", "\"Cancel\" option in dialogs");
cfg.addKeyStroke("general.confirm", "ENTER", "\"Confirm\" option in dialogs");
cfg.addKeyStroke("general.yes", "Y", "\"Yes\" option in dialogs");
cfg.addKeyStroke("general.no", "N", "\"No\" option in dialogs");
cfg.addKeyStroke("game.quit", "ESC", "Quit to menu");
cfg.addKeyStroke("game.save", "CTRL+S", "Save to file");
cfg.addKeyStroke("game.load", "CTRL+L", "Load from file");
@ -42,22 +42,22 @@ public class SetupConfig extends InitTaskConfig {
cfg.addKeyStroke("game.drop", "D", "Drop last picked item");
cfg.addKeyStroke("game.inventory", "I", "Toggle inventory view");
cfg.addKeyStroke("game.pause", "P", "Pause the game");
cfg.addKeyStroke("game.walk.up", "UP", "Walk north");
cfg.addKeyStroke("game.walk.down", "DOWN", "Walk south");
cfg.addKeyStroke("game.walk.left", "LEFT", "Walk west");
cfg.addKeyStroke("game.walk.right", "RIGHT", "Walk east");
cfg.addKeyStroke("game.cheat.xray", "CTRL+SHIFT+X", "Cheat to see unexplored tiles");
cfg.addKeyStroke("game.inv.use", "E", "Use (eat or equip) the selected item");
cfg.addKeyStroke("game.inv.drop", "D", "Drop the selected item");
cfg.addKeyStroke("game.inv.move.left", "LEFT", "Move inventory cursor left");
cfg.addKeyStroke("game.inv.move.right", "RIGHT", "Move inventory cursor right");
cfg.addKeyStroke("game.inv.move.up", "UP", "Move inventory cursor up");
cfg.addKeyStroke("game.inv.move.down", "DOWN", "Move inventory cursor down");
return cfg;
}
}

@ -6,20 +6,20 @@ import mightypork.gamecore.core.init.InitTaskDisplay;
import mightypork.rogue.Const;
public class SetupDisplay extends InitTaskDisplay {
public class RogueSetupDisplay extends InitTaskDisplay {
@Override
public void init()
{
final int w = App.cfg().getValue("display.width");
final int h = App.cfg().getValue("display.height");
final boolean fs = App.cfg().getValue("display.fullscreen");
setSize(w, h);
setResizable(true);
setFullscreen(fs);
setTitle(Const.TITLEBAR);
setTargetFps(Const.FPS_RENDER);
}
}

@ -9,7 +9,7 @@ import mightypork.gamecore.input.Trigger;
import mightypork.utils.eventbus.BusEvent;
public class SetupGlobalKeys extends InitTask {
public class RogueSetupGlobalKeys extends InitTask {
@Override
@ -55,6 +55,6 @@ public class SetupGlobalKeys extends InitTask {
@Override
public String[] getDependencies()
{
return new String[] { "config" };
return new String[] { "config", "backend" };
}
}

@ -6,14 +6,14 @@ import java.io.File;
import mightypork.gamecore.core.init.InitTaskWorkdir;
public class SetupWorkdir extends InitTaskWorkdir {
public SetupWorkdir(File workdir)
public class RogueSetupWorkdir extends InitTaskWorkdir {
public RogueSetupWorkdir(File workdir)
{
super(workdir, true);
}
@Override
public void init()
{

@ -22,48 +22,48 @@ import mightypork.utils.string.StringProvider;
* @author Ondřej Hruška (MightyPork)
*/
public class FpsOverlay extends Overlay {
TextPainter tp;
public FpsOverlay()
{
/*
* Toggle key: F3
*/
bindKey(App.cfg().getKeyStroke("global.fps_meter"), Trigger.RISING, new Action() {
@Override
public void execute()
{
setVisible(!isVisible());
}
});
final IFont font = Res.font("thin");
final Num h = root.height();
final RectBound constraint = root.shrink(h.perc(3)).topRight().startRect().growDown(h.perc(5).max(16));
tp = new TextPainter(font, AlignX.RIGHT, RGB.YELLOW, new StringProvider() {
@Override
public String getString()
{
return App.gfx().getFps() + " fps";
}
});
tp.setRect(constraint);
tp.setShadow(RGB.BLACK_60, Vect.make(tp.height().div(8).round()));
root.add(tp);
setVisible(false); // initially hide.
}
@Override
public int getZIndex()
{

@ -24,32 +24,32 @@ import mightypork.utils.string.StringProvider;
* @author Ondřej Hruška (MightyPork)
*/
public class LoadingOverlay extends Overlay {
private static final double T_IN = 0.5;
private static final double T_OUT = 1;
private final NumAnimated alpha = new NumAnimated(0);
private final StringProvider msgStrProv = new StringProvider() {
@Override
public String getString()
{
return msg == null ? "" : msg;
}
};
private boolean busy;
private String msg;
private Runnable task;
private final TimedTask tt = new TimedTask() {
@Override
public void run()
{
Support.runAsThread(new Runnable() {
@Override
public void run()
{
@ -61,38 +61,38 @@ public class LoadingOverlay extends Overlay {
});
}
};
public LoadingOverlay()
{
final QuadPainter qp = new QuadPainter(PAL16.SEABLUE);
qp.setRect(root);
root.add(qp);
updated.add(alpha);
updated.add(tt);
Rect textRect = root.shrink(Num.ZERO, root.height().perc(48));
textRect = textRect.moveY(root.height().perc(-10));
final TextPainter tp = new TextPainter(Res.font("thick"), AlignX.CENTER, RGB.WHITE, msgStrProv);
tp.setRect(textRect);
tp.setShadow(RGB.BLACK_60, tp.height().mul(1 / 8D).toVectXY());
root.add(tp);
setAlpha(alpha);
}
@Override
public int getZIndex()
{
return 10001; // not too high, so app can put something on top
}
/**
* Show for a task
*
@ -102,14 +102,14 @@ public class LoadingOverlay extends Overlay {
public void show(String message, Runnable task)
{
if (busy) throw new IllegalStateException("Loader is busy with another task.");
this.msg = message;
this.task = task;
this.busy = true;
alpha.setEasing(Easing.SINE_IN);
alpha.fadeIn(T_IN);
tt.start(T_IN);
}
}

@ -2,20 +2,20 @@ package mightypork.rogue.screens;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.events.ShutdownEvent;
import mightypork.gamecore.core.events.ShutdownListener;
import mightypork.gamecore.core.events.ShutdownRequest;
import mightypork.gamecore.core.events.ShutdownRequestListener;
import mightypork.gamecore.gui.screens.LayeredScreen;
import mightypork.rogue.RogueStateManager.RogueState;
import mightypork.rogue.events.RogueStateRequest;
import mightypork.utils.annotations.Stub;
public class RogueScreen extends LayeredScreen implements ShutdownListener {
public class RogueScreen extends LayeredScreen implements ShutdownRequestListener {
@Override
@Stub
public void onShutdown(ShutdownEvent event)
public void onShutdownRequested(ShutdownRequest event)
{
App.bus().send(new RogueStateRequest(RogueState.EXIT));
//event.consume(); FIXME we need a "shutdown request" event AND "shutdown" event.

@ -11,17 +11,17 @@ import mightypork.utils.math.constraints.rect.Rect;
public class HeartBar extends BaseComponent {
private final TxQuad img_on;
private final TxQuad img_off;
private final TxQuad img_half;
private final Num total;
private final Num active;
private final NumVar index = new NumVar(0);
private final Rect heart;
public HeartBar(Num total, Num active, TxQuad img_on, TxQuad img_half, TxQuad img_off, AlignX align)
{
super();
@ -30,10 +30,10 @@ public class HeartBar extends BaseComponent {
this.img_on = img_on;
this.img_off = img_off;
this.img_half = img_half;
final Num h = height();
final Num w = width();
switch (align) {
case LEFT:
heart = leftEdge().growRight(h).moveX(index.mul(h));
@ -47,19 +47,19 @@ public class HeartBar extends BaseComponent {
default:
heart = null; // impossible
}
}
@Override
protected void renderComponent()
{
for (int i = 0; i < total.value(); i++) {
index.setTo(i);
final double rem = active.value() - i;
App.gfx().quad(heart, (rem > 0.6 ? img_on : rem > 0.25 ? img_half : img_off));
}
}
}

@ -12,51 +12,51 @@ import mightypork.utils.math.constraints.rect.RectBound;
public class IngameNav extends LayoutComponent {
private final FlowColumnLayout leftFlow;
private final FlowColumnLayout rightFlow;
private final Rect paintHelper;
private final TxQuad bg;
public IngameNav()
{
this(null);
}
public IngameNav(RectBound context)
{
super(context);
final Rect shr = this.shrink(height().perc(5));
leftFlow = new FlowColumnLayout(context, shr.height(), AlignX.LEFT);
rightFlow = new FlowColumnLayout(context, shr.height(), AlignX.RIGHT);
leftFlow.setRect(shr);
rightFlow.setRect(shr);
attach(leftFlow);
attach(rightFlow);
paintHelper = leftEdge().growRight(height().mul(4));
bg = Res.txQuad("nav.bg");
}
public void addLeft(NavButton comp)
{
leftFlow.add(comp);
}
public void addRight(NavButton comp)
{
rightFlow.add(comp);
}
@Override
public void renderComponent()
{
@ -64,8 +64,8 @@ public class IngameNav extends LayoutComponent {
for (int i = 0; i < Math.ceil(width().value() / paintHelper.width().value()); i++) {
App.gfx().quad(paintHelper.moveX(paintHelper.width().value() * i), bg);
}
super.renderComponent();
}
}

@ -11,7 +11,7 @@ import mightypork.rogue.world.PlayerFacade;
import mightypork.rogue.world.WorldProvider;
import mightypork.rogue.world.item.Item;
import mightypork.rogue.world.item.ItemType;
import mightypork.utils.Support;
import mightypork.utils.Str;
import mightypork.utils.math.AlignX;
import mightypork.utils.math.color.Color;
import mightypork.utils.math.color.pal.RGB;
@ -26,76 +26,76 @@ import mightypork.utils.math.constraints.rect.caching.RectCache;
* @author Ondřej Hruška (MightyPork)
*/
public class InvSlot extends ClickableComponent {
private final TxQuad txBase, txSelected;
protected boolean selected = false;
protected int index;
private final RectCache itemRect;
private final RectCache uiRect;
private final InvSlot[] slots;
private final TextPainter rbTxP;
private final TextPainter rtTxP;
private final RectCache rbTxRect;
private final RectCache rtTxRect;
private final Rect usesRect;
private final Num hAlpha = Num.make(0.7);
public InvSlot(int index, InvSlot[] allSlots)
{
super();
this.txBase = Res.txQuad("inv.slot.base");
this.txSelected = Res.txQuad("inv.slot.selected");
this.index = index;
this.slots = allSlots;
this.uiRect = getRect().shrink(height().perc(16)).cached();
this.itemRect = uiRect.shrink(Num.ZERO, height().perc(14), height().perc(14), Num.ZERO).cached();
this.usesRect = uiRect.topLeft().startRect().grow(Num.ZERO, uiRect.width().perc(40), Num.ZERO, uiRect.height().perc(8));
//@formatter:off
this.rbTxRect = uiRect.bottomEdge()
.moveY(uiRect.height().perc(1*(30/7D)))
.growUp(uiRect.height().perc(30)).cached();
//@formatter:on
rbTxP = new TextPainter(Res.font("tiny"), AlignX.RIGHT, RGB.WHITE);
rbTxP.setRect(rbTxRect);
rbTxP.setShadow(RGB.BLACK_70, rbTxP.getRect().height().div(7).toVectXY());
//@formatter:off
this.rtTxRect = uiRect.topEdge()
.growDown(uiRect.height().perc(30)).cached();
//@formatter:on
rtTxP = new TextPainter(Res.font("tiny"), AlignX.RIGHT, RGB.GREEN);
rtTxP.setRect(rtTxRect);
rtTxP.setShadow(RGB.BLACK_70, rtTxP.getRect().height().div(7).toVectXY());
setAction(new Action() {
@Override
protected void execute()
{
for (final InvSlot sl : slots) {
sl.selected = false;
}
selected = true;
}
});
}
@Override
public void updateLayout()
{
@ -104,23 +104,23 @@ public class InvSlot extends ClickableComponent {
rbTxRect.poll();
rtTxRect.poll();
}
@Override
protected void renderComponent()
{
TxQuad bg;
if (selected) {
bg = txSelected;
} else {
bg = txBase;
}
App.gfx().quad(this, bg);
final PlayerFacade pl = WorldProvider.get().getPlayer();
final Item itm = pl.getInventory().getItem(index);
if (itm != null && !itm.isEmpty()) {
itm.render(itemRect);
@ -129,39 +129,39 @@ public class InvSlot extends ClickableComponent {
rbTxP.setColor(RGB.WHITE);
rbTxP.render();
}
if (pl.getSelectedWeaponIndex() == index) {
rbTxP.setText("*");
rbTxP.setColor(RGB.YELLOW);
rbTxP.render();
}
if (itm.getType() == ItemType.FOOD) {
rtTxP.setText(Support.str(itm.getFoodPoints() / 2D));
rtTxP.setText(Str.val(itm.getFoodPoints() / 2D));
rtTxP.setColor(RGB.GREEN);
rtTxP.render();
} else if (itm.getType() == ItemType.WEAPON) {
final int atk = itm.getAttackPoints();
rtTxP.setText((atk >= 0 ? "+" : "") + atk);
rtTxP.setColor(RGB.CYAN);
rtTxP.render();
}
if (itm.isDamageable()) {
Color.pushAlpha(hAlpha);
App.gfx().quad(usesRect, RGB.BLACK);
final double useRatio = (itm.getRemainingUses() / (double) itm.getMaxUses());
final Color barColor = (useRatio > 0.6 ? RGB.GREEN : useRatio > 0.2 ? RGB.ORANGE : RGB.RED);
App.gfx().quad(usesRect.shrinkRight(usesRect.width().value() * (1 - useRatio)), barColor);
Color.popAlpha();
}
}
}
}

@ -24,18 +24,18 @@ import mightypork.utils.math.constraints.num.Num;
public class LayerAskSave extends FadingLayer {
public Runnable task;
private final ScreenGame gscreen;
public void setTask(Runnable task)
{
this.task = task;
}
@Override
protected void onHideFinished()
{
@ -43,61 +43,61 @@ public class LayerAskSave extends FadingLayer {
gscreen.setState(GScrState.WORLD); // go back..
}
}
public LayerAskSave(final ScreenGame screen)
{
super(screen);
this.gscreen = screen;
// darker down to cover console.
final QuadPainter qp = new QuadPainter(RGB.BLACK_80);
qp.setRect(root);
root.add(qp);
final IFont thick_font = Res.font("thick");
final RowLayout rl = new RowLayout(root, 2);
rl.setRect(root.shrink(Num.ZERO, root.height().perc(40)).moveY(root.height().perc(-10)));
root.add(rl);
final TextPainter txp = new TextPainter(thick_font, AlignX.CENTER, RGB.WHITE, "Save the game?");
rl.add(txp, 1);
txp.setVPaddingPercent(25);
final LinearLayout ll = new LinearLayout(root, AlignX.CENTER);
rl.add(ll);
final double vPadPerc = 20;
final TextButton btn1 = new TextButton(thick_font, "Yes", ScreenGame.COLOR_BTN_GOOD);
btn1.textPainter.setAlign(AlignX.RIGHT);
btn1.textPainter.setVPaddingPercent(vPadPerc);
ll.add(btn1);
ll.gap(50);
final TextButton btn2 = new TextButton(thick_font, "No", ScreenGame.COLOR_BTN_BAD);
btn2.textPainter.setAlign(AlignX.CENTER);
btn2.textPainter.setVPaddingPercent(vPadPerc);
ll.add(btn2);
ll.gap(50);
final TextButton btn3 = new TextButton(thick_font, "Cancel", ScreenGame.COLOR_BTN_CANCEL);
btn3.textPainter.setAlign(AlignX.LEFT);
btn3.textPainter.setVPaddingPercent(vPadPerc);
ll.add(btn3);
final Action cancel = new Action() {
@Override
protected void execute()
{
hide();
}
};
final Action save = new Action() {
@Override
protected void execute()
{
@ -109,39 +109,39 @@ public class LayerAskSave extends FadingLayer {
}
}
};
final Action discard = new Action() {
@Override
protected void execute()
{
if (task != null) task.run();
}
};
btn1.setAction(save);
btn2.setAction(discard);
btn3.setAction(cancel);
final Config cfg = App.cfg();
bindKey(cfg.getKeyStroke("general.close"), Trigger.RISING, cancel);
bindKey(cfg.getKeyStroke("general.cancel"), Trigger.RISING, cancel);
bindKey(cfg.getKeyStroke("general.yes"), Trigger.RISING, save);
bindKey(cfg.getKeyStroke("general.confirm"), Trigger.RISING, save);
bindKey(cfg.getKeyStroke("general.no"), Trigger.RISING, discard);
}
@Override
public int getZIndex()
{
return 300;
}
@Override
public void update(double delta)
{

@ -28,45 +28,45 @@ import mightypork.utils.math.constraints.num.Num;
public class LayerDeath extends FadingLayer {
public LayerDeath(final ScreenGame screen)
{
super(screen);
// darker down to cover console.
final QuadPainter qp = new QuadPainter(RGB.BLACK_80);
qp.setRect(root);
root.add(qp);
final IFont thick_font = Res.font("thick");
final RowLayout rl = new RowLayout(root, 5);
rl.setRect(root.shrink(Num.ZERO, root.height().perc(15)));
root.add(rl);
final TextPainter txp = new TextPainter(thick_font, AlignX.CENTER, RGB.YELLOW, "Rats won!");
rl.add(txp, 1);
txp.setVPaddingPercent(13);
LinearLayout linl = new LinearLayout(root, AlignX.CENTER);
linl.add(new ImagePainter(Res.txQuad("death2")));
rl.add(linl, 3);
linl = new LinearLayout(root, AlignX.CENTER);
rl.add(linl);
final TextButton btn1 = new TextButton(thick_font, "Retry", ScreenGame.COLOR_BTN_GOOD);
btn1.textPainter.setVPaddingPercent(25);
linl.add(btn1);
linl.add(new LinearGap(50));
final TextButton btn2 = new TextButton(thick_font, "Leave", ScreenGame.COLOR_BTN_BAD);
btn2.textPainter.setVPaddingPercent(25);
linl.add(btn2);
final Action load = new Action() {
@Override
protected void execute()
{
@ -78,31 +78,31 @@ public class LayerDeath extends FadingLayer {
}
}
};
final Action quit = new Action() {
@Override
protected void execute()
{
App.bus().send(new RogueStateRequest(RogueState.MAIN_MENU));
}
};
btn1.setAction(load);
btn2.setAction(quit);
final Config cfg = App.cfg();
bindKey(cfg.getKeyStroke("game.load"), Trigger.RISING, load);
bindKey(cfg.getKeyStroke("general.confirm"), Trigger.RISING, load);
bindKey(cfg.getKeyStroke("general.close"), Trigger.RISING, quit);
}
@Override
public int getZIndex()
{
return 300;
}
}

@ -16,71 +16,71 @@ import mightypork.utils.string.StringProvider;
public class LayerGameUi extends ScreenLayer {
private final Num playerHealthTotal = new Num() {
@Override
public double value()
{
return WorldProvider.get().getPlayer().getHealthMax() / 2D;
}
};
private final Num playerHealthActive = new Num() {
@Override
public double value()
{
return WorldProvider.get().getPlayer().getHealth() / 2D;
}
};
protected Minimap miniMap;
private final ScreenGame gameScreen;
public LayerGameUi(ScreenGame screen)
{
super(screen);
this.gameScreen = screen;
buildNav();
buildDisplays();
buildMinimap();
buildConsole();
}
private void buildConsole()
{
final Num rh = root.height();
final Num rw = root.width();
final Rect consoleRect = root.shrink(rw.perc(2), Num.ZERO, rh.perc(6), rh.perc(16));
final Num perRow = consoleRect.height().div(20).max(12).min(32);
final WorldConsoleRenderer wcr = new WorldConsoleRenderer(perRow);
wcr.setRect(consoleRect);
root.add(wcr);
}
private void buildMinimap()
{
miniMap = new Minimap();
miniMap.setRect(root.shrink(root.width().perc(5), root.height().perc(15)));
root.add(miniMap);
}
private void buildDisplays()
{
final Num h = root.height();
//@formatter:off
final HeartBar hearts = new HeartBar(
playerHealthTotal,
@ -90,13 +90,13 @@ public class LayerGameUi extends ScreenLayer {
Res.txQuad("hud.heart.off"),
AlignX.LEFT);
//@formatter:on
final Rect hearts_box = root.shrink(h.perc(3)).topEdge().growDown(h.perc(6));
hearts.setRect(hearts_box);
root.add(hearts);
final TextPainter levelText = new TextPainter(Res.font("tiny"), AlignX.RIGHT, RGB.WHITE, new StringProvider() {
@Override
public String getString()
{
@ -104,62 +104,62 @@ public class LayerGameUi extends ScreenLayer {
return (w.isPaused() ? "[P] " : "") + "Floor " + (1 + w.getPlayer().getLevelNumber());
}
});
levelText.setRect(hearts_box.moveY(hearts_box.height().mul(1 / 7D)));
root.add(levelText);
}
private void buildNav()
{
final IngameNav nav = new IngameNav();
nav.setRect(root.bottomEdge().growUp(root.height().perc(12)));
root.add(nav);
NavButton btn;
nav.addRight(btn = new NavButton(Res.txQuad("nav.button.fg.inventory")));
btn.setAction(gameScreen.actionToggleInv);
nav.addRight(btn = new NavButton(Res.txQuad("nav.button.fg.eat")));
btn.setAction(gameScreen.actionEat);
nav.addRight(btn = new NavButton(Res.txQuad("nav.button.fg.pause")));
btn.setAction(gameScreen.actionTogglePause);
// TODO actions
//nav.addLeft(new NavButton(Res.txq("nav.button.fg.options")));
//nav.addLeft(new NavButton(Res.txq("nav.button.fg.help")));
nav.addLeft(btn = new NavButton(Res.txQuad("nav.button.fg.menu")));
btn.setAction(gameScreen.actionMenu);
nav.addLeft(btn = new NavButton(Res.txQuad("nav.button.fg.save")));
btn.setAction(gameScreen.actionSave);
nav.addLeft(btn = new NavButton(Res.txQuad("nav.button.fg.load")));
btn.setAction(gameScreen.actionLoad);
nav.addLeft(btn = new NavButton(Res.txQuad("nav.button.fg.map")));
btn.setAction(gameScreen.actionToggleMinimap);
nav.addLeft(btn = new NavButton(Res.txQuad("nav.button.fg.magnify")));
btn.setAction(gameScreen.actionToggleZoom);
}
@Override
public int getZIndex()
{
return 101;
}
@Override
public int getEventPriority()
{
return 400;
}
}

@ -26,34 +26,34 @@ import mightypork.utils.string.StringProvider;
public class LayerInv extends FadingLayer {
private static final int SLOT_COUNT = 8;
private static final int SLOT_ROW = 4;
private final KeyStroke keyUse = App.cfg().getKeyStroke("game.inv.use");
private final KeyStroke keyDrop = App.cfg().getKeyStroke("game.inv.drop");
private final KeyStroke keyClose = App.cfg().getKeyStroke("general.close");
private final StringProvider contextStrProv = new StringProvider() {
@Override
public String getString()
{
String s = keyClose + "-close";
final int selected = getSelectedSlot();
if (selected != -1) {
final PlayerFacade pl = WorldProvider.get().getPlayer();
final Item itm = pl.getInventory().getItem(selected);
if (itm != null && !itm.isEmpty()) {
s = keyDrop + "-drop," + s;
if (itm.getType() == ItemType.FOOD) {
s = keyUse + "-eat," + s;
}
if (itm.getType() == ItemType.WEAPON) {
s = keyUse + "-equip," + s;
}
@ -61,15 +61,15 @@ public class LayerInv extends FadingLayer {
} else {
s = "Click-select," + s;
}
return s;
}
};
private final InvSlot[] slots = new InvSlot[SLOT_COUNT];
private ScreenGame gscreen;
private int getSelectedSlot()
{
for (final InvSlot sl : slots) {
@ -80,20 +80,20 @@ public class LayerInv extends FadingLayer {
}
return -1;
}
private void selectSlot(int i)
{
for (final InvSlot sl : slots) {
sl.selected = false;
}
if (i >= 0 && i < SLOT_COUNT) {
slots[i].selected = true;
}
}
@Override
protected void onHideFinished()
{
@ -101,45 +101,45 @@ public class LayerInv extends FadingLayer {
gscreen.actionToggleInv.run();
}
}
public LayerInv(final ScreenGame screen)
{
super(screen);
this.gscreen = screen;
final Rect fg = root.shrink(root.height().perc(15));
// darker down to cover console.
final QuadPainter qp = new QuadPainter(RGB.BLACK_30, RGB.BLACK_30, RGB.BLACK_90, RGB.BLACK_90);
qp.setRect(root);
root.add(qp);
int pos = 0;
final GridLayout gl = new GridLayout(fg, 10, 1);
root.add(gl);
final TextPainter txp = new TextPainter(Res.font("thick"), AlignX.CENTER, RGB.YELLOW, "Inventory");
gl.put(txp, pos, 0, 1, 1);
txp.setVPaddingPercent(5);
pos += 1;
final FlowColumnLayout row1 = new FlowColumnLayout(root, null, AlignX.LEFT);
row1.setElementWidth(row1.height());
final ConstraintLayout cl1 = new ConstraintLayout(root);
row1.setRect(cl1.axisV().grow(cl1.height().mul(2), Num.ZERO));
cl1.add(row1);
gl.put(cl1, pos, 0, 4, 1);
pos += 4;
row1.add(slots[0] = new InvSlot(0, slots));
row1.add(slots[1] = new InvSlot(1, slots));
row1.add(slots[2] = new InvSlot(2, slots));
row1.add(slots[3] = new InvSlot(3, slots));
final FlowColumnLayout row2 = new FlowColumnLayout(root, null, AlignX.LEFT);
row2.setElementWidth(row2.height());
final ConstraintLayout cl2 = new ConstraintLayout(root);
@ -147,18 +147,18 @@ public class LayerInv extends FadingLayer {
cl2.add(row2);
gl.put(cl2, pos, 0, 4, 1);
pos += 4;
row2.add(slots[4] = new InvSlot(4, slots));
row2.add(slots[5] = new InvSlot(5, slots));
row2.add(slots[6] = new InvSlot(6, slots));
row2.add(slots[7] = new InvSlot(7, slots));
final TextPainter txp2 = new TextPainter(Res.font("thick"), AlignX.CENTER, RGB.WHITE, contextStrProv);
gl.put(txp2, pos, 0, 1, 1);
txp2.setVPaddingPercent(25);
bindKey(keyClose, Trigger.RISING, new Runnable() {
@Override
public void run()
{
@ -166,150 +166,150 @@ public class LayerInv extends FadingLayer {
hide();
}
});
bindKey(keyUse, Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
if (WorldProvider.get().getPlayer().isDead()) return;
useSelectedItem();
}
});
bindKey(keyDrop, Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
final int selected = getSelectedSlot();
if (selected != -1) {
WorldProvider.get().getPlayer().dropItem(selected);
}
}
});
setupGridWalkKeys();
}
protected void useSelectedItem()
{
final int selected = getSelectedSlot();
if (selected != -1) {
final World world = WorldProvider.get().getWorld();
final PlayerFacade pl = WorldProvider.get().getPlayer();
final Item itm = pl.getInventory().getItem(selected);
if (itm != null && !itm.isEmpty()) {
final ItemType type = itm.getType();
if (type == ItemType.FOOD) {
if (pl.eatFood(itm)) {
pl.getInventory().clean();
}
} else if (type == ItemType.WEAPON) {
if (pl.getSelectedWeaponIndex() == selected) {
pl.selectWeapon(-1);
} else {
pl.selectWeapon(selected);
}
world.getConsole().msgEquipWeapon(pl.getSelectedWeapon());
}
}
}
}
private void setupGridWalkKeys()
{
final Config cfg = App.cfg();
bindKey(cfg.getKeyStroke("game.inv.move.left"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
final int sel = getSelectedSlot();
if (sel == -1) {
selectSlot(0);
return;
}
selectSlot((SLOT_COUNT + (sel - 1)) % SLOT_COUNT);
}
});
bindKey(cfg.getKeyStroke("game.inv.move.right"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
final int sel = getSelectedSlot();
if (sel == -1) {
selectSlot(0);
return;
}
selectSlot((SLOT_COUNT + (sel + 1)) % SLOT_COUNT);
}
});
bindKey(cfg.getKeyStroke("game.inv.move.up"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
final int sel = getSelectedSlot();
if (sel == -1) {
selectSlot(0);
return;
}
selectSlot((SLOT_COUNT + (sel - SLOT_ROW)) % SLOT_COUNT);
}
});
bindKey(cfg.getKeyStroke("game.inv.move.down"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
if (!isEnabled()) return;
final int sel = getSelectedSlot();
if (sel == -1) {
selectSlot(0);
return;
}
selectSlot((sel + SLOT_ROW) % SLOT_COUNT);
}
});
}
@Override
public int getZIndex()
{
return 300;
}
}

@ -10,41 +10,41 @@ import mightypork.utils.math.constraints.num.Num;
public class LayerMapView extends ScreenLayer {
protected final MapView map;
public LayerMapView(Screen screen)
{
super(screen);
// render component
map = new MapView();
// map input plugins
map.addPlugin(new MIPKeyboard(map));
map.addPlugin(new MIPMouse(map));
// size of lower navbar
final Num lownav = root.height().perc(12);
map.setRect(root.shrinkBottom(lownav));
root.add(map);
}
@Override
public int getZIndex()
{
return 0; // stay down
}
@Override
public int getEventPriority()
{
return 100;
}
}

@ -24,69 +24,69 @@ import mightypork.utils.math.constraints.num.Num;
public class LayerWin extends FadingLayer {
public LayerWin(final ScreenGame screen)
{
super(screen);
// darker down to cover console.
final QuadPainter qp = new QuadPainter(RGB.BLACK_80);
qp.setRect(root);
root.add(qp);
final IFont thick_font = Res.font("thick");
final RowLayout rl = new RowLayout(root, 5);
rl.setRect(root.shrink(Num.ZERO, root.height().perc(15)));
root.add(rl);
final TextPainter txp = new TextPainter(thick_font, AlignX.CENTER, RGB.YELLOW, "You won!");
rl.add(txp, 1);
txp.setVPaddingPercent(13);
LinearLayout linl = new LinearLayout(root, AlignX.CENTER);
linl.add(new ImagePainter(Res.txQuad("win")));
rl.add(linl, 3);
linl = new LinearLayout(root, AlignX.CENTER);
rl.add(linl);
final TextButton btn1 = new TextButton(thick_font, "Leave", ScreenGame.COLOR_BTN_GOOD);
btn1.textPainter.setVPaddingPercent(25);
linl.add(btn1);
final Action quit = new Action() {
@Override
protected void execute()
{
// delete the save file
final File f = WorldProvider.get().getWorld().getSaveFile();
f.delete();
App.bus().send(new RogueStateRequest(RogueState.MAIN_MENU));
}
};
btn1.setAction(quit);
bindKey(App.cfg().getKeyStroke("general.confirm"), Trigger.RISING, quit);
bindKey(App.cfg().getKeyStroke("general.close"), Trigger.RISING, quit);
}
@Override
public int getZIndex()
{
return 300;
}
@Override
protected void onShowFinished()
{
App.sound().fadeOutAllLoops();
Res.sound("game.win").play(1);
}
}

@ -13,10 +13,10 @@ import mightypork.gamecore.resources.Res;
* @author Ondřej Hruška (MightyPork)
*/
public class NavButton extends ClickableComponent {
private final TxQuad base, hover, down, fg;
public NavButton(TxQuad fg)
{
super();
@ -25,13 +25,13 @@ public class NavButton extends ClickableComponent {
this.down = Res.txQuad("nav.button.bg.down");
this.fg = fg;
}
@Override
protected void renderComponent()
{
TxQuad bg;
if (btnDownOver) {
bg = down;
} else if (isMouseOver()) {
@ -39,11 +39,11 @@ public class NavButton extends ClickableComponent {
} else {
bg = base;
}
if (!isEnabled()) bg = base; // override effects
if (!isEnabled()) bg = base; // override effects
App.gfx().quad(this, bg);
App.gfx().quad(this, fg);
}
}

@ -5,7 +5,7 @@ import java.io.File;
import mightypork.gamecore.core.App;
import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.core.events.ShutdownEvent;
import mightypork.gamecore.core.events.ShutdownRequest;
import mightypork.gamecore.gui.Action;
import mightypork.gamecore.gui.ActionGroup;
import mightypork.gamecore.input.Trigger;
@ -25,11 +25,11 @@ import mightypork.utils.math.color.Color;
public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameWinHandler {
public static final Color COLOR_BTN_GOOD = Color.fromHex(0x28CB2D);
public static final Color COLOR_BTN_BAD = Color.fromHex(0xCB2828);
public static final Color COLOR_BTN_CANCEL = Color.fromHex(0xFFFB55);
/**
* Game gui state.
*
@ -39,20 +39,20 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
{
WORLD, INV, DEATH, WIN, GOTO_MENU, GOTO_QUIT;
}
private LayerInv invLayer;
private LayerGameUi hudLayer;
private LayerDeath deathLayer;
private LayerWin winLayer;
private LayerMapView worldLayer;
private GScrState state = null;
private final ActionGroup worldActions = new ActionGroup();
public Action actionEat = new Action() {
@Override
public void execute()
{
@ -61,50 +61,50 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
pl.tryToEatSomeFood();
}
};
public Action actionToggleInv = new Action() {
@Override
public void execute()
{
setState(getState() == GScrState.INV ? GScrState.WORLD : GScrState.INV);
}
};
public Action actionToggleMinimap = new Action() {
@Override
public void execute()
{
hudLayer.miniMap.setVisible(!hudLayer.miniMap.isVisible());
}
};
public Action actionTogglePause = new Action() {
@Override
public void execute()
{
App.bus().send(new WorldPauseRequest(PauseAction.TOGGLE));
}
};
public Action actionToggleZoom = new Action() {
@Override
public void execute()
{
worldLayer.map.toggleMag();
}
};
public Action actionSave = new Action() {
@Override
public void execute()
{
if (WorldProvider.get().getWorld().isGameOver()) return;
try {
WorldProvider.get().saveWorld();
WorldProvider.get().getWorld().getConsole().msgWorldSaved();
@ -114,46 +114,46 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
}
}
};
public Action actionLoad = new Action() {
@Override
public void execute()
{
if (WorldProvider.get().getWorld().isGameOver()) return;
try {
final File f = WorldProvider.get().getWorld().getSaveFile();
WorldProvider.get().loadWorld(f);
WorldProvider.get().getWorld().getConsole().msgReloaded();
} catch (final Exception e) {
Log.e("Could not load the world.", e);
WorldProvider.get().getWorld().getConsole().msgLoadFailed();
}
}
};
public Action actionMenu = new Action() {
@Override
public void execute()
{
setState(GScrState.GOTO_MENU);
}
};
public Action actionQuit = new Action() {
@Override
public void execute()
{
setState(GScrState.GOTO_QUIT);
}
};
public Action actionDropLastPickedItem = new Action() {
@Override
public void execute()
{
@ -163,8 +163,8 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
}
};
private LayerAskSave askSaveLayer;
/**
* Set gui state (overlay)
*
@ -173,52 +173,52 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
public void setState(GScrState nstate)
{
if (this.state == nstate) return;
final boolean gameOver = WorldProvider.get().getWorld().isGameOver();
if (nstate != GScrState.WORLD && state == GScrState.WORLD) { // leaving world.
App.bus().send(new WorldPauseRequest(PauseAction.PAUSE));
worldActions.setEnabled(false); // disable world actions
}
if (nstate != GScrState.DEATH) deathLayer.hide();
if (nstate != GScrState.WIN) winLayer.hide();
if (nstate != GScrState.INV) invLayer.hide();
if (nstate != GScrState.GOTO_MENU && nstate != GScrState.GOTO_QUIT) askSaveLayer.hide();
Runnable task;
switch (nstate) {
case WORLD:
App.bus().send(new WorldPauseRequest(PauseAction.RESUME));
worldActions.setEnabled(true);
break;
case INV:
invLayer.show();
break;
case DEATH:
deathLayer.show();
break;
case WIN:
winLayer.show();
break;
case GOTO_MENU:
case GOTO_QUIT:
final RogueState goal = (nstate == GScrState.GOTO_MENU) ? RogueState.MAIN_MENU : RogueState.EXIT;
task = new Runnable() {
@Override
public void run()
{
App.bus().send(new RogueStateRequest(goal));
}
};
if (gameOver) {
task.run();
} else {
@ -226,41 +226,41 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
askSaveLayer.show();
}
}
this.state = nstate;
}
public GScrState getState()
{
return state;
}
public ScreenGame()
{
addLayer(invLayer = new LayerInv(this));
addLayer(deathLayer = new LayerDeath(this));
addLayer(winLayer = new LayerWin(this));
addLayer(hudLayer = new LayerGameUi(this));
addLayer(worldLayer = new LayerMapView(this));
addLayer(askSaveLayer = new LayerAskSave(this));
final Config cfg = App.cfg();
bindKey(cfg.getKeyStroke("game.pause"), Trigger.RISING, actionTogglePause);
bindKey(cfg.getKeyStroke("game.inventory"), Trigger.RISING, actionToggleInv);
bindKey(cfg.getKeyStroke("game.drop"), Trigger.RISING, actionDropLastPickedItem);
bindKey(cfg.getKeyStroke("game.eat"), Trigger.RISING, actionEat);
bindKey(cfg.getKeyStroke("game.minimap"), Trigger.RISING, actionToggleMinimap);
bindKey(cfg.getKeyStroke("game.zoom"), Trigger.RISING, actionToggleZoom);
bindKey(cfg.getKeyStroke("game.load"), Trigger.RISING, actionLoad);
bindKey(cfg.getKeyStroke("game.save"), Trigger.RISING, actionSave);
bindKey(cfg.getKeyStroke("game.quit"), Trigger.RISING, actionMenu);
// bindKey(new KeyStroke(Keys.W), Edge.RISING, new Runnable() {
//
// @Override
@ -269,26 +269,26 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
// setState(GScrState.WIN);
// }
// });
// add as actions - enableables.
worldActions.add(worldLayer);
worldActions.add(hudLayer);
worldActions.add(actionEat);
worldActions.add(actionToggleMinimap);
worldActions.add(actionTogglePause);
worldActions.add(actionToggleZoom);
worldActions.add(actionSave);
worldActions.add(actionLoad);
worldActions.add(actionMenu);
worldActions.add(actionDropLastPickedItem);
worldActions.setEnabled(true);
// CHEAT - X-ray
bindKey(cfg.getKeyStroke("game.cheat.xray"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
@ -296,32 +296,32 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
}
});
}
@Override
protected void onScreenEnter()
{
super.onScreenEnter();
WorldProvider.get().setListening(true);
setState(GScrState.WORLD);
hideAllPopups();
App.sound().fadeOutAllLoops();
Res.loop("music.dungeon").fadeIn();
}
@Override
protected void onScreenLeave()
{
super.onScreenLeave();
WorldProvider.get().setListening(false);
Res.loop("music.dungeon").fadeOut();
}
private void hideAllPopups()
{
invLayer.hideImmediate();
@ -329,29 +329,29 @@ public class ScreenGame extends RogueScreen implements PlayerDeathHandler, GameW
winLayer.hideImmediate();
askSaveLayer.hideImmediate();
}
@Override
public void onPlayerKilled()
{
setState(GScrState.DEATH);
}
@Override
public void onGameWon()
{
setState(GScrState.WIN);
}
@Override
public void onShutdown(ShutdownEvent event)
public void onShutdownRequested(ShutdownRequest event)
{
// if player is dead, don't ask don't ask for save
final PlayerFacade pl = WorldProvider.get().getPlayer();
if (pl.isDead()) return;
actionQuit.run();
event.consume();
}

@ -29,47 +29,47 @@ import mightypork.utils.math.constraints.rect.Rect;
* @author Ondřej Hruška (MightyPork)
*/
public class ScreenMainMenu extends RogueScreen {
/**
* The layer
*
* @author Ondřej Hruška (MightyPork)
*/
class MenuLayer extends ScreenLayer {
public MenuLayer(Screen screen)
{
super(screen);
init();
}
private void init()
{
final Rect menuBox = root.shrink(Num.ZERO, root.height().perc(15)).moveY(root.height().perc(-4));
final QuadPainter bg = QuadPainter.gradV(Color.fromHex(0x007eb3), PAL16.SEABLUE);
bg.setRect(root);
root.add(bg);
final RowLayout rows = new RowLayout(menuBox, 13);
rows.enableCaching(true);
root.add(rows);
final LinearLayout linlayout = new LinearLayout(root, AlignX.CENTER);
linlayout.add(new ImagePainter(Res.txQuad("logo")));
rows.add(linlayout, 4);
rows.skip(1);
TextButton btn;
final IFont btnFont = Res.font("thick");
// world button
btn = new TextButton(btnFont, "Play", PAL16.SLIMEGREEN);
btn.setAction(new Action() {
@Override
protected void execute()
{
@ -78,10 +78,10 @@ public class ScreenMainMenu extends RogueScreen {
});
rows.add(btn, 2);
rows.skip(1);
btn = new TextButton(btnFont, "Story", PAL16.CLOUDBLUE);
btn.setAction(new Action() {
@Override
protected void execute()
{
@ -90,11 +90,11 @@ public class ScreenMainMenu extends RogueScreen {
});
rows.add(btn, 2);
rows.skip(1);
// quit button
btn = new TextButton(btnFont, "Exit", PAL16.BLOODRED);
btn.setAction(new Action() {
@Override
protected void execute()
{
@ -102,9 +102,9 @@ public class ScreenMainMenu extends RogueScreen {
}
});
rows.add(btn, 2);
bindKey(App.cfg().getKeyStroke("general.close"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
@ -112,28 +112,28 @@ public class ScreenMainMenu extends RogueScreen {
}
});
}
@Override
public int getZIndex()
{
return 2;
}
}
public ScreenMainMenu()
{
addLayer(new MenuLayer(this));
}
@Override
protected void onScreenEnter()
{
super.onScreenEnter();
App.sound().fadeOutAllLoops();
Res.loop("music.menu").fadeIn();
}

@ -27,57 +27,57 @@ import mightypork.utils.math.constraints.rect.Rect;
* @author Ondřej Hruška (MightyPork)
*/
public class ScreenSelectWorld extends RogueScreen {
public ScreenSelectWorld()
{
addLayer(new WorldsLayer(this));
}
class WorldsLayer extends ScreenLayer {
private WorldSlot slot1;
private WorldSlot slot2;
private WorldSlot slot3;
public WorldsLayer(Screen screen)
{
super(screen);
init();
}
private void init()
{
final Rect menuBox = root.shrink(root.width().perc(25), root.height().perc(30)).moveY(root.height().perc(-10));
final QuadPainter bg = QuadPainter.gradV(Color.fromHex(0x007eb3), PAL16.SEABLUE);
bg.setRect(root);
root.add(bg);
final RowLayout rows = new RowLayout(menuBox, 4);
rows.enableCaching(true);
root.add(rows);
TextPainter tp;
rows.add(tp = new TextPainter(Res.font("thick"), AlignX.CENTER, RGB.YELLOW, "Save slot:"));
tp.setVPaddingPercent(20);
tp.setShadow(RGB.BLACK_50, tp.height().mul(0.6 / 8D).toVectXY());
slot1 = new WorldSlot(WorkDir.getFile("slot1"));
rows.add(slot1);
slot2 = new WorldSlot(WorkDir.getFile("slot2"));
rows.add(slot2);
slot3 = new WorldSlot(WorkDir.getFile("slot3"));
rows.add(slot3);
// escape to quitn from here
bindKey(App.cfg().getKeyStroke("general.close"), Trigger.RISING, new Runnable() {
@Override
public void run()
{
@ -85,20 +85,20 @@ public class ScreenSelectWorld extends RogueScreen {
}
});
}
@Override
public int getZIndex()
{
return 2;
}
@Override
protected void onScreenEnter()
{
super.onScreenEnter();
Log.f3("Refreshing save slots");
slot1.refresh();
slot2.refresh();

@ -26,121 +26,121 @@ import mightypork.utils.string.StringProvider;
public class WorldSlot extends ConstraintLayout {
private final StringProvider lblStrp = new StringProvider() {
@Override
public String getString()
{
return label;
}
};
private File file;
private String label;
private IonDataBundle worldBundle;
private TextButton loadBtn;
private TextButton delBtn;
public WorldSlot(File worldFile)
{
this.file = worldFile;
final Rect innerRect = shrink(height().perc(5));
final QuadPainter qp = new QuadPainter(RGB.BLACK.withAlpha(new Num() {
@Override
public double value()
{
return isMouseOver() ? 0.15 : 0.1;
}
}));
qp.setRect(innerRect);
add(qp);
final GridLayout gridl = new GridLayout(1, 8);
final Num shrinkH = width().perc(8);
final Num shrinkV = height().perc(10);
gridl.setRect(innerRect.shrink(shrinkH, shrinkH, shrinkV, shrinkV.half()));
add(gridl);
final IFont font = Res.font("thick");
gridl.put(loadBtn = new TextButton(font, "", RGB.WHITE), 0, 0, 1, 7);
loadBtn.textPainter.setVPaddingPercent(20);
loadBtn.textPainter.setAlign(AlignX.LEFT);
loadBtn.textPainter.setText(lblStrp);
loadBtn.disableHoverEffect();
loadBtn.setAction(new Action() {
@Override
protected void execute()
{
String msg;
if (worldBundle != null) {
msg = "Loading world...";
} else {
msg = "Creating world...";
}
App.bus().send(new LoadingOverlayRequest(msg, new Runnable() {
@Override
public void run()
{
World w;
if (worldBundle == null) {
try {
w = WorldProvider.get().createWorld(Double.doubleToLongBits(Math.random()));
w.setSaveFile(file);
WorldProvider.get().saveWorld();
App.bus().send(new ScreenRequest("game"));
} catch (final Exception t) {
Log.e("Could not create & save the world.", t);
}
} else {
try {
w = new World();
w.setSaveFile(file);
w.load((IonDataBundle) worldBundle.get("world"));
WorldProvider.get().setWorld(w);
App.bus().send(new ScreenRequest("game"));
} catch (final Exception e) {
Log.e("Could not load the world.", e);
}
}
}
}));
}
});
gridl.put(delBtn = new TextButton(font, "X", RGB.RED), 0, 7, 1, 1);
delBtn.textPainter.setVPaddingPercent(20);
delBtn.textPainter.setAlign(AlignX.RIGHT);
delBtn.disableHoverEffect();
delBtn.setAction(new Action() {
@Override
protected void execute()
{
@ -149,30 +149,30 @@ public class WorldSlot extends ConstraintLayout {
refresh();
}
});
refresh();
}
public void refresh()
{
delBtn.setVisible(false);
delBtn.setEnabled(false);
if (!file.exists()) {
label = "<empty>";
worldBundle = null;
} else {
delBtn.setVisible(true);
delBtn.setEnabled(true);
try {
worldBundle = Ion.fromFile(file);
final int lvl = worldBundle.get("level", -1);
if (lvl == -1) throw new RuntimeException("Invalid save format."); // let the catch block handle it
if (lvl == -1) throw new RuntimeException("Invalid save format."); // let the catch block handle it
label = "Level " + (lvl + 1);
} catch (final Exception e) {
Log.w("Error loading world save.", e);
@ -180,5 +180,5 @@ public class WorldSlot extends ConstraintLayout {
}
}
}
}

@ -30,9 +30,9 @@ import mightypork.utils.math.timing.TimedTask;
public class ScreenStory extends RogueScreen implements MouseButtonHandler {
private class LayerSlide extends ScreenLayer {
private final TextPainter tp1;
private final TextPainter tp2;
private final ImagePainter img;
@ -40,30 +40,30 @@ public class ScreenStory extends RogueScreen implements MouseButtonHandler {
private final NumAnimated tx1alpha = new NumAnimated(0, Easing.BOUNCE_OUT, 1);
private final NumAnimated tx2alpha = new NumAnimated(0, Easing.BOUNCE_OUT, 1);
private final NumAnimated txProceedAlpha = new NumAnimated(0, Easing.CIRC_OUT, 1);
private String nextImg, nextT1, nextT2;
private final TimedTask ttNextSlide = new TimedTask() {
@Override
public void run()
{
img.setTxQuad(Res.txQuad(nextImg));
if (nextT1 != null) tp1.setText(nextT1);
if (nextT2 != null) tp2.setText(nextT2);
tx1alpha.setTo(0);
tx2alpha.setTo(0);
txProceedAlpha.setTo(0);
layerAlpha.setTo(0);
layerAlpha.fadeIn();
ttText1.start(1.5);
}
};
private final TimedTask ttText1 = new TimedTask() {
@Override
public void run()
{
@ -75,9 +75,9 @@ public class ScreenStory extends RogueScreen implements MouseButtonHandler {
}
}
};
private final TimedTask ttText2 = new TimedTask() {
@Override
public void run()
{
@ -89,108 +89,107 @@ public class ScreenStory extends RogueScreen implements MouseButtonHandler {
}
}
};
private final TimedTask ttFinish = new TimedTask() {
@Override
public void run()
{
txProceedAlpha.fadeIn();
}
};
private final Color textColor = Color.fromHex(0x7ad8ff);
public LayerSlide(Screen screen)
{
super(screen);
final TextPainter help = new TextPainter(Res.font("tiny"), AlignX.CENTER, RGB.WHITE.withAlpha(txProceedAlpha.mul(0.3)),
"Space / click to proceed.");
final TextPainter help = new TextPainter(Res.font("tiny"), AlignX.CENTER, RGB.WHITE.withAlpha(txProceedAlpha.mul(0.3)), "Space / click to proceed.");
help.setRect(root.bottomEdge().growUp(root.height().perc(4)));
help.setVPaddingPercent(5);
root.add(help);
final Rect contentRect = root.shrink(Num.ZERO, Num.ZERO, root.height().perc(2), root.height().perc(6));
final RowLayout rl = new RowLayout(root, 9);
rl.setRect(contentRect);
root.add(rl);
final LinearLayout ll = new LinearLayout(root, AlignX.CENTER);
rl.add(ll, 7);
img = new ImagePainter(Res.txQuad("story_1"));
ll.add(img);
tp1 = new TextPainter(Res.font("tiny"), AlignX.CENTER, textColor.withAlpha(tx1alpha), "");
rl.add(tp1);
tp1.setVPaddingPercent(19);
tp2 = new TextPainter(Res.font("tiny"), AlignX.CENTER, textColor.withAlpha(tx2alpha), "");
rl.add(tp2);
tp2.setVPaddingPercent(19);
updated.add(layerAlpha);
updated.add(txProceedAlpha);
updated.add(tx1alpha);
updated.add(tx2alpha);
updated.add(ttText1);
updated.add(ttText2);
updated.add(ttFinish);
updated.add(ttNextSlide);
setAlpha(layerAlpha);
}
@Override
public int getZIndex()
{
return 1;
}
public void showSlide(String image, String text1, String text2)
{
ttFinish.stop();
ttNextSlide.stop();
ttText1.stop();
ttText2.stop();
this.nextImg = image;
this.nextT1 = text1;
this.nextT2 = text2;
layerAlpha.fadeOut();
ttNextSlide.start(1);
}
public void reset()
{
ttFinish.stop();
ttNextSlide.stop();
ttText1.stop();
ttText2.stop();
layerAlpha.setTo(0);
}
}
private LayerSlide slideLayer;
private final Action next = new Action() {
@Override
protected void execute()
{
showSlide(++slide);
}
};
private final Action prev = new Action() {
@Override
protected void execute()
{
@ -198,33 +197,33 @@ public class ScreenStory extends RogueScreen implements MouseButtonHandler {
showSlide(slide);
}
};
private final Action close = new Action() {
@Override
protected void execute()
{
App.bus().send(new RogueStateRequest(RogueState.MAIN_MENU));
}
};
public ScreenStory()
{
addLayer(new LayerColor(this, Color.fromHex(0x040c1e), 0));
addLayer(slideLayer = new LayerSlide(this));
bindKey(new KeyStroke(Keys.SPACE), Trigger.RISING, next);
bindKey(new KeyStroke(Keys.RIGHT), Trigger.RISING, next);
bindKey(new KeyStroke(Keys.BACKSPACE), Trigger.RISING, prev);
bindKey(new KeyStroke(Keys.LEFT), Trigger.RISING, prev);
bindKey(App.cfg().getKeyStroke("general.close"), Trigger.RISING, close);
}
private int slide = 0;
@Override
protected void onScreenEnter()
{
@ -232,29 +231,29 @@ public class ScreenStory extends RogueScreen implements MouseButtonHandler {
slideLayer.reset();
showSlide(slide);
}
private void showSlide(int slide)
{
switch (slide) {
case 0:
slideLayer.showSlide("story_1", "Man, it's so hot today!", "Makes me real thirsty, ya know.");
break;
case 1:
slideLayer.showSlide("story_2", "'Guess I'll go get some beer", "from the cellar.");
break;
case 2:
slideLayer.showSlide("story_3", "Here we go.. HEY GIVE IT BACK!", "I'll hunt you down, thieves!");
break;
case 3:
App.bus().send(new RogueStateRequest(RogueState.MAIN_MENU));
}
}
@Override
public void receive(MouseButtonEvent event)
{

@ -11,59 +11,59 @@ import mightypork.utils.ion.IonOutput;
public class Inventory implements IonBinary {
public static final short ION_MARK = 54;
private Item[] items;
private int lastAddIndex = 0;
public Inventory(int size)
{
this.items = new Item[size];
}
public Inventory()
{
// ION constructor
}
@Override
public void load(IonInput in) throws IOException
{
final int size = in.readIntByte();
items = new Item[size];
// for all items in sequence
while (in.hasNextEntry()) {
// load item index
final int i = in.readIntByte();
// load item
setItem(i, Items.loadItem(in));
}
}
@Override
public void save(IonOutput out) throws IOException
{
// write length
out.writeIntByte(getSize());
// find items that are writable
for (int i = 0; i < getSize(); i++) {
final Item item = getItem(i);
if (item != null && !item.isEmpty()) {
// start sequence entry
out.startEntry();
// write index
out.writeIntByte(i);
// write item at index
Items.saveItem(out, item);
}
@ -71,8 +71,8 @@ public class Inventory implements IonBinary {
// close sequence
out.endSequence();
}
/**
* Get item in a slot
*
@ -82,22 +82,22 @@ public class Inventory implements IonBinary {
public Item getItem(int i)
{
if (i < 0 || i > getSize()) return null;
verifyIndex(i);
final Item itm = items[i];
if (itm == null || itm.isEmpty()) return null;
return itm;
}
private void verifyIndex(int i)
{
if (i < 0 || i > getSize()) {
throw new IndexOutOfBoundsException("Invalid inventory index: " + i + ", size: " + getSize());
}
}
/**
* Put item in a slot
*
@ -110,8 +110,8 @@ public class Inventory implements IonBinary {
items[i] = item;
lastAddIndex = i;
}
/**
* @return inventory size
*/
@ -119,8 +119,8 @@ public class Inventory implements IonBinary {
{
return items.length;
}
/**
* Add an item, try to merge first.
*
@ -139,7 +139,7 @@ public class Inventory implements IonBinary {
}
}
}
// try to place in a free slot
for (int i = 0; i < getSize(); i++) {
final Item itm = getItem(i);
@ -149,12 +149,12 @@ public class Inventory implements IonBinary {
return true;
}
}
// could not insert.
return false;
}
/**
* Clean empty items
*/
@ -166,24 +166,24 @@ public class Inventory implements IonBinary {
if (itm.isEmpty()) setItem(i, null);
}
}
public int getLastAddIndex()
{
return lastAddIndex;
}
@Override
public String toString()
{
String s = "Inv[";
for (int i = 0; i < getSize(); i++) {
if (i > 0) s += ", ";
s += i + ": ";
final Item itm = getItem(i);
if (itm == null) s += "<null>";
else s += itm;
}

@ -15,82 +15,82 @@ import mightypork.utils.math.constraints.vect.Vect;
public abstract class PlayerControl {
protected Set<EntityMoveListener> playerMoveListeners = new HashSet<>();
private World lastWorld;
/**
* Implementing classes should return a world instance from this method.
*
* @return
*/
protected abstract World provideWorld();
public World getWorld()
{
final World newWorld = provideWorld();
lastWorld = newWorld;
return newWorld;
}
public PlayerFacade getPlayer()
{
if (getWorld() == null) return null;
return getWorld().getPlayer();
}
public void goNorth()
{
go(Move.NORTH);
}
public void goSouth()
{
go(Move.SOUTH);
}
public void goEast()
{
go(Move.EAST);
}
public void goWest()
{
go(Move.WEST);
}
public void navigateTo(Coord pos)
{
if (!getLevel().getTile(pos).isExplored()) return;
getPlayer().navigateTo(pos);
}
public Level getLevel()
{
return getWorld().getPlayer().getLevel();
}
public boolean canGo(Move side)
{
return getPlayer().canGoTo(side);
}
/**
* Click tile on player's side
*
@ -101,44 +101,44 @@ public abstract class PlayerControl {
{
return doClickTile(getPlayer().getCoord().add(side).toVect());
}
public boolean clickTile(Vect pos)
{
if (pos.dist(getPlayer().getVisualPos().add(0.5, 0.5)).value() < 1.5) {
return doClickTile(pos);
}
return false;
}
private boolean doClickTile(Vect pos)
{
//try to click tile
if (getLevel().getTile(Coord.fromVect(pos)).onClick()) return true;
final Tile t = getLevel().getTile(Coord.fromVect(pos));
if (!t.isPotentiallyWalkable()) return false; // no point in attacking entity thru wall, right?
//try to hit entity
final Entity prey = getLevel().getClosestEntity(pos, EntityType.MONSTER, 1);
if (prey != null) {
getPlayer().attack(prey);
return true;
}
return false;
}
public void go(Move side)
{
getPlayer().cancelPath();
getPlayer().addPathStep(side);
}
public boolean tryGo(Move e)
{
if (!canGo(e)) return false;

@ -12,32 +12,32 @@ import mightypork.utils.ion.IonDataBundle;
* @author Ondřej Hruška (MightyPork)
*/
public class PlayerData implements IonBundled {
/** Player inventory size */
private static final int INV_SIZE = 8;
/** Attack str with bare hands */
public static final int BARE_ATTACK = 1;
private int eid = -1; // marks not initialized
private int level;
private Inventory inventory = new Inventory(INV_SIZE);
private int selectedWeapon = -1;
@Override
public void load(IonDataBundle bundle)
{
eid = bundle.get("eid", -1);
level = bundle.get("floor", -1);
selectedWeapon = bundle.get("weapon", -1);
inventory = bundle.get("inv", inventory);
}
@Override
public void save(IonDataBundle bundle)
{
@ -46,69 +46,69 @@ public class PlayerData implements IonBundled {
bundle.put("weapon", selectedWeapon);
bundle.put("inv", inventory);
}
public void setEID(int eid)
{
if (isInitialized()) throw new RuntimeException("Cannot change player EID.");
this.eid = eid;
}
public void setLevelNumber(int level)
{
this.level = level;
}
public int getEID()
{
return eid;
}
public int getLevelNumber()
{
return level;
}
public boolean isInitialized()
{
return eid != -1;
}
public Inventory getInventory()
{
return inventory;
}
public int getSelectedWeaponIndex()
{
return selectedWeapon;
}
public boolean hasWeaponSelected()
{
return !(selectedWeapon < 0 || selectedWeapon >= getInventory().getSize());
}
public Item getSelectedWeapon()
{
if (!hasWeaponSelected()) return null;
return inventory.getItem(selectedWeapon);
}
public void selectWeapon(int selectedWeapon)
{
this.selectedWeapon = selectedWeapon;
if (!hasWeaponSelected()) selectedWeapon = -1; // normalize
}
}

@ -21,11 +21,11 @@ import mightypork.utils.math.constraints.vect.Vect;
* @author Ondřej Hruška (MightyPork)
*/
public class PlayerFacade {
/** a world */
private final World world;
/**
* @return true if can go up
*/
@ -33,8 +33,8 @@ public class PlayerFacade {
{
return world.playerData.getLevelNumber() > 0;
}
/**
* @param world
*/
@ -42,8 +42,8 @@ public class PlayerFacade {
{
this.world = world;
}
/**
* @return true if can go down
*/
@ -51,48 +51,48 @@ public class PlayerFacade {
{
return world.playerData.getLevelNumber() < world.levels.size() - 1;
}
/**
* Go one level down if applicable
*/
public void descend()
{
if (!canDescend()) return;
final int lvl_num = getLevelNumber();
getLevel().removeEntity(world.playerEntity);
world.playerData.setLevelNumber(lvl_num + 1);
getLevel().forceFreeTile(getLevel().getEnterPoint());
getLevel().addEntity(world.playerEntity, getLevel().getEnterPoint());
getLevel().explore(getCoord());
world.console.msgEnterFloor(getLevelNumber());
}
/**
* Go one level up if applicable
*/
public void ascend()
{
if (!canAscend()) return;
final int lvl_num = getLevelNumber();
getLevel().removeEntity(world.playerEntity);
world.playerData.setLevelNumber(lvl_num - 1);
getLevel().forceFreeTile(getLevel().getExitPoint());
getLevel().addEntity(world.playerEntity, getLevel().getExitPoint());
getLevel().explore(getCoord());
world.console.msgEnterFloor(getLevelNumber());
}
/**
* @return current level number, zero based.
*/
@ -100,8 +100,8 @@ public class PlayerFacade {
{
return world.playerData.getLevelNumber();
}
/**
* @return current level
*/
@ -109,8 +109,8 @@ public class PlayerFacade {
{
return world.levels.get(world.playerData.getLevelNumber());
}
/**
* @return entity ID
*/
@ -118,8 +118,8 @@ public class PlayerFacade {
{
return world.playerData.getEID();
}
/**
* @return entity coordinate in level
*/
@ -127,8 +127,8 @@ public class PlayerFacade {
{
return world.playerEntity.getCoord();
}
/**
* @return entity visual pos in level
*/
@ -136,8 +136,8 @@ public class PlayerFacade {
{
return world.playerEntity.pos.getVisualPos();
}
/**
* Find path to
*
@ -147,8 +147,8 @@ public class PlayerFacade {
{
world.playerEntity.pos.navigateTo(pos);
}
/**
* Discard steps in buffer
*/
@ -156,66 +156,66 @@ public class PlayerFacade {
{
world.playerEntity.pos.cancelPath();
}
public void addPathStep(Move step)
{
world.playerEntity.pos.addStep(step);
}
public boolean isMoving()
{
return world.playerEntity.pos.isMoving();
}
public double getMoveProgress()
{
return world.playerEntity.pos.getProgress();
}
public boolean isDead()
{
return world.playerEntity.isDead();
}
public int getHealth()
{
return world.playerEntity.health.getHealth();
}
public int getHealthMax()
{
return world.playerEntity.health.getHealthMax();
}
public Inventory getInventory()
{
return world.playerData.getInventory();
}
public Entity getEntity()
{
return world.playerEntity;
}
public int getAttackStrength()
{
final Item weapon = world.playerData.getSelectedWeapon();
if (weapon == null) return PlayerData.BARE_ATTACK;
return PlayerData.BARE_ATTACK + weapon.getAttackPoints();
}
/**
* Eat food.
*
@ -225,39 +225,39 @@ public class PlayerFacade {
public boolean eatFood(Item itm)
{
if (itm == null || itm.isEmpty() || itm.getType() != ItemType.FOOD) return false;
if (getHealth() < getHealthMax()) {
world.playerEntity.health.addHealth(itm.getFoodPoints());
itm.consume();
world.console.msgEat(itm);
return true;
}
return false;
}
public void selectWeapon(int selected)
{
world.playerData.selectWeapon(selected);
}
public Item getSelectedWeapon()
{
return world.playerData.getSelectedWeapon();
}
public int getSelectedWeaponIndex()
{
return world.playerData.getSelectedWeaponIndex();
}
public void tryToEatSomeFood()
{
final List<Item> foods = new ArrayList<>();
@ -267,63 +267,63 @@ public class PlayerFacade {
foods.add(itm);
}
}
// sort from smallest to biggest foods
Collections.sort(foods, new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2)
{
return (o1.getFoodPoints() - o2.getFoodPoints());
}
});
for (final Item itm : foods) {
if (eatFood(itm)) {
getInventory().clean();
return;
}
}
if (getHealth() < getHealthMax()) {
world.console.msgNoMoreFood();
} else {
world.console.msgNotHungry();
}
}
public void attack(Entity prey)
{
final int attackPoints = getAttackStrength();
prey.receiveAttack(world.getPlayer().getEntity(), attackPoints);
if (prey.isDead()) {
world.console.msgKill(prey);
}
final Item wpn = getSelectedWeapon();
if (wpn != null) {
wpn.use();
if (wpn.isEmpty()) {
world.console.msgWeaponBreak(wpn);
getInventory().clean();
selectWeapon(-1);
pickBestWeaponIfNoneSelected();
}
}
}
private void pickBestWeaponIfNoneSelected()
{
if (getSelectedWeapon() != null) return;
final List<Item> wpns = new ArrayList<>();
for (int i = 0; i < getInventory().getSize(); i++) {
final Item itm = getInventory().getItem(i);
@ -331,17 +331,17 @@ public class PlayerFacade {
wpns.add(itm);
}
}
// sort from smallest to biggest foods
Collections.sort(wpns, new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2)
{
return (o2.getAttackPoints() - o1.getAttackPoints());
}
});
for (final Item itm : wpns) {
for (int i = 0; i < getInventory().getSize(); i++) {
final Item itm2 = getInventory().getItem(i);
@ -352,75 +352,75 @@ public class PlayerFacade {
}
break; // just one cycle
}
world.console.msgEquipWeapon(getSelectedWeapon());
}
public boolean addItem(Item item)
{
if (!getInventory().addItem(item)) {
world.console.msgCannotPick();
return false;
}
world.console.msgPick(item);
if (item.getType() == ItemType.WEAPON) {
if (getSelectedWeapon() != null) {
if (item.getAttackPoints() > getSelectedWeapon().getAttackPoints()) {
selectWeapon(-1); // unselect to grab the best one
}
}
pickBestWeaponIfNoneSelected();
}
return true;
}
public void setHealth(int health)
{
world.playerEntity.health.setHealth(health);
}
public void setHealthMax(int health)
{
world.playerEntity.health.setHealthMax(health);
}
public World getWorld()
{
return world;
}
public boolean canGoTo(Move side)
{
return getEntity().pos.canGoTo(side);
}
public void dropItem(int itemIndex)
{
final Item itm = getInventory().getItem(itemIndex);
if (itm != null && !itm.isEmpty()) {
final Item piece = itm.split(1);
getInventory().clean();
Coord dropPos;
if (world.playerEntity.pos.isMoving()) {
dropPos = world.playerEntity.pos.getLastPos();
} else {
dropPos = getCoord();
}
if (!getLevel().getTile(dropPos).dropItem(piece)) {
getInventory().addItem(piece); // add back
} else {

@ -26,67 +26,67 @@ import mightypork.utils.math.algo.Coord;
* @author Ondřej Hruška (MightyPork)
*/
public class World implements DelegatingClient, IonBundled, Pauseable, Updateable, PlayerDeathHandler, GameWinHandler {
// not saved stuffs
private final PlayerFacade playerFacade = new PlayerFacade(this);
final WorldConsole console = new WorldConsole();
Entity playerEntity;
private int pauseDepth = 0;
private boolean gameOver = false;
/** List of world's levels */
final ArrayList<Level> levels = new ArrayList<>();
/** Player data saved together with world */
final PlayerData playerData = new PlayerData();
/** World seed */
private long seed;
/** Next entity ID */
private int eid;
private File saveFile;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Collection getChildClients()
{
return levels;
}
@Override
public boolean doesDelegate()
{
return !isPaused();
}
@Override
public void load(IonDataBundle in)
{
seed = in.get("seed", seed);
eid = in.get("next_eid", eid);
in.loadSequence("levels", levels);
// join levels to world
for (final Level lvl : levels) {
lvl.setWorld(this);
}
in.loadBundled("player", playerData);
final int eid = playerData.getEID();
final int lvl = playerData.getLevelNumber();
playerEntity = levels.get(lvl).getEntity(eid);
if (playerEntity == null) {
Log.e("Player entity not found in the world: " + eid + " on floor " + lvl);
for (int i = 0; i < levels.size(); i++) {
final Entity ent = levels.get(i).getEntity(eid);
if (ent != null) {
@ -94,12 +94,12 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
break;
}
}
throw new RuntimeException("Player not found in world.");
}
}
@Override
public void save(IonDataBundle out)
{
@ -107,31 +107,31 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
out.put("next_eid", eid);
out.putSequence("levels", levels);
out.putBundled("player", playerData);
// used for peking in before actually loading the world.
out.put("meta.last_level", playerData.getLevelNumber());
}
public void addLevel(Level level)
{
levels.add(level);
level.setWorld(this);
}
public void setSeed(long seed)
{
this.seed = seed;
}
public long getSeed()
{
return seed;
}
/**
* @return new entity ID
*/
@ -139,79 +139,79 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
{
return eid++;
}
public void createPlayer()
{
if (playerData.isInitialized()) {
throw new RuntimeException("Player already created.");
}
// make entity
final int playerEid = getNewEID();
final Level floor = levels.get(0);
playerEntity = Entities.PLAYER.createEntity(playerEid);
final Coord spawn = floor.getEnterPoint();
floor.forceFreeTile(spawn);
final Random rand = new Random(seed + 71);
while (!floor.addEntity(playerEntity, spawn)) {
spawn.x += -1 + rand.nextInt(3);
spawn.y += -1 + rand.nextInt(3);
}
floor.explore(spawn);
playerData.setLevelNumber(0);
playerData.setEID(playerEid);
console.msgEnterFloor(0);
}
@Override
public void pause()
{
pauseDepth++;
}
@Override
public void resume()
{
if (pauseDepth > 0) pauseDepth--;
}
@Override
public boolean isPaused()
{
return pauseDepth > 0;
}
public PlayerFacade getPlayer()
{
return playerFacade;
}
@Override
public void update(double delta)
{
if (isPaused()) return;
// update console timing - not as child client
console.update(delta);
if (saveFile == null) Log.w("World has no save file.");
}
/**
* @return world console
*/
@ -219,8 +219,8 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
{
return console;
}
/**
* Set file for saving
*
@ -230,8 +230,8 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
{
this.saveFile = file;
}
/**
* @return assigned file fro saving
*/
@ -239,22 +239,22 @@ public class World implements DelegatingClient, IonBundled, Pauseable, Updateabl
{
return saveFile;
}
@Override
public void onGameWon()
{
gameOver = true;
}
@Override
public void onPlayerKilled()
{
gameOver = true;
}
public boolean isGameOver()
{
return gameOver || getPlayer().isDead();

@ -19,219 +19,219 @@ import mightypork.utils.math.animation.NumAnimated;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldConsole implements Updateable {
/** Used only for display */
public Item lastPickupItem;
public double timeSinceLastPickup = Integer.MAX_VALUE;
private static final double DURATION = 5;
public class Entry implements Updateable {
private final String text;
private final NumAnimated fadeout;
private boolean fading = false;
private double elapsed = 0;
private Entry(String text)
{
this.text = text;
this.fadeout = new NumAnimated(1, Easing.LINEAR);
this.fadeout.setDefaultDuration(0.5);
}
@Override
public void update(double delta)
{
elapsed += delta;
if (fading) {
fadeout.update(delta);
return;
}
if (elapsed > DURATION) {
fading = true;
fadeout.fadeOut();
}
}
private boolean canRemove()
{
return fading && fadeout.isFinished();
}
public double getAlpha()
{
return !fading ? 1 : fadeout.value();
}
public String getMessage()
{
return text;
}
public double getAge()
{
return elapsed;
}
}
private final Deque<Entry> entries = new LinkedBlockingDeque<>();
@Override
public void update(double delta)
{
for (final Iterator<Entry> iter = entries.iterator(); iter.hasNext();) {
final Entry e = iter.next();
e.update(delta);
if (e.canRemove()) {
iter.remove();
}
}
timeSinceLastPickup += delta;
}
public void addMessage(String message)
{
entries.addFirst(new Entry(message));
}
public Collection<Entry> getEntries()
{
return entries;
}
public void clear()
{
entries.clear();
}
public void msgCannotPick()
{
addMessage("Can't collect items, inventory is full.");
}
public void msgDie(Entity attacker)
{
addMessage("You've been defeated by " + addArticle(attacker.getVisualName()) + "!");
}
public void msgDiscoverSecretDoor()
{
addMessage("You've discovered a secret door.");
}
public void msgEat(Item item)
{
addMessage("You've eaten " + addArticle(item.getVisualName()) + ".");
}
public void msgEnterFloor(int floor)
{
addMessage("~ Floor " + (1 + floor) + " ~");
}
public void msgEquipWeapon(Item item)
{
addMessage("You're now wielding " + (item == null ? "NOTHING" : addArticle(item.getVisualName())) + ".");
}
public void msgHeartPiece()
{
addMessage("Your health capacity has been increased.");
}
public void msgKill(Entity prey)
{
addMessage("You've killed " + addArticle(prey.getVisualName()) + ".");
}
public void msgNoMoreFood()
{
addMessage("You don't have any food!");
}
public void msgNotHungry()
{
addMessage("You are not hungry.");
}
public void msgPick(Item item)
{
addMessage("You've picked up " + addArticle(item.getVisualName()) + ".");
lastPickupItem = item;
timeSinceLastPickup = 0;
}
public void msgDroppedItem(Item item)
{
addMessage("You've dropped " + addArticle(item.getVisualName()) + ".");
lastPickupItem = null;
}
public void msgWeaponBreak(Item item)
{
addMessage("Your " + item.getVisualName() + " has broken!");
}
public void msgWorldSaved()
{
addMessage("Game saved to file.");
}
public void msgWorldSaveError()
{
addMessage("Error while saving; See the log for details.");
}
public void msgReloaded()
{
addMessage("World loaded from file.");
}
public void msgLoadFailed()
{
addMessage("Error while loading; See the log for details.");
}
public void msgOpenChest()
{
addMessage("You've opened a treasure chest!");
}
private String addArticle(String name)
{
switch (Character.toLowerCase(name.charAt(0))) {

@ -18,35 +18,35 @@ import mightypork.utils.logging.Log;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldProvider extends BusNode {
public WorldProvider()
{
setListening(false);
}
private static WorldProvider inst = new WorldProvider();
public static WorldProvider get()
{
if (inst == null) {
throw new IllegalStateException("World provider not initialized.");
}
return inst;
}
private World world;
private final PlayerControl playerControl = new PlayerControl() {
@Override
protected World provideWorld()
{
return world;
}
};
/**
* Create and register a world based on a seed
*
@ -60,35 +60,35 @@ public class WorldProvider extends BusNode {
setWorld(w);
return w;
}
public World getWorld()
{
return world;
}
public void setWorld(World newWorld)
{
if (world != null) removeChildClient(world);
world = newWorld;
if (newWorld == null) return;
addChildClient(world);
}
public void loadWorld(File file) throws IOException
{
Log.f2("Loading world from: " + file);
final IonDataBundle bu = Ion.fromFile(file);
setWorld(bu.loadBundled("world", new World()));
world.setSaveFile(file);
}
public void saveWorld(File file) throws IOException
{
if (world == null) {
@ -97,21 +97,21 @@ public class WorldProvider extends BusNode {
if (file == null) {
throw new IllegalStateException("Trying to save world to a NULL file.");
}
if (world.isGameOver()) {
throw new IllegalStateException("Cannot save, player is dead.");
}
Log.f2("Saving world to: " + file);
final IonDataBundle bu = new IonDataBundle();
bu.put("level", world.getPlayer().getLevelNumber());
bu.putBundled("world", world);
Ion.toFile(file, bu);
}
/**
* Save to world's assigned save file.
*
@ -121,20 +121,20 @@ public class WorldProvider extends BusNode {
{
saveWorld(world.getSaveFile());
}
public Level getCurrentLevel()
{
return getWorld().getPlayer().getLevel();
}
public PlayerFacade getPlayer()
{
return getWorld().getPlayer();
}
/**
* @return constant player control (world independent)
*/
@ -142,5 +142,5 @@ public class WorldProvider extends BusNode {
{
return playerControl;
}
}

@ -24,89 +24,89 @@ import mightypork.utils.math.constraints.vect.VectConst;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldRenderer extends RectProxy {
private static final boolean USE_BATCH_RENDERING = true;
private final Num tileSize;
// can be changed
private RectConst mapRect;
private Level activeLevel;
private final Rect rightShadow;
private final Rect leftShadow;
private final Rect topShadow;
private final Rect bottomShadow;
private TileRenderContext trc;
public WorldRenderer(Rect viewport, Num tileSize)
{
super(viewport);
this.tileSize = tileSize;
final Num grX = width().perc(30);
leftShadow = leftEdge().growRight(grX);
rightShadow = rightEdge().growLeft(grX);
final Num grY = height().perc(20);
topShadow = topEdge().growDown(grY);
bottomShadow = bottomEdge().growUp(grY);
}
private void prepareRenderContextIfNeeded()
{
final Level level = WorldProvider.get().getCurrentLevel();
if (activeLevel == level) return;
activeLevel = level;
mapRect = Rect.make(0, 0, level.getWidth(), level.getHeight());
trc = new TileRenderContext(activeLevel, mapRect);
}
private VectConst getOffset()
{
return Vect.make(WorldProvider.get().getPlayer().getVisualPos().neg().sub(0.5, 0.5)).freeze();
}
public void render()
{
prepareRenderContextIfNeeded();
final GraphicsModule gfx = App.gfx();
gfx.pushGeometry();
gfx.setColor(RGB.WHITE);
gfx.translate(center());
gfx.scaleXY(tileSize.value());
gfx.translate(getOffset());
// tiles to render
final Coord pos = WorldProvider.get().getPlayer().getCoord();
final double w = width().value();
final double h = height().value();
final double ts = tileSize.value();
final int xtilesh = (int) (w / (ts * 2)) + 1;
final int ytilesh = (int) (h / (ts * 2)) + 1;
final int x1 = pos.x - xtilesh;
final int y1 = pos.y - ytilesh;
final int x2 = pos.x + xtilesh;
final int y2 = pos.y + ytilesh;
// === TILES ===
// batch rendering of the tiles
// TODO
// if (USE_BATCH_RENDERING) {
@ -121,32 +121,32 @@ public class WorldRenderer extends RectProxy {
// if (USE_BATCH_RENDERING) {
// gfx.leaveBatchTexturedQuadMode();
// }
// === ITEMS ON TILES ===
for (trc.pos.x = x1; trc.pos.x <= x2; trc.pos.x++) {
for (trc.pos.y = y1; trc.pos.y <= y2; trc.pos.y++) {
trc.renderItems();
}
}
// === ENTITIES ===
for (final Entity e : activeLevel.getEntities()) {
// avoid entities out of view rect
final Vect entPos = e.pos.getVisualPos();
final int x = (int) Math.round(entPos.x());
final int y = (int) Math.round(entPos.y());
if (x < x1 - ts || x > x2 + ts) continue;
if (y < y1 - ts || y > y2 + ts) continue;
e.render(trc);
}
// === unexplored fog ===
// batch rendering of the tiles
// TODO
// if (USE_BATCH_RENDERING) {
@ -163,24 +163,24 @@ public class WorldRenderer extends RectProxy {
// if (USE_BATCH_RENDERING) {
// gfx.leaveBatchTexturedQuadMode();
// }
// === OVERLAY SHADOW ===
gfx.popGeometry();
gfx.quad(leftShadow, new GradH(RGB.BLACK, RGB.NONE));
gfx.quad(rightShadow, new GradH(RGB.NONE, RGB.BLACK));
gfx.quad(topShadow, new GradV(RGB.BLACK, RGB.NONE));
gfx.quad(bottomShadow, new GradV(RGB.NONE, RGB.BLACK));
}
public Vect getClickedTile(Vect clickPos)
{
final int ts = (int) tileSize.value();
final Vect v = clickPos.sub(center().add(getOffset().mul(ts)));
return Vect.make(v.x() / ts, v.y() / ts);
}
}

@ -7,17 +7,17 @@ import mightypork.utils.math.timing.TaskRepeater;
public abstract class AiTimer extends TaskRepeater implements IonBundled {
public AiTimer(double duration)
{
super(duration);
}
@Override
public abstract void run();
@Override
public void load(IonDataBundle bundle)
{
@ -27,12 +27,12 @@ public abstract class AiTimer extends TaskRepeater implements IonBundled {
} else {
resume();
}
setProgress(bundle.get("progress", getProgress()));
setDuration(bundle.get("duration", getDuration()));
}
@Override
public void save(IonDataBundle bundle)
{
@ -40,5 +40,5 @@ public abstract class AiTimer extends TaskRepeater implements IonBundled {
bundle.put("progress", getProgress());
bundle.put("duration", getDuration());
}
}

@ -18,41 +18,41 @@ import mightypork.utils.ion.IonOutput;
* @author Ondřej Hruška (MightyPork)
*/
public final class Entities {
private static final EntityModel[] entities = new EntityModel[256];
public static final EntityModel PLAYER = new EntityModel(1, EntityPlayer.class);
public static final EntityModel RAT_GRAY = new EntityModel(2, EntityGrayRat.class);
public static final EntityModel RAT_BROWN = new EntityModel(3, EntityBrownRat.class);
public static final EntityModel RAT_BOSS = new EntityModel(4, EntityBossRat.class);
public static void register(int id, EntityModel model)
{
if (id < 0 || id >= entities.length) {
throw new IllegalArgumentException("Entity model ID " + id + " is out of range.");
}
if (entities[id] != null) {
throw new IllegalArgumentException("Entity model ID " + id + " already in use.");
}
entities[id] = model;
}
public static EntityModel get(int id)
{
final EntityModel e = entities[id];
if (e == null) {
throw new IllegalArgumentException("No entity model with ID " + id + ".");
}
return e;
}
public static void loadEntities(IonInput in, Collection<Entity> entities) throws IOException
{
entities.clear();
@ -60,34 +60,34 @@ public final class Entities {
entities.add(loadEntity(in));
}
}
public static void saveEntities(IonOutput out, Collection<Entity> entities) throws IOException
{
for (final Entity entity : entities) {
out.startEntry();
saveEntity(out, entity);
}
out.endSequence();
}
public static Entity loadEntity(IonInput in) throws IOException
{
final int id = in.readIntByte();
final EntityModel model = get(id);
return model.loadEntity(in);
}
public static void saveEntity(IonOutput out, Entity entity) throws IOException
{
final EntityModel model = entity.getModel();
out.writeIntByte(model.id);
model.saveEntity(out, entity);
}
}

@ -27,80 +27,80 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
* @author Ondřej Hruška (MightyPork)
*/
public abstract class Entity implements IonBundled, Updateable, DelegatingClient {
private Level level;
private final EntityModel model;
/** Entity ID */
private int entityId = -1;
private final Map<String, EntityModule> modules = new HashMap<>();
// default modules
public final EntityModulePosition pos = new EntityModulePosition(this);
public final EntityModuleHealth health = new EntityModuleHealth(this);
private double despawnDelay = 1;
protected Entity lastAttacker;
private boolean freed;
public Entity(EntityModel model, int eid)
{
this.entityId = eid;
this.model = model;
// register modules
addModule("pos", pos);
addModule("health", health);
}
@Override
public final void save(IonDataBundle bundle)
{
bundle.put("eid", entityId);
final IonDataBundle modulesBundle = new IonDataBundle();
for (final Entry<String, EntityModule> entry : modules.entrySet()) {
modulesBundle.putBundled(entry.getKey(), entry.getValue());
}
bundle.put("modules", modulesBundle);
final IonDataBundle extra = new IonDataBundle();
saveExtra(extra);
bundle.put("extra", extra);
}
@Stub
protected void saveExtra(IonDataBundle bundle)
{
}
@Override
public final void load(IonDataBundle bundle)
{
entityId = bundle.get("eid", -1);
if (entityId < 0) throw new IllegalValueException("Bad entity id: " + entityId);
final IonDataBundle modulesBundle = bundle.get("modules", new IonDataBundle());
for (final Entry<String, EntityModule> entry : modules.entrySet()) {
modulesBundle.loadBundled(entry.getKey(), entry.getValue());
}
final IonDataBundle extra = bundle.get("extra", new IonDataBundle());
loadExtra(extra);
}
@Stub
protected void loadExtra(IonDataBundle bundle)
{
}
protected final void addModule(String key, EntityModule module)
{
if (modules.containsKey(key)) {
@ -108,8 +108,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
}
modules.put(key, module);
}
/**
* @return unique entity id
*/
@ -117,53 +117,53 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
{
return entityId;
}
public void setLevel(Level level)
{
if (level != null && entityId < 0) {
entityId = level.getWorld().getNewEID();
}
if (level != null) level.freeTile(getCoord());
this.level = level;
if (level != null) level.occupyTile(getCoord());
}
public final Level getLevel()
{
return level;
}
public final World getWorld()
{
return level.getWorld();
}
public final EntityModel getModel()
{
return model;
}
public abstract PathFinder getPathFinder();
@Stub
public final void render(MapRenderContext context)
{
getRenderer().render(context);
}
protected abstract EntityRenderer getRenderer();
@Override
public void update(double delta)
{
@ -172,14 +172,14 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
freed = true;
}
}
/**
* @return entity type (used for AI targeting)
*/
public abstract EntityType getType();
/**
* @return entity coord in level
*/
@ -187,18 +187,18 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
{
return pos.getCoord();
}
public void setCoord(Coord coord)
{
if (level != null) level.freeTile(getCoord());
pos.setCoord(coord);
if (level != null) level.occupyTile(getCoord());
}
/**
* Called right after the entity's health reaches zero.
*/
@ -206,8 +206,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
public void onKilled()
{
}
/**
* @return true if dead
*/
@ -215,8 +215,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
{
return health.isDead();
}
/**
* @return whether this dead entity can be removed from level
*/
@ -225,8 +225,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
{
return isDead() && health.getTimeSinceLastDamage() > despawnDelay;
}
/**
* Called after the corpse has been cleaned from level.
*/
@ -234,8 +234,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
public void onCorpseRemoved()
{
}
/**
* Receive damage from an attacker.<br>
* The entity can decide whether to dodge, reduce damage etc.
@ -249,8 +249,8 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
this.lastAttacker = attacker;
health.receiveDamage(attackStrength);
}
/**
* Set how long after being killed is the corpse elligible for removal
*
@ -260,37 +260,37 @@ public abstract class Entity implements IonBundled, Updateable, DelegatingClient
{
this.despawnDelay = despawnDelay;
}
public double getDespawnDelay()
{
return despawnDelay;
}
public abstract String getVisualName();
@Override
public boolean doesDelegate()
{
return true;
}
@Override
public Collection<?> getChildClients()
{
return modules.values();
}
public Entity getLastAttacker()
{
return lastAttacker;
}
public double getLastAttackTime()
{
return health.getTimeSinceLastDamage();

@ -15,26 +15,26 @@ import mightypork.utils.ion.IonOutput;
* @author Ondřej Hruška (MightyPork)
*/
public final class EntityModel {
/** Model ID */
public final int id;
public final Class<? extends Entity> tileClass;
public EntityModel(int id, Class<? extends Entity> entity)
{
Entities.register(id, this);
this.id = id;
this.tileClass = entity;
}
public Entity createEntity(World world)
{
return createEntity(world.getNewEID());
}
public Entity createEntity(int eid)
{
try {
@ -43,8 +43,8 @@ public final class EntityModel {
throw new RuntimeException("Could not instantiate a tile.", e);
}
}
/**
* Create entitiy without EID. EID will be assigned when the entity is added
* to a level.
@ -55,8 +55,8 @@ public final class EntityModel {
{
return createEntity(-1);
}
public Entity loadEntity(IonInput in) throws IOException
{
final IonDataBundle bundle = in.readBundle();
@ -64,8 +64,8 @@ public final class EntityModel {
ent.load(bundle);
return ent;
}
public void saveEntity(IonOutput out, Entity entity) throws IOException
{
final IonDataBundle bundle = new IonDataBundle();

@ -14,36 +14,36 @@ import mightypork.utils.ion.IonDataBundle;
* @author Ondřej Hruška (MightyPork)
*/
public abstract class EntityModule implements IonBundled, Updateable {
protected final Entity entity;
public EntityModule(Entity entity)
{
this.entity = entity;
}
/**
* @return whether the module should be saved into a world file
*/
public abstract boolean isModuleSaved();
@Override
@Stub
public void load(IonDataBundle bundle)
{
}
@Override
@Stub
public void save(IonDataBundle bundle)
{
}
@Override
@Stub
public void update(double delta)

@ -16,48 +16,48 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
* @author Ondřej Hruška (MightyPork)
*/
public class EntityPathFinder extends PathFinder {
protected final Entity entity;
public EntityPathFinder(Entity entity)
{
this.entity = entity;
}
@Override
public boolean isAccessible(Coord pos)
{
return entity.getLevel().isWalkable(pos);
}
@Override
public int getCost(Coord from, Coord to)
{
return 10;
}
@Override
public int getMinCost()
{
return 10;
}
@Override
public Heuristic getHeuristic()
{
return PathFinder.CORNER_HEURISTIC;
}
@Override
public List<Move> getWalkSides()
{
return Moves.CARDINAL_SIDES;
}
}

@ -10,7 +10,7 @@ import mightypork.rogue.world.level.render.MapRenderContext;
* @author Ondřej Hruška (MightyPork)
*/
public abstract class EntityRenderer {
public abstract void render(MapRenderContext context);
}

@ -7,48 +7,48 @@ import mightypork.utils.math.Calc;
public class BossRatAi extends GrayRatAi {
private final AiTimer healTimer = new AiTimer(0.5) {
@Override
public void run()
{
entity.health.addHealth(1); // heal
}
};
public BossRatAi(Entity entity)
{
super(entity);
setAttackTime(0.3);
}
@Override
protected int getAttackStrength()
{
return Calc.randInt(2, 3);
}
@Override
protected int getPreyAbandonDistance()
{
return Calc.randInt(12, 18);
}
@Override
public void update(double delta)
{
super.update(delta);
healTimer.update(delta);
}
@Override
protected double getStepTime()
{

@ -6,37 +6,37 @@ import mightypork.utils.math.Calc;
public class BrownRatAi extends GrayRatAi {
public BrownRatAi(Entity entity)
{
super(entity);
setAttackTime(1.2);
setScanTime(1.3);
}
@Override
protected double getScanRadius()
{
return isIdle() ? Calc.randInt(2, 4) : Calc.randInt(5, 8);
}
@Override
protected int getAttackStrength()
{
return Calc.randInt(2, 4);
}
@Override
protected int getPreyAbandonDistance()
{
return Calc.randInt(7, 12);
}
@Override
protected double getStepTime()
{

@ -13,63 +13,63 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
public class EntityBossRat extends Entity {
private EntityRenderer renderer;
/** Navigation PFC */
private final PathFinder pathf = new EntityPathFinder(this);
private final BossRatAi ai = new BossRatAi(this);
public EntityBossRat(EntityModel model, int eid)
{
super(model, eid);
addModule("ai", ai);
pos.addMoveListener(ai);
setDespawnDelay(1);
health.setHealthMax(80);
health.setHealth(80);
health.setHitCooldownTime(0.33);
}
@Override
protected EntityRenderer getRenderer()
{
if (renderer == null) {
renderer = new EntityRendererMobLR(this, "sprite.rat.boss");
}
return renderer;
}
@Override
public EntityType getType()
{
return EntityType.MONSTER;
}
@Override
public PathFinder getPathFinder()
{
return pathf;
}
@Override
public void onKilled()
{
// send kill event to listeners, after the entity has despawned (disappeared)
App.bus().sendDelayed(new GameWinEvent(), getDespawnDelay() * 1.5); // dramatic pause
}
@Override
public String getVisualName()
{

@ -13,70 +13,70 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
public class EntityBrownRat extends Entity {
private EntityRenderer renderer;
private final PathFinder pathf = new EntityPathFinder(this);
private final BrownRatAi ai = new BrownRatAi(this);
public EntityBrownRat(EntityModel model, int eid)
{
super(model, eid);
addModule("ai", ai);
pos.addMoveListener(ai);
setDespawnDelay(1);
health.setHealthMax(16);
health.setHealth(Calc.randInt(10, 16)); // tougher to kill
}
@Override
protected EntityRenderer getRenderer()
{
if (renderer == null) {
renderer = new EntityRendererMobLR(this, "sprite.rat.brown");
}
return renderer;
}
@Override
public EntityType getType()
{
return EntityType.MONSTER;
}
@Override
public PathFinder getPathFinder()
{
return pathf;
}
@Override
public void onKilled()
{
// drop rat stuff
if (Calc.rand.nextInt(2) != 0) {
getLevel().dropNear(getCoord(), Items.MEAT.createItem());
return;
}
if (Calc.rand.nextInt(3) == 0) {
getLevel().dropNear(getCoord(), Items.CHEESE.createItem());
return;
}
}
@Override
public String getVisualName()
{

@ -13,75 +13,75 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
public class EntityGrayRat extends Entity {
private final PathFinder pathf = new EntityPathFinder(this);
private final GrayRatAi ai = new GrayRatAi(this);
private EntityRenderer renderer;
public EntityGrayRat(EntityModel model, int eid)
{
super(model, eid);
addModule("ai", ai);
pos.addMoveListener(ai);
setDespawnDelay(1);
health.setHealthMax(7);
health.setHealth(Calc.randInt(4, 7));
}
@Override
public PathFinder getPathFinder()
{
return pathf;
}
@Override
public EntityType getType()
{
return EntityType.MONSTER;
}
@Override
protected EntityRenderer getRenderer()
{
if (renderer == null) {
renderer = new EntityRendererMobLR(this, "sprite.rat.gray");
}
return renderer;
}
@Override
public void onKilled()
{
// drop rat stuff
if (Calc.rand.nextInt(6) == 0) {
getLevel().dropNear(getCoord(), Items.BONE.createItemDamaged(40));
return;
}
if (Calc.rand.nextInt(3) == 0) {
getLevel().dropNear(getCoord(), Items.CHEESE.createItem());
return;
}
if (Calc.rand.nextInt(3) == 0) {
getLevel().dropNear(getCoord(), Items.MEAT.createItem());
return;
}
}
@Override
public String getVisualName()
{

@ -19,32 +19,32 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
public class EntityPlayer extends Entity {
class PlayerAi extends EntityModule implements EntityMoveListener {
public PlayerAi(Entity entity)
{
super(entity);
setDespawnDelay(2);
health.setHealthMax(6); // 3 hearts
health.fill(); // fill health bar to max
health.setHitCooldownTime(0.5);
}
@Override
public void onStepFinished()
{
entity.getLevel().explore(entity.pos.getCoord());
fireEvt();
// try to pickup items
final Tile t = getLevel().getTile(getCoord());
if (t.hasItem()) {
final Item item = t.pickItem();
if (item.pickUp(getWorld().getPlayer())) {
// player picked item
} else {
@ -52,102 +52,102 @@ public class EntityPlayer extends Entity {
}
}
}
@Override
public void onPathFinished()
{
fireEvt();
}
@Override
public void onPathInterrupted()
{
}
private void fireEvt()
{
App.bus().send(new PlayerStepEndEvent(EntityPlayer.this));
}
@Override
public boolean isModuleSaved()
{
return false;
}
}
private PathFinder pathf;
private EntityRenderer renderer;
private final PlayerAi ai = new PlayerAi(this);
public EntityPlayer(EntityModel model, int eid)
{
super(model, eid);
pos.setStepTime(0.25);
addModule("ai", ai);
pos.addMoveListener(ai);
}
@Override
public PathFinder getPathFinder()
{
if (pathf == null) {
pathf = new EntityPathFinder(this) {
@Override
public int getCost(Coord from, Coord to)
{
if (!getLevel().getTile(pos.getCoord()).isExplored()) {
return 1000; // avoid unexplored, but allow them if there's no other way
}
return super.getCost(from, to);
}
};
}
return pathf;
}
@Override
protected EntityRenderer getRenderer()
{
if (renderer == null) {
renderer = new EntityRendererMobLR(this, "sprite.player");
}
return renderer;
}
@Override
public EntityType getType()
{
return EntityType.PLAYER;
}
@Override
public void onKilled()
{
// send kill event to listeners, after the entity has despawned (disappeared)
App.bus().sendDelayed(new PlayerKilledEvent(), getDespawnDelay());
getWorld().getConsole().msgDie(lastAttacker);
}
@Override
public String getVisualName()
{

@ -6,44 +6,44 @@ import mightypork.utils.math.Calc;
public class GrayRatAi extends MonsterAi {
public GrayRatAi(Entity entity)
{
super(entity);
setAttackTime(1.2);
setScanTime(1.5);
}
@Override
protected double getScanRadius()
{
return isIdle() ? Calc.randInt(2, 3) : Calc.randInt(4, 6);
}
@Override
protected double getAttackDistance()
{
return 1.1;
}
@Override
protected int getAttackStrength()
{
return Calc.randInt(1, 2);
}
@Override
protected int getPreyAbandonDistance()
{
return Calc.randInt(7, 11);
}
@Override
protected double getStepTime()
{

@ -20,11 +20,11 @@ import mightypork.utils.math.algo.pathfinding.PathFinderProxy;
public class MonsterAi extends EntityModule implements EntityMoveListener {
private boolean chasing = false;
private final AiTimer timerFindPrey = new AiTimer(3) {
@Override
public void run()
{
@ -32,144 +32,144 @@ public class MonsterAi extends EntityModule implements EntityMoveListener {
lookForTarget();
}
};
private final AiTimer timerAttack = new AiTimer(1) {
@Override
public void run()
{
if (!isChasing()) return;
final Entity prey = getPreyEntity();
if (prey == null || prey.isDead()) return;
attackPrey(prey);
}
};
private final AiTimer timerRandomWalk = new AiTimer(0.2) {
@Override
public void run()
{
if (!isIdle()) return;
// annoyed by attacking.
if (entity.getLastAttackTime() < 0.5) {
lookForTarget();
return;
}
if (entity.pos.isMoving()) return;
if (Calc.rand.nextInt(10) == 0) {
entity.pos.addStep(Moves.randomCardinal());
}
}
};
private PathFinder noDoorPf;
/** Prey id */
private int preyId = -1;
public MonsterAi(final Entity entity)
{
super(entity);
noDoorPf = new PathFinderProxy(entity.getPathFinder()) {
@Override
public boolean isAccessible(Coord pos)
{
final Tile t = entity.getLevel().getTile(pos);
if (t.isDoor()) return false;
return super.isAccessible(pos);
}
};
noDoorPf.setIgnoreEnd(true);
timerAttack.start();
timerFindPrey.start();
}
@Override
public void onStepFinished()
{
if (entity.isDead()) return;
if (isChasing()) {
final Entity prey = getPreyEntity();
if (!isPreyValid(prey)) {
stopChasing();
return;
}
if (!isPreyInAttackRange(prey)) {
stepTowardsPrey(prey);
}
}
}
@Override
public void onPathFinished()
{
}
@Override
public void onPathInterrupted()
{
}
@Override
public void save(IonDataBundle bundle)
{
bundle.putBundled("tscan", timerFindPrey);
bundle.putBundled("tattack", timerAttack);
bundle.put("chasing", chasing);
bundle.put("prey", preyId);
}
@Override
public void load(IonDataBundle bundle)
{
bundle.loadBundled("tscan", timerFindPrey);
bundle.loadBundled("tattack", timerAttack);
chasing = bundle.get("chasing", chasing);
preyId = bundle.get("prey", preyId);
}
@Override
public boolean isModuleSaved()
{
return true;
}
@Override
public void update(double delta)
{
if (entity.isDead()) return;
timerFindPrey.update(delta);
timerAttack.update(delta);
timerRandomWalk.update(delta);
// go after the prey
if (isChasing() && !entity.pos.isMoving()) {
final Entity prey = getPreyEntity();
@ -177,170 +177,170 @@ public class MonsterAi extends EntityModule implements EntityMoveListener {
// prey killed and cleaned from level
stopChasing();
}
if (!isPreyInAttackRange(prey)) {
stepTowardsPrey(prey);
}
}
}
public boolean isIdle()
{
return !chasing;
}
public boolean isChasing()
{
return chasing;
}
private void lookForTarget()
{
if (entity.isDead()) return;
final Entity prey = entity.getLevel().getClosestEntity(entity.pos.getVisualPos(), EntityType.PLAYER, getScanRadius());
if (prey != null) {
// check if reachable without leaving room
final List<Coord> noDoorPath = noDoorPf.findPath(entity.getCoord(), prey.getCoord());
if (noDoorPath == null) return; // cant reach, give up
if (noDoorPath == null) return; // cant reach, give up
startChasing(prey);
}
}
private Entity getPreyEntity()
{
return entity.getLevel().getEntity(preyId);
}
private boolean isPreyInAttackRange(Entity prey)
{
return prey.getCoord().dist(entity.getCoord()) <= getAttackDistance();
}
private boolean isPreyValid(Entity prey)
{
return prey != null && !prey.isDead();
}
private void startChasing(Entity prey)
{
if (entity.isDead()) return;
preyId = prey.getEntityId();
chasing = true;
entity.pos.setStepTime(getStepTime());
// follow this one prey
timerFindPrey.pause();
onStepFinished(); // go towards prey
}
private void stopChasing()
{
chasing = false;
entity.pos.setStepTime(getStepTime());
preyId = -1;
timerFindPrey.restart();
}
private List<Move> getPathToPrey(Entity prey)
{
if (!isPreyValid(prey)) return null;
return entity.getPathFinder().findPathRelative(entity.getCoord(), prey.getCoord(), false, true);
}
private void stepTowardsPrey(Entity prey)
{
if (entity.isDead()) return;
if (!isPreyValid(prey)) return;
// if close enough
if (isPreyInAttackRange(prey)) {
// attack using the timed loop
return;
}
final List<Move> preyPath = getPathToPrey(prey);
if (preyPath == null || preyPath.size() > getPreyAbandonDistance()) {
stopChasing();
return;
}
entity.pos.cancelPath();
entity.pos.addSteps(preyPath);
}
private void attackPrey(Entity prey)
{
if (entity.isDead()) return;
if (!isPreyInAttackRange(prey)) return;
prey.receiveAttack(entity, getAttackStrength());
}
protected void setAttackTime(double secs)
{
timerAttack.setDuration(secs);
}
protected void setScanTime(double secs)
{
timerFindPrey.setDuration(secs);
}
@Stub
protected double getScanRadius()
{
return isIdle() ? Calc.randInt(1, 3) : Calc.randInt(4, 8); // For override
}
@Stub
protected int getPreyAbandonDistance()
{
return Calc.randInt(5, 8); // For override
}
@Stub
protected double getAttackDistance()
{
return 1;
}
@Stub
protected int getAttackStrength()
{
return 1; // For override
}
@Stub
protected double getStepTime()
{

@ -9,20 +9,20 @@ import mightypork.utils.math.Calc;
public class EntityModuleHealth extends EntityModule {
public EntityModuleHealth(Entity entity)
{
super(entity);
}
protected int health = 1;
protected int maxHealth = 1;
private double hitCooldownTime = 0.36;
protected boolean dead = false;
private double timeSinceLastDamage = Integer.MAX_VALUE;
@Override
public void load(IonDataBundle bundle)
{
@ -30,8 +30,8 @@ public class EntityModuleHealth extends EntityModule {
maxHealth = bundle.get("max_health", maxHealth);
dead = bundle.get("dead", dead);
}
@Override
public void save(IonDataBundle bundle)
{
@ -39,86 +39,86 @@ public class EntityModuleHealth extends EntityModule {
bundle.put("max_health", maxHealth);
bundle.put("dead", dead);
}
@Override
public boolean isModuleSaved()
{
return true;
}
public int getHealth()
{
return health;
}
public void setHealth(int health)
{
this.health = Calc.clamp(health, 0, maxHealth);
if (health <= 0) {
setDead(true);
entity.onKilled();
}
}
public int getHealthMax()
{
return maxHealth;
}
public void setHealthMax(int maxHealth)
{
if (maxHealth <= 0) throw new IllegalValueException("Max health out of allowed range: " + maxHealth);
this.maxHealth = maxHealth;
}
public boolean isDead()
{
return dead;
}
public void setDead(boolean dead)
{
this.dead = dead;
}
public void receiveDamage(int attackStrength)
{
if (timeSinceLastDamage < hitCooldownTime) return;
setHealth(health - attackStrength);
timeSinceLastDamage = 0;
}
public void addHealth(int healthPoints)
{
setHealth(health + healthPoints);
}
public void fill()
{
setHealth(maxHealth);
}
@Override
public void update(double delta)
{
if (timeSinceLastDamage < 3600) timeSinceLastDamage += delta;
}
/**
* @return seconds since last attack received (can be used for rendering)
*/
@ -126,8 +126,8 @@ public class EntityModuleHealth extends EntityModule {
{
return timeSinceLastDamage;
}
/**
* Set how long after hit another hit can be received.
*

@ -16,28 +16,28 @@ import mightypork.utils.math.constraints.vect.VectConst;
public class EntityModulePosition extends EntityModule {
/** Last pos, will be freed upon finishing move */
private Coord lastPos = new Coord(0, 0);
private boolean walking = false;
private final Queue<Move> path = new LinkedList<>();
private final EntityPos entityPos = new EntityPos();
private double stepTime = 0.5;
// marks for simple renderers
public int lastXDir = 1;
public int lastYDir = 1;
private final Set<EntityMoveListener> moveListeners = new LinkedHashSet<>();
public EntityModulePosition(Entity entity)
{
super(entity);
}
@Override
public void save(IonDataBundle bundle)
{
@ -46,26 +46,26 @@ public class EntityModulePosition extends EntityModule {
bundle.putBundled("pos", entityPos);
bundle.put("step_time", stepTime);
}
@Override
public void load(IonDataBundle bundle)
{
bundle.loadSequence("path", path);
lastPos = bundle.get("lpos", lastPos);
bundle.loadBundled("pos", entityPos);
stepTime = bundle.get("step_time", stepTime);
}
@Override
public boolean isModuleSaved()
{
return true;
}
/**
* Set coord without animation
*
@ -74,15 +74,15 @@ public class EntityModulePosition extends EntityModule {
public void setCoord(Coord coord)
{
freeTile(); // release old tile
entityPos.setTo(coord);
lastPos.setTo(coord);
cancelPath(); // discard remaining steps
occupyTile();
}
/**
* Occupy current tile in level
*/
@ -92,8 +92,8 @@ public class EntityModulePosition extends EntityModule {
entity.getLevel().occupyTile(getCoord());
}
}
/**
* free current tile in level
*/
@ -103,8 +103,8 @@ public class EntityModulePosition extends EntityModule {
entity.getLevel().freeTile(getCoord());
}
}
/**
* @param delta delta time
*/
@ -112,47 +112,47 @@ public class EntityModulePosition extends EntityModule {
public void update(double delta)
{
if (entity.isDead()) return; // corpses dont walk
if (!entityPos.isFinished()) {
entityPos.update(delta);
}
if (walking && entityPos.isFinished()) {
walking = false;
for (final EntityMoveListener l : moveListeners) {
l.onStepFinished();
}
if (path.isEmpty()) {
for (final EntityMoveListener l : moveListeners) {
l.onPathFinished();
}
}
}
if (!walking && !path.isEmpty()) {
walking = true;
final Move step = path.poll();
final Coord planned = entityPos.getCoord().add(step.toCoord());
if (!entity.getLevel().isWalkable(planned)) {
cancelPath();
for (final EntityMoveListener l : moveListeners) {
l.onPathInterrupted();
}
walking = false;
} else {
// tmp for renderer
if (step.x() != 0) this.lastXDir = step.x();
if (step.y() != 0) this.lastYDir = step.y();
freeTile();
lastPos.setTo(entityPos.getCoord());
entityPos.walk(step, stepTime);
@ -160,8 +160,8 @@ public class EntityModulePosition extends EntityModule {
}
}
}
/**
* @return true if path buffer is empty
*/
@ -169,8 +169,8 @@ public class EntityModulePosition extends EntityModule {
{
return entityPos.isFinished() && path.isEmpty();
}
/**
* Add a step to path buffer
*
@ -179,11 +179,11 @@ public class EntityModulePosition extends EntityModule {
public void addStep(Move step)
{
if (path.isEmpty() && !canGoTo(step)) return;
path.add(step);
}
/**
* Discard steps in buffer
*/
@ -191,8 +191,8 @@ public class EntityModulePosition extends EntityModule {
{
path.clear();
}
/**
* Find path to
*
@ -203,14 +203,14 @@ public class EntityModulePosition extends EntityModule {
{
if (target.equals(getCoord())) return true;
final List<Move> newPath = entity.getPathFinder().findPathRelative(entityPos.getCoord(), target);
if (newPath == null) return false;
cancelPath();
addSteps(newPath);
return true;
}
/**
* Add a move listener. If already present, do nothing.
*
@ -220,8 +220,8 @@ public class EntityModulePosition extends EntityModule {
{
moveListeners.add(listener);
}
/**
* Add steps to path buffer
*
@ -231,8 +231,8 @@ public class EntityModulePosition extends EntityModule {
{
this.path.addAll(path);
}
/**
* @return coord in level
*/
@ -240,8 +240,8 @@ public class EntityModulePosition extends EntityModule {
{
return entityPos.getCoord();
}
/**
* Set step time (seconds)
*
@ -251,8 +251,8 @@ public class EntityModulePosition extends EntityModule {
{
this.stepTime = stepTime;
}
/**
* @return step progress 0..1
*/
@ -260,8 +260,8 @@ public class EntityModulePosition extends EntityModule {
{
return entityPos.getProgress();
}
/**
* @return visual pos in level; interpolated from last to new coord
*/
@ -269,29 +269,29 @@ public class EntityModulePosition extends EntityModule {
{
return entityPos.getVisualPos();
}
public boolean isMoving()
{
return walking;
}
public boolean hasPath()
{
return isMoving() || !path.isEmpty();
}
public boolean canGoTo(Move side)
{
return entity.getPathFinder().isAccessible(getCoord().add(side));
}
public Coord getLastPos()
{
return lastPos;
}
}

@ -2,22 +2,22 @@ package mightypork.rogue.world.entity.modules;
public interface EntityMoveListener {
/**
* One step of a path finished
*/
void onStepFinished();
/**
* Scheduled path finished
*/
void onPathFinished();
/**
* Path was interrupted (bumped into a wall or entity)
*/
void onPathInterrupted();
}

@ -18,141 +18,141 @@ import mightypork.utils.math.constraints.vect.VectConst;
* @author Ondřej Hruška (MightyPork)
*/
class EntityPos implements IonBundled, Updateable {
private Coord coord = new Coord(0, 0);
private final VectAnimated walkOffset = new VectAnimated(Vect.ZERO, Easing.LINEAR);
public EntityPos(Coord pos)
{
this.coord.setTo(pos);
}
public EntityPos(int x, int y)
{
this.coord.setTo(x, y);
}
public EntityPos()
{
}
public double getProgress()
{
return walkOffset.getProgress();
}
@Override
public void load(IonDataBundle in)
{
coord = in.get("pos", coord);
walkOffset.reset();
}
@Override
public void save(IonDataBundle out)
{
out.put("pos", coord);
}
public int x()
{
return coord.x;
}
public int y()
{
return coord.y;
}
public double visualX()
{
return coord.x + walkOffset.x();
}
public double visualY()
{
return coord.y + walkOffset.y();
}
public double visualXOffset()
{
return walkOffset.x();
}
public double visualYOffset()
{
return walkOffset.y();
}
public void setTo(int x, int y)
{
coord.setTo(x, y);
walkOffset.reset();
}
public void setTo(EntityPos pos)
{
setTo(pos.getCoord());
}
public void setTo(Coord c)
{
coord.setTo(c);
walkOffset.reset();
}
@Override
public String toString()
{
return "EntityPos{" + coord + "}";
}
public void walk(Move step, double secs)
{
setTo(coord.x + step.x(), coord.y + step.y());
walkOffset.setTo(-step.x(), -step.y());
walkOffset.animate(0, 0, 0, secs);
}
@Override
public void update(double delta)
{
walkOffset.update(delta);
}
public boolean isFinished()
{
return walkOffset.isFinished();
}
public Coord getCoord()
{
return coord;
}
@Override
public int hashCode()
{
@ -161,8 +161,8 @@ class EntityPos implements IonBundled, Updateable {
result = prime * result + ((coord == null) ? 0 : coord.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
@ -175,8 +175,8 @@ class EntityPos implements IonBundled, Updateable {
} else if (!coord.equals(other.coord)) return false;
return true;
}
public VectConst getVisualPos()
{
return Vect.make(walkOffset.x() + coord.x, walkOffset.y() + coord.y);

@ -24,53 +24,53 @@ import mightypork.utils.math.constraints.vect.Vect;
* @author Ondřej Hruška (MightyPork)
*/
public class EntityRendererMobLR extends EntityRenderer {
private final TxSheet sheet;
protected final Entity entity;
private final NumVar animRedVar = Num.makeVar(0);
private final Color hue = Color.rgb(Num.ONE, animRedVar, animRedVar);
public EntityRendererMobLR(Entity entity, String sheetKey)
{
this.entity = entity;
this.sheet = Res.txSheet(sheetKey);
}
@Override
public void render(MapRenderContext context)
{
final double hurtTime = entity.health.getTimeSinceLastDamage();
TxQuad q = sheet.getQuad(Calc.frag(entity.pos.getProgress()));
if (entity.pos.lastXDir == -1) q = q.flipX();
final Rect tileRect = context.getRectForTile(entity.pos.getCoord());
final double w = tileRect.width().value();
final Vect visualPos = entity.pos.getVisualPos();
final double hurtOffset = (1 - Calc.clamp(hurtTime / 0.1, 0, 1)) * (entity.isDead() ? 0.3 : 0.05);
Rect spriteRect = Rect.make(visualPos.x() * w, (visualPos.y() - hurtOffset) * w, w, w);
spriteRect = spriteRect.shrink(w * 0.05);
animRedVar.setTo(hurtTime / 0.3);
App.gfx().pushGeometry();
App.gfx().translate(spriteRect.center());
if (entity.isDead()) {
App.gfx().rotateZ(Calc.clamp(hurtTime / 0.3, 0, 1) * 90);
}
final double hw = spriteRect.width().half().value();
App.gfx().quad(Vect.ZERO.expand(hw, hw, hw, hw), q, hue.withAlpha(entity.isDead() ? 1 - Easing.CIRC_IN.get(hurtTime / entity.getDespawnDelay()) : 1));
App.gfx().popGeometry();
}

@ -5,7 +5,7 @@ import mightypork.utils.eventbus.BusEvent;
public class GameWinEvent extends BusEvent<GameWinHandler> {
@Override
protected void handleBy(GameWinHandler handler)
{

@ -2,7 +2,7 @@ package mightypork.rogue.world.events;
public interface GameWinHandler {
void onGameWon();
}

@ -2,6 +2,6 @@ package mightypork.rogue.world.events;
public interface PlayerDeathHandler {
void onPlayerKilled();
}

@ -6,13 +6,13 @@ import mightypork.utils.eventbus.BusEvent;
public class PlayerKilledEvent extends BusEvent<PlayerDeathHandler> {
@Override
protected void handleBy(PlayerDeathHandler handler)
{
// not dead, discard event.
if (!WorldProvider.get().getPlayer().isDead()) return;
handler.onPlayerKilled();
}
}

@ -8,21 +8,21 @@ import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
@NotLoggedEvent
public class PlayerStepEndEvent extends BusEvent<PlayerStepEndListener> {
private final EntityPlayer player;
public PlayerStepEndEvent(EntityPlayer player)
{
super();
this.player = player;
}
@Override
protected void handleBy(PlayerStepEndListener handler)
{
handler.onStepFinished(player);
}
}

@ -5,6 +5,6 @@ import mightypork.rogue.world.entity.impl.EntityPlayer;
public interface PlayerStepEndListener {
void onStepFinished(EntityPlayer player);
}

@ -10,11 +10,11 @@ import mightypork.utils.eventbus.BusEvent;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldAscendRequest extends BusEvent<WorldAscendRequestListener> {
@Override
protected void handleBy(WorldAscendRequestListener handler)
{
handler.onAscendRequest();
}
}

@ -2,7 +2,7 @@ package mightypork.rogue.world.events;
public interface WorldAscendRequestListener {
/**
* Player clicked up-stairs
*/

@ -5,11 +5,11 @@ import mightypork.utils.eventbus.BusEvent;
public class WorldDescendRequest extends BusEvent<WorldDescendRequestListener> {
@Override
protected void handleBy(WorldDescendRequestListener handler)
{
handler.onDescendRequest();
}
}

@ -2,7 +2,7 @@ package mightypork.rogue.world.events;
public interface WorldDescendRequestListener {
/**
* Player clicked down-stairs
*/

@ -11,22 +11,22 @@ import mightypork.utils.eventbus.BusEvent;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldPauseRequest extends BusEvent<World> {
public static enum PauseAction
{
PAUSE, RESUME, TOGGLE;
}
private final PauseAction op;
public WorldPauseRequest(PauseAction op)
{
super();
this.op = op;
}
@Override
protected void handleBy(World handler)
{
@ -34,14 +34,14 @@ public class WorldPauseRequest extends BusEvent<World> {
handler.pause();
return;
}
if (op == PauseAction.RESUME) {
handler.resume();
return;
}
// else
// toggle paused state
if (!handler.isPaused()) {
handler.pause();
@ -49,5 +49,5 @@ public class WorldPauseRequest extends BusEvent<World> {
handler.resume();
}
}
}

@ -16,19 +16,19 @@ import mightypork.utils.math.algo.Coord;
public class LevelBuilder {
public static enum BuildOrder
{
FIRST, MIDDLE, LAST
}
private class RoomEntry {
int count;
RoomBuilder room;
boolean important;
public RoomEntry(RoomBuilder room, int count, boolean important)
{
this.count = count;
@ -36,45 +36,45 @@ public class LevelBuilder {
this.important = important;
}
}
private class ItemEntry {
Item item;
boolean important;
public ItemEntry(Item item, boolean important)
{
this.item = item;
this.important = important;
}
}
private class EntityEntry {
Entity entity;
boolean important;
public EntityEntry(Entity item, boolean important)
{
this.entity = item;
this.important = important;
}
}
private final ScratchMap map;
private final Random rand;
private boolean built;
private final LinkedList<RoomEntry> roomsFirst = new LinkedList<>();
private final LinkedList<RoomEntry> roomsMiddle = new LinkedList<>();
private final LinkedList<RoomEntry> roomsLast = new LinkedList<>();
private final LinkedList<ItemEntry> items = new LinkedList<>();
private final LinkedList<EntityEntry> entities = new LinkedList<>();
/**
* make a new level builder instance.
*
@ -87,8 +87,8 @@ public class LevelBuilder {
this.rand = new Random(seed);
this.map = new ScratchMap(max_size, theme, rand);
}
/**
* Add a single room to the room buffer.
*
@ -100,8 +100,8 @@ public class LevelBuilder {
{
addRoom(room, Range.make(1, 1), order, important);
}
/**
* Add multiple rooms of the type to the room buffer.
*
@ -113,91 +113,91 @@ public class LevelBuilder {
public void addRoom(RoomBuilder room, Range count, BuildOrder order, boolean important)
{
final List<RoomEntry> list;
switch (order) {
case FIRST:
list = roomsFirst;
break;
default:
case MIDDLE:
list = roomsMiddle;
break;
case LAST:
list = roomsLast;
break;
}
list.add(new RoomEntry(room, count.randInt(rand), important));
}
private void buildRooms(LinkedList<RoomEntry> list)
{
while (!list.isEmpty()) {
Collections.shuffle(list, rand);
for (final Iterator<RoomEntry> iter = list.iterator(); iter.hasNext();) {
final RoomEntry rge = iter.next();
map.addRoom(rge.room, rge.important);
if ((--rge.count) <= 0) {
iter.remove();
}
}
}
}
private void buildCorridors() throws WorldGenError
{
map.buildCorridors();
}
private void buildEntities()
{
for (final EntityEntry entry : entities) {
final int tries = entry.important ? 200 : 50;
final boolean success = map.addEntityInMap(entry.entity, tries);
if (entry.important && !success) {
throw new WorldGenError("Could not place an important entity: " + entry.entity);
}
}
}
private void buildItems()
{
for (final ItemEntry entry : items) {
final int tries = entry.important ? 200 : 50;
final boolean success = map.addItemInMap(entry.item, tries);
if (entry.important && !success) {
throw new WorldGenError("Could not place an important item: " + entry.item);
}
}
}
private void writeToMap()
{
buildRooms(roomsFirst);
buildRooms(roomsMiddle);
buildRooms(roomsLast);
buildCorridors();
map.fixGlitches();
buildItems();
buildEntities();
}
/**
* Write to a new level instance.
*
@ -211,19 +211,19 @@ public class LevelBuilder {
throw new WorldGenError("Level already built.");
}
built = true;
writeToMap();
final Coord size = map.getNeededSize();
final Level lvl = new Level(size.x, size.y);
lvl.setWorld(world); // important for creating entities
map.writeToLevel(lvl);
return lvl;
}
/**
* Add an item to be added to the level when tiles are built.
*
@ -235,8 +235,8 @@ public class LevelBuilder {
{
items.add(new ItemEntry(item, important));
}
/**
* Add an entity to be added to the level when tiles are built.<br>
* It's EID will be assigned during writing to level.
@ -249,5 +249,5 @@ public class LevelBuilder {
{
entities.add(new EntityEntry(entity, important));
}
}

@ -10,27 +10,27 @@ import mightypork.rogue.world.tile.TileModel;
* @author Ondřej Hruška (MightyPork)
*/
public interface MapTheme {
TileModel wall();
TileModel floor();
TileModel door();
TileModel passage();
TileModel secretDoor();
TileModel entrance();
TileModel exit();
TileModel chest();
}

@ -12,6 +12,6 @@ import mightypork.utils.math.algo.Coord;
* @author Ondřej Hruška (MightyPork)
*/
public interface RoomBuilder {
RoomEntry buildRoom(ScratchMap map, MapTheme theme, Random rand, Coord center) throws WorldGenError;
}

@ -10,44 +10,44 @@ import mightypork.utils.math.algo.Coord;
* @author Ondřej Hruška (MightyPork)
*/
public class RoomEntry {
final Coord min;
final Coord max;
public RoomEntry(Coord min, Coord max)
{
super();
this.min = min;
this.max = max;
}
public boolean intersectsWith(Coord amin, Coord amax)
{
int tw = max.x - min.x;
int th = max.y - min.y;
int rw = amax.x - amin.x;
int rh = amax.y - amin.y;
if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
return false;
}
final int tx = min.x;
final int ty = min.y;
final int rx = amin.x;
final int ry = amin.y;
rw += rx;
rh += ry;
tw += tx;
th += ty;
return ((rw <= rx || rw >= tx) && (rh <= ry || rh >= ty) && (tw <= tx || tw >= rx) && (th <= ty || th >= ry));
}
@Override
public String toString()
{

@ -13,21 +13,21 @@ import mightypork.rogue.world.item.Item;
public class Rooms {
public static final RoomBuilder BASIC = new BasicRoom();
public static final RoomBuilder STORAGE = new StorageRoom();
public static final RoomBuilder DEAD_END = new DeadEndRoom();
public static final RoomBuilder ENTRANCE = new EntranceRoom();
public static final RoomBuilder EXIT = new ExitRoom();
public static final RoomBuilder BOSS = new BossRoom();
public static RoomBuilder treasure(Item item)
{
return new TreasureChestRoom(item);
}
public static RoomBuilder shrine(Item item)
{
return new ItemShrineRoom(item);

@ -13,7 +13,7 @@ import mightypork.rogue.world.level.Level;
import mightypork.rogue.world.tile.Tile;
import mightypork.rogue.world.tile.TileModel;
import mightypork.rogue.world.tile.Tiles;
import mightypork.utils.Support;
import mightypork.utils.Str;
import mightypork.utils.logging.Log;
import mightypork.utils.math.Calc;
import mightypork.utils.math.algo.Coord;
@ -29,21 +29,21 @@ import mightypork.utils.math.algo.pathfinding.PathFinder;
* @author Ondřej Hruška (MightyPork)
*/
public class ScratchMap {
private Tile[][] map;
private final int width;
private final int height;
private final List<RoomEntry> rooms = new ArrayList<>();
/** Coords to connect with corridors */
private final List<Coord> nodes = new ArrayList<>();
private final List<Coord> occupied = new ArrayList<>();
private final List<Entity> entities = new ArrayList<>();
private final PathFinder pathf = new PathFinder() {
@Override
public boolean isAccessible(Coord pos)
{
@ -52,109 +52,109 @@ public class ScratchMap {
if (t.isStairs()) return false;
return t.isPotentiallyWalkable() || (t.genData.protection != TileProtectLevel.STRONG);
}
@Override
public int getCost(Coord last, Coord pos)
{
final Tile t = getTile(pos);
switch (t.getType()) {
case NULL:
return 60;
case DOOR:
case PASSAGE:
return 10;
case STAIRS:
case FLOOR:
return 20;
case WALL:
if (t.genData.protection != TileProtectLevel.NONE) return 2000;
return 100;
default:
throw new WorldGenError("Unknown tile type: " + t.getType());
}
}
@Override
public int getMinCost()
{
return 10;
}
@Override
public Heuristic getHeuristic()
{
return PathFinder.CORNER_HEURISTIC;
}
@Override
public List<Move> getWalkSides()
{
return Moves.CARDINAL_SIDES;
}
};
{
// needed for when the path starts / ends at stairs.
pathf.setIgnoreEnd(true);
pathf.setIgnoreStart(true);
}
Coord genMin;
Coord genMax;
private final MapTheme theme;
private final Random rand;
private final Coord enterPoint = new Coord();
private final Coord exitPoint = new Coord();
private static final boolean FIX_GLITCHES = true;
public ScratchMap(int max_size, MapTheme theme, Random rand)
{
map = new Tile[max_size][max_size];
genMin = Coord.make((max_size / 2) - 1, (max_size / 2) - 1);
genMax = genMin.add(1, 1);
width = max_size;
height = max_size;
this.rand = rand;
this.theme = theme;
fill(Coord.make(0, 0), Coord.make(width - 1, height - 1), Tiles.NULL);
}
public void addRoom(RoomBuilder rb, boolean critical) throws WorldGenError
{
try {
if (rooms.size() > 0) minimizeBounds();
final Coord roomPos = Coord.make(0, 0);
int failed = 0;
int failed_total = 0;
while (true) {
final int sizeX = genMax.x - genMin.x;
final int sizeY = genMax.y - genMin.y;
roomPos.x = genMin.x + rand.nextInt(sizeX + 1);
roomPos.y = genMin.y + rand.nextInt(sizeY + 1);
switch (rand.nextInt(4)) {
case 0:
roomPos.x += (failed_total / 35);
@ -168,47 +168,47 @@ public class ScratchMap {
case 3:
roomPos.y -= (failed_total / 35);
}
final RoomEntry rd = rb.buildRoom(this, theme, rand, roomPos);
if (rd != null) {
rooms.add(rd);
genMin.x = Math.min(genMin.x, rd.min.x);
genMin.y = Math.min(genMin.y, rd.min.y);
genMax.x = Math.max(genMax.x, rd.max.x);
genMax.y = Math.max(genMax.y, rd.max.y);
clampBounds();
nodes.add(roomPos);
return;
} else {
failed++;
failed_total++;
if (failed_total > 1000) {
throw new WorldGenError("Failed to add a room.");
}
if (failed > 300) {
Log.w("Faild to build room.");
if (critical) {
// expand gen bounds
genMin.x -= 5;
genMin.y -= 5;
genMax.x += 5;
genMax.y += 5;
clampBounds();
failed = 0;
Log.f3("Trying again.");
continue;
} else {
throw new WorldGenError("Failed to add a room.");
}
@ -225,8 +225,8 @@ public class ScratchMap {
}
}
}
/**
* Clamp bounds to available area
*/
@ -237,8 +237,8 @@ public class ScratchMap {
genMax.x = Calc.clamp(genMax.x, 0, width - 1);
genMax.y = Calc.clamp(genMax.y, 0, height - 1);
}
/**
* Minimize gen bounds based on defined room bounds
*/
@ -246,124 +246,124 @@ public class ScratchMap {
{
final Coord low = Coord.make(width, height);
final Coord high = Coord.make(0, 0);
for (final RoomEntry rd : rooms) {
low.x = Math.min(low.x, rd.min.x);
low.y = Math.min(low.y, rd.min.y);
high.x = Math.max(high.x, rd.max.x);
high.y = Math.max(high.y, rd.max.y);
}
genMin.setTo(low);
genMax.setTo(high);
}
public boolean isIn(Coord pos)
{
return pos.x >= 0 && pos.x < width && pos.y >= 0 && pos.y < height;
}
public Tile getTile(Coord pos)
{
if (!isIn(pos)) {
throw new WorldGenError("Tile not in map: " + pos);
}
return map[pos.y][pos.x];
}
public boolean set(Coord pos, TileModel tm)
{
return set(pos, tm.createTile());
}
public boolean set(Coord pos, Tile tile)
{
if (!isIn(pos)) {
throw new WorldGenError("Tile not in map: " + pos);
}
map[pos.y][pos.x] = tile;
return true;
}
public boolean isClear(Coord min, Coord max)
{
if (!isIn(min) || !isIn(max)) return false;
for (final RoomEntry r : rooms) {
if (r.intersectsWith(min, max)) return false;
}
return true;
}
public void protect(Coord pos, TileProtectLevel prot)
{
protect(pos, pos, prot);
}
public void protect(Coord min, Coord max, TileProtectLevel prot)
{
if (!isIn(min) || !isIn(max)) throw new WorldGenError("Tile(s) not in map: " + min + " , " + max);
final Coord c = Coord.make(0, 0);
for (c.x = min.x; c.x <= max.x; c.x++)
for (c.y = min.y; c.y <= max.y; c.y++)
getTile(c).genData.protection = prot;
}
public void fill(Coord min, Coord max, TileModel tm)
{
if (!isIn(min) || !isIn(max)) throw new WorldGenError("Tile(s) not in map: " + min + " , " + max);
final Coord c = Coord.make(0, 0);
for (c.x = min.x; c.x <= max.x; c.x++)
for (c.y = min.y; c.y <= max.y; c.y++)
set(c, tm.createTile());
}
public void border(Coord min, Coord max, TileModel tm)
{
if (!isIn(min) || !isIn(max)) throw new WorldGenError("Tile(s) not in map: " + min + " , " + max);
final Coord c = Coord.make(0, 0);
// top
for (c.x = min.x, c.y = min.y; c.x <= max.x; c.x++)
set(c, tm.createTile());
//bottom
for (c.x = min.x, c.y = max.y; c.x <= max.x; c.x++)
set(c, tm.createTile());
//left
for (c.x = min.x, c.y = min.y + 1; c.y < max.y; c.y++)
set(c, tm.createTile());
//right
for (c.x = max.x, c.y = min.y + 1; c.y < max.y; c.y++)
set(c, tm.createTile());
}
public void buildCorridors()
{
// Log.f3("Building corridors.");
Coord start = nodes.get(0);
final Set<Coord> starts = new HashSet<>();
for (int i = 0; i < 2 + rooms.size() / 5; i++) {
if (!starts.contains(start)) {
for (int j = 0; j < nodes.size(); j++) {
@ -374,24 +374,24 @@ public class ScratchMap {
start = Calc.pick(rand, nodes);
}
}
private void buildCorridor(Coord node1, Coord node2)
{
// Log.f3("Building corridor " + node1 + " -> " + node2);
final List<Coord> steps = pathf.findPath(node1, node2);
if (steps == null) {
Log.w("Could not build corridor " + node1 + "->" + node2);
return;
}
for (final Coord c : steps) {
buildCorridorPiece(c);
}
}
private void buildCorridorPiece(Coord pos)
{
final Coord c = Coord.make(0, 0);
@ -399,17 +399,17 @@ public class ScratchMap {
for (i = -1, c.x = pos.x - 1; c.x <= pos.x + 1; c.x++, i++) {
for (j = -1, c.y = pos.y - 1; c.y <= pos.y + 1; c.y++, j++) {
if (!isIn(c)) continue;
genMin.x = Math.min(genMin.x, c.x);
genMin.y = Math.min(genMin.y, c.y);
genMax.x = Math.max(genMax.x, c.x);
genMax.y = Math.max(genMax.y, c.y);
clampBounds();
final Tile current = getTile(c);
if (!current.isNull() && (current.isPotentiallyWalkable() || current.isStairs())) continue; // floor already, let it be
if (i == 0 && j == 0) {
set(c, theme.floor());
} else {
@ -419,8 +419,8 @@ public class ScratchMap {
}
}
}
/**
* @return dimensions of the area taken by non-null tiles
*/
@ -428,141 +428,141 @@ public class ScratchMap {
{
return Coord.make(genMax.x - genMin.x + 1, genMax.y - genMin.y + 1);
}
public byte findWalls(Coord pos)
{
byte walls = 0;
for (int i = 0; i < 8; i++) {
final Coord cc = pos.add(Moves.getSide(i));
if (!isIn(cc)) continue;
if (getTile(cc).isWall()) {
walls |= Moves.getBit(i);
}
}
return walls;
}
public byte findFloors(Coord pos)
{
byte floors = 0;
for (int i = 0; i <= 7; i++) {
final Coord cc = pos.add(Moves.getSide(i));
if (!isIn(cc)) continue;
if (getTile(cc).isFloor()) {
floors |= Moves.getBit(i);
}
}
return floors;
}
public byte findDoors(Coord pos)
{
byte doors = 0;
for (int i = 0; i <= 7; i++) {
final Coord cc = pos.add(Moves.getSide(i));
if (!isIn(cc)) continue;
if (getTile(cc).isDoor()) {
doors |= Moves.getBit(i);
}
}
return doors;
}
public byte findNils(Coord pos)
{
byte nils = 0;
for (int i = 0; i <= 7; i++) {
final Coord cc = pos.add(Moves.getSide(i));
if (!isIn(cc) || getTile(cc).isNull()) {
nils |= Moves.getBit(i);
}
}
return nils;
}
/**
* Fix generator glitches and reduce size to the actual used size
*/
public void fixGlitches()
{
final Tile[][] out = new Tile[height][width];
// bounds will be adjusted by the actual tiles in the map
genMin.x = width;
genMin.y = height;
genMax.x = 0;
genMax.y = 0;
final Coord c = Coord.make(0, 0);
for (c.x = 0; c.x < width; c.x++) {
for (c.y = 0; c.y < height; c.y++) {
final Tile t = getTile(c);
final boolean isNull = t.isNull();
final boolean isDoor = !isNull && t.isDoor();
final boolean isFloor = !isNull && t.isFloor();
final boolean isWall = !isNull && t.isWall();
// bitmasks
final byte walls = findWalls(c);
final byte nils = findNils(c);
final byte floors = findFloors(c);
boolean toWall = false;
boolean toFloor = false;
boolean toNull = false;
do {
if (isWall && floors == 0) {
toNull = true;
break;
}
if (isFloor && (nils & Moves.BITS_CARDINAL) != 0) {
toWall = true; // floor with adjacent cardinal null
break;
}
if (isNull && (floors & Moves.BITS_DIAGONAL) != 0) {
toWall = true; // null with adjacent diagonal floor
break;
}
if (isDoor) {
if (Calc.countBits((byte) (floors & Moves.BITS_CARDINAL)) < 2) {
toWall = true;
break;
}
if (Calc.countBits((byte) (walls & Moves.BITS_CARDINAL)) > 2) {
toWall = true;
break;
}
if (Calc.countBits((byte) (floors & Moves.BITS_CARDINAL)) > 2) {
toFloor = true;
break;
}
if ((floors & Moves.BITS_NW_CORNER) == Moves.BITS_NW_CORNER) toWall = true;
if ((floors & Moves.BITS_NE_CORNER) == Moves.BITS_NE_CORNER) toWall = true;
if ((floors & Moves.BITS_SW_CORNER) == Moves.BITS_SW_CORNER) toWall = true;
if ((floors & Moves.BITS_SE_CORNER) == Moves.BITS_SE_CORNER) toWall = true;
}
} while (false);
if (toNull) {
out[c.y][c.x] = Tiles.NULL.createTile();
} else if (toWall) {
@ -572,21 +572,21 @@ public class ScratchMap {
} else {
out[c.y][c.x] = map[c.y][c.x];
}
if (!out[c.y][c.x].isNull()) {
genMin.x = Math.min(genMin.x, c.x);
genMin.y = Math.min(genMin.y, c.y);
genMax.x = Math.max(genMax.x, c.x);
genMax.y = Math.max(genMax.y, c.y);
}
}
}
map = out;
}
/**
* Write tiles and entities into a level
*
@ -597,23 +597,23 @@ public class ScratchMap {
if (level.getWorld() == null) {
throw new WorldGenError("Level has no world assigned."); // need for entities
}
// make sure no walkable are at edges.
final Coord c = Coord.make(0, 0);
final Coord c1 = Coord.make(0, 0);
for (c.x = genMin.x, c1.x = 0; c.x <= genMax.x; c.x++, c1.x++) {
for (c.y = genMin.y, c1.y = 0; c.y <= genMax.y; c.y++, c1.y++) {
level.setTile(c1, getTile(c));
}
}
final Coord entrance = new Coord(enterPoint.x - genMin.x, enterPoint.y - genMin.y);
level.setEnterPoint(entrance);
final Coord exit = new Coord(exitPoint.x - genMin.x, exitPoint.y - genMin.y);
level.setExitPoint(exit);
for (final Entity e : entities) {
final Coord pos = e.getCoord().add(-genMin.x, -genMin.y);
if (!level.addEntityNear(e, pos)) {
@ -621,7 +621,7 @@ public class ScratchMap {
//@formatter:off
throw new WorldGenError(
"Could not put entity into a level map: e_pos=" + pos
+ ", tile: " + Support.str(t)
+ ", tile: " + Str.val(t)
+ ", t.wa " + t.isWalkable()
+ ", t.oc " + t.isOccupied()
+ ", ent.. " + e.getVisualName());
@ -629,101 +629,101 @@ public class ScratchMap {
}
}
}
public void setEntrance(Coord pos)
{
enterPoint.setTo(pos);
}
public void setExit(Coord pos)
{
exitPoint.setTo(pos);
}
public boolean addItem(Item item, Coord pos)
{
return addItem(item, pos, true);
}
public boolean addItem(Item item, Coord pos, boolean canStack)
{
if (!isIn(pos)) return false;
final Tile t = getTile(pos);
if (!canStack && t.hasItem()) return false;
if (t.dropItem(item)) return true;
return false;
}
public boolean addItemInArea(Item item, Coord min, Coord max, int tries)
{
final Coord pos = Coord.zero();
for (int i = 0; i < tries / 2; i++) {
pos.x = Calc.randInt(rand, min.x, max.x);
pos.y = Calc.randInt(rand, min.y, max.y);
if (addItem(item, pos, false)) return true;
}
for (int i = 0; i < tries - (tries / 2); i++) {
pos.x = Calc.randInt(rand, min.x, max.x);
pos.y = Calc.randInt(rand, min.y, max.y);
if (addItem(item, pos, true)) return true;
}
return false;
}
public boolean addItemInMap(Item item, int tries)
{
return addItemInArea(item, genMin, genMax, tries);
}
public boolean addEntityInArea(Entity entity, Coord min, Coord max, int tries)
{
final Coord pos = Coord.zero();
for (int i = 0; i < tries; i++) {
pos.x = Calc.randInt(rand, min.x, max.x);
pos.y = Calc.randInt(rand, min.y, max.y);
if (!isIn(pos)) continue;
if (addEntity(entity, pos)) return true;
}
return false;
}
public boolean addEntityInMap(Entity entity, int tries)
{
return addEntityInArea(entity, genMin, genMax, tries);
}
public boolean addEntity(Entity entity, Coord pos)
{
if (!isIn(pos)) return false;
if (pos.dist(enterPoint) < 4) return false; // protected distance.
if (pos.dist(enterPoint) < 4) return false; // protected distance.
final Tile t = getTile(pos);
if (!t.isWalkable()) return false;
if (occupied.contains(pos)) return false;
occupied.add(pos.copy());
entity.setCoord(pos);
entities.add(entity);
return true;
}
}

@ -19,103 +19,103 @@ import mightypork.utils.math.Range;
public class WorldCreator {
public static Random rand = new Random();
public static World createWorld(long seed)
{
synchronized (rand) {
Log.f2("Generating a new world...");
rand.setSeed(seed);
final MapTheme theme = new ThemeBrick();
final World w = new World();
w.setSeed(seed);
final LevelBuilder levelBuilders[] = new LevelBuilder[7];
// build the level rooms
for (int floor = 1; floor <= 7; floor++) {
Log.f3("Placing rooms for level: " + floor);
final LevelBuilder lb = prepareFloor(rand.nextLong(), floor, theme, floor == 7);
levelBuilders[floor - 1] = lb;
}
Log.f3("Placing items...");
final List<ItemModel> weaponsBasic = new ArrayList<>();
weaponsBasic.add(Items.ROCK);
weaponsBasic.add(Items.BONE);
weaponsBasic.add(Items.TWIG);
final List<ItemModel> weaponsMedium = new ArrayList<>();
weaponsMedium.add(Items.CLUB);
weaponsMedium.add(Items.KNIFE);
final List<ItemModel> weaponsGood = new ArrayList<>();
weaponsGood.add(Items.AXE);
weaponsGood.add(Items.SWORD);
for (int i = 0; i < Calc.randInt(rand, 12, 20); i++) {
final Item item = Calc.pick(rand, weaponsBasic).createItemDamaged(40);
final LevelBuilder lb = levelBuilders[-1 + Calc.randInt(1, 7)];
lb.addItem(item, false);
}
for (int i = 0; i < Calc.randInt(rand, 2, 4); i++) {
final Item item = Calc.pick(rand, weaponsMedium).createItemDamaged(50);
final LevelBuilder lb = levelBuilders[-1 + Calc.randInt(3, 5)];
lb.addRoom(Rooms.treasure(item), BuildOrder.MIDDLE, true);
}
for (int i = 0; i < Calc.randInt(rand, 2, 4); i++) {
final Item item = Calc.pick(rand, weaponsGood).createItemDamaged(60);
final LevelBuilder lb = levelBuilders[-1 + Calc.randInt(4, 7)];
lb.addRoom(Rooms.treasure(item), BuildOrder.LAST, true);
}
// place random foods
final List<ItemModel> randomFood = new ArrayList<>();
randomFood.add(Items.CHEESE);
randomFood.add(Items.MEAT);
for (int level = 1; level <= 7; level++) {
final LevelBuilder lb = levelBuilders[level - 1];
final Range amount = Range.make(level / 2D, level * 2);
for (int i = 0; i < amount.randInt(rand); i++) {
lb.addItem(Calc.pick(rand, randomFood).createItem(), false);
}
}
// place monsters
Log.f3("Placing monsters...");
for (int level = 1; level <= 7; level++) {
final LevelBuilder lb = levelBuilders[level - 1];
final Range amount = Range.make(3 + (level) * 2, 3 + level * 3);
for (int i = 0; i < amount.randInt(rand); i++) {
Entity e;
if (level >= 2 && Calc.randInt(rand, 0, (int) (3.5 - (level / 2D))) == 0) {
e = Entities.RAT_BROWN.createEntity();
} else {
e = Entities.RAT_GRAY.createEntity();
}
lb.addEntity(e, false);
}
}
// compile levels
Log.f3("Building levels...");
int i = 1;
@ -124,34 +124,34 @@ public class WorldCreator {
w.addLevel(lb.build(w));
i++;
}
w.createPlayer();
Log.f2("World generation finished.");
return w;
}
}
public static LevelBuilder prepareFloor(long seed, int floor, MapTheme theme, boolean lastLevel) throws WorldGenError
{
final LevelBuilder lb = new LevelBuilder(128, theme, seed);
lb.addRoom(Rooms.ENTRANCE, BuildOrder.FIRST, true);
lb.addRoom(Rooms.BASIC, Range.make(floor / 2, 2 + floor), BuildOrder.MIDDLE, false);
lb.addRoom(Rooms.DEAD_END, Range.make(1, floor), BuildOrder.MIDDLE, false);
lb.addRoom(Rooms.STORAGE, Range.make(1, Math.ceil(floor / 3D)), BuildOrder.MIDDLE, false);
if (lastLevel) lb.addRoom(Rooms.BOSS, BuildOrder.LAST, true);
if (!lastLevel) lb.addRoom(Rooms.EXIT, BuildOrder.LAST, true);
if (floor % 2 == 0) {
final RoomBuilder heartRoom = Rooms.shrine(Items.HEART_PIECE.createItem());
lb.addRoom(heartRoom, BuildOrder.LAST, true);
}
return lb;
}
}

@ -7,28 +7,28 @@ package mightypork.rogue.world.gen;
* @author Ondřej Hruška (MightyPork)
*/
public class WorldGenError extends RuntimeException {
public WorldGenError()
{
super();
}
public WorldGenError(String message, Throwable cause)
{
super(message, cause);
}
public WorldGenError(String message)
{
super(message);
}
public WorldGenError(Throwable cause)
{
super(cause);
}
}

@ -15,7 +15,7 @@ import mightypork.utils.math.algo.Moves;
public abstract class AbstractRectRoom implements RoomBuilder {
@Override
public RoomEntry buildRoom(ScratchMap map, MapTheme theme, Random rand, Coord center)
{
@ -23,46 +23,46 @@ public abstract class AbstractRectRoom implements RoomBuilder {
final Coord innerSize = getInnerSize(rand);
final int width = 2 + innerSize.x - 1;
final int height = 2 + innerSize.y - 1;
final int wLow = (int) Math.round(width / 2D);
final int wHigh = width - wLow;
final int hLow = (int) Math.round(height / 2D);
final int hHigh = height - hLow;
final Coord min = new Coord(center.x - wLow, center.y - hLow);
final Coord max = new Coord(center.x + wHigh, center.y + hHigh);
if (!map.isClear(min.add(-1, -1), max)) return null;
map.fill(min, max, getFloor(theme));
map.border(min, max, getWall(theme));
map.protect(min, max, getWallProtectionLevel());
placeDoors(map, theme, rand, min, max);
buildExtras(map, theme, rand, min, max);
return new RoomEntry(min.add(-1, -1), max);
}
protected TileModel getWall(MapTheme theme)
{
return theme.wall();
}
protected TileModel getFloor(MapTheme theme)
{
return theme.floor();
}
protected void placeDoors(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
final int width = max.x - min.x;
final int height = max.y - min.y;
for (int i = 0, j = 0; i < getDoorCount(rand) && j < 100; j++) { // j is to prevent inf loop
final Coord door = min.copy();
switch (rand.nextInt(4)) {
@ -83,29 +83,29 @@ public abstract class AbstractRectRoom implements RoomBuilder {
door.y += 1 + rand.nextInt(height - 1);
break;
}
if ((map.findDoors(door) & Moves.BITS_CARDINAL) == 0) {
map.set(door, getDoorType(theme, rand));
i++; // increment pointer
}
}
}
@Stub
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
}
protected abstract Coord getInnerSize(Random rand);
protected abstract TileProtectLevel getWallProtectionLevel();
protected abstract TileModel getDoorType(MapTheme theme, Random rand);
protected abstract int getDoorCount(Random rand);
}

@ -10,28 +10,28 @@ import mightypork.utils.math.algo.Coord;
public class BasicRoom extends AbstractRectRoom {
@Override
protected TileModel getDoorType(MapTheme theme, Random rand)
{
return rand.nextInt(4) == 0 ? theme.passage() : theme.door();
}
@Override
protected int getDoorCount(Random rand)
{
return 1 + rand.nextInt(5);
}
@Override
protected TileProtectLevel getWallProtectionLevel()
{
return TileProtectLevel.WEAK;
}
@Override
protected Coord getInnerSize(Random rand)
{

@ -12,43 +12,43 @@ import mightypork.utils.math.algo.Coord;
public class BossRoom extends SecretRoom {
@Override
protected int getDoorCount(Random rand)
{
return 1;
}
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
final Coord bossPos = min.add(3, 3);
final Entity boss = Entities.RAT_BOSS.createEntity();
if (!map.addEntity(boss, bossPos)) {
throw new WorldGenError("Could not place boss.");
}
Entity rat;
// 4 guardian rats
rat = Entities.RAT_BROWN.createEntity();
map.addEntity(rat, min.add(1, 1));
rat = Entities.RAT_BROWN.createEntity();
map.addEntity(rat, Coord.make(max.x - 1, min.y + 1));
rat = Entities.RAT_BROWN.createEntity();
map.addEntity(rat, max.add(-1, -1));
rat = Entities.RAT_BROWN.createEntity();
map.addEntity(rat, Coord.make(min.x + 1, max.y - 1));
}
@Override
protected Coord getInnerSize(Random rand)
{

@ -10,28 +10,28 @@ import mightypork.utils.math.algo.Coord;
public class DeadEndRoom extends AbstractRectRoom {
@Override
protected Coord getInnerSize(Random rand)
{
return Coord.make(1, 1);
}
@Override
protected TileProtectLevel getWallProtectionLevel()
{
return TileProtectLevel.STRONG;
}
@Override
protected TileModel getDoorType(MapTheme theme, Random rand)
{
return theme.floor();
}
@Override
protected int getDoorCount(Random rand)
{

@ -12,21 +12,21 @@ import mightypork.utils.math.algo.Coord;
public class EntranceRoom extends AbstractRectRoom {
@Override
protected Coord getInnerSize(Random rand)
{
return Coord.make(3 + rand.nextInt(3), 3 + rand.nextInt(3));
}
@Override
protected TileProtectLevel getWallProtectionLevel()
{
return TileProtectLevel.WEAK;
}
@Override
protected TileModel getDoorType(MapTheme theme, Random rand)
{
@ -40,8 +40,8 @@ public class EntranceRoom extends AbstractRectRoom {
return theme.door();
}
}
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
@ -50,12 +50,12 @@ public class EntranceRoom extends AbstractRectRoom {
map.protect(c, c, TileProtectLevel.STRONG);
map.setEntrance(c.add(1, 0));
}
@Override
protected int getDoorCount(Random rand)
{
return Calc.randInt(rand, 1, 4);
}
}

@ -11,28 +11,28 @@ import mightypork.utils.math.algo.Coord;
public class ExitRoom extends AbstractRectRoom {
@Override
protected Coord getInnerSize(Random rand)
{
return Coord.make(3 + rand.nextInt(2), 3 + rand.nextInt(2));
}
@Override
protected TileProtectLevel getWallProtectionLevel()
{
return TileProtectLevel.NONE;
}
@Override
protected TileModel getDoorType(MapTheme theme, Random rand)
{
return null;
}
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
@ -41,12 +41,12 @@ public class ExitRoom extends AbstractRectRoom {
map.protect(c, c, TileProtectLevel.STRONG);
map.setExit(c.add(-1, 0));
}
@Override
protected int getDoorCount(Random rand)
{
return 0;
}
}

@ -12,34 +12,34 @@ import mightypork.utils.math.algo.Coord;
public class ItemShrineRoom extends SecretRoom {
private final Item item;
public ItemShrineRoom(Item item)
{
this.item = item;
}
@Override
protected int getDoorCount(Random rand)
{
return Calc.randInt(rand, 1, 4);
}
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
final Coord center = min.add(2, 2);
if (!map.addItem(item, center)) {
throw new WorldGenError("Could not place item in chest.");
}
}
@Override
protected Coord getInnerSize(Random rand)
{

@ -11,28 +11,28 @@ import mightypork.utils.math.algo.Coord;
public abstract class SecretRoom extends AbstractRectRoom {
@Override
protected TileModel getDoorType(MapTheme theme, Random rand)
{
return rand.nextInt(5) == 0 ? theme.passage() : theme.secretDoor();
}
@Override
protected int getDoorCount(Random rand)
{
return Calc.randInt(rand, 1, 3);
}
@Override
protected TileProtectLevel getWallProtectionLevel()
{
return TileProtectLevel.STRONG;
}
@Override
protected Coord getInnerSize(Random rand)
{

@ -11,44 +11,44 @@ import mightypork.utils.math.algo.Coord;
public class StorageRoom extends SecretRoom {
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
int maxStuff = Calc.randInt(rand, 3, 5);
// at least one meat or cheese.
final boolean oneMeat = rand.nextBoolean();
for (int i = 0; i < Calc.randInt(rand, oneMeat ? 1 : 0, 3); i++) {
map.addItemInArea(Items.MEAT.createItem(), min, max, 50);
if (--maxStuff == 0) return;
}
for (int i = 0; i < Calc.randInt(rand, oneMeat ? 0 : 1, 2); i++) {
map.addItemInArea(Items.CHEESE.createItem(), min, max, 50);
if (--maxStuff == 0) return;
}
for (int i = 0; i < Calc.randInt(rand, 0, 1); i++) {
map.addItemInArea(Items.ROCK.createItemDamaged(30), min, max, 50);
if (--maxStuff == 0) return;
}
for (int i = 0; i < Calc.randInt(rand, 0, 1); i++) {
map.addItemInArea(Items.SANDWICH.createItem(), min, max, 50);
if (--maxStuff == 0) return;
}
for (int i = 0; i < Calc.randInt(rand, 0, 2); i++) {
map.addItemInArea(Items.TWIG.createItemDamaged(40), min, max, 50);
if (--maxStuff == 0) return;
}
for (int i = 0; i < Calc.randInt(rand, 0, 2); i++) {
map.addItemInArea(Items.BONE.createItemDamaged(40), min, max, 50);
if (--maxStuff == 0) return;
}
}
}

@ -11,22 +11,22 @@ import mightypork.utils.math.algo.Coord;
public class TreasureChestRoom extends ItemShrineRoom {
public TreasureChestRoom(Item item)
{
super(item);
}
@Override
protected void buildExtras(ScratchMap map, MapTheme theme, Random rand, Coord min, Coord max)
{
// set tile
final Coord center = min.add(2, 2);
map.set(center, theme.chest());
map.protect(center, TileProtectLevel.STRONG);
// drop item
super.buildExtras(map, theme, rand, min, max);
}

@ -8,56 +8,56 @@ import mightypork.rogue.world.tile.Tiles;
// basic dungeon theme
public class ThemeBrick implements MapTheme {
@Override
public TileModel wall()
{
return Tiles.BRICK_WALL;
}
@Override
public TileModel floor()
{
return Tiles.BRICK_FLOOR;
}
@Override
public TileModel door()
{
return Tiles.BRICK_DOOR;
}
@Override
public TileModel passage()
{
return Tiles.BRICK_PASSAGE;
}
@Override
public TileModel secretDoor()
{
return Tiles.BRICK_HIDDEN_DOOR;
}
@Override
public TileModel entrance()
{
return Tiles.BRICK_ENTRANCE;
}
@Override
public TileModel exit()
{
return Tiles.BRICK_EXIT;
}
@Override
public TileModel chest()
{

@ -33,24 +33,24 @@ import mightypork.utils.math.timing.TimedTask;
* @author Ondřej Hruška (MightyPork)
*/
public class MapView extends InputComponent implements DelegatingClient, MouseButtonHandler, Updateable, WorldAscendRequestListener,
WorldDescendRequestListener {
WorldDescendRequestListener {
private static final double transition_time = 0.8;
protected final WorldRenderer worldRenderer;
public final PlayerControl plc;
private final Set<MapInteractionPlugin> plugins = new LinkedHashSet<>();
private final NumAnimated zoom = new NumAnimated(0, Easing.SINE_BOTH);
private boolean zoom_in = true;
private final NumAnimated descFadeAnim = new NumAnimated(0);
private final Color blackColor = RGB.BLACK.withAlpha(descFadeAnim);
private int descDir = 0;
private final TimedTask timerDesc1 = new TimedTask() {
@Override
public void run()
{
@ -63,53 +63,53 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
}
}
};
private final TimedTask timerDesc2 = new TimedTask() {
@Override
public void run()
{
WorldProvider.get().getWorld().resume();
}
};
private final Num tileSize;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Collection getChildClients()
{
return plugins;
}
@Override
public boolean doesDelegate()
{
return true;
}
public MapView()
{
this.tileSize = height().min(width()).div(9).max(32).mul(Num.make(1).sub(zoom.mul(0.5)));
this.worldRenderer = new WorldRenderer(this, tileSize);
plc = WorldProvider.get().getPlayerControl();
zoom.setDefaultDuration(0.5);
}
@Override
protected void renderComponent()
{
worldRenderer.render();
App.gfx().quad(this, blackColor);
}
/**
* Get tile coord at a screen position
*
@ -120,13 +120,13 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
{
return worldRenderer.getClickedTile(pos);
}
@Override
public void receive(MouseButtonEvent event)
{
if (!event.isOver(this)) return;
if (event.isButtonEvent()) {
for (final MapInteractionPlugin p : plugins) {
if (p.onClick(event.getPos(), event.getButton(), event.isDown())) {
@ -135,7 +135,7 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
}
}
}
if (event.isWheelEvent()) {
final int delta = event.getWheelDelta();
if (!zoom.isFinished()) return;
@ -148,8 +148,8 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
}
}
}
public void toggleMag()
{
if (zoom_in) {
@ -160,8 +160,8 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
zoom_in = true;
}
}
/**
* Add interaction plugin
*
@ -171,8 +171,8 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
{
plugins.add(plugin);
}
@Override
public void update(double delta)
{
@ -181,40 +181,40 @@ public class MapView extends InputComponent implements DelegatingClient, MouseBu
timerDesc1.update(delta);
timerDesc2.update(delta);
}
@Override
public void onAscendRequest()
{
if (descFadeAnim.isInProgress()) return;
final World w = WorldProvider.get().getWorld();
if (w.getPlayer().canAscend()) {
descDir = -1;
startDescAnim();
}
}
private void startDescAnim()
{
WorldProvider.get().getWorld().pause();
timerDesc2.stop();
timerDesc1.start(transition_time);
descFadeAnim.setTo(0);
descFadeAnim.fadeIn(transition_time);
}
@Override
public void onDescendRequest()
{
if (descFadeAnim.isInProgress()) return;
final World w = WorldProvider.get().getWorld();
if (w.getPlayer().canDescend()) {
descDir = 1;
startDescAnim();

@ -17,32 +17,32 @@ import mightypork.utils.math.constraints.vect.Vect;
public class Minimap extends InputComponent implements MouseButtonHandler {
private final RectVar bounds = Rect.makeVar();
private int unit = 0;
private final Num translucency = Num.make(0.8);
private final Color playerColor = RGB.RED;
@Override
protected void renderComponent()
{
Color.pushAlpha(translucency);
final Level lvl = WorldProvider.get().getCurrentLevel();
unit = (int) Math.min(Math.max(2, Math.ceil((height().value() / 2) / (lvl.getHeight() + 2))), 10);
final Vect plCoord = WorldProvider.get().getPlayer().getVisualPos();
final int lw = lvl.getWidth();
final int lh = lvl.getHeight();
final Vect tl = topRight().sub(unit * lw, 0);
bounds.setTo(tl, unit * lw, unit * lh);
final Coord point = new Coord(tl.xi(), tl.yi());
// FIXME do not use LWJGL directly
/*
GL11.glDisable(GL11.GL_TEXTURE_2D);
@ -78,11 +78,11 @@ public class Minimap extends InputComponent implements MouseButtonHandler {
GL11.glVertex2d(plx, ply + unit);
GL11.glEnd();*/
Color.popAlpha();
}
@Override
public void receive(MouseButtonEvent event)
{
@ -90,14 +90,14 @@ public class Minimap extends InputComponent implements MouseButtonHandler {
if (event.isUp()) {
final Vect relative = event.getPos().sub(bounds.origin());
final Coord actual = Coord.make(relative.xi() / unit, relative.yi() / unit);
final PlayerFacade player = WorldProvider.get().getPlayer();
if (player.getLevel().getTile(actual).isExplored()) {
player.navigateTo(actual);
}
}
event.consume();
}
}

@ -21,70 +21,70 @@ import mightypork.utils.math.constraints.rect.Rect;
public class WorldConsoleRenderer extends BaseComponent {
private final Num rowHeight;
private final FontRenderer fr;
private final Rect itemViewRect;
public WorldConsoleRenderer(Num rowHeight)
{
this.rowHeight = rowHeight;
this.fr = new FontRenderer(Res.font("tiny"));
final Num itmsize = height().perc(25).min(256).max(16);
this.itemViewRect = bottomRight().sub(itmsize.perc(25), itmsize.perc(25)).startRect().grow(itmsize, Num.ZERO, itmsize, Num.ZERO);
}
@Override
protected void renderComponent()
{
final double rh = rowHeight.value();
final Rect lowRow = bottomEdge().growUp(rowHeight);
final WorldConsole console = WorldProvider.get().getWorld().getConsole();
final Collection<WorldConsole.Entry> entries = console.getEntries();
int cnt = 0;
final NumVar alph = Num.makeVar();
Color.pushAlpha(alph);
try {
for (final WorldConsole.Entry entry : entries) {
alph.setTo(entry.getAlpha());
final Rect rrr = lowRow.moveY(-rh * cnt);
fr.draw(entry.getMessage(), rrr.move(rh / 8D, rh / 8D), AlignX.LEFT, RGB.BLACK_60);
fr.draw(entry.getMessage(), rrr, AlignX.LEFT, RGB.WHITE);
cnt++;
}
} catch (final ConcurrentModificationException e) {
Log.e(e); // this should not happen
}
if (console.lastPickupItem != null) {
double alpha = 1;
if (console.timeSinceLastPickup > 2) {
alpha = 1 - Easing.CIRC_OUT.get(Calc.clamp((console.timeSinceLastPickup - 2) / 1, 0, 1));
}
alph.setTo(alpha);
console.lastPickupItem.render(itemViewRect);
}
Color.popAlpha();
}
}

@ -22,26 +22,26 @@ import mightypork.utils.math.constraints.vect.Vect;
public class MIPKeyboard extends MapInteractionPlugin implements DelegatingClient, PlayerStepEndListener, Updateable {
// FIXME cannot be static.
private static final Move[] sides = { Moves.W, Moves.E, Moves.N, Moves.S };
private final KeyBindingPool kbp = new KeyBindingPool();
private final List<Object> clients = new ArrayList<>();
{
clients.add(kbp);
}
private KeyStroke[] getKeys()
{
//@formatter:off
final Config cfg = App.cfg();
return new KeyStroke[] {
cfg.getKeyStroke("game.walk.left"),
cfg.getKeyStroke("game.walk.right"),
@ -50,34 +50,34 @@ public class MIPKeyboard extends MapInteractionPlugin implements DelegatingClien
};
//@formatter:on
}
@Override
public boolean doesDelegate()
{
return true;
}
@Override
public Collection<?> getChildClients()
{
return clients;
}
public MIPKeyboard(MapView mapView)
{
super(mapView);
final KeyStroke[] keys = getKeys();
final KeyStroke[] keys = getKeys();
// bind keys
for (int i = 0; i < 4; i++) {
final int j = i;
kbp.bindKey(keys[i], Trigger.RISING, new Runnable() {
@Override
public void run()
{
@ -86,43 +86,43 @@ public class MIPKeyboard extends MapInteractionPlugin implements DelegatingClien
});
}
}
@Override
public void onStepFinished(EntityPlayer player)
{
walkByKey();
}
@Override
public boolean onClick(Vect mouse, int button, boolean down)
{
return false;
}
private void clickSide(Move side)
{
if (isImmobile() || getPlayer().isMoving()) return;
mapView.plc.clickTile(side);
}
private boolean walkByKey()
{
if (isImmobile()) return false;
if (mapView.plc.getPlayer().getMoveProgress() < 0.8) return false;
if (Keys.getActiveMods() != Keys.MOD_NONE) return false;
final KeyStroke[] keys = getKeys();
if (Keys.getActiveMods() != Keys.MOD_NONE) return false;
final KeyStroke[] keys = getKeys();
for (int i = 0; i < 4; i++) {
if (keys[i].isDown()) {
final Move side = sides[i];
if (mapView.plc.canGo(side)) {
mapView.plc.go(side);
@ -134,8 +134,8 @@ public class MIPKeyboard extends MapInteractionPlugin implements DelegatingClien
}
return false;
}
@Override
public void update(double delta)
{

@ -15,116 +15,116 @@ import mightypork.utils.math.constraints.vect.Vect;
public class MIPMouse extends MapInteractionPlugin implements PlayerStepEndListener, Updateable {
private static final int LEFT = 0; // left
private static final int RIGHT = 1; // left
public MIPMouse(MapView mapView)
{
super(mapView);
}
@Override
public void update(double delta)
{
if (isImmobile()) return;
final Vect pos = App.input().getMousePos();
if (!pos.isInside(mapView)) return;
if (App.input().isMouseButtonDown(LEFT)) {
if (mouseWalk(pos)) return;
if (mapView.plc.getPlayer().isMoving() && troToNav(pos)) return;
}
}
@Override
public boolean onClick(Vect mouse, int button, boolean down)
{
if (isImmobile()) {
return false;
}
final Vect pos = mapView.toWorldPos(mouse);
if (button == LEFT && !down) {
// try to click tile
if (mapView.plc.clickTile(pos)) return true;
}
final Tile t = mapView.plc.getLevel().getTile(Coord.fromVect(pos));
if (button == RIGHT && !down && t.isWalkable()) {
if (troToNav(mouse)) return true;
return mouseWalk(mouse);
}
return false;
}
private boolean troToNav(Vect mouse)
{
if (isImmobile()) return false;
final Coord plpos = mapView.plc.getPlayer().getCoord();
final Coord clicked = Coord.fromVect(mapView.toWorldPos(mouse));
if (clicked.equals(plpos)) return false;
final Tile t = mapView.plc.getLevel().getTile(clicked);
if (!t.isWalkable() || !t.isExplored()) return false;
mapView.plc.navigateTo(clicked);
return true;
}
private boolean mouseWalk(Vect pos)
{
if (isImmobile()) return false;
final Coord plpos = mapView.plc.getPlayer().getCoord();
final Coord clicked = Coord.fromVect(mapView.toWorldPos(pos));
if (clicked.equals(plpos)) return false;
final Polar p = Polar.fromCoord(clicked.x - plpos.x, clicked.y - plpos.y);
final int dir = Deg.roundTo90(p.getAngleDeg()) / 90;
switch (dir) {
case 0:
return mapView.plc.tryGo(Moves.E);
case 1:
return mapView.plc.tryGo(Moves.S);
case 2:
return mapView.plc.tryGo(Moves.W);
case 3:
return mapView.plc.tryGo(Moves.N);
}
return false;
}
@Override
public void onStepFinished(EntityPlayer player)
{
if (isImmobile()) return;
final Vect pos = App.input().getMousePos();
if (!pos.isInside(mapView)) return;
if (App.input().isMouseButtonDown(LEFT)) {
if (mouseWalk(pos)) return;
if (mapView.plc.getPlayer().isMoving() && troToNav(pos)) return;
}
}
}

@ -9,34 +9,34 @@ import mightypork.utils.math.constraints.vect.Vect;
public abstract class MapInteractionPlugin {
protected final MapView mapView;
public MapInteractionPlugin(MapView mapView)
{
super();
this.mapView = mapView;
}
protected PlayerFacade getPlayer()
{
return mapView.plc.getPlayer();
}
protected boolean isImmobile()
{
return getPlayer().isDead() || getWorld().isPaused();
}
protected World getWorld()
{
return WorldProvider.get().getWorld();
}
public abstract boolean onClick(Vect mouse, int button, boolean down);
}

@ -2,7 +2,7 @@ package mightypork.rogue.world.item;
import mightypork.rogue.world.PlayerFacade;
import mightypork.utils.Support;
import mightypork.utils.Str;
import mightypork.utils.annotations.Stub;
import mightypork.utils.ion.IonBundled;
import mightypork.utils.ion.IonDataBundle;
@ -11,32 +11,32 @@ import mightypork.utils.math.constraints.rect.Rect;
public abstract class Item implements IonBundled {
private final ItemModel model;
private ItemRenderer renderer;
private int amount = 1;
private int uses = 1;
public Item(ItemModel model)
{
this.model = model;
}
public final void render(Rect rect)
{
if (renderer == null) {
renderer = makeRenderer();
}
renderer.render(rect);
}
protected abstract ItemRenderer makeRenderer();
@Override
@Stub
public void save(IonDataBundle out)
@ -44,8 +44,8 @@ public abstract class Item implements IonBundled {
out.put("c", amount);
out.put("u", uses);
}
@Override
@Stub
public void load(IonDataBundle in)
@ -53,30 +53,30 @@ public abstract class Item implements IonBundled {
amount = in.get("c", amount);
uses = in.get("u", uses);
}
public final ItemModel getModel()
{
return model;
}
@Stub
protected int getMaxStackSize()
{
return isStackable() ? 65535 : 1;
}
public boolean canStackWith(Item other)
{
return (getModel().id == other.getModel().id);
}
protected abstract boolean isStackable();
/**
* Add another item to this item
*
@ -89,18 +89,18 @@ public abstract class Item implements IonBundled {
{
if (!canStackWith(added)) return false;
if (added.isEmpty()) return true;
final int room = getMaxStackSize() - this.amount;
final int avail = added.amount;
final int moved = Math.min(room, avail);
this.amount += moved;
added.amount -= moved;
return added.isEmpty();
}
/**
* @return item amount in the stack
*/
@ -108,8 +108,8 @@ public abstract class Item implements IonBundled {
{
return Math.max(0, amount);
}
/**
* Consume one item.
*
@ -118,76 +118,76 @@ public abstract class Item implements IonBundled {
public void consume()
{
if (isEmpty()) throw new RuntimeException("Item is empty, cannot consume.");
amount--;
}
public boolean isEmpty()
{
return getAmount() == 0;
}
public Item split(int removed)
{
if (isEmpty()) throw new RuntimeException("Item is empty, cannot split.");
final int realRemoved = Math.min(removed, amount);
final Item newItm = model.createItem();
newItm.uses = uses;
newItm.amount = realRemoved;
this.amount -= realRemoved;
return newItm;
}
public abstract int getAttackPoints();
public abstract int getFoodPoints();
public abstract ItemType getType();
@Override
public String toString()
{
return Support.str(getClass()) + " x " + getAmount();
return Str.val(getClass()) + " x " + getAmount();
}
public int getRemainingUses()
{
return uses;
}
public abstract int getMaxUses();
public void setRemainingUses(int uses)
{
this.uses = Calc.clamp(uses, 0, getMaxUses());
}
public void use()
{
if (uses > 0) uses--;
if (uses == 0) consume();
}
public abstract boolean isDamageable();
public abstract String getVisualName();
@Stub
public boolean pickUp(PlayerFacade pl)
{

@ -13,20 +13,20 @@ import mightypork.utils.math.Calc;
* @author Ondřej Hruška (MightyPork)
*/
public final class ItemModel {
/** Model ID */
public final int id;
public final Class<? extends Item> itemClass;
public ItemModel(int id, Class<? extends Item> item)
{
Items.register(id, this);
this.id = id;
this.itemClass = item;
}
/**
* @return new item instance of this type
*/
@ -34,32 +34,32 @@ public final class ItemModel {
{
try {
final Item itm = itemClass.getConstructor(ItemModel.class).newInstance(this);
itm.setRemainingUses(itm.getMaxUses());
return itm;
} catch (final Exception e) {
throw new RuntimeException("Could not instantiate an item.", e);
}
}
public Item loadItem(IonDataBundle in) throws IOException
{
final Item t = createItem();
t.load(in);
return t;
}
public void saveItem(IonDataBundle out, Item item) throws IOException
{
if (itemClass != item.getClass()) throw new RuntimeException("Item class mismatch.");
item.save(out);
}
public Item createItemDamaged(int minimalHealthPercent)
{
final Item item = createItem();

@ -5,16 +5,16 @@ import mightypork.utils.math.constraints.rect.Rect;
public abstract class ItemRenderer {
protected final Item item;
public ItemRenderer(Item item)
{
this.item = item;
}
public abstract void render(Rect r);
}

@ -26,9 +26,9 @@ import mightypork.utils.ion.IonOutput;
* @author Ondřej Hruška (MightyPork)
*/
public final class Items {
private static final ItemModel[] items = new ItemModel[256];
public static final ItemModel MEAT = new ItemModel(1, ItemMeat.class);
public static final ItemModel CHEESE = new ItemModel(2, ItemCheese.class);
public static final ItemModel BONE = new ItemModel(3, ItemBone.class);
@ -40,54 +40,54 @@ public final class Items {
public static final ItemModel HEART_PIECE = new ItemModel(9, ItemHeartPiece.class);
public static final ItemModel TWIG = new ItemModel(10, ItemTwig.class);
public static final ItemModel KNIFE = new ItemModel(11, ItemKnife.class);
public static void register(int id, ItemModel model)
{
if (id < 0 || id >= items.length) {
throw new IllegalArgumentException("Item ID " + id + " is out of range.");
}
if (items[id] != null) {
throw new IllegalArgumentException("Item ID " + id + " already in use.");
}
items[id] = model;
}
public static ItemModel get(int id)
{
final ItemModel m = items[id];
if (m == null) {
throw new IllegalArgumentException("No item with ID " + id + ".");
}
return m;
}
public static Item loadItem(IonInput in) throws IOException
{
final int id = in.readIntByte();
final ItemModel model = get(id);
return model.loadItem(in.readBundle());
}
public static void saveItem(IonOutput out, Item item) throws IOException
{
final ItemModel model = item.getModel();
out.writeIntByte(model.id);
final IonDataBundle ib = new IonDataBundle();
model.saveItem(ib, item);
out.writeBundle(ib);
}
public static void loadItems(IonInput in, Collection<Item> items) throws IOException
{
items.clear();
@ -95,16 +95,16 @@ public final class Items {
items.add(loadItem(in));
}
}
public static void saveItems(IonOutput out, Collection<Item> items) throws IOException
{
for (final Item entity : items) {
out.startEntry();
saveItem(out, entity);
}
out.endSequence();
}
}

@ -7,41 +7,41 @@ import mightypork.rogue.world.item.ItemType;
public abstract class ItemBaseFood extends Item {
public ItemBaseFood(ItemModel model)
{
super(model);
}
@Override
public boolean isStackable()
{
return true;
}
@Override
public final int getAttackPoints()
{
return 0;
}
@Override
public final ItemType getType()
{
return ItemType.FOOD;
}
@Override
public boolean isDamageable()
{
return false;
}
@Override
public int getMaxUses()
{

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save