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

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();
}