mirror of
https://github.com/array-in-a-matrix/brainwine.git
synced 2025-04-02 11:11:58 -04:00
NPC & damage system phase 1
This commit is contained in:
parent
5ce226813f
commit
a1aff443b3
45 changed files with 3106 additions and 94 deletions
|
@ -7,11 +7,13 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import brainwine.gameserver.command.CommandManager;
|
import brainwine.gameserver.command.CommandManager;
|
||||||
|
import brainwine.gameserver.entity.EntityRegistry;
|
||||||
import brainwine.gameserver.entity.player.PlayerManager;
|
import brainwine.gameserver.entity.player.PlayerManager;
|
||||||
import brainwine.gameserver.loot.LootManager;
|
import brainwine.gameserver.loot.LootManager;
|
||||||
import brainwine.gameserver.prefab.PrefabManager;
|
import brainwine.gameserver.prefab.PrefabManager;
|
||||||
import brainwine.gameserver.server.NetworkRegistry;
|
import brainwine.gameserver.server.NetworkRegistry;
|
||||||
import brainwine.gameserver.server.Server;
|
import brainwine.gameserver.server.Server;
|
||||||
|
import brainwine.gameserver.zone.EntityManager;
|
||||||
import brainwine.gameserver.zone.ZoneManager;
|
import brainwine.gameserver.zone.ZoneManager;
|
||||||
import brainwine.gameserver.zone.gen.ZoneGenerator;
|
import brainwine.gameserver.zone.gen.ZoneGenerator;
|
||||||
|
|
||||||
|
@ -38,6 +40,8 @@ public class GameServer {
|
||||||
logger.info("Starting GameServer ...");
|
logger.info("Starting GameServer ...");
|
||||||
CommandManager.init();
|
CommandManager.init();
|
||||||
GameConfiguration.init();
|
GameConfiguration.init();
|
||||||
|
EntityRegistry.init();
|
||||||
|
EntityManager.loadEntitySpawns();
|
||||||
lootManager = new LootManager();
|
lootManager = new LootManager();
|
||||||
prefabManager = new PrefabManager();
|
prefabManager = new PrefabManager();
|
||||||
ZoneGenerator.init();
|
ZoneGenerator.init();
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package brainwine.gameserver.behavior;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.composed.CrawlerBehavior;
|
||||||
|
import brainwine.gameserver.behavior.composed.FlyerBehavior;
|
||||||
|
import brainwine.gameserver.behavior.composed.WalkerBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.ClimbBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FallBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FlyBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FlyTowardBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FollowBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.IdleBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.RandomlyTargetBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.ShielderBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.SpawnAttackBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.TurnBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.WalkBehavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heavily based on Deepworld's original "rubyhave" (ha ha very punny) behavior system.
|
||||||
|
*
|
||||||
|
* https://github.com/bytebin/deepworld-gameserver/tree/master/vendor/rubyhave
|
||||||
|
* https://github.com/bytebin/deepworld-gameserver/tree/master/models/npcs/behavior
|
||||||
|
*/
|
||||||
|
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
|
||||||
|
@JsonSubTypes({
|
||||||
|
// Composed
|
||||||
|
@Type(name = "walker", value = WalkerBehavior.class),
|
||||||
|
@Type(name = "crawler", value = CrawlerBehavior.class),
|
||||||
|
@Type(name = "flyer", value = FlyerBehavior.class),
|
||||||
|
// Parts
|
||||||
|
@Type(name = "idle", value = IdleBehavior.class),
|
||||||
|
@Type(name = "walk", value = WalkBehavior.class),
|
||||||
|
@Type(name = "fall", value = FallBehavior.class),
|
||||||
|
@Type(name = "turn", value = TurnBehavior.class),
|
||||||
|
@Type(name = "follow", value = FollowBehavior.class),
|
||||||
|
@Type(name = "climb", value = ClimbBehavior.class),
|
||||||
|
@Type(name = "fly", value = FlyBehavior.class),
|
||||||
|
@Type(name = "fly_toward", value = FlyTowardBehavior.class),
|
||||||
|
@Type(name = "shielder", value = ShielderBehavior.class),
|
||||||
|
@Type(name = "spawn_attack", value = SpawnAttackBehavior.class),
|
||||||
|
@Type(name = "randomly_target", value = RandomlyTargetBehavior.class)
|
||||||
|
})
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public abstract class Behavior {
|
||||||
|
|
||||||
|
protected final Npc entity;
|
||||||
|
|
||||||
|
public Behavior(Npc entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean behave();
|
||||||
|
|
||||||
|
public boolean canBehave() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package brainwine.gameserver.behavior;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.InjectableValues;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.shared.JsonHelper;
|
||||||
|
|
||||||
|
public abstract class CompositeBehavior extends Behavior {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
protected final List<Behavior> children = new ArrayList<>();
|
||||||
|
|
||||||
|
public CompositeBehavior(Npc entity, Map<String, Object> config) {
|
||||||
|
super(entity);
|
||||||
|
addChildren(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompositeBehavior(Npc entity) {
|
||||||
|
this(entity, Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addChildren(Map<String, Object> config) {
|
||||||
|
// Override
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(Class<? extends Behavior> type, Map<String, Object> config) {
|
||||||
|
try {
|
||||||
|
addChild(JsonHelper.readValue(config, type, new InjectableValues.Std().addValue(Npc.class, entity)));
|
||||||
|
} catch(IOException e) {
|
||||||
|
logger.error("Could not add child behavior of type {}.", type.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(Behavior child) {
|
||||||
|
if(children.contains(child)) {
|
||||||
|
logger.warn("Duplicate child instance {} for behavior {}", child, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
children.add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeChild(Behavior child) {
|
||||||
|
children.remove(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Behavior> getChildren() {
|
||||||
|
return Collections.unmodifiableCollection(children);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package brainwine.gameserver.behavior;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class SelectorBehavior extends CompositeBehavior {
|
||||||
|
|
||||||
|
public SelectorBehavior(Npc entity, Map<String, Object> config) {
|
||||||
|
super(entity, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectorBehavior(Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
for(Behavior child : children) {
|
||||||
|
if(child.canBehave() && child.behave()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package brainwine.gameserver.behavior;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.InjectableValues;
|
||||||
|
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
import brainwine.shared.JsonHelper;
|
||||||
|
|
||||||
|
public class SequenceBehavior extends CompositeBehavior {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
private static final List<String> loggedInvalidTypes = new ArrayList<>();
|
||||||
|
|
||||||
|
public SequenceBehavior(Npc entity, Map<String, Object> config) {
|
||||||
|
super(entity, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SequenceBehavior(Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SequenceBehavior createBehaviorTree(Npc npc, List<Map<String, Object>> behavior) {
|
||||||
|
SequenceBehavior root = new SequenceBehavior(npc);
|
||||||
|
|
||||||
|
for(Map<String, Object> config : behavior) {
|
||||||
|
try {
|
||||||
|
root.addChild(JsonHelper.readValue(config, Behavior.class, new InjectableValues.Std().addValue(Npc.class, npc)));
|
||||||
|
} catch(InvalidTypeIdException e) {
|
||||||
|
String type = e.getTypeId();
|
||||||
|
|
||||||
|
// TODO get rid of this once we add the remaining behaviors
|
||||||
|
if(!loggedInvalidTypes.contains(type)) {
|
||||||
|
logger.warn("No implementation exists for behavior type '{}'", type);
|
||||||
|
loggedInvalidTypes.add(type);
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
logger.error("Could not add behavior type '{}' to behavior tree for entity with type '{}'",
|
||||||
|
MapHelper.getString(config, "type", "unknown"), npc.getType(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
for(Behavior child : children) {
|
||||||
|
if(!child.canBehave() || !child.behave()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package brainwine.gameserver.behavior.composed;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.SelectorBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.ClimbBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FallBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.IdleBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.TurnBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.WalkBehavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
|
||||||
|
public class CrawlerBehavior extends SelectorBehavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private CrawlerBehavior(@JacksonInject Npc entity,
|
||||||
|
Map<String, Object> config) {
|
||||||
|
super(entity, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CrawlerBehavior(Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addChildren(Map<String, Object> config) {
|
||||||
|
if(config.containsKey("idle")) {
|
||||||
|
addChild(IdleBehavior.class, MapHelper.getMap(config, "idle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild(new WalkBehavior(entity));
|
||||||
|
addChild(new ClimbBehavior(entity));
|
||||||
|
addChild(new TurnBehavior(entity));
|
||||||
|
addChild(new ClimbBehavior(entity));
|
||||||
|
addChild(new FallBehavior(entity));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package brainwine.gameserver.behavior.composed;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.SelectorBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FlyBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FlyTowardBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.IdleBehavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
|
||||||
|
public class FlyerBehavior extends SelectorBehavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private FlyerBehavior(@JacksonInject Npc entity,
|
||||||
|
Map<String, Object> config) {
|
||||||
|
super(entity, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlyerBehavior(Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addChildren(Map<String, Object> config) {
|
||||||
|
if(config.containsKey("idle")) {
|
||||||
|
addChild(IdleBehavior.class, MapHelper.getMap(config, "idle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild(FlyTowardBehavior.class, config);
|
||||||
|
addChild(FlyBehavior.class, config);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package brainwine.gameserver.behavior.composed;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.SelectorBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.FallBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.IdleBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.TurnBehavior;
|
||||||
|
import brainwine.gameserver.behavior.parts.WalkBehavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
|
||||||
|
public class WalkerBehavior extends SelectorBehavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private WalkerBehavior(@JacksonInject Npc entity,
|
||||||
|
Map<String, Object> config) {
|
||||||
|
super(entity, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalkerBehavior(Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addChildren(Map<String, Object> config) {
|
||||||
|
if(config.containsKey("idle")) {
|
||||||
|
addChild(IdleBehavior.class, MapHelper.getMap(config, "idle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild(new WalkBehavior(entity));
|
||||||
|
addChild(new FallBehavior(entity));
|
||||||
|
addChild(new TurnBehavior(entity));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class ClimbBehavior extends Behavior {
|
||||||
|
|
||||||
|
protected int lastClimbSide = 1;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public ClimbBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
if(climb(lastClimbSide)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return climb(lastClimbSide * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean climb(int side) {
|
||||||
|
FacingDirection direction = entity.getDirection();
|
||||||
|
int y = side * direction.getId() * -1;
|
||||||
|
|
||||||
|
if((entity.isBlocked(side, 0) || entity.isBlocked(side, y)) && !entity.isBlocked(0, y)) {
|
||||||
|
lastClimbSide = side;
|
||||||
|
entity.move(0, y, entity.getBaseSpeed() * 0.75F, side == -1 ? "climb-left" : "climb-right");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class FallBehavior extends Behavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public FallBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
entity.move(0, 1, entity.getSpeed() + 0.5F, "fall");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBehave() {
|
||||||
|
return !entity.isOnGround();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
|
||||||
|
public class FlyBehavior extends Behavior {
|
||||||
|
|
||||||
|
protected boolean blockable = true;
|
||||||
|
protected String animation = "fly";
|
||||||
|
protected Vector2i targetPoint;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public FlyBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
if((targetPoint = getTargetPoint()) != null) {
|
||||||
|
Vector2i next = entity.getZone().raynext((int)entity.getX(), (int)entity.getY(), targetPoint.getX(), targetPoint.getY());
|
||||||
|
|
||||||
|
if(next != null) {
|
||||||
|
int moveX = (int)(next.getX() - entity.getX());
|
||||||
|
int moveY = (int)(next.getY() - entity.getY());
|
||||||
|
|
||||||
|
if(!blockable || !entity.isBlocked(moveX, moveY)) {
|
||||||
|
entity.move(moveX, moveY, entity.getBaseSpeed() * getSpeedMultiplier(), animation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPoint = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getSpeedMultiplier() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2i getTargetPoint() {
|
||||||
|
return targetPoint == null ? getRandomPoint(10, 30) : targetPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2i getRandomPoint(int minDistance, int maxDistance) {
|
||||||
|
int distance = ThreadLocalRandom.current().nextInt(minDistance, maxDistance);
|
||||||
|
double theta = Math.random() * 2 * Math.PI;
|
||||||
|
int x = (int)(entity.getX() + distance * Math.cos(theta));
|
||||||
|
int y = (int)(entity.getY() + distance * Math.sin(theta));
|
||||||
|
return new Vector2i(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockable(boolean blockable) {
|
||||||
|
this.blockable = blockable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimation(String animation) {
|
||||||
|
this.animation = animation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.Entity;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
|
||||||
|
public class FlyTowardBehavior extends FlyBehavior {
|
||||||
|
|
||||||
|
protected long lastNearbyAt;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public FlyTowardBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBehave() {
|
||||||
|
return entity.hasTarget() && !shouldBackOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getSpeedMultiplier() {
|
||||||
|
return 1.25F;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vector2i getTargetPoint() {
|
||||||
|
return new Vector2i((int)entity.getTarget().getX(), (int)entity.getTarget().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldBackOff() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(now < lastNearbyAt + 2000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity target = entity.getTarget();
|
||||||
|
|
||||||
|
if(target != null && entity.inRange(target, 2)) {
|
||||||
|
lastNearbyAt = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class FollowBehavior extends Behavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public FollowBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
if(entity.hasTarget()) {
|
||||||
|
entity.setDirection(entity.getTarget().getX() - entity.getX() > 0 ? FacingDirection.EAST : FacingDirection.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class IdleBehavior extends Behavior {
|
||||||
|
|
||||||
|
protected int delay = 60;
|
||||||
|
protected int duration = 60;
|
||||||
|
protected double random = 0.5;
|
||||||
|
protected String[] animations = new String[] {"idle"};
|
||||||
|
protected long until;
|
||||||
|
protected boolean idle;
|
||||||
|
protected String currentAnimation;
|
||||||
|
protected long animationSetUntil = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public IdleBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
until = getNextUntil();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
Random random = ThreadLocalRandom.current();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(entity.isOnGround()) {
|
||||||
|
if(now > until) {
|
||||||
|
idle = !idle;
|
||||||
|
until = getNextUntil();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(idle) {
|
||||||
|
if(now > animationSetUntil) {
|
||||||
|
int length = animations.length;
|
||||||
|
currentAnimation = length == 0 ? "idle" : animations[random.nextInt(length)];
|
||||||
|
animationSetUntil = now + (random.nextInt(2) + 1) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.move(0, 0, currentAnimation);
|
||||||
|
|
||||||
|
if(Math.random() < 0.01) {
|
||||||
|
entity.setDirection(entity.getDirection() == FacingDirection.WEST ? FacingDirection.EAST : FacingDirection.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getNextUntil() {
|
||||||
|
int currentDuration = (idle ? duration : delay) * 1000;
|
||||||
|
return (long)(System.currentTimeMillis() + currentDuration + currentDuration * (Math.random() * random));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelay(int delay) {
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(int duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRandom(double random) {
|
||||||
|
this.random = random;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSetter("animation")
|
||||||
|
public void setAnimations(String... animations) {
|
||||||
|
this.animations = animations;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.Entity;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class RandomlyTargetBehavior extends Behavior {
|
||||||
|
|
||||||
|
protected int range = 20;
|
||||||
|
protected boolean friendlyFire;
|
||||||
|
protected boolean blockable = true;
|
||||||
|
protected String animation;
|
||||||
|
protected long targetLockedAt;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public RandomlyTargetBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(now > targetLockedAt + 5000) {
|
||||||
|
entity.setTarget(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!entity.hasTarget()) {
|
||||||
|
Entity target = entity.getZone().getRandomPlayerInRange(entity.getX(), entity.getY(), range);
|
||||||
|
|
||||||
|
if(target != null && !target.isDead() && entity.canSee(target)) {
|
||||||
|
entity.setTarget(target);
|
||||||
|
targetLockedAt = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(animation != null && entity.hasTarget()) {
|
||||||
|
entity.setAnimation(animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRange(int range) {
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFriendlyFire(boolean friendlyFire) {
|
||||||
|
this.friendlyFire = friendlyFire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockable(boolean blockable) {
|
||||||
|
this.blockable = blockable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimation(String animation) {
|
||||||
|
this.animation = animation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.item.DamageType;
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
import brainwine.gameserver.util.Pair;
|
||||||
|
|
||||||
|
public class ShielderBehavior extends Behavior {
|
||||||
|
|
||||||
|
private final Set<DamageType> defenses = new HashSet<>();
|
||||||
|
private int duration = 5;
|
||||||
|
private int recharge = 5;
|
||||||
|
private long shieldStart;
|
||||||
|
private long lastAttackedAt;
|
||||||
|
private DamageType currentShield;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public ShielderBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Collection<Pair<Item, Long>> recentAttacks = entity.getRecentAttacks();
|
||||||
|
|
||||||
|
if(!recentAttacks.isEmpty()) {
|
||||||
|
lastAttackedAt = now;
|
||||||
|
DamageType type = recentAttacks.stream().findFirst().get().getFirst().getDamageType();
|
||||||
|
if(currentShield == null && now >= shieldStart + (recharge * 1000)) {
|
||||||
|
if(defenses.contains(type)) {
|
||||||
|
setShield(type);
|
||||||
|
shieldStart = now;
|
||||||
|
}
|
||||||
|
} else if(currentShield != null) {
|
||||||
|
if(now >= shieldStart + (duration * 1000)) {
|
||||||
|
setShield(null);
|
||||||
|
shieldStart = now;
|
||||||
|
} else if(currentShield != type && defenses.contains(type)) {
|
||||||
|
setShield(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(now >= lastAttackedAt + 2000) {
|
||||||
|
setShield(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setShield(DamageType type) {
|
||||||
|
entity.setProperty("s", type);
|
||||||
|
entity.setDefense(currentShield, 0);
|
||||||
|
currentShield = type;
|
||||||
|
|
||||||
|
if(type != null) {
|
||||||
|
entity.setDefense(type, 1 - entity.getBaseDefense(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefenses(String... defenses) {
|
||||||
|
this.defenses.clear();
|
||||||
|
|
||||||
|
for(String defense : defenses) {
|
||||||
|
switch(defense) {
|
||||||
|
case "all":
|
||||||
|
this.defenses.addAll(Arrays.asList(DamageType.values()));
|
||||||
|
break;
|
||||||
|
case "elemental":
|
||||||
|
DamageType[] elementalDamageTypes = DamageType.getElementalDamageTypes();
|
||||||
|
this.defenses.add(elementalDamageTypes[ThreadLocalRandom.current().nextInt(elementalDamageTypes.length)]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DamageType type = DamageType.fromName(defense);
|
||||||
|
|
||||||
|
if(type != DamageType.NONE) {
|
||||||
|
this.defenses.add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(int duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecharge(int recharge) {
|
||||||
|
this.recharge = recharge;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.entity.EntityStatus;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
||||||
|
|
||||||
|
public class SpawnAttackBehavior extends Behavior {
|
||||||
|
|
||||||
|
protected EntityConfig bulletConfig;
|
||||||
|
protected float speed = 8;
|
||||||
|
protected float frequency = 1;
|
||||||
|
protected float range = 15;
|
||||||
|
protected Object burst;
|
||||||
|
protected long lastAttackAt;;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public SpawnAttackBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
// TODO much more to be done here
|
||||||
|
lastAttackAt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(bulletConfig != null) {
|
||||||
|
Npc bullet = new Npc(entity.getZone(), bulletConfig);
|
||||||
|
bullet.setProperty("<", entity.getId());
|
||||||
|
bullet.setProperty(">", entity.getTarget().getId());
|
||||||
|
bullet.setProperty("*", true);
|
||||||
|
bullet.setProperty("s", speed);
|
||||||
|
|
||||||
|
if(burst != null) {
|
||||||
|
bullet.setProperty("#", burst);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.getZone().sendMessage(new EntityStatusMessage(bullet, EntityStatus.ENTERING));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBehave() {
|
||||||
|
return System.currentTimeMillis() - lastAttackAt >= 1.0F / frequency * 1000
|
||||||
|
&& entity.hasTarget() && entity.inRange(entity.getTarget(), 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSetter("entity")
|
||||||
|
public void setBulletConfig(EntityConfig bulletConfig) {
|
||||||
|
this.bulletConfig = bulletConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeed(float speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrequency(float frequency) {
|
||||||
|
this.frequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRange(float range) {
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBurst(Object burst) {
|
||||||
|
this.burst = burst;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class TurnBehavior extends Behavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public TurnBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
entity.setDirection(entity.getDirection() == FacingDirection.WEST ? FacingDirection.EAST : FacingDirection.WEST);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBehave() {
|
||||||
|
return entity.isBlocked(entity.getDirection().getId(), 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package brainwine.gameserver.behavior.parts;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.Behavior;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
|
||||||
|
public class WalkBehavior extends Behavior {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public WalkBehavior(@JacksonInject Npc entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean behave() {
|
||||||
|
entity.move(entity.getDirection().getId(), 0, "walk");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBehave() {
|
||||||
|
FacingDirection direction = entity.getDirection();
|
||||||
|
return entity.isOnGround(direction.getId()) && !entity.isBlocked(direction.getId(), 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import brainwine.gameserver.command.commands.AdminCommand;
|
import brainwine.gameserver.command.commands.AdminCommand;
|
||||||
import brainwine.gameserver.command.commands.BroadcastCommand;
|
import brainwine.gameserver.command.commands.BroadcastCommand;
|
||||||
|
import brainwine.gameserver.command.commands.EntityCommand;
|
||||||
import brainwine.gameserver.command.commands.ExportCommand;
|
import brainwine.gameserver.command.commands.ExportCommand;
|
||||||
import brainwine.gameserver.command.commands.GenerateZoneCommand;
|
import brainwine.gameserver.command.commands.GenerateZoneCommand;
|
||||||
import brainwine.gameserver.command.commands.GiveCommand;
|
import brainwine.gameserver.command.commands.GiveCommand;
|
||||||
|
@ -69,6 +70,7 @@ public class CommandManager {
|
||||||
registerCommand(new ImportCommand());
|
registerCommand(new ImportCommand());
|
||||||
registerCommand(new PositionCommand());
|
registerCommand(new PositionCommand());
|
||||||
registerCommand(new RickrollCommand());
|
registerCommand(new RickrollCommand());
|
||||||
|
registerCommand(new EntityCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void executeCommand(CommandExecutor executor, String commandLine) {
|
public static void executeCommand(CommandExecutor executor, String commandLine) {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package brainwine.gameserver.command.commands;
|
||||||
|
|
||||||
|
import static brainwine.gameserver.entity.player.NotificationType.ALERT;
|
||||||
|
|
||||||
|
import brainwine.gameserver.command.Command;
|
||||||
|
import brainwine.gameserver.command.CommandExecutor;
|
||||||
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.entity.EntityRegistry;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.entity.player.NotificationType;
|
||||||
|
import brainwine.gameserver.entity.player.Player;
|
||||||
|
import brainwine.gameserver.zone.Zone;
|
||||||
|
|
||||||
|
public class EntityCommand extends Command {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandExecutor executor, String[] args) {
|
||||||
|
if(args.length == 0) {
|
||||||
|
executor.notify(String.format("Usage: %s", getUsage(executor)), ALERT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player)executor;
|
||||||
|
String name = args[0];
|
||||||
|
EntityConfig config = EntityRegistry.getEntityConfig(name);
|
||||||
|
|
||||||
|
if(config == null) {
|
||||||
|
executor.notify(String.format("Entity with name '%s' does not exist.", name), NotificationType.ALERT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zone zone = player.getZone();
|
||||||
|
zone.spawnEntity(new Npc(zone, config), (int)player.getX(), (int)player.getY(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "entity";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Spawns an entity at your current location.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage(CommandExecutor executor) {
|
||||||
|
return "/entity <name>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canExecute(CommandExecutor executor) {
|
||||||
|
return executor.isAdmin() && executor instanceof Player;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +1,100 @@
|
||||||
package brainwine.gameserver.entity;
|
package brainwine.gameserver.entity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.player.Player;
|
||||||
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
||||||
|
import brainwine.gameserver.util.MathUtils;
|
||||||
import brainwine.gameserver.zone.Zone;
|
import brainwine.gameserver.zone.Zone;
|
||||||
|
|
||||||
public abstract class Entity {
|
public abstract class Entity {
|
||||||
|
|
||||||
public static final float POSITION_MODIFIER = 100F;
|
public static final float POSITION_MODIFIER = 100F;
|
||||||
public static final int VELOCITY_MODIFIER = (int)POSITION_MODIFIER;
|
public static final int VELOCITY_MODIFIER = (int)POSITION_MODIFIER;
|
||||||
private static int discriminator;
|
protected final List<Player> trackers = new ArrayList<>();
|
||||||
|
protected int type;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected float health;
|
protected float health;
|
||||||
protected final int id;
|
protected int id;
|
||||||
protected Zone zone;
|
protected Zone zone;
|
||||||
protected float x;
|
protected float x;
|
||||||
protected float y;
|
protected float y;
|
||||||
protected int velocityX;
|
protected float velocityX;
|
||||||
protected int velocityY;
|
protected float velocityY;
|
||||||
protected int targetX;
|
protected int targetX;
|
||||||
protected int targetY;
|
protected int targetY;
|
||||||
protected FacingDirection direction = FacingDirection.WEST;
|
protected FacingDirection direction = FacingDirection.WEST;
|
||||||
protected int animation;
|
protected int animation;
|
||||||
|
|
||||||
public Entity(Zone zone) {
|
public Entity(Zone zone) {
|
||||||
this.id = ++discriminator;
|
|
||||||
this.zone = zone;
|
this.zone = zone;
|
||||||
health = 10; // TODO
|
health = 10; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract EntityType getType();
|
public void tick(float deltaTime) {
|
||||||
|
// Override
|
||||||
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void die(Player killer) {
|
||||||
|
// Override
|
||||||
|
}
|
||||||
|
|
||||||
|
public void damage(float amount) {
|
||||||
|
damage(amount, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void damage(float amount, Player attacker) {
|
||||||
|
setHealth(health - amount);
|
||||||
|
|
||||||
|
if(health <= 0) {
|
||||||
|
die(attacker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canSee(Entity other) {
|
||||||
|
return canSee((int)other.getX(), (int)other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canSee(int x, int y) {
|
||||||
|
return zone.isPointVisibleFrom((int)this.x, (int)this.y, x, y) ||
|
||||||
|
(y > 0 && zone.isPointVisibleFrom((int)this.x, (int)this.y, x, y - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inRange(Entity other, float range) {
|
||||||
|
return inRange(other.getX(), other.getY(), range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inRange(float x, float y, float range) {
|
||||||
|
return MathUtils.inRange(this.x, this.y, x, y, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTracker(Player tracker) {
|
||||||
|
trackers.add(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTracker(Player tracker) {
|
||||||
|
trackers.remove(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Player> getTrackers() {
|
||||||
|
return trackers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +108,7 @@ public abstract class Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHealth(float health) {
|
public void setHealth(float health) {
|
||||||
this.health = health;
|
this.health = health < 0 ? 0 : health;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getHealth() {
|
public float getHealth() {
|
||||||
|
@ -73,16 +128,16 @@ public abstract class Entity {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVelocity(int velocityX, int velocityY) {
|
public void setVelocity(float velocityX, float velocityY) {
|
||||||
this.velocityX = velocityX;
|
this.velocityX = velocityX;
|
||||||
this.velocityY = velocityY;
|
this.velocityY = velocityY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVelocityX() {
|
public float getVelocityX() {
|
||||||
return velocityX;
|
return velocityX;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVelocityY() {
|
public float getVelocityY() {
|
||||||
return velocityY;
|
return velocityY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
package brainwine.gameserver.entity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import brainwine.gameserver.item.DamageType;
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class EntityConfig {
|
||||||
|
|
||||||
|
@JsonProperty("code")
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
@JacksonInject("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@JsonProperty("health")
|
||||||
|
private float maxHealth = 5;
|
||||||
|
|
||||||
|
@JsonProperty("speed")
|
||||||
|
private float baseSpeed = 3;
|
||||||
|
|
||||||
|
@JsonProperty("size")
|
||||||
|
private Vector2i size = new Vector2i(1, 1);
|
||||||
|
|
||||||
|
@JsonProperty("loot")
|
||||||
|
private List<EntityLoot> loot = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonProperty("loot_by_weapon")
|
||||||
|
private Map<Item, List<EntityLoot>> lootByWeapon = new HashMap<>();
|
||||||
|
|
||||||
|
@JsonProperty("defense")
|
||||||
|
private Map<DamageType, Float> resistances = new HashMap<>();
|
||||||
|
|
||||||
|
@JsonProperty("weakness")
|
||||||
|
private Map<DamageType, Float> weaknesses = new HashMap<>();
|
||||||
|
|
||||||
|
@JsonProperty("components")
|
||||||
|
private Map<String, String[]> components = Collections.emptyMap();
|
||||||
|
|
||||||
|
@JsonProperty("set_attachments")
|
||||||
|
private Map<String, String> attachments = Collections.emptyMap();
|
||||||
|
|
||||||
|
@JsonProperty("behavior")
|
||||||
|
private List<Map<String, Object>> behavior = Collections.emptyList();
|
||||||
|
|
||||||
|
@JsonProperty("animations")
|
||||||
|
private List<Map<String, Object>> animations = Collections.emptyList();
|
||||||
|
|
||||||
|
@JsonProperty("slots")
|
||||||
|
private List<String> slots = Collections.emptyList();
|
||||||
|
|
||||||
|
@JsonProperty("attachments")
|
||||||
|
private List<String> possibleAttachments = Collections.emptyList();
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private EntityConfig() {}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public static EntityConfig fromName(String name) {
|
||||||
|
return EntityRegistry.getEntityConfig(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMaxHealth() {
|
||||||
|
return maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBaseSpeed() {
|
||||||
|
return baseSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2i getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EntityLoot> getLoot() {
|
||||||
|
return loot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Item, List<EntityLoot>> getLootByWeapon() {
|
||||||
|
return lootByWeapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<DamageType, Float> getResistances() {
|
||||||
|
return resistances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<DamageType, Float> getWeaknesses() {
|
||||||
|
return weaknesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String[]> getComponents() {
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAttachments() {
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getBehavior() {
|
||||||
|
return behavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getAnimations() {
|
||||||
|
return animations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSlots() {
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPossibleAttachments() {
|
||||||
|
return possibleAttachments;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package brainwine.gameserver.entity;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class EntityLoot {
|
||||||
|
|
||||||
|
private final Item item;
|
||||||
|
private final int quantity;
|
||||||
|
private final int frequency;
|
||||||
|
|
||||||
|
@ConstructorProperties({"item", "quantity", "frequency"})
|
||||||
|
public EntityLoot(Item item, int quantity, int frequency) {
|
||||||
|
this.item = item;
|
||||||
|
this.quantity = quantity < 1 ? 1 : quantity;
|
||||||
|
this.frequency = frequency < 1 ? 1 : frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFrequency() {
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package brainwine.gameserver.entity;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.InjectableValues;
|
||||||
|
|
||||||
|
import brainwine.gameserver.GameConfiguration;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
import brainwine.shared.JsonHelper;
|
||||||
|
|
||||||
|
public class EntityRegistry {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
private static final Map<String, EntityConfig> entities = new HashMap<>();
|
||||||
|
private static boolean initalized;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
if(initalized) {
|
||||||
|
logger.warn("Already initialized!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Map<String, Object>> entityConfigs = MapHelper.getMap(GameConfiguration.getBaseConfig(), "entities");
|
||||||
|
|
||||||
|
if(entityConfigs == null) {
|
||||||
|
logger.warn("No entity configurations exist!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Entry<String, Map<String, Object>> entry : entityConfigs.entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
Map<String, Object> config = entry.getValue();
|
||||||
|
|
||||||
|
if(!config.containsKey("code")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
registerEntityConfig(name, JsonHelper.readValue(entry.getValue(), EntityConfig.class,
|
||||||
|
new InjectableValues.Std().addValue("name", name)));
|
||||||
|
} catch(Exception e) {
|
||||||
|
logger.error("Could not deserialize entity config for entity '{}'", name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int entityCount = entities.size();
|
||||||
|
logger.info("Successfully loaded {} entit{}", entityCount, entityCount == 1 ? "y" : "ies");
|
||||||
|
initalized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerEntityConfig(String name, EntityConfig config) {
|
||||||
|
if(entities.containsKey(name)) {
|
||||||
|
logger.warn("Attempted to register entity with name '{}' twice", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.put(name, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityConfig getEntityConfig(String name) {
|
||||||
|
return entities.get(name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
package brainwine.gameserver.entity;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
|
||||||
|
|
||||||
public enum EntityType {
|
|
||||||
|
|
||||||
PLAYER(0),
|
|
||||||
GHOST(1),
|
|
||||||
TERRAPUS_JUVENLIE(3),
|
|
||||||
TERRAPUS_ADULT(4);
|
|
||||||
|
|
||||||
private final int id;
|
|
||||||
|
|
||||||
private EntityType(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonValue
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,360 @@
|
||||||
|
package brainwine.gameserver.entity.npc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import brainwine.gameserver.behavior.SequenceBehavior;
|
||||||
|
import brainwine.gameserver.entity.Entity;
|
||||||
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.entity.EntityLoot;
|
||||||
|
import brainwine.gameserver.entity.EntityRegistry;
|
||||||
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.player.Player;
|
||||||
|
import brainwine.gameserver.item.DamageType;
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
import brainwine.gameserver.server.messages.EntityChangeMessage;
|
||||||
|
import brainwine.gameserver.util.MapHelper;
|
||||||
|
import brainwine.gameserver.util.Pair;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
import brainwine.gameserver.util.WeightedMap;
|
||||||
|
import brainwine.gameserver.zone.MetaBlock;
|
||||||
|
import brainwine.gameserver.zone.Zone;
|
||||||
|
|
||||||
|
public class Npc extends Entity {
|
||||||
|
|
||||||
|
public static final int ATTACK_RETENTION_TIME = 333;
|
||||||
|
private final Map<String, Object> properties = new HashMap<>();
|
||||||
|
private final Map<DamageType, Float> baseDefenses = new HashMap<>();
|
||||||
|
private final Map<DamageType, Float> activeDefenses = new HashMap<>();
|
||||||
|
private final Map<Player, Pair<Item, Long>> recentAttacks = new HashMap<>();
|
||||||
|
private final WeightedMap<EntityLoot> loot = new WeightedMap<>();
|
||||||
|
private final Map<Item, WeightedMap<EntityLoot>> lootByWeapon = new HashMap<>();
|
||||||
|
private final List<String> animations;
|
||||||
|
private final SequenceBehavior behaviorTree;
|
||||||
|
private final Vector2i size;
|
||||||
|
private final String typeName;
|
||||||
|
private final float baseSpeed;
|
||||||
|
private float speed;
|
||||||
|
private int moveX;
|
||||||
|
private int moveY;
|
||||||
|
private int guardBlock = -1;
|
||||||
|
private Entity target;
|
||||||
|
private long lastBehavedAt = System.currentTimeMillis();
|
||||||
|
private long lastTrackedAt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public Npc(Zone zone, EntityConfig config) {
|
||||||
|
super(zone);
|
||||||
|
|
||||||
|
// Add components & merge relevant configurations if applicable
|
||||||
|
List<Map<String, Object>> behavior = new ArrayList<>(config.getBehavior());
|
||||||
|
Map<String, String> attachments = new HashMap<>(config.getAttachments());
|
||||||
|
Map<String, String[]> components = config.getComponents();
|
||||||
|
|
||||||
|
// TODO to what extent should we merge the configurations with components?
|
||||||
|
// Merging everything is kind of a pain and seems to be overdoing it anyway...
|
||||||
|
|
||||||
|
if(!components.isEmpty()) {
|
||||||
|
List<Integer> selectedComponents = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Entry<String, String[]> entry : components.entrySet()) {
|
||||||
|
String[] pool = entry.getValue();
|
||||||
|
EntityConfig componentConfig = pool.length > 0 ?
|
||||||
|
EntityRegistry.getEntityConfig(pool[ThreadLocalRandom.current().nextInt(pool.length)]) : null;
|
||||||
|
|
||||||
|
if(componentConfig != null) {
|
||||||
|
behavior.addAll(componentConfig.getBehavior());
|
||||||
|
attachments.putAll(componentConfig.getAttachments());
|
||||||
|
selectedComponents.add(componentConfig.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.put("C", selectedComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set attachments if applicable
|
||||||
|
if(!attachments.isEmpty()) {
|
||||||
|
Map<Integer, Object> slots = new HashMap<>();
|
||||||
|
|
||||||
|
for(Entry<String, String> entry : attachments.entrySet()) {
|
||||||
|
String slot = entry.getKey();
|
||||||
|
String attachment = entry.getValue();
|
||||||
|
|
||||||
|
if(attachment != null) {
|
||||||
|
slots.put(config.getSlots().indexOf(slot), config.getPossibleAttachments().indexOf(attachment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.put("sl", slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
type = config.getType();
|
||||||
|
typeName = config.getName();
|
||||||
|
health = config.getMaxHealth();
|
||||||
|
baseSpeed = config.getBaseSpeed();
|
||||||
|
speed = baseSpeed;
|
||||||
|
size = config.getSize();
|
||||||
|
animations = config.getAnimations().stream().map(map -> MapHelper.getString(map, "name")).collect(Collectors.toList());
|
||||||
|
behaviorTree = SequenceBehavior.createBehaviorTree(this, behavior);
|
||||||
|
baseDefenses.putAll(config.getResistances());
|
||||||
|
|
||||||
|
config.getWeaknesses().forEach((type, multiplier) -> {
|
||||||
|
baseDefenses.put(type, baseDefenses.getOrDefault(type, 0F) - multiplier);
|
||||||
|
});
|
||||||
|
|
||||||
|
config.getLoot().forEach(loot -> this.loot.addEntry(loot, loot.getFrequency()));
|
||||||
|
config.getLootByWeapon().forEach((weapon, loot) -> {
|
||||||
|
WeightedMap<EntityLoot> lootMap = new WeightedMap<>();
|
||||||
|
loot.forEach(entry -> lootMap.addEntry(entry, entry.getFrequency()));
|
||||||
|
lootByWeapon.put(weapon, lootMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(float deltaTime) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Clear expired recent attacks
|
||||||
|
recentAttacks.values().removeIf(attack -> now >= attack.getLast() + ATTACK_RETENTION_TIME);
|
||||||
|
|
||||||
|
// Tick behavior when it is ready
|
||||||
|
if(now >= lastBehavedAt + (int)(1000 / speed)) {
|
||||||
|
lastBehavedAt = now;
|
||||||
|
behaviorTree.behave();
|
||||||
|
|
||||||
|
if(moveX != 0 || moveY != 0) {
|
||||||
|
setPosition(x + moveX, y + moveY);
|
||||||
|
setVelocity(moveX * speed, moveY * speed);
|
||||||
|
moveX = 0;
|
||||||
|
moveY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(Math.round(x), Math.round(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!trackers.isEmpty()) {
|
||||||
|
lastTrackedAt = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die(Player killer) {
|
||||||
|
// Grant loot to killer
|
||||||
|
if(killer != null) {
|
||||||
|
EntityLoot loot = getRandomLoot(killer);
|
||||||
|
|
||||||
|
if(loot != null) {
|
||||||
|
Item item = loot.getItem();
|
||||||
|
|
||||||
|
if(!item.isAir()) {
|
||||||
|
killer.getInventory().addItem(item, loot.getQuantity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove itself from the guard block metadata if it was guarding one
|
||||||
|
if(isGuard()) {
|
||||||
|
MetaBlock metaBlock = zone.getMetaBlock(guardBlock);
|
||||||
|
|
||||||
|
if(metaBlock != null) {
|
||||||
|
MapHelper.getList(metaBlock.getMetadata(), "!", Collections.emptyList()).remove(typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getStatusConfig() {
|
||||||
|
Map<String, Object> config = super.getStatusConfig();
|
||||||
|
config.putAll(properties);
|
||||||
|
|
||||||
|
if(isDead()) {
|
||||||
|
config.put("!", "v");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(int x, int y) {
|
||||||
|
move(x, y, baseSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(int x, int y, float speed) {
|
||||||
|
move(x, y, speed, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(int x, int y, String animation) {
|
||||||
|
move(x, y, baseSpeed, animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(int x, int y, float speed, String animation) {
|
||||||
|
this.speed = speed;
|
||||||
|
direction = x > 0 ? FacingDirection.EAST : x < 0 ? FacingDirection.WEST : direction;
|
||||||
|
moveX = x;
|
||||||
|
moveY = y;
|
||||||
|
|
||||||
|
if(animation != null) {
|
||||||
|
setAnimation(animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attack(Player attacker, Item weapon) {
|
||||||
|
Pair<Item, Long> recentAttack = recentAttacks.get(attacker);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Reject the attack if the player already attacked this entity recently
|
||||||
|
if(recentAttack != null && now < recentAttack.getLast() + ATTACK_RETENTION_TIME) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damage(calculateDamage(weapon.getDamage() / 4, weapon.getDamageType()), attacker); // TODO change weapon damage in config
|
||||||
|
recentAttacks.put(attacker, new Pair<>(weapon, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float calculateDamage(float baseDamage, DamageType type) {
|
||||||
|
return baseDamage * (1 - getDefense(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore // TODO Silly Jackson is drunk and errors trying to find a key deserializer for recentAttacks
|
||||||
|
public Collection<Pair<Item, Long>> getRecentAttacks() {
|
||||||
|
return Collections.unmodifiableCollection(recentAttacks.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefense(DamageType type, float amount) {
|
||||||
|
if(amount == 0) {
|
||||||
|
activeDefenses.remove(type);
|
||||||
|
} else {
|
||||||
|
activeDefenses.put(type, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDefense(DamageType type) {
|
||||||
|
return getDefense(type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDefense(DamageType type, boolean includeBaseDefense) {
|
||||||
|
return (includeBaseDefense ? getBaseDefense(type) : 0) + activeDefenses.getOrDefault(type, 0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClearable() {
|
||||||
|
return !isGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperty(String key, Object value) {
|
||||||
|
if(value == null) {
|
||||||
|
properties.remove(key);
|
||||||
|
} else {
|
||||||
|
properties.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Player tracker : trackers) {
|
||||||
|
tracker.sendMessage(new EntityChangeMessage(id, MapHelper.map(key, value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityLoot getRandomLoot(Player awardee) {
|
||||||
|
Item weapon = awardee.getHeldItem();
|
||||||
|
|
||||||
|
if(lootByWeapon.containsKey(weapon)) {
|
||||||
|
return lootByWeapon.get(weapon).next();
|
||||||
|
} else {
|
||||||
|
return loot.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBaseDefense(DamageType type) {
|
||||||
|
return baseDefenses.getOrDefault(type, 0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGuardBlock(int guardBlock) {
|
||||||
|
this.guardBlock = guardBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGuard() {
|
||||||
|
return guardBlock >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGuardBlock() {
|
||||||
|
return guardBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget(Entity target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTarget() {
|
||||||
|
return target != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeed(float speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBaseSpeed() {
|
||||||
|
return baseSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMoveX() {
|
||||||
|
return moveX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMoveY() {
|
||||||
|
return moveY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastTrackedAt() {
|
||||||
|
return lastTrackedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlocked(int oX, int oY) {
|
||||||
|
int x = (int)this.x;
|
||||||
|
int y = (int)this.y;
|
||||||
|
int tX = x + oX;
|
||||||
|
int tY = y + oY;
|
||||||
|
boolean blocked = zone.isBlockSolid(tX, tY) || (oX != 0 && zone.isBlockSolid(tX, y)) || (oY != 0 && zone.isBlockSolid(x, tY));
|
||||||
|
|
||||||
|
if(size.getX() > 1) {
|
||||||
|
int additionalWidth = size.getX() - 1;
|
||||||
|
blocked = blocked || zone.isBlockSolid(tX + additionalWidth, tY)
|
||||||
|
|| (oX != 0 && zone.isBlockSolid(tX + additionalWidth, y))
|
||||||
|
|| (oY != 0 && zone.isBlockSolid(x + additionalWidth, tY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(size.getY() > 1) {
|
||||||
|
int additionalHeight = size.getY() - 1;
|
||||||
|
blocked = blocked || zone.isBlockSolid(tX, tY - additionalHeight)
|
||||||
|
|| (oX != 0 && zone.isBlockSolid(tX, y - additionalHeight))
|
||||||
|
|| (oY != 0 && zone.isBlockSolid(x, tY - additionalHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnGround() {
|
||||||
|
return isOnGround(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnGround(int diagonal) {
|
||||||
|
return isBlocked(0, 1) || (diagonal != 0 && isBlocked(diagonal, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimation(String name) {
|
||||||
|
int index = animations.indexOf(name);
|
||||||
|
setAnimation(index == -1 ? 0 : index);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
|
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
|
||||||
|
@ -27,8 +28,8 @@ import brainwine.gameserver.dialog.DialogSection;
|
||||||
import brainwine.gameserver.dialog.DialogType;
|
import brainwine.gameserver.dialog.DialogType;
|
||||||
import brainwine.gameserver.entity.Entity;
|
import brainwine.gameserver.entity.Entity;
|
||||||
import brainwine.gameserver.entity.EntityStatus;
|
import brainwine.gameserver.entity.EntityStatus;
|
||||||
import brainwine.gameserver.entity.EntityType;
|
|
||||||
import brainwine.gameserver.entity.FacingDirection;
|
import brainwine.gameserver.entity.FacingDirection;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
import brainwine.gameserver.item.Item;
|
import brainwine.gameserver.item.Item;
|
||||||
import brainwine.gameserver.item.ItemRegistry;
|
import brainwine.gameserver.item.ItemRegistry;
|
||||||
import brainwine.gameserver.item.ItemUseType;
|
import brainwine.gameserver.item.ItemUseType;
|
||||||
|
@ -71,6 +72,8 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
public static final int MAX_SPEED_Y = 25;
|
public static final int MAX_SPEED_Y = 25;
|
||||||
public static final int HEARTBEAT_TIMEOUT = 30000;
|
public static final int HEARTBEAT_TIMEOUT = 30000;
|
||||||
public static final int MAX_AUTH_TOKENS = 3;
|
public static final int MAX_AUTH_TOKENS = 3;
|
||||||
|
public static final int TRACKED_ENTITY_UPDATE_INTERVAL = 100;
|
||||||
|
public static final float ENTITY_VISIBILITY_RANGE = 40;
|
||||||
private static int dialogDiscriminator;
|
private static int dialogDiscriminator;
|
||||||
|
|
||||||
@JacksonInject("documentId")
|
@JacksonInject("documentId")
|
||||||
|
@ -109,6 +112,7 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
private final Map<Skill, Integer> skills = new HashMap<>();
|
private final Map<Skill, Integer> skills = new HashMap<>();
|
||||||
private final Set<Integer> activeChunks = new HashSet<>();
|
private final Set<Integer> activeChunks = new HashSet<>();
|
||||||
private final Map<Integer, Consumer<Object[]>> dialogs = new HashMap<>();
|
private final Map<Integer, Consumer<Object[]>> dialogs = new HashMap<>();
|
||||||
|
private final List<Entity> trackedEntities = new ArrayList<>();
|
||||||
private String clientVersion;
|
private String clientVersion;
|
||||||
private Placement lastPlacement;
|
private Placement lastPlacement;
|
||||||
private Item heldItem = Item.AIR;
|
private Item heldItem = Item.AIR;
|
||||||
|
@ -116,6 +120,7 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
private int teleportX;
|
private int teleportX;
|
||||||
private int teleportY;
|
private int teleportY;
|
||||||
private long lastHeartbeat;
|
private long lastHeartbeat;
|
||||||
|
private long lastTrackedEntityUpdate;
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
|
|
||||||
@ConstructorProperties({"documentId", "name", "current_zone"})
|
@ConstructorProperties({"documentId", "name", "current_zone"})
|
||||||
|
@ -143,19 +148,29 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityType getType() {
|
public void tick(float deltaTime) {
|
||||||
return EntityType.PLAYER;
|
long now = System.currentTimeMillis();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
super.tick();
|
|
||||||
|
|
||||||
if(lastHeartbeat != 0) {
|
if(lastHeartbeat != 0) {
|
||||||
if(System.currentTimeMillis() - lastHeartbeat >= HEARTBEAT_TIMEOUT) {
|
if(System.currentTimeMillis() - lastHeartbeat >= HEARTBEAT_TIMEOUT) {
|
||||||
kick("Connection timed out.");
|
kick("Connection timed out.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(now - lastTrackedEntityUpdate >= TRACKED_ENTITY_UPDATE_INTERVAL) {
|
||||||
|
updateTrackedEntities();
|
||||||
|
|
||||||
|
for(Entity entity : trackedEntities) {
|
||||||
|
sendMessage(new EntityPositionMessage(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTrackedEntityUpdate = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void die(Player killer) {
|
||||||
|
sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.DEAD)); // TODO killer id
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -258,11 +273,17 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
clientVersion = null;
|
clientVersion = null;
|
||||||
|
|
||||||
if(zone != null) {
|
if(zone != null) {
|
||||||
zone.removePlayer(this);
|
zone.removeEntity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogs.clear();
|
dialogs.clear();
|
||||||
activeChunks.clear();
|
activeChunks.clear();
|
||||||
|
|
||||||
|
for(Entity entity : trackedEntities) {
|
||||||
|
entity.removeTracker(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedEntities.clear();
|
||||||
GameServer.getInstance().getPlayerManager().onPlayerDisconnect(this);
|
GameServer.getInstance().getPlayerManager().onPlayerDisconnect(this);
|
||||||
connection.setPlayer(null);
|
connection.setPlayer(null);
|
||||||
connection = null;
|
connection = null;
|
||||||
|
@ -310,7 +331,7 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeZone(Zone zone) {
|
public void changeZone(Zone zone) {
|
||||||
this.zone.removePlayer(this);
|
this.zone.removeEntity(this);
|
||||||
this.zone = zone;
|
this.zone = zone;
|
||||||
sendMessage(new EventMessage("playerWillChangeZone", null));
|
sendMessage(new EventMessage("playerWillChangeZone", null));
|
||||||
kick("Teleporting...", true);
|
kick("Teleporting...", true);
|
||||||
|
@ -386,6 +407,10 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void respawn() {
|
public void respawn() {
|
||||||
|
if(isDead()) {
|
||||||
|
health = 10; // TODO max health
|
||||||
|
}
|
||||||
|
|
||||||
int x = spawnPoint.getX();
|
int x = spawnPoint.getX();
|
||||||
int y = spawnPoint.getY();
|
int y = spawnPoint.getY();
|
||||||
sendMessage(new PlayerPositionMessage(x, y));
|
sendMessage(new PlayerPositionMessage(x, y));
|
||||||
|
@ -699,6 +724,42 @@ public class Player extends Entity implements CommandExecutor {
|
||||||
return activeChunks.size();
|
return activeChunks.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTrackedEntities() {
|
||||||
|
List<Entity> entitiesInRange = zone.getEntitiesInRange(x, y, ENTITY_VISIBILITY_RANGE);
|
||||||
|
entitiesInRange.remove(this);
|
||||||
|
List<Entity> enteredEntities = entitiesInRange.stream().filter(entity -> !trackedEntities.contains(entity))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<Entity> departedEntities = trackedEntities.stream().filter(entity -> !entitiesInRange.contains(entity))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
for(Entity entity : enteredEntities) {
|
||||||
|
if(entity instanceof Npc) {
|
||||||
|
sendMessage(new EntityStatusMessage(entity, EntityStatus.ENTERING));
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.addTracker(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Entity entity : departedEntities) {
|
||||||
|
if(entity instanceof Npc) {
|
||||||
|
sendMessage(new EntityStatusMessage(entity, EntityStatus.EXITING));
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.removeTracker(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedEntities.clear();
|
||||||
|
trackedEntities.addAll(entitiesInRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrackingEntity(Entity entity) {
|
||||||
|
return trackedEntities.contains(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getTrackedEntities() {
|
||||||
|
return trackedEntities;
|
||||||
|
}
|
||||||
|
|
||||||
public void setConnection(Connection connection) {
|
public void setConnection(Connection connection) {
|
||||||
if(isOnline()) {
|
if(isOnline()) {
|
||||||
kick("You logged in from another location.");
|
kick("You logged in from another location.");
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package brainwine.gameserver.item;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
public enum DamageType {
|
||||||
|
|
||||||
|
ACID,
|
||||||
|
BLUDGEONING,
|
||||||
|
COLD,
|
||||||
|
CRUSHING,
|
||||||
|
ENERGY,
|
||||||
|
FIRE,
|
||||||
|
PIERCING,
|
||||||
|
SLASHING,
|
||||||
|
|
||||||
|
@JsonEnumDefaultValue
|
||||||
|
NONE;
|
||||||
|
|
||||||
|
public static DamageType[] getPhysicalDamageTypes() {
|
||||||
|
return new DamageType[] { BLUDGEONING, CRUSHING, PIERCING, SLASHING };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DamageType[] getElementalDamageTypes() {
|
||||||
|
return new DamageType[] { ACID, COLD, ENERGY, FIRE };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DamageType fromName(String id) {
|
||||||
|
for(DamageType value : values()) {
|
||||||
|
if(value.toString().equalsIgnoreCase(id)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public String getId() {
|
||||||
|
return toString().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
import brainwine.gameserver.dialog.DialogType;
|
import brainwine.gameserver.dialog.DialogType;
|
||||||
|
import brainwine.gameserver.util.Pair;
|
||||||
import brainwine.gameserver.util.Vector2i;
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@ -66,6 +67,9 @@ public class Item {
|
||||||
@JsonProperty("field")
|
@JsonProperty("field")
|
||||||
private int field;
|
private int field;
|
||||||
|
|
||||||
|
@JsonProperty("guard")
|
||||||
|
private int guardLevel;
|
||||||
|
|
||||||
@JsonProperty("diggable")
|
@JsonProperty("diggable")
|
||||||
private boolean diggable;
|
private boolean diggable;
|
||||||
|
|
||||||
|
@ -84,6 +88,12 @@ public class Item {
|
||||||
@JsonProperty("invulnerable")
|
@JsonProperty("invulnerable")
|
||||||
private boolean invulnerable;
|
private boolean invulnerable;
|
||||||
|
|
||||||
|
@JsonProperty("solid")
|
||||||
|
private boolean solid;
|
||||||
|
|
||||||
|
@JsonProperty("door")
|
||||||
|
private boolean door;
|
||||||
|
|
||||||
@JsonProperty("inventory")
|
@JsonProperty("inventory")
|
||||||
private LazyItemGetter inventoryItem;
|
private LazyItemGetter inventoryItem;
|
||||||
|
|
||||||
|
@ -96,6 +106,9 @@ public class Item {
|
||||||
@JsonProperty("loot")
|
@JsonProperty("loot")
|
||||||
private String[] lootCategories = {};
|
private String[] lootCategories = {};
|
||||||
|
|
||||||
|
@JsonProperty("damage")
|
||||||
|
private Pair<DamageType, Float> damageInfo;
|
||||||
|
|
||||||
@JsonProperty("ingredients")
|
@JsonProperty("ingredients")
|
||||||
private List<CraftingIngredient> ingredients = new ArrayList<>();
|
private List<CraftingIngredient> ingredients = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -206,6 +219,10 @@ public class Item {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGuardLevel() {
|
||||||
|
return guardLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDiggable() {
|
public boolean isDiggable() {
|
||||||
return diggable;
|
return diggable;
|
||||||
}
|
}
|
||||||
|
@ -230,6 +247,18 @@ public class Item {
|
||||||
return invulnerable || !isPlacable();
|
return invulnerable || !isPlacable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDoor() {
|
||||||
|
return door;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSolid() {
|
||||||
|
return solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWeapon() {
|
||||||
|
return damageInfo != null;
|
||||||
|
}
|
||||||
|
|
||||||
public Item getInventoryItem() {
|
public Item getInventoryItem() {
|
||||||
return inventoryItem == null ? this : inventoryItem.get();
|
return inventoryItem == null ? this : inventoryItem.get();
|
||||||
}
|
}
|
||||||
|
@ -246,6 +275,14 @@ public class Item {
|
||||||
return craftingQuantity;
|
return craftingQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DamageType getDamageType() {
|
||||||
|
return isWeapon() ? damageInfo.getFirst() : DamageType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDamage() {
|
||||||
|
return isWeapon() ? damageInfo.getLast() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCraftable() {
|
public boolean isCraftable() {
|
||||||
return !ingredients.isEmpty();
|
return !ingredients.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,12 @@ public class EntityPositionMessage extends Message {
|
||||||
this(entity.getId(), entity.getX(), entity.getY(), entity.getVelocityX(), entity.getVelocityY(), entity.getDirection(), entity.getTargetX(), entity.getTargetY(), entity.getAnimation());
|
this(entity.getId(), entity.getX(), entity.getY(), entity.getVelocityX(), entity.getVelocityY(), entity.getDirection(), entity.getTargetX(), entity.getTargetY(), entity.getAnimation());
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityPositionMessage(int id, float x, float y, int velocityX, int velocityY, FacingDirection direction, int targetX, int targetY, int animation) {
|
public EntityPositionMessage(int id, float x, float y, float velocityX, float velocityY, FacingDirection direction, int targetX, int targetY, int animation) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.x = (int)(x * Entity.POSITION_MODIFIER);
|
this.x = (int)(x * Entity.POSITION_MODIFIER);
|
||||||
this.y = (int)(y * Entity.POSITION_MODIFIER);
|
this.y = (int)(y * Entity.POSITION_MODIFIER);
|
||||||
this.velocityX = velocityX * Entity.VELOCITY_MODIFIER;
|
this.velocityX = (int)(velocityX * Entity.VELOCITY_MODIFIER);
|
||||||
this.velocityY = velocityY * Entity.VELOCITY_MODIFIER;
|
this.velocityY = (int)(velocityY * Entity.VELOCITY_MODIFIER);
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.targetX = targetX * Entity.VELOCITY_MODIFIER;
|
this.targetX = targetX * Entity.VELOCITY_MODIFIER;
|
||||||
this.targetY = targetY * Entity.VELOCITY_MODIFIER;
|
this.targetY = targetY * Entity.VELOCITY_MODIFIER;
|
||||||
|
|
|
@ -5,14 +5,13 @@ import java.util.Map;
|
||||||
import brainwine.gameserver.annotations.MessageInfo;
|
import brainwine.gameserver.annotations.MessageInfo;
|
||||||
import brainwine.gameserver.entity.Entity;
|
import brainwine.gameserver.entity.Entity;
|
||||||
import brainwine.gameserver.entity.EntityStatus;
|
import brainwine.gameserver.entity.EntityStatus;
|
||||||
import brainwine.gameserver.entity.EntityType;
|
|
||||||
import brainwine.gameserver.server.Message;
|
import brainwine.gameserver.server.Message;
|
||||||
|
|
||||||
@MessageInfo(id = 7, collection = true)
|
@MessageInfo(id = 7, collection = true)
|
||||||
public class EntityStatusMessage extends Message {
|
public class EntityStatusMessage extends Message {
|
||||||
|
|
||||||
public int id;
|
public int id;
|
||||||
public EntityType type;
|
public int type;
|
||||||
public String name;
|
public String name;
|
||||||
public EntityStatus status;
|
public EntityStatus status;
|
||||||
public Map<String, Object> details;
|
public Map<String, Object> details;
|
||||||
|
@ -21,7 +20,7 @@ public class EntityStatusMessage extends Message {
|
||||||
this(entity.getId(), entity.getType(), entity.getName(), status, entity.getStatusConfig());
|
this(entity.getId(), entity.getType(), entity.getName(), status, entity.getStatusConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityStatusMessage(int id, EntityType type, String name, EntityStatus status, Map<String, Object> details) {
|
public EntityStatusMessage(int id, int type, String name, EntityStatus status, Map<String, Object> details) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class AuthenticateRequest extends Request {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
zone.addPlayer(player);
|
zone.addEntity(player);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package brainwine.gameserver.server.requests;
|
package brainwine.gameserver.server.requests;
|
||||||
|
|
||||||
import brainwine.gameserver.annotations.RequestInfo;
|
import brainwine.gameserver.annotations.RequestInfo;
|
||||||
|
import brainwine.gameserver.entity.Entity;
|
||||||
|
import brainwine.gameserver.entity.EntityStatus;
|
||||||
import brainwine.gameserver.entity.player.Player;
|
import brainwine.gameserver.entity.player.Player;
|
||||||
import brainwine.gameserver.server.PlayerRequest;
|
import brainwine.gameserver.server.PlayerRequest;
|
||||||
|
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
||||||
|
|
||||||
@RequestInfo(id = 51)
|
@RequestInfo(id = 51)
|
||||||
public class EntitiesRequest extends PlayerRequest {
|
public class EntitiesRequest extends PlayerRequest {
|
||||||
|
@ -10,6 +13,14 @@ public class EntitiesRequest extends PlayerRequest {
|
||||||
public int[] entityIds;
|
public int[] entityIds;
|
||||||
|
|
||||||
public void process(Player player) {
|
public void process(Player player) {
|
||||||
// TODO
|
int count = Math.min(entityIds.length, 10);
|
||||||
|
|
||||||
|
for(int i = 0; i < count; i++) {
|
||||||
|
Entity entity = player.getZone().getEntity(entityIds[i]);
|
||||||
|
|
||||||
|
if(entity != null && player.isTrackingEntity(entity)) {
|
||||||
|
player.sendMessage(new EntityStatusMessage(entity, EntityStatus.ENTERING));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ public class HealthRequest extends PlayerRequest {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
player.damage(player.getHealth() - health, null);
|
||||||
player.setHealth(10);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package brainwine.gameserver.server.requests;
|
package brainwine.gameserver.server.requests;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import brainwine.gameserver.annotations.OptionalField;
|
import brainwine.gameserver.annotations.OptionalField;
|
||||||
import brainwine.gameserver.annotations.RequestInfo;
|
import brainwine.gameserver.annotations.RequestInfo;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
import brainwine.gameserver.entity.player.Player;
|
import brainwine.gameserver.entity.player.Player;
|
||||||
import brainwine.gameserver.item.Item;
|
import brainwine.gameserver.item.Item;
|
||||||
import brainwine.gameserver.server.PlayerRequest;
|
import brainwine.gameserver.server.PlayerRequest;
|
||||||
|
@ -10,6 +13,7 @@ import brainwine.gameserver.server.messages.EntityItemUseMessage;
|
||||||
/**
|
/**
|
||||||
* TODO This request may be sent *before* a {@link CraftRequest} is sent.
|
* TODO This request may be sent *before* a {@link CraftRequest} is sent.
|
||||||
* So basically, we can't really perform any "has item" checks...
|
* So basically, we can't really perform any "has item" checks...
|
||||||
|
* ... Let's do it anyway lol!
|
||||||
*/
|
*/
|
||||||
@RequestInfo(id = 10)
|
@RequestInfo(id = 10)
|
||||||
public class InventoryUseRequest extends PlayerRequest {
|
public class InventoryUseRequest extends PlayerRequest {
|
||||||
|
@ -23,10 +27,34 @@ public class InventoryUseRequest extends PlayerRequest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(Player player) {
|
public void process(Player player) {
|
||||||
|
if(!player.getInventory().hasItem(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(type == 0) {
|
if(type == 0) {
|
||||||
if(status != 2) {
|
if(status != 2) {
|
||||||
player.setHeldItem(item);
|
player.setHeldItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lovely type ambiguity. Always nice.
|
||||||
|
if(details instanceof Collection) {
|
||||||
|
Collection<?> entityIds = (Collection<?>)details;
|
||||||
|
int maxEntityAttackCount = 1; // TODO agility skill
|
||||||
|
|
||||||
|
for(Object id : entityIds) {
|
||||||
|
if(id instanceof Integer) {
|
||||||
|
Npc npc = player.getZone().getNpc((int)id);
|
||||||
|
|
||||||
|
if(npc != null && player.canSee(npc)) {
|
||||||
|
npc.attack(player, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(--maxEntityAttackCount <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
player.sendMessageToPeers(new EntityItemUseMessage(player.getId(), type, item, status));
|
player.sendMessageToPeers(new EntityItemUseMessage(player.getId(), type, item, status));
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class MoveRequest extends PlayerRequest {
|
||||||
player.setVelocity(velocityX, velocityY);
|
player.setVelocity(velocityX, velocityY);
|
||||||
player.setDirection(direction);
|
player.setDirection(direction);
|
||||||
player.setTarget(targetX, targetY);
|
player.setTarget(targetX, targetY);
|
||||||
player.setAnimation(animation);
|
player.setAnimation(player.isDead() ? 55 : animation); // TODO why doesn't death animation sync normally?
|
||||||
player.sendMessageToPeers(new EntityPositionMessage(player));
|
player.sendMessageToPeers(new EntityPositionMessage(player));
|
||||||
zone.exploreArea((int)fX, (int)fY); // TODO xp reward
|
zone.exploreArea((int)fX, (int)fY); // TODO xp reward
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,12 @@ public class MapHelper {
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <K, V> Map<K, V> map(K key, V value) {
|
||||||
|
Map<K, V> map = new HashMap<>();
|
||||||
|
map.put(key, value);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
public static void put(Map<?, ?> map, String path, Object value) {
|
public static void put(Map<?, ?> map, String path, Object value) {
|
||||||
String[] segments = path.split("\\.");
|
String[] segments = path.split("\\.");
|
||||||
Map<Object, Object> current = (Map<Object, Object>)map;
|
Map<Object, Object> current = (Map<Object, Object>)map;
|
||||||
|
|
26
gameserver/src/main/java/brainwine/gameserver/util/Pair.java
Normal file
26
gameserver/src/main/java/brainwine/gameserver/util/Pair.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package brainwine.gameserver.util;
|
||||||
|
|
||||||
|
import java.beans.ConstructorProperties;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
|
||||||
|
public class Pair<K, V> {
|
||||||
|
|
||||||
|
private final K first;
|
||||||
|
private final V last;
|
||||||
|
|
||||||
|
@ConstructorProperties({"first", "last"})
|
||||||
|
public Pair(K first, V last) {
|
||||||
|
this.first = first;
|
||||||
|
this.last = last;
|
||||||
|
}
|
||||||
|
|
||||||
|
public K getFirst() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getLast() {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
@ -160,6 +162,33 @@ public class ChunkManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Chunk> getVisibleChunks() {
|
||||||
|
List<Chunk> visibleChunks = new ArrayList<>();
|
||||||
|
Set<Integer> chunkIndices = new HashSet<>();
|
||||||
|
int chunkWidth = zone.getChunkWidth();
|
||||||
|
int chunkHeight = zone.getChunkHeight();
|
||||||
|
|
||||||
|
for(Player player : zone.getPlayers()) {
|
||||||
|
int x = (int)player.getX();
|
||||||
|
int y = (int)player.getY();
|
||||||
|
|
||||||
|
// TODO take screen size & perception skill into account
|
||||||
|
for(int i = -40; i <= 40; i += chunkWidth) {
|
||||||
|
for(int j = -20; j <= 20; j += chunkHeight) {
|
||||||
|
chunkIndices.add(getChunkIndex(x + i, y + j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int chunkIndex : chunkIndices) {
|
||||||
|
if(isChunkLoaded(chunkIndex)) {
|
||||||
|
visibleChunks.add(chunks.get(chunkIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visibleChunks;
|
||||||
|
}
|
||||||
|
|
||||||
public void putChunk(int index, Chunk chunk) {
|
public void putChunk(int index, Chunk chunk) {
|
||||||
if(!chunks.containsKey(index) && isChunkIndexInBounds(index)) {
|
if(!chunks.containsKey(index) && isChunkIndexInBounds(index)) {
|
||||||
chunk.setModified(true);
|
chunk.setModified(true);
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
package brainwine.gameserver.zone;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.Entity;
|
||||||
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.entity.EntityStatus;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
|
import brainwine.gameserver.entity.player.Player;
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
import brainwine.gameserver.item.Layer;
|
||||||
|
import brainwine.gameserver.item.ModType;
|
||||||
|
import brainwine.gameserver.server.messages.EffectMessage;
|
||||||
|
import brainwine.gameserver.server.messages.EntityPositionMessage;
|
||||||
|
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
||||||
|
import brainwine.gameserver.util.ResourceUtils;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
import brainwine.gameserver.util.WeightedMap;
|
||||||
|
import brainwine.shared.JsonHelper;
|
||||||
|
|
||||||
|
public class EntityManager {
|
||||||
|
|
||||||
|
public static final long ENTITY_CLEAR_TIME = 10000;
|
||||||
|
public static final long SPAWN_INTERVAL = 200;
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
private static final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
private static final Map<Biome, List<EntitySpawn>> spawns = new HashMap<>();
|
||||||
|
private final Map<Integer, Entity> entities = new HashMap<>();
|
||||||
|
private final Map<Integer, Npc> npcs = new HashMap<>();
|
||||||
|
private final Map<Integer, Player> players = new HashMap<>();
|
||||||
|
private final Map<String, Player> playersByName = new HashMap<>();
|
||||||
|
private final Zone zone;
|
||||||
|
private int entityDiscriminator;
|
||||||
|
private long lastSpawnAt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public EntityManager(Zone zone) {
|
||||||
|
this.zone = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadEntitySpawns() {
|
||||||
|
spawns.clear();
|
||||||
|
logger.info("Loading entity spawns ...");
|
||||||
|
File file = new File("spawning.json");
|
||||||
|
ResourceUtils.copyDefaults("spawning.json");
|
||||||
|
|
||||||
|
if(file.isFile()) {
|
||||||
|
try {
|
||||||
|
Map<Biome, List<EntitySpawn>> loot = JsonHelper.readValue(file, new TypeReference<Map<Biome, List<EntitySpawn>>>(){});
|
||||||
|
spawns.putAll(loot);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to load entity spawns", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EntityConfig getRandomEligibleEntity(Biome biome, String locale, double depth, Item baseItem) {
|
||||||
|
WeightedMap<EntityConfig> eligibleEntities = new WeightedMap<>();
|
||||||
|
|
||||||
|
if(spawns.containsKey(biome)) {
|
||||||
|
spawns.get(biome).stream().filter(spawn -> locale.equalsIgnoreCase(spawn.getLocale())
|
||||||
|
&& depth >= spawn.getMinDepth() && depth <= spawn.getMaxDepth()
|
||||||
|
&& ((baseItem.getId() != 5 && baseItem.getId() != 6) || spawn.getOrifice() == baseItem))
|
||||||
|
.forEach(spawn -> eligibleEntities.addEntry(spawn.getEntity(), spawn.getFrequency()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return eligibleEntities.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick(float deltaTime) {
|
||||||
|
clearEntities();
|
||||||
|
|
||||||
|
for(Entity entity : getEntities()) {
|
||||||
|
entity.tick(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(now > lastSpawnAt + SPAWN_INTERVAL &&
|
||||||
|
!players.isEmpty() && getTransientNpcCount() < Math.min(64, players.size() * 8)) {
|
||||||
|
spawnRandomEntity();
|
||||||
|
lastSpawnAt = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnRandomEntity() {
|
||||||
|
boolean immediate = random.nextDouble() < 0.75;
|
||||||
|
List<Chunk> visibleChunks = zone.getVisibleChunks();
|
||||||
|
List<Chunk> chunks = immediate ? visibleChunks : zone.getLoadedChunks().stream()
|
||||||
|
.filter(chunk -> !visibleChunks.contains(chunk)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
if(!chunks.isEmpty()) {
|
||||||
|
List<Vector2i> eligiblePositions = new ArrayList<>();
|
||||||
|
Chunk chunk = chunks.get(random.nextInt(chunks.size()));
|
||||||
|
|
||||||
|
for(int x = chunk.getX(); x < chunk.getX() + chunk.getWidth(); x++) {
|
||||||
|
for(int y = chunk.getY(); y < chunk.getY() + chunk.getHeight(); y++) {
|
||||||
|
Block block = chunk.getBlock(x, y);
|
||||||
|
int base = block.getBaseItem().getId();
|
||||||
|
|
||||||
|
if((immediate && base == 5 || base == 6) ||
|
||||||
|
(!immediate && block.getBackItem().isAir() && block.getFrontItem().isAir())) {
|
||||||
|
eligiblePositions.add(new Vector2i(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!eligiblePositions.isEmpty()) {
|
||||||
|
Vector2i position = eligiblePositions.get(random.nextInt(eligiblePositions.size()));
|
||||||
|
int x = position.getX();
|
||||||
|
int y = position.getY();
|
||||||
|
Block block = chunk.getBlock(x, y);
|
||||||
|
String locale = block.getBaseItem().isAir() ? "sky" : "cave";
|
||||||
|
EntityConfig entity = getRandomEligibleEntity(zone.getBiome(), locale, y / (double)zone.getHeight(), block.getBaseItem());
|
||||||
|
|
||||||
|
if(immediate) {
|
||||||
|
if(tryBustOrifice(x, y, Layer.BACK) || tryBustOrifice(x, y, Layer.FRONT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(entity != null) {
|
||||||
|
spawnEntity(new Npc(zone, entity), x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryBustOrifice(int x, int y, Layer layer) {
|
||||||
|
if(zone.isBlockProtected(x, y, null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block block = zone.getBlock(x, y);
|
||||||
|
Item item = block.getItem(layer);
|
||||||
|
int mod = block.getMod(layer);
|
||||||
|
|
||||||
|
if(!item.isAir()) {
|
||||||
|
if(random.nextBoolean()) {
|
||||||
|
item = item.getMod() == ModType.DECAY && mod < 5 ? item : Item.AIR;
|
||||||
|
mod = item.isAir() ? 0 : Math.min(5, mod + random.nextInt(1, 3));
|
||||||
|
zone.updateBlock(x, y, layer, item, mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearEntities() {
|
||||||
|
List<Npc> clearableEntities = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Npc npc : npcs.values()) {
|
||||||
|
if(npc.isDead() || !zone.isChunkLoaded((int)npc.getX(), (int)npc.getY()) ||
|
||||||
|
(!npc.isGuard() && System.currentTimeMillis() > npc.getLastTrackedAt() + ENTITY_CLEAR_TIME)) {
|
||||||
|
clearableEntities.add(npc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Npc npc : clearableEntities) {
|
||||||
|
removeEntity(npc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getEntitiesInRange(float x, float y, float range) {
|
||||||
|
return getEntities().stream().filter(entity -> entity.inRange(x, y, range)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getRandomPlayerInRange(float x, float y, float range) {
|
||||||
|
List<Player> players = getPlayersInRange(x, y, range);
|
||||||
|
return players.isEmpty() ? null : players.get(random.nextInt(players.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Player> getPlayersInRange(float x, float y, float range) {
|
||||||
|
return getPlayers().stream().filter(player -> player.inRange(x, y, range)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawnEntity(Entity entity, int x, int y) {
|
||||||
|
spawnEntity(entity, x, y, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawnEntity(Entity entity, int x, int y, boolean effect) {
|
||||||
|
if(zone.isChunkLoaded(x, y)) {
|
||||||
|
addEntity(entity);
|
||||||
|
entity.setPosition(x, y);
|
||||||
|
|
||||||
|
if(effect) {
|
||||||
|
zone.sendMessageToChunk(new EffectMessage(x + 0.5F, y + 0.5F, "bomb-teleport", 4), zone.getChunk(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntity(Entity entity) {
|
||||||
|
if(entities.containsValue(entity)) {
|
||||||
|
removeEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int entityId = ++entityDiscriminator;
|
||||||
|
entity.setZone(zone);
|
||||||
|
entity.setId(entityId);
|
||||||
|
|
||||||
|
if(entity instanceof Player) {
|
||||||
|
Player player = (Player)entity;
|
||||||
|
player.onZoneChanged();
|
||||||
|
players.put(entityId, player);
|
||||||
|
playersByName.put(player.getName(), player);
|
||||||
|
player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING));
|
||||||
|
player.sendMessageToPeers(new EntityPositionMessage(player));
|
||||||
|
} else if(entity instanceof Npc) {
|
||||||
|
npcs.put(entityId, (Npc)entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.put(entityId, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeEntity(Entity entity) {
|
||||||
|
int entityId = entity.getId();
|
||||||
|
|
||||||
|
if(entity instanceof Player) {
|
||||||
|
players.remove(entityId);
|
||||||
|
playersByName.remove(entity.getName());
|
||||||
|
zone.sendMessage(new EntityStatusMessage(entity, EntityStatus.EXITING));
|
||||||
|
} else {
|
||||||
|
npcs.remove(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.remove(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity getEntity(int entityId) {
|
||||||
|
return entities.get(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntityCount() {
|
||||||
|
return entities.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Entity> getEntities() {
|
||||||
|
return Collections.unmodifiableCollection(entities.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Npc getNpc(int entityId) {
|
||||||
|
return npcs.get(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNpcCount() {
|
||||||
|
return npcs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTransientNpcCount() {
|
||||||
|
return (int)(getNpcCount() - getNpcs().stream().filter(npc -> npc.isGuard()).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Npc> getNpcs() {
|
||||||
|
return Collections.unmodifiableCollection(npcs.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer(int entityId) {
|
||||||
|
return players.get(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer(String name) {
|
||||||
|
return playersByName.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount() {
|
||||||
|
return players.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Player> getPlayers() {
|
||||||
|
return Collections.unmodifiableCollection(players.values());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package brainwine.gameserver.zone;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.item.Item;
|
||||||
|
|
||||||
|
// TODO groups
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class EntitySpawn {
|
||||||
|
|
||||||
|
@JsonProperty("entity")
|
||||||
|
private EntityConfig entity;
|
||||||
|
|
||||||
|
@JsonProperty("locale")
|
||||||
|
private String locale;
|
||||||
|
|
||||||
|
@JsonProperty("min_depth")
|
||||||
|
private double minDepth;
|
||||||
|
|
||||||
|
@JsonProperty("max_depth")
|
||||||
|
private double maxDepth = 1;
|
||||||
|
|
||||||
|
@JsonProperty("orifice")
|
||||||
|
private Item orifice;
|
||||||
|
|
||||||
|
@JsonProperty("frequency")
|
||||||
|
private double frequency = 1;
|
||||||
|
|
||||||
|
public EntityConfig getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMinDepth() {
|
||||||
|
return minDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMaxDepth() {
|
||||||
|
return maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getOrifice() {
|
||||||
|
return orifice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFrequency() {
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,9 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
import brainwine.gameserver.GameServer;
|
import brainwine.gameserver.GameServer;
|
||||||
import brainwine.gameserver.entity.Entity;
|
import brainwine.gameserver.entity.Entity;
|
||||||
import brainwine.gameserver.entity.EntityStatus;
|
import brainwine.gameserver.entity.EntityConfig;
|
||||||
|
import brainwine.gameserver.entity.EntityRegistry;
|
||||||
|
import brainwine.gameserver.entity.npc.Npc;
|
||||||
import brainwine.gameserver.entity.player.ChatType;
|
import brainwine.gameserver.entity.player.ChatType;
|
||||||
import brainwine.gameserver.entity.player.NotificationType;
|
import brainwine.gameserver.entity.player.NotificationType;
|
||||||
import brainwine.gameserver.entity.player.Player;
|
import brainwine.gameserver.entity.player.Player;
|
||||||
|
@ -36,13 +38,12 @@ import brainwine.gameserver.server.messages.BlockChangeMessage;
|
||||||
import brainwine.gameserver.server.messages.BlockMetaMessage;
|
import brainwine.gameserver.server.messages.BlockMetaMessage;
|
||||||
import brainwine.gameserver.server.messages.ChatMessage;
|
import brainwine.gameserver.server.messages.ChatMessage;
|
||||||
import brainwine.gameserver.server.messages.ConfigurationMessage;
|
import brainwine.gameserver.server.messages.ConfigurationMessage;
|
||||||
import brainwine.gameserver.server.messages.EntityPositionMessage;
|
|
||||||
import brainwine.gameserver.server.messages.EntityStatusMessage;
|
|
||||||
import brainwine.gameserver.server.messages.LightMessage;
|
import brainwine.gameserver.server.messages.LightMessage;
|
||||||
import brainwine.gameserver.server.messages.ZoneExploredMessage;
|
import brainwine.gameserver.server.messages.ZoneExploredMessage;
|
||||||
import brainwine.gameserver.server.messages.ZoneStatusMessage;
|
import brainwine.gameserver.server.messages.ZoneStatusMessage;
|
||||||
import brainwine.gameserver.util.MapHelper;
|
import brainwine.gameserver.util.MapHelper;
|
||||||
import brainwine.gameserver.util.MathUtils;
|
import brainwine.gameserver.util.MathUtils;
|
||||||
|
import brainwine.gameserver.util.Vector2i;
|
||||||
|
|
||||||
public class Zone {
|
public class Zone {
|
||||||
|
|
||||||
|
@ -65,10 +66,9 @@ public class Zone {
|
||||||
private float acidity = 0;
|
private float acidity = 0;
|
||||||
private final ChunkManager chunkManager;
|
private final ChunkManager chunkManager;
|
||||||
private final WeatherManager weatherManager = new WeatherManager();
|
private final WeatherManager weatherManager = new WeatherManager();
|
||||||
|
private final EntityManager entityManager = new EntityManager(this);
|
||||||
private final Queue<DugBlock> digQueue = new ArrayDeque<>();
|
private final Queue<DugBlock> digQueue = new ArrayDeque<>();
|
||||||
private final Set<Integer> pendingSunlight = new HashSet<>();
|
private final Set<Integer> pendingSunlight = new HashSet<>();
|
||||||
private final Map<Integer, Entity> entities = new HashMap<>();
|
|
||||||
private final List<Player> players = new ArrayList<>();
|
|
||||||
private final Map<String, Integer> dungeons = new HashMap<>();
|
private final Map<String, Integer> dungeons = new HashMap<>();
|
||||||
private final Map<Integer, MetaBlock> metaBlocks = new HashMap<>();
|
private final Map<Integer, MetaBlock> metaBlocks = new HashMap<>();
|
||||||
private final Map<Integer, MetaBlock> globalMetaBlocks = new HashMap<>();
|
private final Map<Integer, MetaBlock> globalMetaBlocks = new HashMap<>();
|
||||||
|
@ -107,10 +107,7 @@ public class Zone {
|
||||||
public void tick(float deltaTime) {
|
public void tick(float deltaTime) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
weatherManager.tick(deltaTime);
|
weatherManager.tick(deltaTime);
|
||||||
|
entityManager.tick(deltaTime);
|
||||||
for(Entity entity : getEntities()) {
|
|
||||||
entity.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
// One full cycle = 1200 seconds = 20 minutes
|
// One full cycle = 1200 seconds = 20 minutes
|
||||||
time += deltaTime * (1.0F / 1200.0F);
|
time += deltaTime * (1.0F / 1200.0F);
|
||||||
|
@ -119,7 +116,7 @@ public class Zone {
|
||||||
time -= 1.0F;
|
time -= 1.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!players.isEmpty()) {
|
if(!getPlayers().isEmpty()) {
|
||||||
if(now >= lastStatusUpdate + 4000) {
|
if(now >= lastStatusUpdate + 4000) {
|
||||||
sendMessage(new ZoneStatusMessage(getStatusConfig()));
|
sendMessage(new ZoneStatusMessage(getStatusConfig()));
|
||||||
lastStatusUpdate = now;
|
lastStatusUpdate = now;
|
||||||
|
@ -148,7 +145,7 @@ public class Zone {
|
||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
*/
|
*/
|
||||||
public void sendMessage(Message message) {
|
public void sendMessage(Message message) {
|
||||||
for(Player player : players) {
|
for(Player player : getPlayers()) {
|
||||||
player.sendMessage(message);
|
player.sendMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +157,7 @@ public class Zone {
|
||||||
* @param chunk The chunk near which players must be.
|
* @param chunk The chunk near which players must be.
|
||||||
*/
|
*/
|
||||||
public void sendMessageToChunk(Message message, Chunk chunk) {
|
public void sendMessageToChunk(Message message, Chunk chunk) {
|
||||||
for(Player player : players) {
|
for(Player player : getPlayers()) {
|
||||||
if(player.isChunkActive(chunk)) {
|
if(player.isChunkActive(chunk)) {
|
||||||
player.sendMessage(message);
|
player.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
@ -182,6 +179,132 @@ public class Zone {
|
||||||
sendMessage(new ChatMessage(sender.getId(), text, type));
|
sendMessage(new ChatMessage(sender.getId(), text, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPointVisibleFrom(int x1, int y1, int x2, int y2) {
|
||||||
|
return raycast(x1, y1, x2, y2) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2i raycast(int x1, int y1, int x2, int y2) {
|
||||||
|
List<Vector2i> path = raycast(x1, y1, x2, y2, false, false, false);
|
||||||
|
|
||||||
|
if(path != null && !path.isEmpty()) {
|
||||||
|
return path.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2i raynext(int x1, int y1, int x2, int y2) {
|
||||||
|
List<Vector2i> path = raycast(x1, y1, x2, y2, true, true, true);
|
||||||
|
|
||||||
|
if(path != null && path.size() > 1) {
|
||||||
|
return path.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shamelessly ported from the zone kernel (https://github.com/bytebin/deepworld-gameserver/blob/master/ext/lib/zone.c#L798)
|
||||||
|
private List<Vector2i> raycast(int x1, int y1, int x2, int y2, boolean path, boolean all, boolean next) {
|
||||||
|
List<Vector2i> coords = new ArrayList<>();
|
||||||
|
int x = x1;
|
||||||
|
int y = y1;
|
||||||
|
int diffX = Math.abs(x2 - x1);
|
||||||
|
int diffY = Math.abs(y2 - y1);
|
||||||
|
int signX = (int)Math.signum(x2 - x1);
|
||||||
|
int signY = (int)Math.signum(y2 - y1);
|
||||||
|
boolean swap = false;
|
||||||
|
|
||||||
|
if(diffY > diffX) {
|
||||||
|
int temp = diffX;
|
||||||
|
diffX = diffY;
|
||||||
|
diffY = temp;
|
||||||
|
swap = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int d = 2 * diffY - diffX;
|
||||||
|
|
||||||
|
for(int i = 0; i < diffX; i++) {
|
||||||
|
if(!all && isBlockSolid(x, y)) {
|
||||||
|
if(path) {
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
coords.add(new Vector2i(x, y));
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path) {
|
||||||
|
coords.add(new Vector2i(x, y));
|
||||||
|
|
||||||
|
if(next && i == 1) {
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(d >= 0) {
|
||||||
|
d = d - 2 * diffX;
|
||||||
|
|
||||||
|
if(swap) {
|
||||||
|
x += signX;
|
||||||
|
} else {
|
||||||
|
y += signY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = d + 2 * diffY;
|
||||||
|
|
||||||
|
if(swap) {
|
||||||
|
y += signY;
|
||||||
|
} else {
|
||||||
|
x += signX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return all ? coords : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockSolid(int x, int y) {
|
||||||
|
return isBlockSolid(x, y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockSolid(int x, int y, boolean checkAdjacents) {
|
||||||
|
if(!areCoordinatesInBounds(x, y) || !isChunkLoaded(x, y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block block = getBlock(x, y);
|
||||||
|
Item item = block.getItem(Layer.FRONT);
|
||||||
|
|
||||||
|
if(item.isDoor() && block.getFrontMod() % 2 == 0) {
|
||||||
|
return true;
|
||||||
|
} else if(!item.isDoor() && item.isSolid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(checkAdjacents) {
|
||||||
|
for(int i = -3; i <= 0; i++) {
|
||||||
|
for(int j = 0; j <= 2; j++) {
|
||||||
|
int x1 = x + i;
|
||||||
|
int y1 = y + j;
|
||||||
|
|
||||||
|
if(!areCoordinatesInBounds(x1, y1) || !isChunkLoaded(x1, y1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = getBlock(x1, y1);
|
||||||
|
item = block.getFrontItem();
|
||||||
|
|
||||||
|
if(item.getBlockWidth() > Math.abs(i) && item.getBlockHeight() > Math.abs(j)
|
||||||
|
&& isBlockSolid(x1, y1, false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isBlockOccupied(int x, int y, Layer layer) {
|
public boolean isBlockOccupied(int x, int y, Layer layer) {
|
||||||
if(!areCoordinatesInBounds(x, y)) {
|
if(!areCoordinatesInBounds(x, y)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -193,6 +316,10 @@ public class Zone {
|
||||||
return !item.isAir() && !item.canPlaceOver();
|
return !item.isAir() && !item.canPlaceOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBlockProtected(int x, int y) {
|
||||||
|
return isBlockProtected(x, y, null);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isBlockProtected(int x, int y, Player from) {
|
public boolean isBlockProtected(int x, int y, Player from) {
|
||||||
for(MetaBlock fieldBlock : fieldBlocks.values()) {
|
for(MetaBlock fieldBlock : fieldBlocks.values()) {
|
||||||
Item item = fieldBlock.getItem();
|
Item item = fieldBlock.getItem();
|
||||||
|
@ -200,13 +327,15 @@ public class Zone {
|
||||||
int fY = fieldBlock.getY();
|
int fY = fieldBlock.getY();
|
||||||
int field = fieldBlock.getItem().getField();
|
int field = fieldBlock.getItem().getField();
|
||||||
|
|
||||||
if(item.isDish() && !ownsMetaBlock(fieldBlock, from)) {
|
if(from == null || !ownsMetaBlock(fieldBlock, from)) {
|
||||||
if(MathUtils.inRange(x, y, fX, fY, field)) {
|
if(item.isDish()) {
|
||||||
return true;
|
if(MathUtils.inRange(x, y, fX, fY, field)) {
|
||||||
}
|
return true;
|
||||||
} else if(item.hasField() && !ownsMetaBlock(fieldBlock, from)) {
|
}
|
||||||
if(x == fX && y == fY) {
|
} else if(item.hasField()) {
|
||||||
return true;
|
if(x == fX && y == fY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,6 +480,7 @@ public class Zone {
|
||||||
metadata.put("@", dungeonId);
|
metadata.put("@", dungeonId);
|
||||||
|
|
||||||
if(frontItem.hasUse(ItemUseType.GUARD)) {
|
if(frontItem.hasUse(ItemUseType.GUARD)) {
|
||||||
|
addGuardianEntities(metadata, frontItem.getGuardLevel());
|
||||||
guardBlocks++;
|
guardBlocks++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,9 +536,11 @@ public class Zone {
|
||||||
List<MetaBlock> guardBlocks = getMetaBlocksWithUse(ItemUseType.GUARD);
|
List<MetaBlock> guardBlocks = getMetaBlocksWithUse(ItemUseType.GUARD);
|
||||||
|
|
||||||
for(MetaBlock metaBlock : guardBlocks) {
|
for(MetaBlock metaBlock : guardBlocks) {
|
||||||
String dungeonId = MapHelper.getString(metaBlock.getMetadata(), "@");
|
Map<String, Object> metadata = metaBlock.getMetadata();
|
||||||
|
String dungeonId = MapHelper.getString(metadata, "@");
|
||||||
|
|
||||||
if(dungeonId != null) {
|
if(dungeonId != null) {
|
||||||
|
addGuardianEntities(metadata, metaBlock.getItem().getGuardLevel());
|
||||||
int numGuardBlocks = dungeons.getOrDefault(dungeonId, 0);
|
int numGuardBlocks = dungeons.getOrDefault(dungeonId, 0);
|
||||||
numGuardBlocks++;
|
numGuardBlocks++;
|
||||||
dungeons.put(dungeonId, numGuardBlocks);
|
dungeons.put(dungeonId, numGuardBlocks);
|
||||||
|
@ -416,6 +548,24 @@ public class Zone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addGuardianEntities(Map<String, Object> metadata, int guardLevel) {
|
||||||
|
List<String> guardians = MapHelper.getList(metadata, "!");
|
||||||
|
|
||||||
|
if(guardians == null) {
|
||||||
|
guardians = new ArrayList<>();
|
||||||
|
|
||||||
|
if(guardLevel >= 5) {
|
||||||
|
guardians.add("brains/large");
|
||||||
|
} else if(guardLevel >= 3) {
|
||||||
|
guardians.add("brains/medium");
|
||||||
|
} else {
|
||||||
|
guardians.add("brains/small");
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.put("!", guardians);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void destroyGuardBlock(String dungeonId, Player destroyer) {
|
public void destroyGuardBlock(String dungeonId, Player destroyer) {
|
||||||
if(dungeons.containsKey(dungeonId)) {
|
if(dungeons.containsKey(dungeonId)) {
|
||||||
int guardBlocks = dungeons.get(dungeonId);
|
int guardBlocks = dungeons.get(dungeonId);
|
||||||
|
@ -613,6 +763,10 @@ public class Zone {
|
||||||
return metaBlocks.get(getBlockIndex(x, y));
|
return metaBlocks.get(getBlockIndex(x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MetaBlock getMetaBlock(int index) {
|
||||||
|
return metaBlocks.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
public List<MetaBlock> getMetaBlocksWithUse(ItemUseType useType){
|
public List<MetaBlock> getMetaBlocksWithUse(ItemUseType useType){
|
||||||
return getMetaBlocksWhere(block -> block.getItem().hasUse(useType));
|
return getMetaBlocksWhere(block -> block.getItem().hasUse(useType));
|
||||||
}
|
}
|
||||||
|
@ -640,35 +794,72 @@ public class Zone {
|
||||||
return globalMetaBlocks.values();
|
return globalMetaBlocks.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlayer(Player player) {
|
public List<Entity> getEntitiesInRange(float x, float y, float range) {
|
||||||
addEntity(player);
|
return entityManager.getEntitiesInRange(x, y, range);
|
||||||
player.onZoneChanged();
|
|
||||||
player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING));
|
|
||||||
player.sendMessageToPeers(new EntityPositionMessage(player));
|
|
||||||
players.add(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePlayer(Player player) {
|
public Player getRandomPlayerInRange(float x, float y, float range) {
|
||||||
players.remove(player);
|
return entityManager.getRandomPlayerInRange(x, y, range);
|
||||||
player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.EXITING));
|
|
||||||
removeEntity(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEntity(Entity entity) {
|
public List<Player> getPlayersInRange(float x, float y, float range) {
|
||||||
entity.setZone(this);
|
return entityManager.getPlayersInRange(x, y, range);
|
||||||
entities.put(entity.getId(), entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeEntity(Entity entity) {
|
public void spawnEntity(Entity entity, int x, int y) {
|
||||||
entities.remove(entity.getId());
|
entityManager.spawnEntity(entity, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawnEntity(Entity entity, int x, int y, boolean effect) {
|
||||||
|
entityManager.spawnEntity(entity, x, y, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntity(Entity entity) {
|
||||||
|
entityManager.addEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeEntity(Entity entity) {
|
||||||
|
entityManager.removeEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity getEntity(int entityId) {
|
||||||
|
return entityManager.getEntity(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntityCount() {
|
||||||
|
return entityManager.getEntityCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Entity> getEntities() {
|
public Collection<Entity> getEntities() {
|
||||||
return entities.values();
|
return entityManager.getEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Player> getPlayers() {
|
public Npc getNpc(int entityId) {
|
||||||
return players;
|
return entityManager.getNpc(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNpcCount() {
|
||||||
|
return entityManager.getNpcCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Npc> getNpcs() {
|
||||||
|
return entityManager.getNpcs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer(int entityId) {
|
||||||
|
return entityManager.getPlayer(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer(String name) {
|
||||||
|
return entityManager.getPlayer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount() {
|
||||||
|
return entityManager.getPlayerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Player> getPlayers() {
|
||||||
|
return entityManager.getPlayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean areCoordinatesInBounds(int x, int y) {
|
public boolean areCoordinatesInBounds(int x, int y) {
|
||||||
|
@ -688,8 +879,30 @@ public class Zone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more chunk related thingies, such as surface calculations,
|
// Spawn guardian entities
|
||||||
// entity spawning and block indexing
|
for(int x = 0; x < chunk.getWidth(); x++) {
|
||||||
|
for(int y = 0; y < chunk.getHeight(); y++) {
|
||||||
|
int x1 = chunk.getX() + x;
|
||||||
|
int y1 = chunk.getY() + y;
|
||||||
|
int index = getBlockIndex(x1, y1);
|
||||||
|
MetaBlock metaBlock = getMetaBlock(index);
|
||||||
|
|
||||||
|
if(metaBlock != null) {
|
||||||
|
List<String> guardians = MapHelper.getList(metaBlock.getMetadata(), "!", Collections.emptyList());
|
||||||
|
|
||||||
|
for(String guardian : guardians) {
|
||||||
|
EntityConfig config = EntityRegistry.getEntityConfig(guardian);
|
||||||
|
|
||||||
|
if(config != null) {
|
||||||
|
Npc entity = new Npc(this, config);
|
||||||
|
entity.setPosition(x1, y1);
|
||||||
|
entity.setGuardBlock(index);
|
||||||
|
addEntity(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onChunkUnloaded(Chunk chunk) {
|
protected void onChunkUnloaded(Chunk chunk) {
|
||||||
|
@ -699,6 +912,10 @@ public class Zone {
|
||||||
public void saveChunks() {
|
public void saveChunks() {
|
||||||
chunkManager.saveChunks();
|
chunkManager.saveChunks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Chunk> getVisibleChunks() {
|
||||||
|
return chunkManager.getVisibleChunks();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only be called by zone gen.
|
* Should only be called by zone gen.
|
||||||
|
|
609
gameserver/src/main/resources/defaults/spawning.json
Normal file
609
gameserver/src/main/resources/defaults/spawning.json
Normal file
|
@ -0,0 +1,609 @@
|
||||||
|
{
|
||||||
|
"plain": [
|
||||||
|
{
|
||||||
|
"entity": "creatures/rat",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/skunk",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.3,
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/crow",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/vulture",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/crow-auto",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bluejay",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/cardinal",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/seagull",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-monarch",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency":23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/papilio-ulysses",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-swallowtail",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-moth",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-owl",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-paper-kite",
|
||||||
|
"locale": "sky",
|
||||||
|
"purification": 1.0,
|
||||||
|
"frequency": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat-auto",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/scorpion",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach-large",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/child",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.4,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.7,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/acid",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/fire",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.6,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.35,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.65,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.8,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.9,
|
||||||
|
"frequency": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"arctic": [
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.7,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/frost",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bunny-ice",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.5,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach-large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.5,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.45,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.7,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.6,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.7,
|
||||||
|
"frequency": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hell": [
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-rumanzovia",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 0.05
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"max_depth": 0.7,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/skeleton",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/fire",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.35,
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "ghost",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "revenant",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "dire-revenant",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.5,
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.7,
|
||||||
|
"frequency": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"desert": [
|
||||||
|
{
|
||||||
|
"entity": "creatures/rat",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/armadillo",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/crow",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/vulture",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat-auto",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/scorpion",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/scorpion-large",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach",
|
||||||
|
"locale": "cave",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach-large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"max_depth": 0.7,
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/fire",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"frequency": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"min_depth": 0.55,
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"min_depth": 0.8,
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"min_depth": 0.5,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.7,
|
||||||
|
"frequency": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"brain": [
|
||||||
|
{
|
||||||
|
"entity": "creatures/butterfly-green",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 0.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"max_depth": 0.7,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/acid",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/tiny-flyer",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/large",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.85,
|
||||||
|
"frequency": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deep": [
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat-auto",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/roach-large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/adult",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"max_depth": 0.4,
|
||||||
|
"frequency": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/acid",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/fire",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"min_depth": 0.2,
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "terrapus/queen",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.333,
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"min_depth": 0.5,
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.6,
|
||||||
|
"frequency": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"space": [
|
||||||
|
{
|
||||||
|
"entity": "automata/tiny",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "automata/large",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/tiny-flyer",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/small",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/pipe",
|
||||||
|
"frequency": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/medium",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.4,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "brains/large",
|
||||||
|
"locale": "cave",
|
||||||
|
"min_depth": 0.85,
|
||||||
|
"frequency": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/bat-auto",
|
||||||
|
"locale": "cave",
|
||||||
|
"orifice": "base/maw",
|
||||||
|
"frequency": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "creatures/crow-auto",
|
||||||
|
"locale": "sky",
|
||||||
|
"frequency": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ public class JsonHelper {
|
||||||
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true)
|
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true)
|
||||||
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
||||||
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true)
|
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true)
|
||||||
|
.configure(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL, true)
|
||||||
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||||
private static final ObjectWriter writer = mapper.writer(CustomPrettyPrinter.INSTANCE);
|
private static final ObjectWriter writer = mapper.writer(CustomPrettyPrinter.INSTANCE);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue