Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

136
Java/Microcraft/.gitignore vendored Normal file
View File

@@ -0,0 +1,136 @@
# CUSTOM
.DS_Store
*.dmg
# Created by https://www.toptal.com/developers/gitignore/api/intellij,java
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# End of https://www.toptal.com/developers/gitignore/api/intellij,java

3
Java/Microcraft/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="Minicraft:jar">
<output-path>$PROJECT_DIR$/out/artifacts/Minicraft_jar</output-path>
<root id="archive" name="Minicraft.jar">
<element id="module-output" name="Microcraft" />
</root>
</artifact>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

7
Java/Microcraft/.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

6
Java/Microcraft/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14 (2)" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
Java/Microcraft/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Microcraft.iml" filepath="$PROJECT_DIR$/Microcraft.iml" />
</modules>
</component>
</project>

7
Java/Microcraft/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

21
Java/Microcraft/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 jdah
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

26
Java/Microcraft/README.md Normal file
View File

@@ -0,0 +1,26 @@
# MICROCRAFT
A Minicraft remake with some neat extras. Beat the fearsome **AIR WIZARD** to win the game!
![screenshot](screen.png)
Some of the neat extras:
- Mithril (new material)
- Iron/Gold/Mithril armor
- Fancy lighting system
- Skeleton archers
- Blue and red slimes
- Cool new air wizard powers
- Character customization
- and much more...
## Running
See [Releases](https://github.com/jdah/microcraft/releases)
See below to build and run for yourself or try to run it out of any decent Java IDE.
*Please note* that in order to run the .jar file directly you will need at least the Java 14 runtime installed.
## Building
- Mac: `$ ./package.sh` creates `Microcraft-1.0.dmg` which contains the application MICROCRAFT.app.
- Windows: `package.bat` creates `MICROCRAFT-1.0.exe` file which installs MICROCRAFT.exe to `C:\Program Files\MICROCRAFT`.
- With any OS and a valid JDK 14+ install, both `package.{sh, bat}` create `out\artifacts\Microcraft_jar\Microcraft.jar` which can be run on its own with `java -jar out\artifacts\Microcraft_jar\Microcraft.jar`

53
Java/Microcraft/TODO.txt Normal file
View File

@@ -0,0 +1,53 @@
[X] Rock tools
[X] Place sand on dirt
[X] Grass growing
[X] Unused farmland reverts to dirt
[X] Crafting bench recipe
[X] Anvil recipe
[X] Furnace recipe
[X] Oven recipe
[X] Coal item
[X] Chest recipe
[X] Stone drops coal
[X] Furnace recipes require coal
[X] Ingot recipes
[X] Oven recipes require wood
[X] Glass
[X] Pie
[X] Stairs
[X] Multiple level depths
[X] Iron ore
[X] Gold ore
[X] Gem tile
[X] Gem item
[X] Mithril ore
[X] Mithril ingot
[X] Mithril tools
[X] Hard rock
[X] Basalt
[X] Place rock for cobblestone
[X] Slime item
[X] Slime mobs with color/level variation
[X] Zombie mobs with color/level variation
[X] Skeleton archer mobs
[X] Lantern
[X] Hard rock
[X] Level gen negative level 1, 2, 3
[X] Level gen positive level(s?)
[X] Lava in worldgen
[X] Iron armor
[X] Gold armor
[X] Mithril armor
[X] Score
[X] Death screen
[X] Win screen
[X] Main menu
[X] Fix item drop bug (non-stackable items, drop one being held)
[X] Item instances should have unique IDs
[X] Keep a stack of last equipped items, when currently equipped item is
removed pop ID off the stack instead of defaulting to POW GLOVE
[X] Furnace/oven smoke particles
[X] Cacti destroy items
[X] Click to focus
[X] Bossfight
[X] Projectiles should not spawn in front of firing entities

View File

@@ -0,0 +1,7 @@
mkdir out\production\Microcraft
mkdir out\artifacts\Microcraft_jar
javac -sourcepath src src\com\jdh\microcraft\Main.java -d out\production\Microcraft
mkdir out\artifacts\Microcraft\res
xcopy /s /e res out\production\Microcraft\res
jar -cmvf src\META-INF\MANIFEST.MF out\artifacts\Microcraft_jar\Microcraft.jar -C out\production\Microcraft\ .
jpackage --name MICROCRAFT --input out\artifacts\Microcraft_jar\ --main-jar Microcraft.jar --main-class com.jdh.microcraft.Main --resource-dir res --icon res\icon.ico

View File

@@ -0,0 +1,50 @@
do_build=true
do_icon=true
while getopts 'bi' flag; do
case "${flag}" in
b) do_build=false ;;
i) do_icon=false ;;
*) exit 1 ;;
esac
done
# make icon set
if [ "$do_icon" = true ] ; then
# MAC ICON
ICONSET=res/icon.iconset
ICON=res/icon.png
mkdir $ICONSET
sips -z 16 16 $ICON --out $ICONSET/icon_16x16.png
sips -z 32 32 $ICON --out $ICONSET/icon_16x16@2x.png
sips -z 32 32 $ICON --out $ICONSET/icon_32x32.png
sips -z 64 64 $ICON --out $ICONSET/icon_32x32@2x.png
sips -z 128 128 $ICON --out $ICONSET/icon_128x128.png
sips -z 256 256 $ICON --out $ICONSET/icon_128x128@2x.png
sips -z 256 256 $ICON --out $ICONSET/icon_256x256.png
sips -z 512 512 $ICON --out $ICONSET/icon_256x256@2x.png
sips -z 512 512 $ICON --out $ICONSET/icon_512x512.png
sips -z 1024 1024 $ICON --out $ICONSET/icon_512x512@32x.png
iconutil -c icns $ICONSET
# WINDOWS ICON
ICON_WIN=res/icon.ico
convert $ICONSET/icon_16x16.png\
$ICONSET/icon_32x32.png\
$ICONSET/icon_32x32@2x.png\
$ICONSET/icon_128x128.png\
$ICONSET/icon_256x256.png\
$ICON_WIN
rm -R $ICONSET
fi
if [ "$do_build" = true ] ; then
mkdir -p out/production/Microcraft
mkdir -p out/artifacts/Microcraft_jar
$JAVA_HOME/bin/javac -sourcepath src src/com/jdh/microcraft/Main.java -d out/production/Microcraft
cp -r res/ out/production/Microcraft/
$JAVA_HOME/bin/jar -cmvf src/META-INF/MANIFEST.MF out/artifacts/Microcraft_jar/Microcraft.jar -C out/production/Microcraft/ .
$JAVA_HOME/bin/jpackage --name MICROCRAFT --input out/artifacts/Microcraft_jar/\
--main-jar Microcraft.jar --main-class com.jdh.microcraft.Main --resource-dir res --icon res/icon.icns
fi

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
Java/Microcraft/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.jdh.microcraft.Main

View File

@@ -0,0 +1,221 @@
package com.jdh.microcraft;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.SpawnProperties;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.gui.*;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.gen.LevelGenerator;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.ControlHandler;
import com.jdh.microcraft.util.Time;
import com.jdh.microcraft.util.Window;
public class GameState implements State {
private static final int LEVEL_DEPTH_OFFSET = LevelGenerator.UNDERWORLD_LEVELS;
// all world levels
private final Level[] levels = new Level[LevelGenerator.OVERWORLD_LEVELS + LevelGenerator.UNDERWORLD_LEVELS];
// current level depth, ranges from [-UNDERWORLD_LEVELS, OVERWORLD_LEVELS)
private int levelDepth;
// global, must be kept on game state to keep unique across levels
private int nextEntityId, nextItemInstanceId;
// overall game difficulty multiplier
public double difficulty;
public int score;
public long playerDiedTicks;
public HUD hud;
public Menu menu;
private final int playerColor;
public GameState(double difficulty, int playerColor) {
this.difficulty = difficulty;
this.playerColor = playerColor;
this.nextEntityId = 1;
this.nextItemInstanceId = 1;
this.score = 0;
this.playerDiedTicks = 0;
}
public void init() {
// generate levels
for (int i = 0; i < levels.length; i++) {
this.levels[i] = new Level(Global.ticks + Time.now() + i, i - LEVEL_DEPTH_OFFSET, 256, 256);
LevelGenerator.getGenerator(this.levels[i]).generate();
}
// generate stairs
Global.mainMenu.loadingMenu.setProgress(
Font.Colors.YELLOW + "PLACING STAIRS...", 0.0);
for (int i = 0; i < levels.length - 1; i++) {
LevelGenerator.getStairsGenerator(this.levels[i], this.levels[i + 1]).generate();
Global.mainMenu.loadingMenu.setProgress(i / (double) levels.length);
}
// spawn entities
Global.mainMenu.loadingMenu.setProgress(
Font.Colors.YELLOW + "SPAWNING...", 0.0);
for (int i = 0; i < levels.length; i++) {
Level level = this.levels[i];
level.addEntitySpawns(SpawnProperties.getSpawnProperties(level));
level.populate();
if (level.depth == 1) {
EntityAirWizard wizard = new EntityAirWizard(level);
level.spawnOnTile(wizard, Tile.CLOUD.id, level.width / 2, (level.height / 2) - 6);
}
Global.mainMenu.loadingMenu.setProgress(i / (double) levels.length);
}
// spawn player in overworld
Level level = this.getLevel(0);
this.setLevel(level.depth);
EntityPlayer player = new EntityPlayer(level, playerColor);
this.hud = new HUD(player);
player.x = Level.toPixel(level.width / 2);
player.y = Level.toPixel((level.height / 2) + 1);
player.updateCamera();
level.addEntity(player);
this.setMenu(new WelcomeMenu());
}
@Override
public void tick() {
if (!Window.hasFocus()) {
if (!(this.menu instanceof FocusMenu)) {
this.setMenu(new FocusMenu(this.menu));
}
return;
}
// death screen
if (!(this.menu instanceof LoseMenu) &&
this.playerDiedTicks != 0 &&
(this.playerDiedTicks + 120) <= Global.ticks) {
Sound.LOSE.play();
this.setMenu(new LoseMenu());
}
this.hud.tick();
if (this.menu != null) {
Menu oldMenu = this.menu;
this.menu.tick();
// interact pressed + nothing closed = close the current menu
if (oldMenu == this.menu &&
ControlHandler.INTERACT.pressedTick()) {
this.setMenu(null);
}
} else {
this.getCurrentLevel().tick();
// don't allow toggles on ticks where a new menu was opened
if (this.menu != null) {
return;
}
}
// menu controls
if (ControlHandler.MENU_QUIT.pressedTick()) {
if (this.menu == null) {
this.setMenu(new PauseMenu());
} else {
// quit the current menu
Global.game.setMenu(null);
}
}
}
@Override
public void update() {
if (this.menu != null) {
this.menu.update();
} else {
this.getCurrentLevel().update();
}
this.hud.update();
}
@Override
public void render() {
Level level = this.getCurrentLevel();
level.render();
Renderer.clearLights();
if (level.depth < 0) {
Renderer.addLights(this.getCurrentLevel().getLights(Renderer.getAABB()));
Renderer.light(-111);
}
Renderer.pushCamera();
Renderer.camera.tx = 0;
Renderer.camera.ty = 0;
this.hud.render();
if (this.menu != null) {
this.menu.render();
}
Renderer.popCamera();
}
public void setMenu(Menu menu) {
if (this.menu != null) {
this.menu.destroy();
}
if (menu != null) {
menu.init();
}
this.menu = menu;
}
public Menu getMenu() {
return this.menu;
}
public Level getCurrentLevel() {
return this.levels[this.levelDepth + LEVEL_DEPTH_OFFSET];
}
public Level getLevel(int depth) {
assert (this.isDepthValid(depth));
return this.levels[depth + LEVEL_DEPTH_OFFSET];
}
public void setLevel(int depth) {
assert (this.isDepthValid(depth));
this.levelDepth = depth;
}
public boolean isDepthValid(int depth) {
return depth >= -LEVEL_DEPTH_OFFSET && depth < LevelGenerator.OVERWORLD_LEVELS;
}
public int getNextEntityId() {
return this.nextEntityId++;
}
public int getNextItemInstanceId() {
return this.nextItemInstanceId++;
}
}

View File

@@ -0,0 +1,60 @@
package com.jdh.microcraft;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.gui.mainmenu.MainMenu;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.Time;
import com.jdh.microcraft.util.Window;
import java.util.Random;
// Encapsulates (statically) all game state
public class Global {
public enum StateType {
MENU, GAME
}
public static StateType currentStateType;
public static GameState game;
public static MainMenuState mainMenu;
public static State currentState;
public static Random random = new Random(Time.now());
public static long ticks = 0, frames = 0;
public static void setState(StateType state) {
Global.currentStateType = state;
Renderer.reset();
switch (state) {
case GAME -> {
Sound.START.play();
Global.game = new GameState(
Global.mainMenu.difficultySelectMenu.getSelectedDifficulty(),
Color.get(
Global.mainMenu.colorSelectMenu.colors
));
Global.mainMenu.menu = Global.mainMenu.loadingMenu;
Global.mainMenu.loadingMenu.setProgress("LOADING", 0.0);
Global.game.init();
Global.currentState = Global.game;
}
case MENU -> {
Global.game = null;
Global.mainMenu.mainMenu = new MainMenu();
Global.mainMenu.menu = Global.mainMenu.mainMenu;
Global.currentState = Global.mainMenu;
}
}
}
public static void setLoadingInfo(String text, double progress) {
Global.mainMenu.loadingMenu.progress = progress;
Global.mainMenu.loadingMenu.text = text;
Renderer.clear();
Global.mainMenu.loadingMenu.render();
Window.renderFrame();
}
}

View File

@@ -0,0 +1,92 @@
package com.jdh.microcraft;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.util.Keyboard;
import com.jdh.microcraft.util.Window;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Optional;
public class Main implements Runnable {
public static void main(String[] args) {
System.out.println("CWD is " + Paths.get("").toAbsolutePath().toString());
// initialize FS
Optional<URI> testURI = Optional.ofNullable(Main.class.getResource("/tiles.png")).map(url -> {
try {
return url.toURI();
} catch (URISyntaxException e) {
throw new Error(e);
}
});
if (testURI.isPresent() && testURI.get().getScheme().equals("jar")) {
for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase("jar")) {
try {
provider.getFileSystem(testURI.get());
} catch (FileSystemNotFoundException e) {
try {
provider.newFileSystem(testURI.get(), Map.of());
} catch (IOException ioe) {
throw new Error(ioe);
}
}
}
}
}
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Window.init("MICROCRAFT", screen.width / 2, screen.height / 2);
new Thread(new Main()).start();
}
@Override
public void run() {
try {
Window.loop(
this::init,
this::destroy,
this::tick,
this::update,
this::render
);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
private void init() {
Global.mainMenu = new MainMenuState();
Global.currentState = Global.mainMenu;
}
private void destroy() {
}
private void tick() {
Global.ticks++;
Keyboard.tick();
Global.currentState.tick();
}
private void update() {
Keyboard.update();
Global.currentState.update();
}
private void render() {
Global.frames++;
Renderer.clear();
Global.currentState.render();
}
}

View File

@@ -0,0 +1,45 @@
package com.jdh.microcraft;
import com.jdh.microcraft.gui.ColorSelectMenu;
import com.jdh.microcraft.gui.DifficultySelectMenu;
import com.jdh.microcraft.gui.Menu;
import com.jdh.microcraft.gui.mainmenu.AboutMenu;
import com.jdh.microcraft.gui.mainmenu.HowToPlayMenu;
import com.jdh.microcraft.gui.mainmenu.LoadingMenu;
import com.jdh.microcraft.gui.mainmenu.MainMenu;
public class MainMenuState implements State {
public Menu menu;
public MainMenu mainMenu;
public LoadingMenu loadingMenu;
public HowToPlayMenu howToPlayMenu;
public AboutMenu aboutMenu;
public ColorSelectMenu colorSelectMenu;
public DifficultySelectMenu difficultySelectMenu;
public MainMenuState() {
this.mainMenu = new MainMenu();
this.loadingMenu = new LoadingMenu();
this.howToPlayMenu = new HowToPlayMenu();
this.aboutMenu = new AboutMenu();
this.colorSelectMenu = new ColorSelectMenu();
this.difficultySelectMenu = new DifficultySelectMenu();
this.menu = this.mainMenu;
}
@Override
public void tick() {
this.menu.tick();
}
@Override
public void update() {
this.menu.update();
}
@Override
public void render() {
this.menu.render();
}
}

View File

@@ -0,0 +1,7 @@
package com.jdh.microcraft;
public interface State {
void tick();
void update();
void render();
}

View File

@@ -0,0 +1,235 @@
package com.jdh.microcraft.entity;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.ai.AI;
import com.jdh.microcraft.gfx.Light;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.LevelEntityMetadata;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.level.tile.TileLiquid;
import com.jdh.microcraft.level.tile.TileStair;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
import java.util.List;
public class Entity {
// current location
public int x, y;
// size
public int width, height;
// level this entity belongs to
public Level level;
// set to true on ticks where entity moves
public boolean moving;
// set to true if the entity is in a liquid
public boolean swimming;
// true if the entity is currently on stairs (onStairs) or has entered stairs this tick (onStairsTick)
public boolean onStairs;
// metadata about this entity, tracked by the current level
public final LevelEntityMetadata metadata;
// current tile locations, managed by Level
// location of CENTER of entity
public int tileX = -1, tileY = -1;
// unique entity id
public final int id;
// entity AI, optional
public AI ai;
// current direction and direction last tick
protected Direction direction = Direction.DOWN;
private Direction lastDirection;
// positions at the LAST tick
private int lastX, lastY;
// knockback velocity
private double kx, ky;
public Entity(Level level) {
this.level = level;
this.id = Global.game.getNextEntityId();
this.width = 6;
this.height = 6;
this.metadata = new LevelEntityMetadata(this);
}
public Direction getDirection() {
return this.direction;
}
public Light getLight() {
return null;
}
public void remove() {
this.metadata.remove = true;
}
// schedules this entity to be moved to another level on the next tick
public void moveLevel(int depth, int x, int y) {
this.metadata.moveLevel(depth, x, y);
}
// called when this entity moves between levels
public void onDepthChange(int prevDepth) {
}
public void knockback(double strength, Direction d) {
this.knockback(strength, d.x, d.y);
}
public void knockback(double strength, int x, int y) {
this.kx = Math.signum(x) * strength;
this.ky = Math.signum(y) * strength;
}
public int getRenderOffsetX() {
return 0;
}
public int getRenderOffsetY() {
return 0;
}
protected void onDirectionChange() {
}
public void tick() {
this.moving = this.lastX != this.x || this.lastY != this.y;
int dx = this.x - this.lastX, dy = this.y - this.lastY;
if (dx != 0 || dy != 0) {
this.direction = Direction.get(dx, dy);
}
if (this.direction != this.lastDirection) {
this.onDirectionChange();
}
this.lastDirection = this.direction;
this.lastX = this.x;
this.lastY = this.y;
this.swimming = Tile.TILES[this.level.getTile(this.tileX, this.tileY)] instanceof TileLiquid;
this.onStairs = Tile.TILES[this.level.getTile(this.tileX, this.tileY)] instanceof TileStair;
// apply knockback
this.move(
FMath.tickedDoubleToInt(Global.ticks, this.kx),
FMath.tickedDoubleToInt(Global.ticks, this.ky)
);
this.kx *= 0.85;
this.ky *= 0.85;
if (this.ai != null) {
this.ai.tick();
}
}
public void update() {
if (this.ai != null) {
this.ai.update();
}
}
public void render() {
}
public boolean collides(Entity e) {
return true;
}
public void collide(Entity e) {
}
// called if THIS entity hits E
public boolean hit(Entity e) {
return false;
}
// called when E hits THIS entity
public void onHit(Entity e) {
}
public boolean interact(Entity e) {
return false;
}
public void move(int dx, int dy) {
this.moveAxis(dx, 0);
this.moveAxis(0, dy);
}
// moves along a single axis
protected boolean moveAxis(int dx, int dy) {
assert(dx == 0 || dy == 0);
int ox = this.x, oy = this.y;
this.x += dx;
this.y += dy;
// deny any movement that would move this entity off of the map
if (this.x < 0 || this.y < 0 ||
(this.x + this.width) >= Level.toPixel(this.level.width) ||
(this.y + this.height >= Level.toPixel(this.level.height))) {
this.x = ox;
this.y = oy;
return false;
}
List<Entity> entities = level.getEntityCollisions(this);
for (Entity e : entities) {
if (e != this && this.collides(e) && e.collides(this)) {
this.collide(e);
e.collide(this);
// deny movement if it is TOWARDS the colliding entity
if ((dx != 0 && FMath.sameSign(dx, e.x - this.x)) ||
(dy != 0 && FMath.sameSign(dy, e.y - this.y))) {
this.x = ox;
this.y = oy;
return false;
}
}
}
for (int[] p : level.getTileCollisions(this)) {
Tile t = Tile.TILES[level.getTile(p[0], p[1])];
t.bump(level, p[0], p[1], this);
if (t.collides(level, p[0], p[1], this)) {
this.x = ox;
this.y = oy;
return false;
}
}
return true;
}
public boolean canSwimIn(Tile tile, int x, int y) {
return false;
}
public int getCenterX() {
return this.x + (this.width / 2);
}
public int getCenterY() {
return this.y + (this.height / 2);
}
}

View File

@@ -0,0 +1,115 @@
package com.jdh.microcraft.entity;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.entity.projectile.EntityProjectile;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.Time;
public class EntityItem extends Entity {
public final ItemStack stack;
public int timeToLive, timeToPickup;
private int z;
private double vx, vy, vz, dx, dy;
public EntityItem(Level level, ItemStack stack, int x, int y, double vx, double vy, double vz) {
super(level);
this.stack = stack;
this.x = x;
this.y = y;
this.z = 4;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.dx = 0;
this.dy = 0;
this.timeToLive = Time.TPS * (180 + Global.random.nextInt(60));
this.timeToPickup = 20;
this.width = 8;
this.height = 8;
}
@Override
public boolean collides(Entity e) {
return e instanceof EntityMob ?
((EntityMob) e).canPickup(this) && this.timeToPickup == 0 :
!(e instanceof EntityItem);
}
public static EntityItem spawn(Level level, ItemStack stack, int x, int y, Direction d) {
Global.random.setSeed(stack.hashCode() + (x * 31) ^ (y * 17));
EntityItem result = new EntityItem(
level, stack, x, y,
d.x * (0.5 + Global.random.nextDouble() * 0.1),
d.y * (0.5 + Global.random.nextDouble() * 0.1),
1.0 + (Global.random.nextDouble() * 0.2)
);
level.addEntity(result);
return result;
}
public static EntityItem spawn(Level level, ItemStack stack, int x, int y) {
Global.random.setSeed(stack.hashCode() + (x * 31) ^ (y * 17));
EntityItem result = new EntityItem(
level, stack, x, y,
(Global.random.nextBoolean() ? -1.0 : 1.0) * (0.3 + Global.random.nextDouble() * 0.1),
(Global.random.nextBoolean() ? -1.0 : 1.0) * (0.3 + Global.random.nextDouble() * 0.1),
1.0 + (Global.random.nextDouble() * 0.2)
);
level.addEntity(result);
return result;
}
public boolean canPickup(Entity e) {
return this.timeToPickup == 0;
}
@Override
public void tick() {
super.tick();
if (--this.timeToLive == 0) {
this.remove();
}
this.timeToPickup = Math.max(this.timeToPickup - 1, 0);
this.dx += this.vx;
this.dy += this.vy;
while (Math.abs(this.dx) >= 1.0) {
if (!this.moveAxis((int) Math.signum(this.dx), 0)) {
this.vx *= -0.8;
}
this.dx += -Math.signum(this.dx);
}
while (Math.abs(this.dy) >= 1.0) {
if (!this.moveAxis(0, (int) Math.signum(this.dy))) {
this.vy *= -0.8;
}
this.dy += -Math.signum(this.dy);
}
this.z += this.vz;
// bounce
if (this.z < 0) {
this.z = 0;
this.vx *= 0.4;
this.vy *= 0.4;
this.vz *= -0.6;
}
// gravity
this.vz -= 0.2;
}
@Override
public void render() {
this.stack.instance.item.render(this.stack.instance, this.level, this.x, this.y);
}
}

View File

@@ -0,0 +1,289 @@
package com.jdh.microcraft.entity;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntityHumanoid;
import com.jdh.microcraft.entity.particle.EntitySmashParticle;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Light;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.gui.PlayerInventoryMenu;
import com.jdh.microcraft.gui.TransitionMenu;
import com.jdh.microcraft.gui.crafting.InventoryCraftingMenu;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.consumable.ItemConsumable;
import com.jdh.microcraft.item.tool.ItemTool;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.ControlHandler;
import com.jdh.microcraft.util.Direction;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class EntityPlayer extends EntityHumanoid {
// maintains a list of the previous N equipped items, used so when
// this.equipped is null we can go back to the last existing item from this
// list in the player's current inventory. fx.:
// - player has sword equipped
// - player picks up lantern, lantern is equipped
// - player puts down lantern
// - sword should be re-equipped
private final List<Integer> lastEquippedItems;
private final int color;
public EntityPlayer(Level level, int color) {
super(level, new Inventory(256));
this.lastEquippedItems = new ArrayList<>();
this.color = color;
ItemStack glove = new ItemStack(new ItemInstance(Item.GLOVE, 0), 1);
this.inventory.add(glove);
this.equipped = glove;
}
@Override
public boolean hurt(int amount, boolean ignoreArmor) {
if (super.hurt(amount, ignoreArmor)) {
Sound.PLAYER_HURT.play();
return true;
}
return false;
}
@Override
public Light getLight() {
Light parent = super.getLight();
return parent != null ? parent : new Light(
this.x + (this.width / 2),
this.y + (this.height / 2),
2
);
}
@Override
public void die() {
super.die();
EntitySmashParticle.spawn(
this.level,
this.getCenterX(), this.getCenterY(),
this.getColor(),
10, 30
);
Global.game.playerDiedTicks = Global.ticks;
}
@Override
public boolean canPickup(EntityItem e) {
return true;
}
@Override
public void pickup(ItemStack stack) {
super.pickup(stack);
Sound.PICKUP.play();
Global.game.score++;
}
@Override
public void onDepthChange(int prevDepth) {
super.onDepthChange(prevDepth);
Global.game.setLevel(this.level.depth);
Global.game.setMenu(new TransitionMenu());
}
@Override
public void tick() {
super.tick();
// track currently equipped item
if (this.equipped != null &&
(this.lastEquippedItems.size() == 0 ||
this.lastEquippedItems.get(0) != this.equipped.instance.id)) {
this.lastEquippedItems.add(0, this.equipped.instance.id);
}
// if nothing is equipped, try to equip previously equipped items
// if there are no previous items to equip, equip the POW GLOVE
if (this.equipped == null) {
while (this.lastEquippedItems.size() != 0 && this.equipped == null) {
this.equipped = this.inventory.findByInstanceId(
this.lastEquippedItems.remove(0));
}
if (this.equipped == null) {
this.equipped = this.inventory.find(Item.GLOVE);
}
}
// crafting menu
if (ControlHandler.TOGGLE_CRAFTING.pressedTick()) {
Global.game.setMenu(new InventoryCraftingMenu(this));
return;
}
// item dropping
if (ControlHandler.DROP.pressedTick() &&
this.equipped != null &&
this.equipped.instance.item.isDroppable()) {
this.drop(this.equipped);
}
// get tile player is currently facing
int fx = this.getFacingTileX(), fy = this.getFacingTileY();
Tile ft = Tile.TILES[level.getTile(fx, fy)];
// entities in this tile and the tile being faced, excluding this one
List<Entity> facingEntities = Stream.concat(
level.getEntities(this.tileX, this.tileY).stream(),
level.getEntities(fx, fy).stream()
).filter(e -> e != this).collect(Collectors.toList());
if (ControlHandler.INTERACT.pressedTick()) {
boolean interactEntity = false, interactTile = false;
if (!this.swimming) {
// try to interact with facing entities
for (Entity e : facingEntities) {
interactEntity |= e.interact(this);
}
// interact with facing tile if no entities were interacted with
if (!interactEntity) {
interactTile = ft.interact(this.level, fx, fy, this);
}
}
// toggle inventory
if (!interactEntity && !interactTile) {
Global.game.setMenu(new PlayerInventoryMenu(this, true));
return;
}
}
if (!this.swimming && ControlHandler.HIT.pressedTick()) {
this.updateAnimationFrame(true);
boolean hitEntity = false;
for (Entity e : facingEntities) {
if (this.hit(e)) {
e.onHit(this);
hitEntity = true;
}
}
if (hitEntity) {
this.animateAction();
} else if (this.equipped.instance.item instanceof ItemConsumable) {
this.equipped.instance.item.use(this.equipped.instance, level, this.tileX, this.tileY, this);
} else if (this.equipped.instance.item instanceof ItemTool) {
// try to use the active tool on the facing tile
ItemTool tool = (ItemTool) this.equipped.instance.item;
boolean ideal = (ft.getIdealTools() & tool.type) != 0,
usable = (ft.getUsableTools() & tool.type) != 0;
if (ideal || usable) {
int staminaCost = (int) (tool.getStaminaCostMultiplier() *
(ideal ?
(1 + Global.random.nextInt(2)) :
(3 + Global.random.nextInt(3))));
if (this.takeStamina(staminaCost)) {
Item equippedItem = this.equipped.instance.item;
if (ft.hit(this.level, fx, fy, this)) {
this.animateHit(equippedItem, fx, fy);
} else {
// give stamina back if hit failed
this.stamina += staminaCost;
}
}
}
} else {
// try to use the active item on the facing tile
Item equippedItem = this.equipped.instance.item;
if (equippedItem.use(this.equipped.instance, level, fx, fy, this)) {
this.animateHit(equippedItem, fx, fy);
}
}
if (this.hitTicks == 0 && !hitEntity) {
// no hit? animate an action
this.animateAction();
Sound.MISS.play();
} else {
Sound.HIT.play();
}
}
int dx = 0, dy = 0;
if (ControlHandler.UP.down()) {
dy--;
}
if (ControlHandler.DOWN.down()) {
dy++;
}
if (ControlHandler.LEFT.down()) {
dx--;
}
if (ControlHandler.RIGHT.down()) {
dx++;
}
if (!this.swimming || Global.ticks % 2 == 0) {
this.move(dx, dy);
}
// override entity direction, the player should be able to change their
// direction even when stationary
if (dx != 0 || dy != 0) {
this.direction = Direction.get(dx, dy);
}
// center camera on the player
this.updateCamera();
}
// updates current renderer camera to track the player
public void updateCamera() {
Renderer.camera.centerOn(
this.x + (this.width / 2), this.y + (this.height / 2),
0, 0,
Level.toPixel(this.level.width), Level.toPixel(this.level.height)
);
}
@Override
public double getStaminaRechargeRate() {
return 0.11;
}
@Override
public int getMaxStamina() {
return 10;
}
@Override
public int getMaxHealth() {
return 10;
}
@Override
public int getColor() {
return this.color;
}
@Override
public int getHurtColor() {
return Color.addAll(this.color, 222);
}
}

View File

@@ -0,0 +1,61 @@
package com.jdh.microcraft.entity;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntitySkeleton;
import com.jdh.microcraft.entity.mob.EntitySlime;
import com.jdh.microcraft.entity.mob.EntityZombie;
import com.jdh.microcraft.level.Level;
import java.util.List;
import java.util.function.Function;
public class SpawnProperties {
public final Class<? extends Entity> cls;
public final Function<Level, Entity> spawnFunction;
public final int chance, cap;
// chance is spawn rate/minute
// cap and chance are per 128x128 area
public SpawnProperties(Level level, Class<? extends Entity> cls, Function<Level, Entity> spawnFunction, int chance, int cap) {
this.cls = cls;
this.spawnFunction = spawnFunction;
double scale = ((level.width * level.height) / (128.0 * 128.0)) * Global.game.difficulty;
this.chance = (int) (chance * scale);
this.cap = (int) (cap * scale);
}
public static List<SpawnProperties> getSpawnProperties(Level level) {
return switch (level.depth) {
case 1 -> List.of(
new SpawnProperties(level, EntitySlime.class, EntitySlime::createBlueSlime, 50, 4),
new SpawnProperties(level, EntitySkeleton.class, EntitySkeleton::createSkeleton, 100, 4)
);
case 0 -> List.of(
new SpawnProperties(level, EntitySlime.class, EntitySlime::createGreenSlime, 300, 24),
new SpawnProperties(level, EntityZombie.class, EntityZombie::createEasyZombie, 80, 3)
);
case -1 -> List.of(
new SpawnProperties(level, EntitySlime.class, EntitySlime::createGreenSlime, 200, 24),
new SpawnProperties(level, EntitySlime.class, EntitySlime::createBlueSlime, 100, 4),
new SpawnProperties(level, EntityZombie.class, EntityZombie::createEasyZombie, 200, 20),
new SpawnProperties(level, EntitySkeleton.class, EntitySkeleton::createSkeleton, 200, 24)
);
case -2 -> List.of(
new SpawnProperties(level, EntitySlime.class, EntitySlime::createGreenSlime, 200, 32),
new SpawnProperties(level, EntitySlime.class, EntitySlime::createBlueSlime, 220, 32),
new SpawnProperties(level, EntitySlime.class, EntitySlime::createRedSlime, 80, 4),
new SpawnProperties(level, EntityZombie.class, EntityZombie::createMediumZombie, 200, 24),
new SpawnProperties(level, EntitySkeleton.class, EntitySkeleton::createSkeleton, 250, 24)
);
case -3 -> List.of(
new SpawnProperties(level, EntitySlime.class, EntitySlime::createGreenSlime, 100, 32),
new SpawnProperties(level, EntitySlime.class, EntitySlime::createBlueSlime, 100, 32),
new SpawnProperties(level, EntitySlime.class, EntitySlime::createRedSlime, 400, 32),
new SpawnProperties(level, EntityZombie.class, EntityZombie::createHardZombie, 250, 32),
new SpawnProperties(level, EntitySkeleton.class, EntitySkeleton::createSkeleton, 250, 32)
);
default -> List.of();
};
}
}

View File

@@ -0,0 +1,19 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.entity.Entity;
public class AI {
protected final Entity entity;
public AI(Entity entity) {
this.entity = entity;
}
public void tick() {
}
public void update() {
}
}

View File

@@ -0,0 +1,54 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.attack.WizardAttackBeam;
import com.jdh.microcraft.entity.attack.WizardAttackBurst;
import com.jdh.microcraft.entity.attack.WizardAttackSpiral;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.Direction;
public class AIAirWizard extends AIHostileHumanoid {
private long lastAttack;
private final EntityAirWizard wizard;
private Direction randomDirection;
public AIAirWizard(EntityAirWizard wizard) {
super(wizard, 0.75, 3.0 * wizard.strength);
this.wizard = wizard;
this.lastAttack = Global.ticks;
}
@Override
public void tick() {
if (this.wizard.attack == null) {
super.tick();
} else {
// move randomly
if (this.randomDirection == null || Global.random.nextInt(80) == 0) {
this.randomDirection = Direction.ALL.get(Global.random.nextInt(4));
}
this.moveTowards(
this.wizard.x + this.randomDirection.x * 10,
this.wizard.y + this.randomDirection.y * 10
);
}
if ((Global.ticks - this.lastAttack) > 120 + Global.random.nextInt(480)) {
this.wizard.attack = switch (Global.random.nextInt(3)) {
case 0 -> new WizardAttackBurst(this.wizard);
case 1 -> new WizardAttackSpiral(this.wizard);
default -> new WizardAttackBeam(this.wizard);
};
if (Renderer.inBounds(this.wizard.getCenterX(), this.wizard.getCenterY())) {
Sound.WIZARD_ATTACK.play();
}
this.lastAttack = Global.ticks;
}
}
}

View File

@@ -0,0 +1,68 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntityHumanoid;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
public class AIHostileHumanoid extends AIHostileMob {
private final EntityHumanoid humanoid;
private final double speed, strength;
private Direction randomMoveDirection;
public AIHostileHumanoid(EntityHumanoid humanoid, double speed, double strength) {
super(humanoid, strength);
this.humanoid = humanoid;
this.speed = speed;
this.strength = strength;
}
@Override
protected void moveTowards(int x, int y) {
double dx = FMath.sign(x - this.humanoid.x),
dy = FMath.sign(y - this.humanoid.y),
l = FMath.norm(dx, dy);
dx /= l;
dy /= l;
dx = Double.isNaN(dx) ? 0 : dx;
dy = Double.isNaN(dy) ? 0 : dy;
dx *= this.speed;
dy *= this.speed;
if (!this.humanoid.swimming || Global.ticks % 2 == 0) {
int mx = FMath.tickedDoubleToInt(Global.ticks, dx),
my = FMath.tickedDoubleToInt(Global.ticks, dy);
if (this.shouldMove(mx, 0)) {
this.humanoid.move(mx, 0);
}
if (this.shouldMove(0, my)) {
this.humanoid.move(0, my);
}
}
}
@Override
public void tick() {
super.tick();
if (this.target == null) {
if (this.randomMoveDirection == null && Global.random.nextInt(240) == 0) {
this.randomMoveDirection = Direction.ALL.get(Global.random.nextInt(4));
} else if (this.randomMoveDirection != null) {
this.moveTowards(
this.humanoid.x + this.randomMoveDirection.x * 10,
this.humanoid.y + this.randomMoveDirection.y * 10
);
if (Global.random.nextInt(120) == 0) {
this.randomMoveDirection = null;
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.level.tile.TileLava;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
import com.jdh.microcraft.util.Time;
import java.util.ArrayList;
import java.util.stream.Collectors;
public abstract class AIHostileMob extends AI {
protected final double sightDistance;
protected final int targetPermanenceTicks;
protected final double strength;
protected int ticksSinceTargetSeen;
public Entity target;
public AIHostileMob(Entity entity, double strength) {
super(entity);
this.strength = strength;
this.sightDistance = (16 * 10) * this.strength;
this.targetPermanenceTicks = (int) (180 * this.strength);
}
// avoid really dangerous stuff
protected boolean shouldMove(int dx, int dy) {
int cx = this.entity.getCenterX() + dx,
cy = this.entity.getCenterY() + dy,
tx = Level.toTile(cx),
ty = Level.toTile(cy);
return !(Tile.TILES[this.entity.level.getTile(tx, ty)] instanceof TileLava);
}
protected abstract void moveTowards(int x, int y);
@Override
public void tick() {
super.tick();
if (this.entity.level.player != null &&
FMath.norm(this.entity.x - this.entity.level.player.x,
this.entity.y - this.entity.level.player.y) <= this.sightDistance) {
this.target = this.entity.level.player;
this.ticksSinceTargetSeen = 0;
} else {
this.ticksSinceTargetSeen++;
// lose target if it has been too long
if (this.ticksSinceTargetSeen >= this.targetPermanenceTicks) {
this.target = null;
}
}
if (this.target != null) {
int tcx = this.target.x + (this.target.width / 2),
tcy = this.target.y + (this.target.height / 2),
tdx = tcx - this.entity.x,
tdy = tcy - this.entity.y;
double pd = this.getPreferredTargetDistance(),
pdt = this.getPreferredTargetDistanceThreshold();
if (Math.abs(tdx) - pd > pdt ||
Math.abs(tdy) - pd > pdt) {
this.moveTowards(this.target.x, this.target.y);
} else if (pd != 0.0 && pdt != 0.0) {
// ranged mobs: strafe in directions not towards the target
Global.random.setSeed(Global.ticks / Time.TPS);
if (Global.random.nextInt(3) == 0) {
Direction td = Direction.get(tdx, tdy);
Direction strafeDir = new ArrayList<>(Direction.ALL)
.stream()
.filter(d -> d != td && d != td.opposite())
.collect(Collectors.toList())
.get(Global.random.nextInt(2));
this.moveTowards(this.entity.x + strafeDir.x, this.entity.y + strafeDir.y);
}
}
}
}
protected double getPreferredTargetDistance() {
return 0.0;
}
protected double getPreferredTargetDistanceThreshold() {
return 0.0;
}
}

View File

@@ -0,0 +1,29 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.entity.mob.EntitySkeleton;
public class AISkeleton extends AIHostileHumanoid {
private final EntitySkeleton skeleton;
public AISkeleton(EntitySkeleton skeleton) {
super(skeleton, 0.5 * skeleton.strength, skeleton.strength);
this.skeleton = skeleton;
}
@Override
public void tick() {
super.tick();
if (this.target != null) {
this.skeleton.shoot(this.target.x, this.target.y);
}
}
protected double getPreferredTargetDistance() {
return 16 * 3;
}
protected double getPreferredTargetDistanceThreshold() {
return 24;
}
}

View File

@@ -0,0 +1,41 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntitySlime;
import com.jdh.microcraft.util.Direction;
public class AISlime extends AIHostileMob {
private final EntitySlime slime;
private long lastJump;
private final int jumpDelay;
public AISlime(EntitySlime slime) {
super(slime, slime.strength);
this.slime = slime;
this.jumpDelay = (int) Math.max(140 - (20 * slime.strength), 60);
}
private boolean canJump() {
return Global.ticks - this.lastJump >= this.jumpDelay;
}
@Override
protected void moveTowards(int x, int y) {
if (!this.slime.jumping && this.canJump()) {
this.lastJump = Global.ticks;
this.slime.jump(x - this.slime.x, y - this.slime.y, 0.5 * slime.strength);
}
}
@Override
public void tick() {
super.tick();
Global.random.setSeed(Global.ticks * this.slime.id);
if (this.target == null && Global.random.nextInt(180) == 0) {
this.lastJump = Global.ticks;
this.slime.jump(Direction.ALL.get(Global.random.nextInt(4)), 0.5);
}
}
}

View File

@@ -0,0 +1,9 @@
package com.jdh.microcraft.entity.ai;
import com.jdh.microcraft.entity.mob.EntityZombie;
public class AIZombie extends AIHostileHumanoid {
public AIZombie(EntityZombie zombie) {
super(zombie, 0.45 * zombie.strength, zombie.strength);
}
}

View File

@@ -0,0 +1,21 @@
package com.jdh.microcraft.entity.attack;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
public abstract class WizardAttack {
protected final EntityAirWizard wizard;
public int time;
public WizardAttack(EntityAirWizard wizard, int time) {
this.wizard = wizard;
this.time = time;
}
public boolean done() {
return this.time <= 0;
}
public void tick() {
this.time--;
}
}

View File

@@ -0,0 +1,33 @@
package com.jdh.microcraft.entity.attack;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.ai.AIHostileMob;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
import com.jdh.microcraft.entity.projectile.EntityAirBlast;
public class WizardAttackBeam extends WizardAttack {
public WizardAttackBeam(EntityAirWizard wizard) {
super(wizard, 60);
}
@Override
public void tick() {
super.tick();
if ((Global.ticks % 2) != 0) {
return;
}
AIHostileMob ai = (AIHostileMob) this.wizard.ai;
if (ai.target != null) {
this.wizard.level.addEntity(
new EntityAirBlast(
this.wizard.level, this.wizard,
this.wizard.getCenterX(), this.wizard.getCenterY(),
ai.target.getCenterX(), ai.target.getCenterY(),
1.4,
(int) (3 * this.wizard.strength))
);
}
}
}

View File

@@ -0,0 +1,42 @@
package com.jdh.microcraft.entity.attack;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
import com.jdh.microcraft.entity.projectile.EntityAirBlast;
public class WizardAttackBurst extends WizardAttack {
private static final int SIZE = 32;
private boolean attacked = false;
public WizardAttackBurst(EntityAirWizard wizard) {
super(wizard, 8);
}
@Override
public void tick() {
super.tick();
if (this.attacked) {
return;
}
this.attacked = true;
for (int i = 0; i < SIZE; i++) {
int dx = (int) (Math.cos((i / (double) SIZE) * (2 * Math.PI)) * 16),
dy = (int) (Math.sin((i / (double) SIZE) * (2 * Math.PI)) * 16);
this.wizard.level.addEntity(
new EntityAirBlast(
this.wizard.level, this.wizard,
this.wizard.getCenterX() + dx,
this.wizard.getCenterY() + dy,
this.wizard.getCenterX() + dx * 10,
this.wizard.getCenterY() + dy * 10,
1.2,
(int) (3 * this.wizard.strength)
)
);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.jdh.microcraft.entity.attack;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntityAirWizard;
import com.jdh.microcraft.entity.projectile.EntityAirBlast;
public class WizardAttackSpiral extends WizardAttack {
public WizardAttackSpiral(EntityAirWizard wizard) {
super(wizard, 240);
}
@Override
public void tick() {
super.tick();
if ((Global.ticks % 2) != 0) {
return;
}
int dx = (int) (Math.cos(((Global.ticks % 60) / 60.0) * (2 * Math.PI)) * 16),
dy = (int) (Math.sin(((Global.ticks % 60) / 60.0) * (2 * Math.PI)) * 16);
this.wizard.level.addEntity(
new EntityAirBlast(
this.wizard.level, this.wizard,
this.wizard.getCenterX() + dx,
this.wizard.getCenterY() + dy,
this.wizard.getCenterX() + dx * 10,
this.wizard.getCenterY() + dy * 10,
1.4,
(int) (3 * this.wizard.strength)
)
);
}
}

View File

@@ -0,0 +1,24 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.crafting.AnvilCraftingMenu;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.level.Level;
public class EntityAnvil extends EntityFurniture {
public EntityAnvil(Level level) {
super(level, Item.ANVIL, 15, 10);
}
@Override
public boolean interact(Entity e) {
if (!(e instanceof EntityPlayer)) {
return false;
}
Global.game.setMenu(new AnvilCraftingMenu((EntityPlayer) e, this));
return true;
}
}

View File

@@ -0,0 +1,42 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.ChestMenu;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
public class EntityChest extends EntityFurniture {
public Inventory inventory = new Inventory(512);
public EntityChest(Level level) {
super(level, Item.CHEST, 15, 13);
}
@Override
public boolean hit(Entity e) {
if (super.hit(e)) {
for (ItemStack s : this.inventory.stacks) {
EntityItem.spawn(this.level, s, Level.toCenter(e.tileX), Level.toCenter(e.tileY));
}
return true;
}
return false;
}
@Override
public boolean interact(Entity e) {
if (!(e instanceof EntityPlayer)) {
return false;
}
Global.game.setMenu(new ChestMenu((EntityPlayer) e, this));
return true;
}
}

View File

@@ -0,0 +1,24 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.crafting.BenchCraftingMenu;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.level.Level;
public class EntityCraftingBench extends EntityFurniture {
public EntityCraftingBench(Level level) {
super(level, Item.CRAFTING_BENCH, 15, 12);
}
@Override
public boolean interact(Entity e) {
if (!(e instanceof EntityPlayer)) {
return false;
}
Global.game.setMenu(new BenchCraftingMenu((EntityPlayer) e, this));
return true;
}
}

View File

@@ -0,0 +1,24 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.crafting.FurnaceMenu;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.level.Level;
public class EntityFurnace extends EntityFurniture {
public EntityFurnace(Level level) {
super(level, Item.FURNACE, 15, 14);
}
@Override
public boolean interact(Entity e) {
if (!(e instanceof EntityPlayer)) {
return false;
}
Global.game.setMenu(new FurnaceMenu((EntityPlayer) e, this));
return true;
}
}

View File

@@ -0,0 +1,106 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.entity.projectile.EntityProjectile;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.furniture.ItemFurniture;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.util.Direction;
import java.util.List;
public abstract class EntityFurniture extends Entity {
private final ItemFurniture item;
private int pushTime;
public EntityFurniture(Level level, ItemFurniture item, int width, int height) {
super(level);
this.item = item;
this.width = width;
this.height = height;
}
@Override
public void render() {
super.render();
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= 1; j++) {
Renderer.render(
this.getSpriteOffsetX() + i, this.getSpriteOffsetY() + j,
this.x + this.getRenderOffsetX() + (i * 8),
this.y + this.getRenderOffsetY() + (j * 8),
this.getColor(), Renderer.FLIP_NONE
);
}
}
}
@Override
public boolean collides(Entity e) {
return true;
}
@Override
public void collide(Entity e) {
super.collide(e);
if (e instanceof EntityMob) {
this.pushTime++;
if (this.pushTime == 3) {
// push in the direction the mob is moving
EntityMob mob = ((EntityMob) e);
Direction d = mob.getDirection();
this.move(d.x, d.y);
this.pushTime = 0;
}
} else if (e instanceof EntityProjectile) {
for (ItemStack s : this.getDrops()) {
EntityItem.spawn(level, s, Level.toCenter(this.tileX), Level.toCenter(this.tileY));
}
this.remove();
}
}
@Override
public void onHit(Entity e) {
if (!(e instanceof EntityMob)) {
return;
}
for (ItemStack s : this.getDrops()) {
EntityItem.spawn(level, s, Level.toCenter(this.tileX), Level.toCenter(this.tileY));
}
this.remove();
}
@Override
public boolean canSwimIn(Tile tile, int x, int y) {
return false;
}
protected List<ItemStack> getDrops() {
return List.of(new ItemStack(new ItemInstance(this.item)));
}
public int getSpriteOffsetX() {
return this.item.getTileSpriteX();
}
public int getSpriteOffsetY() {
return this.item.getTileSpriteY();
}
public int getColor() {
return this.item.getColor();
}
@Override
public abstract boolean interact(Entity e);
}

View File

@@ -0,0 +1,50 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.gfx.Light;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
public class EntityLantern extends EntityFurniture {
public static final int LIGHT_POWER = 6;
public EntityLantern(Level level) {
super(level, Item.LANTERN, 13, 13);
}
@Override
public int getRenderOffsetX() {
return -2;
}
@Override
public int getRenderOffsetY() {
return -3;
}
@Override
public Light getLight() {
return new Light(
this.getCenterX(),
this.getCenterY(),
LIGHT_POWER
);
}
@Override
public boolean interact(Entity e) {
ItemStack s = new ItemStack(new ItemInstance(Item.LANTERN, 0), 1);
if (e instanceof EntityMob && ((EntityMob) e).inventory.add(s)) {
EntityMob mob = (EntityMob) e;
mob.equipped = s;
this.remove();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,24 @@
package com.jdh.microcraft.entity.furniture;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.crafting.OvenMenu;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.level.Level;
public class EntityOven extends EntityFurniture {
public EntityOven(Level level) {
super(level, Item.OVEN, 15, 13);
}
@Override
public boolean interact(Entity e) {
if (!(e instanceof EntityPlayer)) {
return false;
}
Global.game.setMenu(new OvenMenu((EntityPlayer) e, this));
return true;
}
}

View File

@@ -0,0 +1,120 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.ai.AIAirWizard;
import com.jdh.microcraft.entity.attack.WizardAttack;
import com.jdh.microcraft.entity.particle.EntitySmokeParticle;
import com.jdh.microcraft.entity.projectile.EntityProjectile;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gui.WinMenu;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.sound.Sound;
public class EntityAirWizard extends EntityHumanoid {
public final double strength = 1.0 * Global.game.difficulty;
public WizardAttack attack;
public EntityAirWizard(Level level) {
super(level, Inventory.NONE);
this.health = this.getMaxHealth();
this.stamina = this.getMaxStamina();
this.ai = new AIAirWizard(this);
}
@Override
public void die() {
super.die();
Sound.WIZARD_DEATH.play();
Global.game.score += 5000;
Global.game.setMenu(new WinMenu());
}
@Override
public boolean hurt(int amount) {
return super.hurt(amount == 1 ? 0 : amount);
}
@Override
public boolean collides(Entity e) {
return !(e instanceof EntityProjectile) && super.collides(e);
}
@Override
public void collide(Entity e) {
if (e instanceof EntityPlayer) {
this.hit(e);
}
}
@Override
public void tick() {
super.tick();
if (Global.random.nextInt(10) == 0) {
EntitySmokeParticle.spawn(this.level, this.getCenterX(), this.getCenterY(), Tile.CLOUD.getColor(), 1, 4);
}
if (this.attack != null) {
if (this.attack.done()) {
this.attack = null;
} else {
this.attack.tick();
}
}
}
@Override
public int getHitDamage(Entity e) {
return (int) ((2 + Global.random.nextInt(5)) * this.strength);
}
@Override
public int getBaseSpriteX() {
return 8;
}
@Override
public int getBaseSpriteY() {
return 9;
}
@Override
public int getCarrySpriteX() {
return 8;
}
@Override
public int getCarrySpriteY() {
return 9;
}
@Override
public int getColor() {
return Color.get(111, 224, 441, 555);
}
@Override
public int getHurtColor() {
return Color.get(444, 335, 552, 555);
}
@Override
public double getStaminaRechargeRate() {
return 0.3;
}
@Override
public int getMaxStamina() {
return (int) (this.strength * 100);
}
@Override
public int getMaxHealth() {
return (int) (this.strength * 80);
}
}

View File

@@ -0,0 +1,335 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Light;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.armor.*;
import com.jdh.microcraft.item.tool.ItemTool;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.level.tile.TileLiquid;
import com.jdh.microcraft.util.Direction;
public abstract class EntityHumanoid extends EntityMob {
public static final int BASE_SPRITE_X = 0, BASE_SPRITE_Y = 11;
public static final int CARRY_SPRITE_X = 0, CARRY_SPRITE_Y = 9;
private static final int SWIM_SPRITE_X = 9, SWIM_SPRITE_Y = 11;
private static final int SWIM_COLOR = Color.get(334, 445, 555, 555);
// current sprite location
private int spriteX, spriteY;
// current animation frame ticks and flipped/frame state
private int animationTicks;
private boolean animationFlipped, animationXFrame;
// equipped armor
public ItemStack[] armor = new ItemStack[ItemArmor.NUM_TYPES];
public EntityHumanoid(Level level, Inventory inventory) {
super(level, inventory);
this.width = 11;
this.height = 11;
this.animationTicks = this.getAnimationFrameTicks();
this.updateAnimationFrame(false);
}
// level must be in [1..3]
public static void giveRandomEquipment(EntityHumanoid entity, int level, boolean armor, boolean weapon) {
assert (level >= 1 && level <= 3);
Item[][] armorTable = new Item[][]{
{Item.IRON_HELMET, Item.IRON_CHESTPLATE, Item.IRON_LEGGINGS, Item.IRON_BOOTS},
{Item.GOLD_HELMET, Item.GOLD_CHESTPLATE, Item.GOLD_LEGGINGS, Item.GOLD_BOOTS},
{Item.GOLD_HELMET, Item.GOLD_CHESTPLATE, Item.GOLD_LEGGINGS, Item.GOLD_BOOTS}
};
Item[][] weaponTable = new Item[][]{
{Item.ROCK_SWORD, Item.ROCK_AXE},
{Item.IRON_SWORD, Item.IRON_AXE},
{Item.GOLD_SWORD, Item.GEM_SWORD, Item.MITHRIL_SWORD}
};
if (armor) {
for (int i = 0; i < ItemArmor.NUM_TYPES; i++) {
if (Global.random.nextInt((4 - level) * 7) == 0) {
ItemStack item = new ItemStack(new ItemInstance(armorTable[level - 1][i], 0), 1);
entity.armor[i] = item;
entity.inventory.add(item);
}
}
}
if (weapon && Global.random.nextInt((4 - level) * 8) == 0) {
ItemStack item = new ItemStack(
new ItemInstance(
weaponTable[level - 1][Global.random.nextInt(weaponTable[level - 1].length)],
0),
1
);
entity.equipped = item;
entity.inventory.add(item);
}
}
@Override
public ItemStack removeItem(Item item, int count) {
ItemStack removed = super.removeItem(item, count);
for (int i = 0; i < this.armor.length; i++) {
if (this.armor[i] == removed) {
this.armor[i] = null;
}
}
return removed;
}
@Override
public void die() {
super.die();
for (ItemStack s : this.inventory.stacks) {
EntityItem.spawn(this.level, s, this.getCenterX(), this.getCenterY());
}
}
@Override
public void pickup(ItemStack stack) {
super.pickup(stack);
if (stack.instance.item instanceof ItemArmor) {
ItemArmor armor = (ItemArmor) stack.instance.item;
if (this.armor[armor.slot] == null) {
this.armor[armor.slot] = stack;
}
}
}
protected int getAnimationFrameTicks() {
return 10;
}
public boolean hurt(int amount, boolean ignoreArmor) {
if (ignoreArmor) {
return super.hurt(amount);
}
double reduction = 0;
for (ItemStack s : this.armor) {
if (s != null) {
reduction += ((ItemArmor) s.instance.item).getDamageReduction(s.instance);
}
}
reduction = reduction > 0 ? Global.random.nextInt(Math.max((int) (reduction / 2.5), 2)) : 0;
return super.hurt((int) Math.max(amount - reduction, 0));
}
@Override
public boolean hurt(int amount) {
return this.hurt(amount, false);
}
@Override
public Light getLight() {
if (this.equipped != null) {
int power = this.equipped.instance.item.getLightPower();
if (power > 0) {
return new Light(
this.getCenterX(),
this.getCenterY(),
power
);
}
}
return null;
}
@Override
public int getHitDamage(Entity e) {
return (this.equipped != null && (this.equipped.instance.item instanceof ItemTool)) ?
((ItemTool) this.equipped.instance.item).getDamage(this.equipped.instance) : 1;
}
@Override
public int getRenderOffsetX() {
return -3;
}
@Override
public int getRenderOffsetY() {
return -2;
}
@Override
protected void onDirectionChange() {
super.onDirectionChange();
this.updateAnimationFrame(false);
}
@Override
public void tick() {
super.tick();
// drowning
if (this.swimming && (Global.ticks % 60) == 0) {
if (this.stamina == 0) {
this.hurt(1, true);
} else {
this.takeStamina(1);
}
}
if (this.moving) {
this.animationTicks--;
if (this.animationTicks == 0) {
this.updateAnimationFrame(true);
}
}
// pick the correct current animation frame but don't switch
this.updateAnimationFrame(false);
}
protected boolean carrying() {
return this.equipped != null && this.equipped.instance.item.carry(this);
}
public int getBaseSpriteX() {
return BASE_SPRITE_X;
}
public int getBaseSpriteY() {
return BASE_SPRITE_Y;
}
public int getCarrySpriteX() {
return CARRY_SPRITE_X;
}
public int getCarrySpriteY() {
return CARRY_SPRITE_Y;
}
protected int getSpritesheetOffsetX() {
return this.carrying() ? this.getCarrySpriteX() : this.getBaseSpriteX();
}
protected int getSpritesheetOffsetY() {
return this.carrying() ? this.getCarrySpriteY() : this.getBaseSpriteY();
}
protected void updateAnimationFrame(boolean switchFrame) {
if (switchFrame) {
this.animationTicks = this.getAnimationFrameTicks();
}
this.spriteY = this.getSpritesheetOffsetY();
switch (this.direction) {
case NORTH, SOUTH -> {
if (switchFrame) {
this.animationFlipped = !this.animationFlipped;
}
this.spriteX = this.getSpritesheetOffsetX() +
(this.direction == Direction.NORTH ? 2 : 0);
}
case EAST, WEST -> {
if (switchFrame) {
this.animationXFrame = !this.animationXFrame;
}
this.animationFlipped = this.direction == Direction.LEFT;
this.spriteX = this.getSpritesheetOffsetX() +
(this.animationXFrame ? 4 : 6);
}
}
}
private void renderArmor(ItemArmor item) {
if (this.swimming && item.type != ItemArmor.TYPE_HELMET) {
return;
}
int sx = this.spriteX - this.getCarrySpriteX(),
sy = this.spriteY - this.getCarrySpriteY();
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= 1; j++) {
Renderer.render(
sx + item.getSpriteBaseX() + (this.animationFlipped ? (1 - i) : i),
sy + item.getSpriteBaseY() + j,
this.x + (i * 8) + this.getRenderOffsetX(),
this.y + (j * 8) + this.getRenderOffsetY(),
item.getColor(),
this.animationFlipped ? Renderer.FLIP_X : Renderer.FLIP_NONE
);
}
}
}
@Override
public void render() {
super.render();
// flash with hurt color on hurt
int color = this.invulnerableTicks > 0 && ((Global.ticks / 4) % 2) == 0 ?
this.getHurtColor() :
this.getColor();
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= (this.swimming ? 0 : 1); j++) {
Renderer.render(
this.spriteX + (this.animationFlipped ? (1 - i) : i), this.spriteY + j,
this.x + (i * 8) + this.getRenderOffsetX(),
this.y + (j * 8) + this.getRenderOffsetY(),
color,
this.animationFlipped ? Renderer.FLIP_X : Renderer.FLIP_NONE
);
}
}
// render armor
for (ItemStack s : this.armor) {
if (s != null) {
this.renderArmor((ItemArmor) s.instance.item);
}
}
if (this.swimming) {
for (int i = 0; i <= 1; i++) {
Renderer.render(
SWIM_SPRITE_X, SWIM_SPRITE_Y,
this.x + (i * 8) + this.getRenderOffsetX(),
this.y + 2 + this.getRenderOffsetY(),
SWIM_COLOR,
i == 0 ? Renderer.FLIP_NONE : Renderer.FLIP_X
);
}
}
// render carrying item
if (this.equipped != null && this.equipped.instance.item.carry(this)) {
this.equipped.instance.item.renderCarry(this.equipped.instance, this.level, this);
}
}
@Override
public boolean canSwimIn(Tile tile, int x, int y) {
return tile instanceof TileLiquid;
}
public abstract int getColor();
public abstract int getHurtColor();
}

View File

@@ -0,0 +1,327 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.furniture.EntityFurniture;
import com.jdh.microcraft.entity.particle.EntityTextParticle;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
public abstract class EntityMob extends Entity {
private static final int HIT_SPRITE_X = 9, HIT_SPRITE_Y = 12;
private static final int HIT_COLOR = Color.get(222, 533, 544, 555);
private static final int ACTION_SPRITE_X = 8, ACTION_SPRITE_Y = 11;
private static final int ACTION_COLOR = Color.get(555, 555, 555, 555);
private static final int ACTION_TICKS = 6;
private static final int HIT_TICKS = 4;
private static final int USE_ITEM_TICKS = 4;
private static final int STAMINA_RECHARGE_DELAY_TICKS = 40;
private static final int INVULNERABLE_TICKS = 30;
protected int actionTicks;
protected int hitTicks, hitX, hitY;
protected Item useItem;
protected int useItemTicks;
public int health, stamina;
public int invulnerableTicks;
public int staminaRechargeDelayTicks;
public final Inventory inventory;
public ItemStack equipped;
public EntityMob(Level level, Inventory inventory) {
super(level);
this.health = this.getMaxHealth();
this.stamina = this.getMaxStamina();
this.inventory = inventory;
}
public void drop(ItemStack stack) {
assert(this.inventory.contains(stack));
assert(stack.instance.item.isDroppable());
EntityItem item = EntityItem.spawn(
this.level,
this.removeItem(this.equipped.instance.item, this.equipped.size),
this.getCenterX(), this.getCenterY(),
this.getDirection()
);
item.timeToPickup = 80;
}
public int getFacingTileX() {
return switch (this.direction) {
case EAST -> tileX + 1;
case WEST -> tileX - 1;
default -> tileX;
};
}
public int getFacingTileY() {
return switch (this.direction) {
case NORTH -> tileY - 1;
case SOUTH -> tileY + 1;
default -> tileY;
};
}
public ItemStack removeItem(Item item, int count) {
ItemStack removed = this.inventory.remove(item, count);
// remove equipped item if equipped stack was removed
if (this.equipped == removed && count == this.equipped.size) {
this.equipped = null;
}
return removed;
}
@Override
protected void onDirectionChange() {
this.actionTicks = 0;
this.useItemTicks = 0;
this.hitTicks = 0;
}
public int getHitDamage(Entity e) {
return 1;
}
public boolean canPickup(EntityItem e) {
return false;
}
public void pickup(ItemStack stack) {
}
@Override
public boolean hit(Entity e) {
if (!this.swimming && e instanceof EntityMob && this.takeApproxStamina(2)) {
EntityMob mob = (EntityMob) e;
mob.hurt(Math.max(this.getHitDamage(e), 1), this);
return true;
}
return e instanceof EntityFurniture;
}
@Override
public void collide(Entity e) {
super.collide(e);
if (!e.metadata.remove && e instanceof EntityItem &&
((EntityItem) e).canPickup(this) &&
this.canPickup(((EntityItem) e))) {
EntityItem item = ((EntityItem) e);
if (this.inventory.add(item.stack)) {
this.pickup(item.stack);
item.remove();
}
}
}
@Override
public boolean collides(Entity e) {
return !(e instanceof EntityMob && this.swimming);
}
protected void animateHit(Item used, int x, int y) {
this.useItem = used;
this.useItemTicks = USE_ITEM_TICKS;
this.hitTicks = HIT_TICKS;
this.hitX = Level.toPixel(x);
this.hitY = Level.toPixel(y);
}
protected void animateAction() {
this.actionTicks = ACTION_TICKS;
}
@Override
public void render() {
super.render();
// used item
if (this.useItemTicks > 0) {
ItemInstance instance = new ItemInstance(this.useItem, 0);
switch (this.direction) {
case NORTH -> this.useItem.render(
instance, this.level,
this.x + 2, this.y - 6);
case SOUTH -> this.useItem.render(
instance, this.level,
this.x + 2, this.y + this.height);
case EAST -> this.useItem.render(
instance, this.level,
this.x + this.width, this.y + 2);
case WEST -> this.useItem.render(
instance, this.level,
this.x - 6, this.y + 2);
}
}
if (this.hitTicks > 0) {
Renderer.render(HIT_SPRITE_X, HIT_SPRITE_Y,
this.hitX + 0, this.hitY + 0, HIT_COLOR, Renderer.FLIP_Y);
Renderer.render(HIT_SPRITE_X, HIT_SPRITE_Y,
this.hitX + 8, this.hitY + 0, HIT_COLOR, Renderer.FLIP_XY);
Renderer.render(HIT_SPRITE_X, HIT_SPRITE_Y,
this.hitX + 0, this.hitY + 8, HIT_COLOR, Renderer.FLIP_NONE);
Renderer.render(HIT_SPRITE_X, HIT_SPRITE_Y,
this.hitX + 8, this.hitY + 8, HIT_COLOR, Renderer.FLIP_X);
} else if (this.actionTicks > 0) {
int px = this.x + this.getRenderOffsetX(), py = this.y + this.getRenderOffsetY();
switch (this.direction) {
case NORTH -> {
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y,
px, py - 4, ACTION_COLOR, Renderer.FLIP_X);
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y,
px + 8, py - 4, ACTION_COLOR, Renderer.FLIP_NONE);
}
case SOUTH -> {
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y,
px, py + this.height + 1, ACTION_COLOR, Renderer.FLIP_XY);
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y,
px + 8, py + this.height + 1, ACTION_COLOR, Renderer.FLIP_Y);
}
case EAST -> {
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y + 1,
px + this.width, py + 0, ACTION_COLOR, Renderer.FLIP_NONE);
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y + 1,
px + this.width, py + 8, ACTION_COLOR, Renderer.FLIP_Y);
}
case WEST -> {
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y + 1,
px - 4, py + 0, ACTION_COLOR, Renderer.FLIP_X);
Renderer.render(ACTION_SPRITE_X, ACTION_SPRITE_Y + 1,
px - 4, py + 8, ACTION_COLOR, Renderer.FLIP_XY);
}
}
}
}
public void heal(int amount) {
assert (amount >= 0);
this.health = Math.min(this.health + amount, this.getMaxHealth());
}
// hurt from entity
public boolean hurt(int amount, Entity e) {
return this.hurt(
amount,
this.moving ?
this.getDirection().opposite() :
Direction.get(this.x - e.x, this.y - e.y)
);
}
// hurt with knockback
public boolean hurt(int amount, Direction direction) {
if (this.hurt(amount)) {
this.knockback(4.0, direction);
return true;
}
return false;
}
public boolean hurt(int amount) {
assert (amount >= 0);
if (this.invulnerableTicks > 0) {
return false;
}
EntityTextParticle.spawn(
this.level, this.x, this.y,
Integer.toString(amount),
amount == 0 ? 114 : 500);
this.invulnerableTicks = INVULNERABLE_TICKS;
this.health -= amount;
if (this.health <= 0) {
this.die();
this.remove();
}
return true;
}
// amount is not exact, it's a multiplier. the true amount is random.
public boolean takeApproxStamina(int amount) {
return this.takeStamina(amount - 1 + Global.random.nextInt(1 + amount + (amount / 2)));
}
// returns true if stamina was taken
public boolean takeStamina(int amount) {
if (this.stamina - amount >= 0) {
this.stamina -= amount;
return true;
}
return false;
}
private void rechargeStamina() {
if (this.swimming) {
return;
}
double rr = this.getStaminaRechargeRate();
if (rr >= 1.0) {
this.stamina += (int) rr;
} else if (Global.ticks % ((int) (1.0 / this.getStaminaRechargeRate())) == 0) {
this.stamina++;
}
this.stamina = FMath.clamp(this.stamina, 1, this.getMaxStamina());
}
@Override
public void tick() {
super.tick();
this.invulnerableTicks = Math.max(this.invulnerableTicks - 1, 0);
this.hitTicks = Math.max(this.hitTicks - 1, 0);
this.actionTicks = Math.max(this.actionTicks - 1, 0);
this.useItemTicks = Math.max(this.useItemTicks - 1, 0);
this.staminaRechargeDelayTicks = Math.max(this.staminaRechargeDelayTicks - 1, 0);
if (this.staminaRechargeDelayTicks > 0) {
this.staminaRechargeDelayTicks--;
if (this.staminaRechargeDelayTicks == 0) {
this.rechargeStamina();
}
} else if (this.stamina <= 0) {
this.staminaRechargeDelayTicks = STAMINA_RECHARGE_DELAY_TICKS;
this.stamina = 0;
} else if (this.stamina != this.getMaxStamina()) {
this.rechargeStamina();
}
}
public void die() {
}
// per tick
public abstract double getStaminaRechargeRate();
public abstract int getMaxStamina();
public abstract int getMaxHealth();
}

View File

@@ -0,0 +1,137 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.ai.AISkeleton;
import com.jdh.microcraft.entity.particle.EntitySmashParticle;
import com.jdh.microcraft.entity.projectile.EntityArrow;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
public class EntitySkeleton extends EntityHumanoid {
private static final Item[] LOOT_TABLE = {
Item.GEM, Item.GOLD_INGOT,
Item.COAL, Item.COAL, Item.IRON_INGOT, Item.IRON_INGOT,
Item.COAL, Item.COAL, Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE,
Item.GLASS, Item.GLASS, Item.GLASS, Item.PIE, Item.IRON_ORE,
Item.ROCK, Item.ROCK, Item.ROCK, Item.ROCK, Item.SAND, Item.SAND, Item.SAND
};
private long lastShotTicks;
public final double strength;
public EntitySkeleton(Level level, int x, int y, double strength) {
super(level, new Inventory(4));
this.x = x;
this.y = y;
this.strength = strength * Global.game.difficulty;
this.ai = new AISkeleton(this);
this.health = this.getMaxHealth();
this.stamina = this.getMaxStamina();
}
public static EntitySkeleton createSkeleton(Level level) {
EntitySkeleton skeleton = new EntitySkeleton(level, 0, 0, 1.0);
EntityHumanoid.giveRandomEquipment(skeleton, 1, true, false);
return skeleton;
}
private boolean canShoot() {
return (Global.ticks - this.lastShotTicks) >= Math.max(180 - (30 * this.strength), 80);
}
public void shoot(int dx, int dy) {
if (Tile.TILES[this.level.getTile(this.getFacingTileX(), this.getFacingTileY())].isSolid()) {
// only shoot if facing tile is empty
return;
} else if (!this.canShoot()) {
return;
}
this.updateAnimationFrame(true);
this.lastShotTicks = Global.ticks;
this.level.addEntity(new EntityArrow(
this.level, this,
this.getCenterX(), this.getCenterY(),
dx, dy,
2.0,
(int) (1 * this.strength + Global.random.nextInt((int) (2 * this.strength)))
));
}
@Override
protected int getAnimationFrameTicks() {
return Math.max(12 - ((int) (2.0 / this.strength)), 6);
}
@Override
public void die() {
super.die();
EntitySmashParticle.spawn(this.level, this.x, this.y, this.getColor(), 4, 8);
for (int i = Global.random.nextInt(3); i > 0; i--) {
EntityItem.spawn(
this.level,
new ItemStack(new ItemInstance(Item.BONE)),
this.x, this.y
);
}
if (Global.random.nextBoolean()) {
EntityItem.spawn(
this.level,
new ItemStack(new ItemInstance(LOOT_TABLE[Global.random.nextInt(LOOT_TABLE.length)], 0), 1),
this.x, this.y
);
}
Global.game.score += 25 * this.strength;
}
@Override
public int getHitDamage(Entity e) {
return 1;
}
@Override
public void collide(Entity e) {
super.collide(e);
if (e instanceof EntityPlayer) {
this.hit(e);
e.onHit(this);
}
}
@Override
public int getColor() {
return Color.get(331, 555, 555, 554);
}
@Override
public int getHurtColor() {
return Color.get(444, 555, 555, 554);
}
@Override
public double getStaminaRechargeRate() {
return 0.2;
}
@Override
public int getMaxStamina() {
return (int) (this.strength * 10);
}
@Override
public int getMaxHealth() {
return (int) (this.strength * 10);
}
}

View File

@@ -0,0 +1,209 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.ai.AISlime;
import com.jdh.microcraft.entity.particle.EntitySmashParticle;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
public class EntitySlime extends EntityMob {
private static final int BASE_SPRITE_X = 16, BASE_SPRITE_Y = 3;
public boolean jumping;
private double vx, vy, vz;
private final boolean jumpTrail;
private final int color, hitColor, trailColor;
public final double strength;
public EntitySlime(Level level, int x, int y, int color, int hitColor,
boolean jumpTrail, int trailColor, double strength) {
super(level, Inventory.NONE);
this.x = x;
this.y = y;
this.color = color;
this.hitColor = hitColor;
this.jumpTrail = jumpTrail;
this.trailColor = trailColor;
this.strength = strength * Global.game.difficulty;
this.width = 12;
this.height = 8;
this.health = this.getMaxHealth();
this.stamina = this.getMaxStamina();
this.ai = new AISlime(this);
}
public static EntitySlime createGreenSlime(Level level) {
return new EntitySlime(
level,
0, 0,
Color.get(010, 020, 151, 353),
Color.get(010, 020, 353, 454),
false, 0,
0.6
);
}
public static EntitySlime createBlueSlime(Level level) {
return new EntitySlime(
level,
0, 0,
Color.get(001, 002, 114, 225),
Color.get(001, 002, 335, 445),
false, 0,
1.0
);
}
public static EntitySlime createRedSlime(Level level) {
return new EntitySlime(
level,
0, 0,
Color.get(100, 200, 411, 522),
Color.get(100, 200, 533, 544),
true, Color.get(330, 440, 550, 551),
1.15
);
}
@Override
public int getHitDamage(Entity e) {
return (int) (this.strength + Global.random.nextInt((int) (4 * this.strength)));
}
@Override
public void collide(Entity e) {
super.collide(e);
if (e instanceof EntityPlayer) {
this.hit(e);
e.onHit(this);
}
}
@Override
public void die() {
super.die();
EntitySmashParticle.spawn(this.level, this.x, this.y, this.color, 4, 10);
for (int i = 1 + Global.random.nextInt(1 + (2 * (int) this.strength)); i > 0; i--) {
EntityItem.spawn(
this.level,
new ItemStack(new ItemInstance(Item.SLIME, 0), 1),
this.x, this.y
);
}
Global.game.score += 20 * this.strength;
}
public void jump(double dx, double dy, double v) {
double l = FMath.norm(dx, dy);
this.jumping = true;
this.vx = 5.0 * (dx / l) * v;
this.vy = 5.0 * (dy / l) * v;
this.vz = 5.0 * v;
}
public void jump(Direction d, double v) {
this.jump(
d.x + (d == Direction.NORTH || d == Direction.SOUTH ? 0.1 : 0.0),
d.y + (d == Direction.EAST || d == Direction.WEST ? 0.3 : 0.0),
v
);
}
@Override
public void tick() {
super.tick();
if (this.jumping && this.jumpTrail && Global.random.nextInt(6) == 0) {
EntitySmashParticle.spawn(this.level, this.x, this.y, this.trailColor, 2, 4);
}
if (this.vz > 0.0) {
this.vx *= 0.97;
this.vy *= 0.97;
this.vz -= 0.11;
} else if (this.vz <= 0.0) {
this.jumping = false;
this.vz = 0.0;
this.vx *= 0.6;
this.vy *= 0.6;
}
if (Math.abs(this.vx) < 0.001) {
this.vx = 0.0;
}
if (Math.abs(this.vy) < 0.001) {
this.vy = 0.0;
}
this.move(
FMath.tickedDoubleToInt(Global.ticks, this.vx),
FMath.tickedDoubleToInt(Global.ticks, this.vy)
);
}
@Override
public int getRenderOffsetX() {
return -2;
}
@Override
public int getRenderOffsetY() {
return -8;
}
@Override
public void render() {
super.render();
Global.random.setSeed(this.id);
int color = this.invulnerableTicks > 0 && ((Global.ticks / 4) % 2) == 0 ? this.hitColor : this.color;
boolean sprite = this.jumping || ((Global.ticks + this.id) / 30) % 2 == 0;
int sx = sprite ? BASE_SPRITE_X + 2 : BASE_SPRITE_X, sy = BASE_SPRITE_Y;
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= 1; j++) {
Renderer.render(
sx + i, sy + j,
this.x + this.getRenderOffsetX() + (i * 8),
this.y + this.getRenderOffsetY() + (j * 8),
color, Renderer.FLIP_NONE
);
}
}
}
@Override
public double getStaminaRechargeRate() {
return 0.2 * this.strength;
}
@Override
public int getMaxStamina() {
return (int) (10 * this.strength);
}
@Override
public int getMaxHealth() {
return (int) (10 * this.strength);
}
@Override
public boolean canSwimIn(Tile tile, int x, int y) {
return false;
}
}

View File

@@ -0,0 +1,148 @@
package com.jdh.microcraft.entity.mob;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.ai.AIZombie;
import com.jdh.microcraft.entity.particle.EntitySmashParticle;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.armor.ItemArmor;
import com.jdh.microcraft.item.tool.ItemTool;
import com.jdh.microcraft.level.Level;
public class EntityZombie extends EntityHumanoid {
private static final Item[] LOOT_TABLE = {
Item.GEM, Item.GOLD_INGOT, Item.MITHRIL_INGOT,
Item.IRON_INGOT, Item.IRON_INGOT, Item.IRON_INGOT, Item.IRON_INGOT,
Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE,
Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE, Item.IRON_ORE,
Item.ROCK, Item.ROCK, Item.ROCK, Item.ROCK, Item.ROCK, Item.ROCK, Item.ROCK
};
private final int pantsColor, shirtColor;
public final double strength;
public EntityZombie(Level level, int x, int y, double strength) {
super(level, new Inventory(5));
this.strength = strength * Global.game.difficulty;
this.x = x;
this.y = y;
this.pantsColor = Color.randomRGB(1, 5);
this.shirtColor = Color.randomRGB(1, 5);
this.ai = new AIZombie(this);
this.health = this.getMaxHealth();
this.stamina = this.getMaxStamina();
}
public static EntityZombie createEasyZombie(Level level) {
EntityZombie zombie = new EntityZombie(
level, 0, 0, 0.9
);
EntityHumanoid.giveRandomEquipment(zombie, 1, true, true);
return zombie;
}
public static EntityZombie createMediumZombie(Level level) {
EntityZombie zombie = new EntityZombie(
level, 0, 0, 1.0
);
EntityHumanoid.giveRandomEquipment(zombie, 2, true, true);
return zombie;
}
public static EntityZombie createHardZombie(Level level) {
EntityZombie zombie = new EntityZombie(
level, 0, 0, 1.15
);
EntityHumanoid.giveRandomEquipment(zombie, 3, true, true);
return zombie;
}
@Override
protected int getAnimationFrameTicks() {
return Math.max(10 - ((int) (2.0 / this.strength)), 6);
}
@Override
public void die() {
super.die();
EntitySmashParticle.spawn(this.level, this.x, this.y, this.getColor(), 4, 8);
if (Global.random.nextBoolean()) {
EntityItem.spawn(
this.level,
new ItemStack(new ItemInstance(LOOT_TABLE[Global.random.nextInt(LOOT_TABLE.length)], 0), 1),
this.x, this.y
);
}
Global.game.score += 30 * this.strength;
}
@Override
public int getHitDamage(Entity e) {
return (int) (super.getHitDamage(e) * this.strength);
}
@Override
public void collide(Entity e) {
super.collide(e);
if (e instanceof EntityPlayer) {
this.hit(e);
e.onHit(this);
}
}
@Override
public boolean canPickup(EntityItem e) {
return (e.stack.instance.item instanceof ItemTool && this.equipped == null) ||
(e.stack.instance.item instanceof ItemArmor &&
this.armor[((ItemArmor) e.stack.instance.item).slot] == null);
}
@Override
public void pickup(ItemStack stack) {
super.pickup(stack);
if (stack.instance.item instanceof ItemTool && this.equipped == null) {
this.equipped = stack;
} else if (stack.instance.item instanceof ItemArmor) {
ItemArmor armor = (ItemArmor) stack.instance.item;
if (this.armor[armor.slot] == null) {
this.armor[armor.slot] = stack;
}
}
}
@Override
public int getColor() {
return Color.get(000, this.pantsColor, this.shirtColor, 141);
}
@Override
public int getHurtColor() {
return Color.get(544, Color.add(this.pantsColor, 222), Color.add(this.shirtColor, 222), 555);
}
@Override
public double getStaminaRechargeRate() {
return 0.15;
}
@Override
public int getMaxStamina() {
return (int) (10 * this.strength);
}
@Override
public int getMaxHealth() {
return (int) (10 * this.strength);
}
}

View File

@@ -0,0 +1,70 @@
package com.jdh.microcraft.entity.particle;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
public class EntityParticle extends Entity {
protected int timeToLive;
protected double vx, vy, dx, dy;
protected boolean gravity;
public EntityParticle(Level level, int x, int y) {
super(level);
this.x = x;
this.y = y;
this.width = 2;
this.height = 2;
this.timeToLive = 45 + Global.random.nextInt(60);
this.vx = (Global.random.nextBoolean() ? -1.0 : 1.0) * (0.2 + (Global.random.nextDouble() * 0.4));
this.vy = -(0.8 + (Global.random.nextDouble() * 0.5));
this.gravity = true;
}
protected int getRenderX() {
return (int) (this.x + this.dx);
}
protected int getRenderY() {
return (int) (this.y + this.dy);
}
@Override
public void tick() {
if (--this.timeToLive == 0) {
this.remove();
return;
}
this.dx += this.vx;
this.dy += this.vy;
if (this.gravity && this.dy >= 8.0) {
// bounce at bottom
this.vx *= 0.3;
this.vy *= (this.vy > 0 ? -1.0 : 1.0) * 0.4;
} else if (Math.abs(this.vy) < 0.01) {
// clamp small values
this.vx = 0.0;
this.vy = 0.0;
} else if (this.gravity) {
// gravity
this.vy += 0.1;
}
if (Math.abs(this.vx) < 0.01) {
this.vx = 0.0;
}
}
@Override
public boolean collides(Entity e) {
return false;
}
@Override
public boolean canSwimIn(Tile tile, int x, int y) {
return true;
}
}

View File

@@ -0,0 +1,33 @@
package com.jdh.microcraft.entity.particle;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.level.Level;
public class EntitySmashParticle extends EntityParticle {
private static final int SPRITE_X = 12, SPRITE_Y = 2;
private final int color;
public EntitySmashParticle(Level level, int x, int y, int color) {
super(level, x, y);
this.color = color;
}
public static void spawn(Level level, int x, int y, int color, int min, int max) {
int n = min + Global.random.nextInt(max - min + 1);
for (int i = 0; i < n; i++) {
level.addEntity(new EntitySmashParticle(level, x, y, color));
}
}
@Override
public void render() {
Global.random.setSeed(this.id);
Renderer.render(
SPRITE_X, SPRITE_Y, this.getRenderX(), this.getRenderY(), color,
(Global.random.nextBoolean() ? Renderer.FLIP_X : Renderer.FLIP_NONE) |
(Global.random.nextBoolean() ? Renderer.FLIP_Y : Renderer.FLIP_NONE)
);
}
}

View File

@@ -0,0 +1,36 @@
package com.jdh.microcraft.entity.particle;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.level.Level;
public class EntitySmokeParticle extends EntityParticle {
private static final int SPRITE_X = 12, SPRITE_Y = 2;
private final int color;
public EntitySmokeParticle(Level level, int x, int y, int color) {
super(level, x, y);
this.color = color;
this.gravity = false;
this.vx = (Global.random.nextBoolean() ? -1.0 : 1.0) * (0.1 + (Global.random.nextDouble() * 0.1));
this.vy = -(0.4 + (Global.random.nextDouble() * 0.3));
}
public static void spawn(Level level, int x, int y, int color, int min, int max) {
int n = min + Global.random.nextInt(max - min + 1);
for (int i = 0; i < n; i++) {
level.addEntity(new EntitySmokeParticle(level, x, y, color));
}
}
@Override
public void render() {
Global.random.setSeed(this.id);
Renderer.render(
SPRITE_X, SPRITE_Y, this.getRenderX(), this.getRenderY(), color,
(Global.random.nextBoolean() ? Renderer.FLIP_X : Renderer.FLIP_NONE) |
(Global.random.nextBoolean() ? Renderer.FLIP_Y : Renderer.FLIP_NONE)
);
}
}

View File

@@ -0,0 +1,29 @@
package com.jdh.microcraft.entity.particle;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.level.Level;
public class EntityTextParticle extends EntityParticle {
private final String text;
private final int color;
public EntityTextParticle(Level level, int x, int y, String text, int color) {
super(level, x, y);
this.text = text;
this.color = color;
this.timeToLive = 30 + Global.random.nextInt(30);
}
public static void spawn(Level level, int x, int y, String text, int color) {
level.addEntity(new EntityTextParticle(level, x, y, text, color));
}
@Override
public void render() {
Global.random.setSeed(this.id);
Font.render(this.text, this.getRenderX() + 1, this.getRenderY() + 1, Color.add(this.color, -222));
Font.render(this.text, this.getRenderX(), this.getRenderY(), this.color);
}
}

View File

@@ -0,0 +1,64 @@
package com.jdh.microcraft.entity.projectile;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.level.Level;
public class EntityAirBlast extends EntityProjectile {
public EntityAirBlast(Level level, Entity shooter, int x, int y, int dx, int dy, double s, int damage) {
super(level, shooter, x, y, dx, dy, s, damage, 180);
this.width = 4;
this.height = 4;
}
@Override
public boolean hit(Entity e) {
if (e instanceof EntityItem) {
e.remove();
return true;
} else if (!(e instanceof EntityMob)) {
return false;
}
((EntityMob) e).hurt(this.damage, this);
this.remove();
return true;
}
@Override
protected boolean moveAxis(int dx, int dy) {
if (!super.moveAxis(dx, dy)) {
this.remove();
return false;
}
return true;
}
@Override
public int getColor() {
return Color.get(222, 333, 445, 555);
}
@Override
protected int getHorizontalSpriteX() {
return 14;
}
@Override
protected int getHorizontalSpriteY() {
return 8;
}
@Override
protected int getVerticalSpriteX() {
return 14;
}
@Override
protected int getVerticalSpriteY() {
return 8;
}
}

View File

@@ -0,0 +1,54 @@
package com.jdh.microcraft.entity.projectile;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.level.Level;
public class EntityArrow extends EntityProjectile {
public EntityArrow(Level level, Entity shooter, int x, int y, int dx, int dy, double s, int damage) {
super(level, shooter, x, y, dx, dy, s, damage, 180);
this.width = 4;
this.height = 4;
}
@Override
public boolean hit(Entity e) {
if (e instanceof EntityItem) {
e.remove();
return true;
} else if (!(e instanceof EntityMob)) {
return false;
}
((EntityMob) e).hurt(this.damage, this);
this.remove();
return true;
}
@Override
public int getColor() {
return Color.get(220, 330, 333, 444);
}
@Override
protected int getHorizontalSpriteX() {
return 1;
}
@Override
protected int getHorizontalSpriteY() {
return 8;
}
@Override
protected int getVerticalSpriteX() {
return 0;
}
@Override
protected int getVerticalSpriteY() {
return 8;
}
}

View File

@@ -0,0 +1,122 @@
package com.jdh.microcraft.entity.projectile;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.Entity;
import com.jdh.microcraft.entity.EntityItem;
import com.jdh.microcraft.entity.furniture.EntityFurnace;
import com.jdh.microcraft.entity.furniture.EntityFurniture;
import com.jdh.microcraft.entity.mob.EntityMob;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.level.Level;
import com.jdh.microcraft.level.tile.Tile;
import com.jdh.microcraft.util.Direction;
import com.jdh.microcraft.util.FMath;
public abstract class EntityProjectile extends Entity {
protected final int damage;
protected final Entity shooter;
private int timeToLive;
private double vx, vy;
public EntityProjectile(Level level, Entity shooter, int x, int y, int dx, int dy, double s, int damage, int ttl) {
super(level);
this.shooter = shooter;
this.damage = damage;
this.timeToLive = ttl;
this.x = x;
this.y = y;
// compute velocities in direction of dx, dy
double xx = FMath.sign(dx - this.x),
yy = FMath.sign(dy - this.y),
l = FMath.norm(xx, yy);
xx = (xx / l) * s;
yy = (yy / l) * s;
this.vx = Double.isNaN(xx) ? 0 : xx;
this.vy = Double.isNaN(yy) ? 0 : yy;
}
@Override
public void tick() {
super.tick();
this.move(
FMath.tickedDoubleToInt(Global.ticks, this.vx),
FMath.tickedDoubleToInt(Global.ticks, this.vy)
);
if (--this.timeToLive == 0) {
this.remove();
}
}
@Override
public void render() {
int sx = 0, sy = 0, flip = 0;
switch (this.direction) {
case NORTH, SOUTH -> {
sx = this.getVerticalSpriteX();
sy = this.getVerticalSpriteY();
flip = this.direction == Direction.SOUTH ? Renderer.FLIP_Y : 0;
}
case EAST, WEST -> {
sx = this.getHorizontalSpriteX();
sy = this.getHorizontalSpriteY();
flip = this.direction == Direction.WEST ? Renderer.FLIP_X : 0;
}
}
Renderer.render(
sx, sy,
this.x + this.getRenderOffsetX(), this.y + this.getRenderOffsetY(),
this.getColor(), flip
);
}
@Override
public boolean collides(Entity e) {
return e != this.shooter &&
!(e instanceof EntityProjectile) &&
(Math.abs(this.vx) > 0.0 || Math.abs(this.vy) > 0.0);
}
@Override
public void collide(Entity e) {
super.collide(e);
if (this.hit(e)) {
e.onHit(this);
}
}
@Override
protected boolean moveAxis(int dx, int dy) {
if (!super.moveAxis(dx, dy)) {
this.vx = 0.0;
this.vy = 0.0;
return false;
}
return true;
}
@Override
public boolean canSwimIn(Tile tile, int x, int y) {
return true;
}
@Override
public abstract boolean hit(Entity e);
public abstract int getColor();
protected abstract int getHorizontalSpriteX();
protected abstract int getHorizontalSpriteY();
protected abstract int getVerticalSpriteX();
protected abstract int getVerticalSpriteY();
}

View File

@@ -0,0 +1,13 @@
package com.jdh.microcraft.gfx;
import com.jdh.microcraft.util.FMath;
public class Camera {
// translation
public int tx = 0, ty = 0;
public void centerOn(int x, int y, int minX, int minY, int maxX, int maxY) {
this.tx = FMath.clamp(x - (Renderer.WIDTH / 2), minX, maxX - Renderer.WIDTH);
this.ty = FMath.clamp(y - (Renderer.HEIGHT / 2), minY, maxY - Renderer.HEIGHT);
}
}

View File

@@ -0,0 +1,87 @@
package com.jdh.microcraft.gfx;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.util.FMath;
public class Color {
// get(a, b, c, d) but with array elements
public static int get(int[] abcd) {
return Color.get(abcd[0], abcd[1], abcd[2], abcd[3]);
}
// gets color palette integer of specified 4 colors in format RGB where each component is one (base 10) digit
public static int get(int a, int b, int c, int d) {
return ((Color.map(d) & 0xFF) << 24) |
((Color.map(c) & 0xFF) << 16) |
((Color.map(b) & 0xFF) << 8) |
(Color.map(a) & 0xFF);
}
// replaces color c component i with RGB triple x
public static int withComponent(int c, int x, int i) {
return (c & ~(0xFF << (i * 8))) | ((Color.map(x) & 0xFF) << (i * 8));
}
// gets a single component of a color as a triple
public static int component(int c, int i) {
return Color.iToRGB((c >> (i * 8)) & 0xFF);
}
// palette index to rgb triple
public static int iToRGB(int i) {
int r = i / 36,
g = ((i - (r * 36)) / 6),
b = ((i - (r * 36)) % 6);
return (r * 100) + (g * 10) + b;
}
// maps RGB (where R, G, B are base 10 digits in [0..5]) to palette index
public static int map(int d) {
if (d < 0) {
return 0;
}
return ((d / 100) % 10) * 36 + ((d / 10) % 10) * 6 + (d % 10);
}
// adds and clamps a single RGB triple
public static int add(int c, int v) {
return FMath.clamp(((c / 100) % 10) + ((v / 100) % 10), 0, 5) * 100 +
FMath.clamp(((c / 10) % 10) + ((v / 10) % 10), 0, 5) * 10 +
FMath.clamp((c % 10) + (v % 10), 0, 5);
}
// multiplies and clamps a single RGB triple
public static int mul(int c, int v) {
return FMath.clamp(((c / 100) % 10) * ((v / 100) % 10), 0, 5) * 100 +
FMath.clamp(((c / 10) % 10) * ((v / 10) % 10), 0, 5) * 10 +
FMath.clamp((c % 10) * (v % 10), 0, 5);
}
// adds and clamps all RGB triples in a color
public static int addAll(int c, int v) {
return get(
add(component(c, 0), v),
add(component(c, 1), v),
add(component(c, 2), v),
add(component(c, 3), v)
);
}
public static int randomRGB(int min, int max) {
return
(Global.random.nextInt(max - min + 1)) * 100 +
(Global.random.nextInt(max - min + 1)) * 10 +
(Global.random.nextInt(max - min + 1));
}
// gets r, g, or b from an RGB triple
public static int getRGBComponent(int rgb, int i) {
return (rgb / ((int) Math.pow(10, i))) % 10;
}
// gets RGB triple from R, G, B components in 0..5 range
public static int getRGB(int r, int g, int b) {
return (r * 100) + (g * 10) + b;
}
}

View File

@@ -0,0 +1,125 @@
package com.jdh.microcraft.gfx;
import java.util.Arrays;
import java.util.Optional;
public class Font {
public enum Colors {
RED('R', 500),
ORANGE('O', 441),
YELLOW('Y', 550),
GREEN('G', 050),
BLUE('B', 005),
PURPLE('P', 404),
PINK('I', 533),
WHITE('W', 555),
GREY('E', 333),
BLACK('L', 000),
DARK_GREY('D', 111);
public final char c;
public final int color;
Colors(char c, int color) {
this.c = c;
this.color = color;
}
@Override
public String toString() {
return "$" + this.c;
}
}
private static final int BASE_OFFSET_X = 0, BASE_OFFSET_Y = 13;
private static final String[] LAYOUT = {
"ABCDEFGHIJKLMNOP",
"QRSTUVWXYZ!/<>:+",
"1234567890&=().?"
};
public static int width(String s) {
int w = 0;
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '$' && i != (chars.length - 1)) {
if (chars[i + 1] != '$') {
i++;
}
continue;
}
w += 8;
}
return w;
}
public static int offset(char c) {
for (int i = 0; i < LAYOUT.length; i++) {
int j;
if ((j = LAYOUT[i].indexOf(c)) != -1) {
return ((BASE_OFFSET_Y + i) * Renderer.spritesheet.sizeSprites) + (BASE_OFFSET_X + j);
}
}
// character not found -> ?
return 15 * Renderer.spritesheet.sizeSprites + 15;
}
public static void render(char c, int x, int y, int color) {
Font.render(c, x, y, color, -1);
}
public static void render(char c, int x, int y, int color, int backgroundColor) {
if (Character.isWhitespace(c)) {
return;
}
if (backgroundColor != -1) {
Renderer.fill(x, y, 8, 8, backgroundColor);
}
Renderer.render(
Font.offset(c),
x, y,
Color.get(color, color, color, color),
Renderer.FLIP_NONE
);
}
public static void render(String s, int x, int y, int color) {
Font.render(s, x, y, color, -1);
}
// color, backgroundColor should be single RGB digit triple
// no background if backgroundColor == -1
public static void render(String s, int x, int y, int color, int backgroundColor) {
int count = 0;
int currentColor = color;
for (int i = 0; i < s.length(); i++) {
char c = Character.toUpperCase(s.charAt(i));
if (c == '$' && (i != (s.length() - 1))) {
char n = s.charAt(i + 1);
if (n == '$') {
continue;
} else {
Optional<Colors> cl = Arrays.stream(Colors.values()).filter(e -> e.c == n).findFirst();
if (cl.isEmpty()) {
throw new IllegalStateException();
}
currentColor = cl.get().color;
i++;
continue;
}
}
render(c, x + (count * 8), y, currentColor, backgroundColor);
count++;
}
}
}

View File

@@ -0,0 +1,11 @@
package com.jdh.microcraft.gfx;
public class Light {
public final int x, y, power;
public Light(int x, int y, int power) {
this.x = x;
this.y = y;
this.power = power;
}
}

View File

@@ -0,0 +1,224 @@
package com.jdh.microcraft.gfx;
import com.jdh.microcraft.util.AABB;
import com.jdh.microcraft.util.FMath;
import java.util.*;
public class Renderer {
public static final int[] DITHER = new int[]{0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
// renderer flags
public static final int FLIP_NONE = 0x00, FLIP_X = 0x01, FLIP_Y = 0x02, FLIP_XY = FLIP_X | FLIP_Y;
// target width/height
public static final int WIDTH = 256, HEIGHT = 144;
// screen pixels, each entry in [0..216) referring to palette entry
// palette is determined by Window
public static int[] pixels = new int[WIDTH * HEIGHT];
// global spritesheet
public static Spritesheet spritesheet = new Spritesheet("/tiles.png", 8);
// stored camera stack
private static Stack<Camera> cameraStack = new Stack<>();
// lights
private static List<Light> lights = new ArrayList<>();
// current camera
public static Camera camera = new Camera();
// generates color palette, 24-bpp RGB
public static int[] generatePalette() {
int[] result = new int[256];
int i = 0;
for (int r = 0; r < 6; r++) {
for (int g = 0; g < 6; g++) {
for (int b = 0; b < 6; b++) {
int rr = (r * 255) / 5,
gg = (g * 255) / 5,
bb = (b * 255) / 5,
m = (rr * 30 + gg * 59 + bb * 11) / 100;
result[i++] = ((((rr + m) / 2) * 230 / 255 + 10) << 16) |
((((gg + m) / 2) * 230 / 255 + 10) << 8) |
((((bb + m) / 2) * 230 / 255 + 10) << 0);
}
}
}
return result;
}
public static void reset() {
Renderer.clear();
Renderer.clearLights();
Renderer.cameraStack.clear();
Renderer.camera = new Camera();
}
public static void pushCamera() {
Renderer.cameraStack.push(Renderer.camera);
Renderer.camera = new Camera();
}
public static void popCamera() {
Renderer.camera = Renderer.cameraStack.pop();
}
public static void clearLights() {
Renderer.lights.clear();
}
public static void addLights(Collection<Light> lights) {
Renderer.lights.addAll(lights);
}
public static AABB getAABB() {
return new AABB(
Renderer.camera.tx,
Renderer.camera.ty,
Renderer.camera.tx + Renderer.WIDTH,
Renderer.camera.ty + Renderer.HEIGHT
);
}
public static boolean inBounds(int x, int y) {
int xt = x - Renderer.camera.tx, yt = y - Renderer.camera.ty;
return xt >= 0 && yt >= 0 && xt < Renderer.WIDTH && yt < Renderer.HEIGHT;
}
public static void clear() {
Arrays.fill(Renderer.pixels, 0);
}
public static void fill(int x, int y, int w, int h, int color) {
int xt = x - Renderer.camera.tx, yt = y - Renderer.camera.ty;
// check if entirely offscreen
if (xt + w < 0 || yt + h < 0) {
return;
}
for (int yy = yt; yy < yt + h && yy < Renderer.HEIGHT; yy++) {
if (yy < 0) {
continue;
}
for (int xx = xt; xx < xt + w && xx < Renderer.WIDTH; xx++) {
if (xx < 0) {
continue;
}
Renderer.pixels[yy * Renderer.WIDTH + xx] = Color.map(color);
}
}
}
public static void render(Sprite sprite, int x, int y, int color) {
int xt = x - Renderer.camera.tx, yt = y - Renderer.camera.ty;
if (xt + sprite.width < 0 || yt + sprite.height < 0) {
return;
}
for (int yy = yt, ys = 0; yy < yt + sprite.height && yy < Renderer.HEIGHT; yy++, ys++) {
if (yy < 0) {
continue;
}
for (int xx = xt, xs = 0; xx < xt + sprite.width && xx < Renderer.WIDTH; xx++, xs++) {
if (xx < 0) {
continue;
}
int p = sprite.pixels[ys * sprite.width + xs];
if (p >= 0) {
Renderer.pixels[yy * Renderer.WIDTH + xx] = (color >> (p * 8)) & 0xFF;
}
}
}
}
public static void render(int s, int x, int y, int color, int flags) {
Renderer.render(
s % Renderer.spritesheet.sizeSprites,
s / Renderer.spritesheet.sizeSprites,
x, y, color, flags
);
}
public static void render(int sx, int sy, int x, int y, int color, int flags) {
int posX = x - camera.tx, posY = y - camera.ty,
minX = sx * Renderer.spritesheet.size, minY = sy * Renderer.spritesheet.size,
maxX = minX + Renderer.spritesheet.size, maxY = minY + Renderer.spritesheet.size;
// sprite will not be shown at all
if (posX + Renderer.spritesheet.size < 0 || posY + Renderer.spritesheet.size < 0) {
return;
}
for (int ys = minY, yr = posY; ys < maxY && yr < Renderer.HEIGHT; ys++, yr++) {
if (yr < 0) {
continue;
}
for (int xs = minX, xr = posX; xs < maxX && xr < Renderer.WIDTH; xs++, xr++) {
if (xr < 0) {
continue;
}
int p = Renderer.spritesheet.pixels[
((flags & FLIP_Y) == 0 ? ys : (Renderer.spritesheet.size - (ys - minY) - 1 + minY))
* Renderer.spritesheet.width +
((flags & FLIP_X) == 0 ? xs : (Renderer.spritesheet.size - (xs - minX) - 1 + minX))];
if (p >= 0) {
Renderer.pixels[yr * Renderer.WIDTH + xr] = (color >> (p * 8)) & 0xFF;
}
}
}
}
// lights the current frame stored by the renderer
public static void light(int d) {
AABB aabb = Renderer.getAABB();
// compute (approx.) lights which affect this frame
Light[] lights = Renderer.lights.stream()
.filter(l -> {
int p = l.power * 16;
return AABB.collide(
aabb.minX, aabb.minY, aabb.maxX, aabb.maxY,
l.x - p, l.y - p, l.x + p, l.y + p
);
}).toArray(Light[]::new);
int[] oldPixels = Renderer.pixels.clone();
Arrays.fill(Renderer.pixels, 0);
int tx = Renderer.camera.tx, ty = Renderer.camera.ty;
for (int i = 0; i < (Renderer.WIDTH * Renderer.HEIGHT); i++) {
int x = i % Renderer.WIDTH, y = i / Renderer.WIDTH;
// compute contribution from each light
for (Light l : lights) {
int dx = l.x - (x + tx),
dy = l.y - (y + ty),
dist = (int) FMath.norm(dx, dy);
if (dist < (l.power * 4) ||
(dist / l.power) <= DITHER[(((Math.abs(dy) % 4) * 4) + (Math.abs(dx) % 4))]) {
Renderer.pixels[i] = d != 0 ?
Color.map(Color.add(Color.iToRGB(oldPixels[i]), d)) :
oldPixels[i];
break;
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
package com.jdh.microcraft.gfx;
import com.jdh.microcraft.Main;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Sprite {
// sprite size in pixels
public int width, height;
// sprite pixels, mapped into [0..3] space
public int[] pixels;
public Sprite(String path) {
BufferedImage image;
try {
image = ImageIO.read(Main.class.getResourceAsStream(path));
} catch (IOException e) {
throw new Error(e);
}
this.width = image.getWidth();
this.height = image.getHeight();
this.pixels = new int[this.width * this.height];
int[] imagePixels = image.getRGB(
0, 0, image.getWidth(), image.getHeight(),
null, 0, image.getWidth());
for (int i = 0; i < this.width * this.height; i++) {
this.pixels[i] = ((imagePixels[i] >> 24) & 0xFF) != 0xFF ? -1 : (imagePixels[i] & 0xFF) / 64;
}
}
}

View File

@@ -0,0 +1,48 @@
package com.jdh.microcraft.gfx;
import com.jdh.microcraft.Main;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Spritesheet {
// spritesheet size in pixels
public int width, height;
// spritesheet sprite size
public int size;
// spritesheet size in sprites
public int sizeSprites;
// spritesheet pixels, mapped into [0..3] space
public int[] pixels;
public Spritesheet(String path, int size) {
this.size = size;
BufferedImage image;
try {
image = ImageIO.read(Main.class.getResourceAsStream(path));
} catch (IOException e) {
throw new Error(e);
}
this.width = image.getWidth();
this.height = image.getHeight();
this.pixels = new int[this.width * this.height];
assert(this.width == this.height);
this.sizeSprites = this.width / this.size;
int[] imagePixels = image.getRGB(
0, 0, image.getWidth(), image.getHeight(),
null, 0, image.getWidth());
for (int i = 0; i < this.width * this.height; i++) {
this.pixels[i] = ((imagePixels[i] >> 24) & 0xFF) != 0xFF ? -1 : (imagePixels[i] & 0xFF) / 64;
}
}
}

View File

@@ -0,0 +1,106 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.furniture.EntityChest;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.util.ControlHandler;
import java.util.List;
public class ChestMenu extends Menu {
private class ChestPlayerInventoryMenu extends PlayerInventoryMenu {
public ChestPlayerInventoryMenu(EntityPlayer player) {
super(player, false);
this.focused = true;
}
}
private class ChestInventoryMenu extends InventoryMenu {
public ChestInventoryMenu(EntityChest chest) {
super(chest.inventory, false);
this.focused = false;
}
@Override
public String getName() {
return "CHEST";
}
}
private final ChestPlayerInventoryMenu playerMenu;
private final ChestInventoryMenu chestMenu;
private final List<Menu> submenus;
public ChestMenu(EntityPlayer player, EntityChest chest) {
this.playerMenu = new ChestPlayerInventoryMenu(player);
this.chestMenu = new ChestInventoryMenu(chest);
this.submenus = List.of(this.playerMenu, this.chestMenu);
}
@Override
public void init() {
super.init();
for (Menu m : this.submenus) {
m.init();
}
}
@Override
public void destroy() {
super.destroy();
for (Menu m : this.submenus) {
m.destroy();
}
}
@Override
public void tick() {
super.tick();
if (ControlHandler.MENU_LEFT.pressedTick() && !this.playerMenu.focused) {
this.playerMenu.focused = true;
this.chestMenu.focused = false;
}
if (ControlHandler.MENU_RIGHT.pressedTick() && !this.chestMenu.focused) {
this.playerMenu.focused = false;
this.chestMenu.focused = true;
}
boolean drop = ControlHandler.DROP.pressedTick();
if (ControlHandler.MENU_SELECT.pressedTick() || drop) {
InventoryMenu from = this.playerMenu.focused ? this.playerMenu : this.chestMenu,
to = from == this.playerMenu ? this.chestMenu : this.playerMenu;
List<ItemStack> fromItems = from.getItems();
if (from.selectedIndex >= 0 && from.selectedIndex < fromItems.size()) {
ItemStack s = fromItems.get(from.selectedIndex);
if (s.instance.item.isDroppable()) {
to.inventory.add(from.inventory.remove(s.instance.item, drop ? 1 : s.size));
}
}
}
for (Menu m : this.submenus) {
m.tick();
}
}
@Override
public void update() {
super.update();
for (Menu m : this.submenus) {
m.update();
}
}
@Override
public void render() {
super.render();
for (Menu m : this.submenus) {
m.render();
}
}
}

View File

@@ -0,0 +1,184 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.mob.EntityHumanoid;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.ControlHandler;
import com.jdh.microcraft.util.FMath;
public class ColorSelectMenu extends Menu {
private static final String[] TITLE = new String[]{
"CHOOSE YOUR",
Font.Colors.YELLOW + "APPEARANCE"
};
private static final String[] LABELS = new String[]{
"OUTLINE/HAIR", "PANTS", "SHIRT", "SKIN"
};
private static final String[] COLOR_LABELS = new String[]{
"R", "G", "B"
};
private static final String[] INSTRUCTIONS = new String[] {
Font.Colors.GREY + "(" + Font.Colors.YELLOW + "X" + Font.Colors.GREY + " TO DECREASE/" +
Font.Colors.YELLOW + "C" + Font.Colors.GREY + " TO INCREASE)",
Font.Colors.GREY + "(" + Font.Colors.YELLOW + "R" + Font.Colors.GREY + " TO RANDOMIZE" +
Font.Colors.GREY + ")",
"",
Font.Colors.GREY + "PRESS " + Font.Colors.YELLOW + "ENTER"
};
public int[] colors;
private int colorIndex, rgbIndex;
public ColorSelectMenu() {
this.colorIndex = 0;
this.rgbIndex = 0;
this.randomize();
}
private void randomize() {
this.colors = new int[]{
Color.randomRGB(0, 2),
Color.randomRGB(0, 5),
Color.randomRGB(0, 5),
Color.randomRGB(0, 5)
};
}
// updates currently selected color component by the specified change (d)
private void updateCurrentComponent(int d) {
int rgb = this.colors[this.colorIndex];
int[] components = {
Color.getRGBComponent(rgb, 0),
Color.getRGBComponent(rgb, 1),
Color.getRGBComponent(rgb, 2)
};
components[2 - this.rgbIndex] = FMath.clamp(components[2 - this.rgbIndex] + d, 0, 5);
this.colors[this.colorIndex] = Color.getRGB(
components[2],
components[1],
components[0]
);
}
@Override
public void tick() {
super.tick();
if (ControlHandler.MENU_UP.pressedTick() && this.colorIndex > 0) {
this.colorIndex--;
}
if (ControlHandler.MENU_DOWN.pressedTick() && this.colorIndex < (LABELS.length - 1)) {
this.colorIndex++;
}
if (ControlHandler.MENU_LEFT.pressedTick() && this.rgbIndex > 0) {
this.rgbIndex--;
}
if (ControlHandler.MENU_RIGHT.pressedTick() && this.rgbIndex < (COLOR_LABELS.length - 1)) {
this.rgbIndex++;
}
if (ControlHandler.MENU_INCREASE.pressedTick()) {
this.updateCurrentComponent(+1);
}
if (ControlHandler.MENU_DECREASE.pressedTick()) {
this.updateCurrentComponent(-1);
}
if (ControlHandler.MENU_QUIT.pressedTick()) {
Global.mainMenu.menu = Global.mainMenu.mainMenu;
}
if (ControlHandler.MENU_SELECT.pressedTick()) {
Sound.CRAFT.play();
Global.mainMenu.menu = Global.mainMenu.difficultySelectMenu;
}
if (ControlHandler.MENU_RANDOMIZE.pressedTick()) {
this.randomize();
}
}
@Override
public void render() {
super.render();
int y = 8;
for (String s : TITLE) {
Font.render(s, (Renderer.WIDTH - Font.width(s)) / 2, y, 555);
y += 8;
}
y += 8;
int px = (Renderer.WIDTH - 16) / 2, py = y,
sx = EntityHumanoid.BASE_SPRITE_X, sy = EntityHumanoid.BASE_SPRITE_Y;
boolean flip = (Global.ticks / 15) % 2 == 0;
for (int i = 0; i <= 1; i++) {
for (int j = 0; j <= 1; j++) {
Renderer.render(
sx + (flip ? (1 - i) : i), sy + j,
px + (i * 8), py + (j * 8),
Color.get(colors[0], colors[1], colors[2], colors[3]),
flip ? Renderer.FLIP_X : Renderer.FLIP_NONE
);
}
}
y += 24;
// labels/colors
int maxWidth = 0;
for (String label : LABELS) {
int w = Font.width(label);
if (w > maxWidth) {
maxWidth = w;
}
}
for (int i = 0; i < 4; i++) {
int x = (Renderer.WIDTH - (maxWidth + 8 + (24 * 3) + 4 + 8)) / 2;
if (i == this.colorIndex) {
Font.render(">", x - 8, y, 555);
}
Font.render(LABELS[i], x, y, i == this.colorIndex ? 555 : 333);
x += maxWidth + 8;
for (int j = 0; j < 3; j++) {
Font.render(COLOR_LABELS[j], x, y, (i == this.colorIndex && j == this.rgbIndex) ? 550 : 333);
Renderer.fill(
x + 12, y, 8, 8,
Color.getRGBComponent(this.colors[i], 2 - j) * (int) Math.pow(10, 2 - j)
);
x += 24;
}
x += 4;
Renderer.fill(x, y, 8, 8, this.colors[i]);
x += 8;
if (i == this.colorIndex) {
Font.render("<", x, y, 555);
}
y += 12;
}
y += 8;
for (String s : INSTRUCTIONS) {
Font.render(s, (Renderer.WIDTH - Font.width(s)) / 2, y, 555);
y += 8;
}
}
}

View File

@@ -0,0 +1,97 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.util.Keyboard;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DialogMenu extends Menu {
protected final String title;
protected final String[] lines;
protected final int w;
protected final int h;
protected final Runnable onClose;
protected final int x, y;
public DialogMenu(String title, String[] lines, int w, int h, Runnable onClose) {
this.title = title;
this.lines = lines;
this.w = w;
this.h = h;
this.x = (Renderer.WIDTH - (this.w * 8)) / 2;
this.y = (Renderer.HEIGHT - (this.h * 8)) / 2;
this.onClose = onClose;
this.focused = true;
}
protected boolean shouldCenterText() {
return false;
}
protected boolean showPressAnyKey() {
return true;
}
protected boolean shouldAlignToMaxWidth() {
return false;
}
protected String[] getLines() {
return this.lines;
}
@Override
public void render() {
super.render();
this.renderMenu(title, x, y, this.w, this.h);
List<String> allLines = new ArrayList<>(Arrays.asList(this.getLines()));
if (this.showPressAnyKey()) {
allLines.add(" ");
allLines.add(Font.Colors.GREY + "PRESS ANY KEY");
}
int maxWidth = 0;
for (String line : this.getLines()) {
maxWidth = Math.max(maxWidth, Font.width(line));
}
for (int i = 0; i < allLines.size(); i++) {
String s = allLines.get(i);
int tx;
if (this.shouldCenterText() ||
(this.shouldAlignToMaxWidth() && i == (allLines.size() - 1))) {
tx = x + (((this.w * 8) - Font.width(s)) / 2);
} else if (this.shouldAlignToMaxWidth()) {
tx = x + (((this.w * 8) - maxWidth) / 2);
} else {
tx = x + 8;
}
Font.render(s, tx, y + ((i + 1) * 8), 555, -1);
}
}
@Override
public void tick() {
super.tick();
if (this.showPressAnyKey()) {
for (Keyboard.Key k : Keyboard.keys) {
if (k.pressedTick) {
if (Global.game != null) {
Global.game.setMenu(null);
}
this.onClose.run();
}
}
}
}
}

View File

@@ -0,0 +1,99 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.util.ControlHandler;
public class DifficultySelectMenu extends Menu {
private static final String[] TITLE = {
Font.Colors.WHITE + "CHOOSE YOUR",
Font.Colors.RED + "DIFFICULTY"
};
private static final String[] INSTRUCTIONS = new String[] {
Font.Colors.GREY + "PRESS " + Font.Colors.YELLOW + "ENTER" + Font.Colors.GREY + " TO START!"
};
private static final int COUNT = 5;
private static final String[] DIFFICULTY_NAMES = {
"TOO EASY",
"EASY",
"NORMAL",
"KINDA HARD",
"HARD"
};
private static final double[] DIFFICULTY_VALUES = {
0.70,
0.85,
1.0,
1.15,
1.30
};
private int index;
public DifficultySelectMenu() {
this.index = 2;
}
public double getSelectedDifficulty() {
return DIFFICULTY_VALUES[this.index];
}
@Override
public void tick() {
super.tick();
if (ControlHandler.MENU_UP.pressedTick() && this.index > 0) {
this.index--;
}
if (ControlHandler.MENU_DOWN.pressedTick() && this.index < (COUNT - 1)) {
this.index++;
}
if (ControlHandler.MENU_QUIT.pressedTick()) {
Global.mainMenu.menu = Global.mainMenu.colorSelectMenu;
}
if (ControlHandler.MENU_SELECT.pressedTick()) {
Global.setState(Global.StateType.GAME);
}
}
@Override
public void render() {
super.render();
int y = 8;
for (String s : TITLE) {
Font.render(s, (Renderer.WIDTH - Font.width(s)) / 2, y, 555);
y += 8;
}
y += 32;
int maxWidth = 0;
for (String s : DIFFICULTY_NAMES) {
maxWidth = Math.max(maxWidth, Font.width(s));
}
for (int i = 0; i < COUNT; i++) {
String s = DIFFICULTY_NAMES[i];
Font.render(
s,
(Renderer.WIDTH - Font.width(s)) / 2, y + (i * 8),
i == this.index ? 555 : 333);
}
Font.render(">", ((Renderer.WIDTH - maxWidth) / 2) - 8, y + (this.index * 8), 555);
Font.render("<", (Renderer.WIDTH + maxWidth) / 2, y + (this.index * 8), 555);
for (String s : INSTRUCTIONS) {
Font.render(s, (Renderer.WIDTH - Font.width(s)) / 2, Renderer.HEIGHT - 8, 555);
}
}
}

View File

@@ -0,0 +1,31 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.util.Window;
public class FocusMenu extends DialogMenu {
private final Menu oldMenu;
public FocusMenu(Menu oldMenu) {
super("", new String[] { " ", "CLICK TO FOCUS!" }, 16, 5, () -> {});
this.oldMenu = oldMenu;
}
@Override
protected boolean shouldCenterText() {
return true;
}
@Override
protected boolean showPressAnyKey() {
return false;
}
@Override
public void update() {
super.update();
if (Window.hasFocus()) {
Global.game.setMenu(this.oldMenu);
}
}
}

View File

@@ -0,0 +1,74 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
public class HUD extends Menu {
private static final int HEALTH_SPRITE_X = 10, HEALTH_SPRITE_Y = 11;
private static final int STAMINA_SPRITE_X = 10, STAMINA_SPRITE_Y = 12;
private final EntityPlayer player;
public HUD(EntityPlayer player) {
this.player = player;
this.focused = true;
}
@Override
public void render() {
Renderer.fill(0, Renderer.HEIGHT - 16, Renderer.WIDTH, 16, 000);
// health
for (int i = 0; i < this.player.getMaxHealth(); i++) {
boolean flashing = this.player.health <= 2 && ((Global.frames / 10) % 2) == 0;
Renderer.render(
HEALTH_SPRITE_X, HEALTH_SPRITE_Y,
2 + (i * 8), Renderer.HEIGHT - 16,
i < this.player.health ?
(flashing ?
Color.get(311, 511, 533, 555) :
Color.get(100, 400, 511, 533)
) :
Color.get(100, 111, 222, 333),
Renderer.FLIP_NONE
);
}
// stamina
for (int i = 0; i < this.player.getMaxStamina(); i++) {
boolean flashing = this.player.staminaRechargeDelayTicks > 0 && Global.frames % 3 == 0;
Renderer.render(
STAMINA_SPRITE_X, STAMINA_SPRITE_Y,
2 + (i * 8), Renderer.HEIGHT - 8,
i < this.player.stamina ?
Color.get(110, 331, 441, 551) :
(flashing ?
Color.get(333, 444, 555, 555) :
Color.get(110, 222, 333, 444)),
Renderer.FLIP_NONE
);
}
// equipped item
if (player.equipped != null) {
this.player.equipped.instance.item.renderIcon(
this.player.equipped.instance,
Renderer.WIDTH - 100, Renderer.HEIGHT - 12
);
int xx = Renderer.WIDTH - 90;
if (player.equipped.size > 1) {
String s = Integer.toString(player.equipped.size);
Font.render(s, xx, Renderer.HEIGHT - 12, 444);
xx += Font.width(s) + 8;
}
Font.render(
this.player.equipped.instance.item.getName(),
xx, Renderer.HEIGHT - 12, 555
);
}
}
}

View File

@@ -0,0 +1,40 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.item.Inventory;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.util.ControlHandler;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class InventoryMenu extends ItemSelectMenu {
protected final Inventory inventory;
public InventoryMenu(Inventory inventory, boolean leftSide) {
super(leftSide, ControlHandler.MENU_UP, ControlHandler.MENU_DOWN);
this.inventory = inventory;
}
@Override
public List<ItemStack> getItems() {
// give a sorted list of items with POW GLOVE always on top
return inventory.stacks.stream()
.filter(s -> s.instance.item.showInMenu())
.sorted(Comparator
.<ItemStack, Boolean>comparing(s -> s.instance.item.id != Item.GLOVE.id)
.thenComparing(s -> s.instance.item.getName()))
.collect(Collectors.toList());
}
@Override
public int getItemTextColor(int index) {
return 555;
}
@Override
public String getName() {
return "INVENTORY";
}
}

View File

@@ -0,0 +1,62 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.util.FMath;
import java.util.List;
public abstract class ItemListMenu extends Menu {
public final int x, y, width, height;
private int offset = 0;
public ItemListMenu(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
protected void setOffset(int offset) {
this.offset = FMath.clamp(offset, 0,this.getItems().size());
}
public int getOffset() {
return offset;
}
@Override
public void render() {
super.render();
List<ItemStack> items = this.getItems();
this.renderMenu(this.getName(), this.x, this.y, this.width, this.height);
// render item list
for (int i = 0, j = offset; i < this.height - 2 && j < items.size(); i++, j++) {
int yy = this.y + ((i + 1) * 8);
ItemStack stack = items.get(j);
int color = this.getItemTextColor(j);
stack.instance.item.renderIcon(stack.instance, x + 8, yy);
int xx = x + 16;
if (stack.size > 1) {
String s = Integer.toString(stack.size);
Font.render(s, xx, yy, Color.add(color, -111));
xx += Font.width(s) + 8;
}
Font.render(this.getItemName(j), xx, yy, color);
}
}
public abstract List<ItemStack> getItems();
public abstract String getItemName(int index);
public abstract int getItemTextColor(int index);
public abstract String getName();
}

View File

@@ -0,0 +1,75 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.util.Control;
import com.jdh.microcraft.util.FMath;
import java.util.List;
public abstract class ItemSelectMenu extends ItemListMenu {
private static final int WIDTH = 14, HEIGHT = (Renderer.HEIGHT - 32) / 8;
protected final boolean leftSide;
protected final Control up, down;
protected int selectedIndex = 0;
public ItemSelectMenu(boolean leftSide, Control up, Control down) {
super(
leftSide ? 8 : (8 + WIDTH * 8 + 16), 8,
14, (Renderer.HEIGHT - 32) / 8
);
this.leftSide = leftSide;
this.up = up;
this.down = down;
}
protected void updateOffset() {
if (this.selectedIndex < this.getOffset()) {
this.setOffset(this.selectedIndex);
}
if (this.selectedIndex >= this.getOffset() + (HEIGHT - 2)) {
this.setOffset(this.selectedIndex - (HEIGHT - 2) + 1);
}
}
@Override
public void tick() {
super.tick();
List<ItemStack> items = this.getItems();
if (this.focused) {
if (this.up.pressedTick() && this.selectedIndex > 0) {
this.selectedIndex--;
}
if (this.down.pressedTick() && this.selectedIndex < (items.size() - 1)) {
this.selectedIndex++;
}
}
// clamp selection index if its gone out of bounds
this.selectedIndex = FMath.clamp(this.selectedIndex, 0, items.size() - 1);
this.updateOffset();
}
@Override
public void render() {
super.render();
// render selection indicator
if (this.selectedIndex != -1) {
int color = this.focused ? 555 : 333;
int yy = this.y + (((this.selectedIndex - this.getOffset()) + 1) * 8);
Font.render(">", this.x, yy, color, Menu.MENU_BG_COLOR);
Font.render("<", this.x + ((WIDTH - 1) * 8), yy, color, Menu.MENU_BG_COLOR);
}
}
@Override
public String getItemName(int index) {
return this.getItems().get(index).instance.item.getName();
}
}

View File

@@ -0,0 +1,14 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Font;
public class LoseMenu extends DialogMenu {
public LoseMenu() {
super(":(", new String[]{
"",
"YOU " + Font.Colors.RED + "DIED",
"SCORE: " + Font.Colors.YELLOW + Global.game.score,
}, 22, 8, () -> Global.setState(Global.StateType.MENU));
}
}

View File

@@ -0,0 +1,75 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.gfx.Color;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.gfx.Renderer;
public class Menu {
public boolean focused = false;
private static final int MENU_SPRITES_X = 11, MENU_SPRITES_Y = 11;
public static final int MENU_BG_COLOR = 115;
protected void renderMenu(String title, int x, int y, int wt, int ht) {
int color = this.focused ?
Color.get(111, 333, MENU_BG_COLOR, 444) :
Color.get(000, 111, MENU_BG_COLOR, 222);
// corners
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y,
x, y, color, Renderer.FLIP_NONE);
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y,
x + ((wt - 1) * 8), y, color, Renderer.FLIP_X);
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y,
x, y + ((ht - 1) * 8), color, Renderer.FLIP_Y);
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y,
x + ((wt - 1) * 8), y + ((ht - 1) * 8), color, Renderer.FLIP_XY);
// vertical sides
for (int i = 0; i < (ht - 2); i++) {
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y + 1,
x, y + ((i + 1) * 8), color, Renderer.FLIP_NONE);
Renderer.render(MENU_SPRITES_X, MENU_SPRITES_Y + 1,
x + ((wt - 1) * 8), y + ((i + 1) * 8), color, Renderer.FLIP_X);
}
// horizontal sides
for (int i = 0; i < (wt - 2); i++) {
Renderer.render(MENU_SPRITES_X + 1, MENU_SPRITES_Y,
x + ((i + 1) * 8), y, color, Renderer.FLIP_NONE);
Renderer.render(MENU_SPRITES_X + 1, MENU_SPRITES_Y,
x + ((i + 1) * 8), y + ((ht - 1) * 8), color, Renderer.FLIP_Y);
}
// center
Renderer.fill(
x + 8, y + 8,
(wt - 2) * 8, (ht - 2) * 8,
MENU_BG_COLOR
);
// title
int titleX = x + (((wt * 8) / 2) - (Font.width(title) / 2));
Font.render(title, titleX, y, this.focused ? 551 : 331, MENU_BG_COLOR);
}
public void init() {
}
public void destroy() {
}
public void tick() {
}
public void update() {
}
public void render() {
}
}

View File

@@ -0,0 +1,54 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Font;
import com.jdh.microcraft.util.ControlHandler;
public class PauseMenu extends DialogMenu {
private boolean onYes;
public PauseMenu() {
super("", new String[]{}, 20, 4, () -> {});
this.onYes = false;
}
@Override
protected boolean showPressAnyKey() {
return false;
}
@Override
protected boolean shouldCenterText() {
return true;
}
@Override
protected String[] getLines() {
return new String[] {
"EXIT TO MAIN MENU?",
(this.onYes ? Font.Colors.YELLOW : Font.Colors.GREY) + "YES" +
(this.onYes ? Font.Colors.GREY : Font.Colors.YELLOW) + " NO"
};
}
@Override
public void tick() {
super.tick();
if (ControlHandler.MENU_SELECT.pressedTick()) {
if (this.onYes) {
Global.setState(Global.StateType.MENU);
} else {
Global.game.setMenu(null);
}
}
if (ControlHandler.MENU_LEFT.pressedTick() && !this.onYes) {
this.onYes = true;
}
if (ControlHandler.MENU_RIGHT.pressedTick() && this.onYes) {
this.onYes = false;
}
}
}

View File

@@ -0,0 +1,119 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.crafting.InventoryCraftingMenu;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.armor.*;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.ControlHandler;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PlayerInventoryMenu extends InventoryMenu {
private class PlayerArmorMenu extends ItemListMenu {
public PlayerArmorMenu() {
super(
PlayerInventoryMenu.this.x + (PlayerInventoryMenu.this.width + 2) * 8,
PlayerInventoryMenu.this.y,
14, 6
);
}
@Override
public List<ItemStack> getItems() {
return Arrays.stream(PlayerInventoryMenu.this.player.armor)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override
public String getItemName(int index) {
return this.getItems().get(index).instance.item.getName();
}
@Override
public int getItemTextColor(int index) {
return 444;
}
@Override
public String getName() {
return "ARMOR";
}
}
private final PlayerArmorMenu armorMenu;
private final EntityPlayer player;
private final boolean showArmor;
public PlayerInventoryMenu(EntityPlayer player, boolean showArmor) {
super(player.inventory, true);
this.player = player;
this.showArmor = showArmor;
this.focused = true;
this.armorMenu = new PlayerArmorMenu();
List<ItemStack> items = this.getItems();
int selected = items.indexOf(this.player.equipped);
this.selectedIndex = selected == -1 ? 0 : selected;
this.updateOffset();
}
@Override
public void tick() {
super.tick();
List<ItemStack> items = this.getItems();
ItemStack selectedItem = this.selectedIndex >= 0 && this.selectedIndex < items.size() ?
items.get(this.selectedIndex) : null;
this.player.equipped = selectedItem;
if (this.showArmor) {
if (ControlHandler.MENU_EQUIP.pressedTick() &&
selectedItem != null &&
selectedItem.instance.item instanceof ItemArmor) {
ItemArmor armor = (ItemArmor) selectedItem.instance.item;
if (this.player.armor[armor.slot] == selectedItem) {
this.player.armor[armor.slot] = null;
} else {
Sound.EQUIP.play();
this.player.armor[armor.slot] = selectedItem;
}
}
this.armorMenu.tick();
}
if (ControlHandler.DROP.pressedTick() &&
selectedItem != null &&
selectedItem.instance.item.isDroppable()) {
this.player.drop(selectedItem);
}
if (ControlHandler.TOGGLE_CRAFTING.pressedTick()) {
// direct inventory -> crafting transition
Global.game.setMenu(new InventoryCraftingMenu(this.player));
}
}
@Override
public void render() {
super.render();
if (this.showArmor) {
this.armorMenu.render();
}
}
@Override
public void update() {
if (this.showArmor) {
this.armorMenu.update();
}
}
}

View File

@@ -0,0 +1,44 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Renderer;
import com.jdh.microcraft.util.FMath;
public class TransitionMenu extends Menu {
private static final int TIME = 10;
// time to live
private int ticks;
public TransitionMenu() {
this.ticks = 0;
}
@Override
public void tick() {
super.tick();
if (++this.ticks == TIME) {
Global.game.setMenu(null);
}
}
@Override
public void render() {
super.render();
for (int y = 0; y < Renderer.HEIGHT / 8; y++) {
for (int x = 0; x < Renderer.WIDTH / 8; x++) {
int distance = (int) (
FMath.norm(
((double) (x * 8) - (double) (Renderer.WIDTH / 2)) / (Renderer.WIDTH / 2),
((double) (y * 8) - (double) (Renderer.HEIGHT / 2)) / (Renderer.HEIGHT / 2)
) * TIME);
if (distance > this.ticks) {
Renderer.fill(x * 8, y * 8, 8, 8, 000);
}
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.gfx.Font;
public class WelcomeMenu extends DialogMenu {
public WelcomeMenu() {
super("WELCOME", new String[]{
"",
"YOU MUST DEFEAT ",
"THE EVIL " + Font.Colors.RED + "AIR WIZARD!",
"",
"ONE SHOULD GO " + Font.Colors.GREEN + "DOWN",
"BEFORE MAKING A",
"WAY" + Font.Colors.YELLOW + " UP",
}, 22, 11, () -> {});
}
}

View File

@@ -0,0 +1,25 @@
package com.jdh.microcraft.gui;
import com.jdh.microcraft.Global;
import com.jdh.microcraft.gfx.Font;
public class WinMenu extends DialogMenu {
private int ticks;
public WinMenu() {
super("YAY!", new String[]{
"",
"YOU WIN! ",
"THE EVIL " + Font.Colors.RED + "AIR WIZARD",
"HAS BEEN " + Font.Colors.YELLOW + "BESTED.",
"SCORE: " + Font.Colors.YELLOW + Global.game.score,
}, 22, 10, () -> Global.setState(Global.StateType.MENU));
}
@Override
public void tick() {
if (++this.ticks > 120) {
super.tick();
}
}
}

View File

@@ -0,0 +1,21 @@
package com.jdh.microcraft.gui.crafting;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.furniture.EntityAnvil;
import com.jdh.microcraft.item.crafting.Recipe;
public class AnvilCraftingMenu extends FurnitureCraftingMenu {
public AnvilCraftingMenu(EntityPlayer player, EntityAnvil anvil) {
super(player, anvil);
}
@Override
public int getStation() {
return Recipe.STATION_ANVIL;
}
@Override
public String getName() {
return "ANVIL";
}
}

View File

@@ -0,0 +1,21 @@
package com.jdh.microcraft.gui.crafting;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.furniture.EntityCraftingBench;
import com.jdh.microcraft.item.crafting.Recipe;
public class BenchCraftingMenu extends FurnitureCraftingMenu {
public BenchCraftingMenu(EntityPlayer player, EntityCraftingBench bench) {
super(player, bench);
}
@Override
public int getStation() {
return Recipe.STATION_ALL_CRAFTING;
}
@Override
public String getName() {
return "CRAFTING";
}
}

View File

@@ -0,0 +1,236 @@
package com.jdh.microcraft.gui.crafting;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.gui.ItemListMenu;
import com.jdh.microcraft.gui.ItemSelectMenu;
import com.jdh.microcraft.gui.Menu;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemInstance;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.crafting.Recipe;
import com.jdh.microcraft.sound.Sound;
import com.jdh.microcraft.util.Control;
import com.jdh.microcraft.util.ControlHandler;
import java.util.List;
import java.util.stream.Collectors;
public abstract class CraftingMenu extends Menu {
private class CraftingList extends ItemSelectMenu {
private final Control craft;
public CraftingList(Control up, Control down, Control craft) {
super(true, up, down);
this.craft = craft;
this.focused = true;
}
public Recipe getSelectedRecipe() {
if (CraftingMenu.this.recipes.size() == 0) {
return null;
}
return CraftingMenu.this.recipes.get(this.selectedIndex);
}
private boolean canCraft(int index) {
return this.selectedIndex < CraftingMenu.this.recipes.size() &&
CraftingMenu.this.recipes.get(index)
.canMake(CraftingMenu.this.player.inventory, CraftingMenu.this.getStation());
}
@Override
public void tick() {
super.tick();
if (this.craft.pressedTick() && this.canCraft(this.selectedIndex)) {
ItemStack result = this.getSelectedRecipe().make(CraftingMenu.this.player.inventory);
CraftingMenu.this.player.inventory.add(result);
CraftingMenu.this.onCraft(result);
}
}
@Override
public List<ItemStack> getItems() {
return CraftingMenu.this.items;
}
@Override
public int getItemTextColor(int index) {
return this.canCraft(index) ? 555 : 333;
}
@Override
public String getName() {
return CraftingMenu.this.getName();
}
}
private class HaveMenu extends ItemListMenu {
public HaveMenu() {
super(
CraftingMenu.this.craftingList.x + (CraftingMenu.this.craftingList.width + 2) * 8,
CraftingMenu.this.craftingList.y,
10, 3
);
}
@Override
public List<ItemStack> getItems() {
Recipe recipe = CraftingMenu.this.craftingList.getSelectedRecipe();
return recipe == null ?
List.of() :
List.of(new ItemStack(recipe.result.instance, 1));
}
@Override
public String getItemName(int index) {
Recipe recipe = CraftingMenu.this.craftingList.getSelectedRecipe();
assert (recipe != null);
return Integer.toString(CraftingMenu.this.player.inventory.count(recipe.result.instance.item));
}
@Override
public int getItemTextColor(int index) {
return 555;
}
@Override
public String getName() {
return "HAVE";
}
}
private class CostMenu extends ItemListMenu {
public CostMenu() {
super(
CraftingMenu.this.craftingList.x + (CraftingMenu.this.craftingList.width + 2) * 8,
CraftingMenu.this.craftingList.y + (CraftingMenu.this.haveMenu.height + 1) * 8,
10, CraftingMenu.this.craftingList.height - CraftingMenu.this.haveMenu.height - 1
);
}
@Override
public List<ItemStack> getItems() {
Recipe recipe = CraftingMenu.this.craftingList.getSelectedRecipe();
return recipe == null ?
List.of() :
recipe.ingredients.stream()
.map(i -> new ItemStack(new ItemInstance(i.item, 0), 0))
.collect(Collectors.toList());
}
@Override
public String getItemName(int index) {
Recipe recipe = CraftingMenu.this.craftingList.getSelectedRecipe();
assert (recipe != null);
ItemStack item = this.getItems().get(index);
int have = CraftingMenu.this.player.inventory.count(item.instance.item),
required = recipe.ingredients.stream()
.filter(i -> i.item.id == item.instance.item.id)
.findFirst().get().count;
return required + "/" + have;
}
@Override
public int getItemTextColor(int index) {
return 555;
}
@Override
public String getName() {
return "COST";
}
}
protected final CraftingList craftingList;
protected final HaveMenu haveMenu;
protected final CostMenu costMenu;
protected final List<Menu> submenus;
protected final EntityPlayer player;
protected final List<Recipe> recipes;
protected final List<ItemStack> items;
public CraftingMenu(EntityPlayer player) {
this.player = player;
this.recipes = Item.STATION_RECIPES.get(this.getStation());
this.items = this.recipes.stream().map(r -> r.result).collect(Collectors.toList());
this.craftingList = new CraftingList(ControlHandler.MENU_UP, ControlHandler.MENU_DOWN, ControlHandler.MENU_SELECT);
this.haveMenu = new HaveMenu();
this.costMenu = new CostMenu();
this.submenus = List.of(this.craftingList, this.haveMenu, this.costMenu);
this.sort();
}
private void sort() {
this.recipes.sort(
(var a, var b) -> {
boolean ma = a.canMake(this.player.inventory, this.getStation()),
mb = b.canMake(this.player.inventory, this.getStation());
return ma == mb ? String.CASE_INSENSITIVE_ORDER.compare(
a.result.instance.item.getName(),
b.result.instance.item.getName()
) : (ma ? -1 : 1);
}
);
this.items.clear();
for (Recipe r : this.recipes) {
this.items.add(r.result);
}
}
@Override
public void init() {
super.init();
for (Menu m : submenus) {
m.init();
}
}
@Override
public void destroy() {
super.destroy();
for (Menu m : submenus) {
m.destroy();
}
}
@Override
public void tick() {
super.tick();
this.sort();
for (Menu m : submenus) {
m.tick();
}
}
@Override
public void update() {
super.update();
for (Menu m : submenus) {
m.update();
}
}
@Override
public void render() {
super.render();
for (Menu m : submenus) {
m.render();
}
}
protected void onCraft(ItemStack stack) {
Sound.CRAFT.play();
}
public abstract int getStation();
public abstract String getName();
}

View File

@@ -0,0 +1,35 @@
package com.jdh.microcraft.gui.crafting;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.furniture.EntityFurnace;
import com.jdh.microcraft.entity.particle.EntitySmokeParticle;
import com.jdh.microcraft.item.Item;
import com.jdh.microcraft.item.ItemStack;
import com.jdh.microcraft.item.crafting.Recipe;
public class FurnaceMenu extends FurnitureCraftingMenu {
public FurnaceMenu(EntityPlayer player, EntityFurnace furnace) {
super(player, furnace);
}
@Override
protected void onCraft(ItemStack stack) {
super.onCraft(stack);
EntitySmokeParticle.spawn(
this.furniture.level,
this.furniture.getCenterX(), this.furniture.getCenterY(),
Item.COAL.getColor(),
3, 6
);
}
@Override
public int getStation() {
return Recipe.STATION_FURNACE;
}
@Override
public String getName() {
return "FURNACE";
}
}

View File

@@ -0,0 +1,13 @@
package com.jdh.microcraft.gui.crafting;
import com.jdh.microcraft.entity.EntityPlayer;
import com.jdh.microcraft.entity.furniture.EntityFurniture;
public abstract class FurnitureCraftingMenu extends CraftingMenu {
protected final EntityFurniture furniture;
public FurnitureCraftingMenu(EntityPlayer player, EntityFurniture furniture) {
super(player);
this.furniture = furniture;
}
}

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