Compare commits

...

51 commits

Author SHA1 Message Date
Array in a Matrix f31cf9cc29
Update README.md
Some checks failed
Build / build (push) Has been cancelled
2024-05-01 17:26:34 -04:00
Array in a Matrix f89ef059a3
Update Dockerfile 2024-05-01 17:22:54 -04:00
Array in a Matrix b7936705d3
Merge branch 'develop' into master 2024-05-01 17:15:22 -04:00
Array in a Matrix bf3f9f314e
add disablegui flag 2024-05-01 16:53:28 -04:00
Kuroppoi 1eda98539c
Custom build logic (#49) 2024-05-01 16:16:17 +02:00
kuroppoi 261ec4e314 Minor refactors 2024-04-28 15:21:52 +02:00
kuroppoi ab1147ba89 Undo bfeb93be80 2024-04-26 23:10:25 +02:00
kuroppoi a58575369b Refactor commands 2024-04-26 22:59:57 +02:00
kuroppoi 4303c338a5 Automatic default zone sizes 2024-04-26 21:36:46 +02:00
kuroppoi 056e0631ac Add account lock remover for MacOS 2024-03-09 19:53:50 +01:00
kuroppoi 59e321292a Test 2024-03-07 00:21:34 +01:00
kuroppoi 75b1291818 Fix oopsie 2024-03-03 23:49:32 +01:00
kuroppoi 5de602f0df Update chunk data format 2024-03-03 23:35:05 +01:00
kuroppoi e9a046c8ed Add creature trapping & implement trapper achievements 2024-02-25 23:50:30 +01:00
kuroppoi a9ba16815f Space biomes should have a higher surface level 2024-02-25 19:15:10 +01:00
kuroppoi 03e96be0a4 Add skeleton burying & undertaker achievement type 2024-02-25 03:25:31 +01:00
kuroppoi bfeb93be80 Biome survival skill requirement check 2024-02-24 22:30:15 +01:00
kuroppoi 94cb2e16f8 Don't track explosion triggerer in burst interaction 2024-02-24 21:28:47 +01:00
kuroppoi e330b13a74 Prevent explosions from destroying unlooted containers 2024-02-24 21:15:32 +01:00
kuroppoi 455e63f927 Swap biome & size parameters in /genzone command 2024-02-24 20:06:58 +01:00
kuroppoi c444371612 Send killer entity ID in player death status message 2024-02-24 03:27:56 +01:00
kuroppoi 882396013e Temporary fix for system notifications playing karma warning sound on v2 2024-02-24 02:31:03 +01:00
kuroppoi 1466ea0176 Fix player name inconsistency on v2 clients 2024-02-24 00:46:23 +01:00
kuroppoi 7a06129e8b Prepack EntityItemUseMessage 2024-02-24 00:34:53 +01:00
kuroppoi 5dbc7555de Fix appearance update oopsie 2024-02-24 00:30:47 +01:00
kuroppoi b737d46b1d Make /give all not exclude clothing items 2024-02-24 00:05:08 +01:00
kuroppoi 34ca131f61 Fix zone status compatibility with older game versions 2024-02-24 00:00:32 +01:00
kuroppoi 39f75f8322 Player appearance rework 2024-02-23 23:46:27 +01:00
kuroppoi c5ff0ba62e Fix an oopsie 2024-02-20 04:21:37 +01:00
kuroppoi e7c48133cc Add hatchet functionality 2024-02-20 04:05:27 +01:00
kuroppoi 5928ec5c98 Implement steam system 2024-02-20 01:58:25 +01:00
kuroppoi 38cf041870 Add a bunch of utility functions 2024-02-18 23:51:50 +01:00
kuroppoi 20a9b5b826 Remove unused import 2024-02-18 22:05:04 +01:00
kuroppoi 188b78d5b1 Implement persistence for certain NPCs 2024-02-18 22:04:58 +01:00
kuroppoi 9dc5cbd34f Lazy fix for potential crashes 2024-02-18 21:42:50 +01:00
kuroppoi 9ec3175eb1 Improve damage tracking 2024-02-18 20:30:40 +01:00
kuroppoi 45787c3dba Damage check 2024-02-18 18:52:19 +01:00
kuroppoi df698a349b Check if entity is dead before applying damage 2024-02-18 18:39:51 +01:00
kuroppoi 29dd9ac540 Add note functionality 2024-02-18 18:25:54 +01:00
kuroppoi f986587ef1 Fix inventory use sync issue with v2 clients 2024-02-18 18:08:38 +01:00
kuroppoi dcb5e8a107 Fix lava bombs 2024-02-18 02:31:53 +01:00
kuroppoi 49ab4c42f4 Abstract entity damaging 2024-02-18 02:18:22 +01:00
kuroppoi 310174864e Move behavior to NPC package 2024-02-17 23:25:37 +01:00
kuroppoi 887e235d9d Fix potential mechanical sign crash 2024-02-17 23:22:03 +01:00
kuroppoi 2b0900b350 Fix space biome not configuring properly 2024-02-17 22:39:04 +01:00
kuroppoi 3bba0893b5 Add layer separator zone generator feature 2024-02-17 22:33:20 +01:00
kuroppoi c6779f0f61 Place spawn buildings closer to the top in fill terrain types 2024-02-17 22:10:59 +01:00
kuroppoi ac1069c3a9 Change default deep biome size to 1200x1000 2024-02-17 22:05:25 +01:00
Kuroppoi 81ceaa4542
Implement a bunch of block interactions (#34)
* Block interaction abstraction, block timers & timed switch functionality

Also fixes #31 :)

* Add chest o' plenty functionality

* Add spawn teleporter functionality

* Add transmitter functionality

* Ignore transmit limit if the player has god mode enabled

* Oops, gotta use this one

* Bomb explosions test

* Explosion raycasting

* Oops forgot this

* Add proximity burst & spawn functionality

* Forgot to switch this back

* Add player stealth check for mines

* Touchplates, spawners, exploders, and other goodies

* Diamond & onyx target teleporter functionality

* Fireplace stuff

* Check for entity spawns on block mine

* Spawn bomb functionality

* Liquid bomb functionality
2024-02-17 21:57:36 +01:00
kuroppoi 8a9bc80c49 Send skill data before inventory data 2024-02-05 01:13:52 +01:00
Kuroppoi 86fe6f2138
Add consumables (#32)
* Consumable item abstraction & add convert action handler

* Add heal & refill action handlers

* Pass action details

* Add teleport action handler

* Notify player if consumable action isn't implemented

* Add skill action handler

* Convert tabs to spaces

* Remove annoying line break

* Add skill reset action handler

* Init bumped skills map

* Better dialog cancellation handling

* Add name changer functionality

* Add stealth cloak functionality

* Remove note

* Fix stealth cloak duration

* Fix convert consumable crash caused by duplicate item names
2024-02-05 01:11:38 +01:00
176 changed files with 5005 additions and 1739 deletions

2
.gitattributes vendored
View file

@ -1,3 +1,5 @@
/gradlew text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View file

@ -32,5 +32,5 @@ jobs:
uses: actions/upload-artifact@v3.1.1
with:
name: brainwine
path: build/libs/brainwine.jar
path: build/dist/brainwine.jar
retention-days: 7

3
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Gradle
.gradle
build
!**/src/**/build
# Eclipse
*.launch
@ -11,4 +12,4 @@ build
bin
# Misc
run
run

View file

@ -17,7 +17,7 @@ ARG GATEWAY_PORT=5001
ARG SERVER_PORT=5002
ARG PORTAL_PORT=5003
EXPOSE $GATEWAY_PORT $SERVER_PORT $PORTAL_PORT
CMD ["sh", "-c", "java -jar /app/brainwine.jar"]
CMD ["sh", "-c", "java -jar /app/brainwine.jar disablegui"]
LABEL org.opencontainers.image.title="Brainwine"
LABEL org.opencontainers.image.description=" A portable private server for Deepworld."

View file

@ -1,39 +1,41 @@
# Brainwine
[![build](https://github.com/kuroppoi/brainwine/actions/workflows/build.yml/badge.svg)](https://github.com/kuroppoi/brainwine/actions)
<h1 align="center">Brainwine</h1>
<p align="center">
<a href="https://github.com/kuroppoi/brainwine/actions"><img src="https://github.com/kuroppoi/brainwine/actions/workflows/build.yml/badge.svg" alt="build"/></a>
<a href="https://github.com/kuroppoi/brainwine/releases/latest"><img src="https://img.shields.io/github/v/release/kuroppoi/brainwine?labelColor=30373D&label=Release&logoColor=959DA5&logo=github" alt="release"/></a>
</p>
Brainwine is a Deepworld private server written in Java, made with user-friendliness and portability in mind.
Due to the time it will take for this project to be complete (and my inconsistent working on it), brainwine has been prematurely open-sourced
and is free for all to use.\
Keep in mind, though, that this server is not finished yet. Expect to encounter bad code, bugs and missing features!\
Brainwine is currently compatible with the following versions of Deepworld:
- Steam: `v3.13.1`
Brainwine is a Deepworld private server written in Java, designed to be portable and easy to use.\
It's still a work in progress, so keep in mind that it's not yet feature-complete. (A to-do list can be found [here](https://github.com/kuroppoi/brainwine/projects/1).)\
Brainwine currently supports the following versions of Deepworld:
- Windows: `v3.13.1`
- iOS: `v2.11.0.1`
- MacOS: `v2.11.1`
## Features
A list of all planned, in-progress and finished features can be found [here.](https://github.com/kuroppoi/brainwine/projects/1)
## Quick Local Setup
## Setup
- Install [Java 8](https://adoptium.net/temurin/releases/?package=jdk&version=8).
- Download the [latest Brainwine release](https://github.com/kuroppoi/brainwine/releases/latest).
- Run Brainwine, go to the server tab and start the server.
- Go to the game tab and start the game.
- If this isn't available for you, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your platform and follow the instructions there.
- Register a new account and play the game.
### Setting up the client
## Building
Before you can connect to a server, a few modifications need to be made to the Deepworld game client.\
The exact process of this differs per platform.\
You may download an installation package for your desired platform [here.](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0)
### Prerequisites
### Setting up the server
- Java 8 Development Kit
#### Prerequisites
```sh
git clone --recurse-submodules https://github.com/kuroppoi/brainwine.git
cd brainwine
./gradlew dist
```
- Java 8 or newer
The output will be located in the `/build/dist` directory.
You can download the latest release [here.](https://github.com/kuroppoi/brainwine/releases/latest)\
Alternatively, if you wish to build from source, clone this repository with the `--recurse-submodules` flag\
and run `gradlew dist` in the root directory of the repository.\
After the build has finished, the output jar will be located in `build/libs`.\
You may then start the server through the gui, or start it directly by running the jar with the `disablegui` flag.
#### Using docker
### Using docker
To host brainwine using a docker you first need to build the image. On your server run the following:
@ -59,21 +61,15 @@ docker compose up
The server configuration files and the world data is saved in a docker volume and will accessible from `/data/` in the container. Feel free to add or remove options passed to docker or edit the compose file.
#### Configurations
## Usage
On first-time startup, configuration files will be generated which you may modify however you like:
- `api.json` Configuration file for news & API connectivity information.
- `loottables.json` Configuration file for which loot may be obtained from containers.
- `spawning.json` Configuration file for entity spawns per biome.
- `generators` Folder containing configuration files for zone generators.
Execute `brainwine.jar` to start the program. Navigate to the server tab and press the button to start the server.\
It is also possible to start the server immediately with no user interface:
## Contributions
```sh
# This behavior is the default on platforms that do not support Java's Desktop API.
java -jar brainwine.jar disablegui
```
Disagree with how I did something? Found a potential error? See some room for improvement? Or just want to add a feature?
Glad to hear it! Feel free to make a pull request anytime. Just make sure you follow the code style!
And, apologies in advance for the lack of documentation. Haven't gotten around to do it yet. Sorry!
## Issues
Found a bug? Before posting an issue, make sure your build is up-to-date and your issue has not already been posted before.
Provide a detailed explanation of the issue, and how to reproduce it. I'll get to it ASAP!
To connect to a local or remote server, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your desired platform.\
Alternatively, Windows users may use the program's user interface to configure the host settings and start the game.

View file

@ -8,9 +8,5 @@ repositories {
dependencies {
implementation 'io.javalin:javalin:4.6.8'
implementation project(':shared')
}
jar {
archiveBaseName = 'brainwine-api'
implementation project(':brainwine-shared')
}

View file

@ -9,6 +9,7 @@ public interface DataFetcher {
public boolean isPlayerNameTaken(String name);
public String registerPlayer(String name);
public String login(String name, String password);
public String fetchPlayerName(String name);
public boolean verifyAuthToken(String name, String token);
public boolean verifyApiToken(String apiToken);
public Collection<ZoneInfo> fetchZoneInfo();

View file

@ -23,6 +23,11 @@ public class DefaultDataFetcher implements DataFetcher {
throw exception;
}
@Override
public String fetchPlayerName(String name) {
throw exception;
}
@Override
public boolean verifyAuthToken(String name, String token) {
throw exception;

View file

@ -111,7 +111,7 @@ public class GatewayService {
return;
}
ctx.json(new ServerConnectInfo(api.getGameServerHost(), name, token));
ctx.json(new ServerConnectInfo(api.getGameServerHost(), dataFetcher.fetchPlayerName(name), token));
}
/**

28
build-logic/build.gradle Normal file
View file

@ -0,0 +1,28 @@
plugins {
id 'java-gradle-plugin'
}
sourceSets {
boot {
}
main {
compileClasspath += sourceSets.boot.output
runtimeClasspath += sourceSets.boot.output
}
}
gradlePlugin {
plugins {
distributionPlugin {
id = "brainwine.distribution"
implementationClass = "brainwine.build.DistributionPlugin"
}
}
}
jar {
from sourceSets.boot.output
}
version = '1.0.0-SNAPSHOT'

View file

@ -0,0 +1,84 @@
package brainwine.bootstrap;
import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY;
import static brainwine.bootstrap.Constants.CLASS_PATH_KEY;
import static brainwine.bootstrap.Constants.LIBRARY_PATH;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class Bootstrap {
public static void main(String[] args) {
new Bootstrap().run(args);
}
private void run(String[] args) {
Attributes attributes = null;
try {
Enumeration<URL> resources = getClass().getClassLoader().getResources(JarFile.MANIFEST_NAME);
while(resources.hasMoreElements()) {
try(InputStream inputStream = resources.nextElement().openStream()) {
Manifest manifest = new Manifest(inputStream);
if(getClass().getName().equals(manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS))) {
attributes = manifest.getMainAttributes();
break;
}
}
}
} catch(IOException e) {
System.err.println("Could not load manifest file");
e.printStackTrace();
System.exit(-1);
}
String[] libraryNames = attributes.getValue(CLASS_PATH_KEY).split(";");
URL[] libraryUrls = new URL[libraryNames.length];
try {
for(int i = 0; i < libraryNames.length; i++) {
String libraryName = libraryNames[i];
try(InputStream inputStream = getClass().getResourceAsStream(String.format("/%s/%s", LIBRARY_PATH, libraryName))) {
File outputFile = new File("libraries", libraryName);
libraryUrls[i] = outputFile.toURI().toURL();
if(outputFile.exists()) {
continue;
}
outputFile.getParentFile().mkdirs();
Files.copy(inputStream, outputFile.toPath());
}
}
} catch(Exception e) {
System.err.println("Could not extract library JARs");
e.printStackTrace();
System.exit(-1);
}
URLClassLoader classLoader = new URLClassLoader(libraryUrls, getClass().getClassLoader().getParent());
Thread.currentThread().setContextClassLoader(classLoader);
try {
Class<?> mainClass = Class.forName(attributes.getValue(BOOT_CLASS_KEY), true, classLoader);
Method method = mainClass.getMethod("main", String[].class);
method.invoke(null, (Object)args);
} catch(ReflectiveOperationException e) {
System.err.println("Could not invoke entry point");
e.printStackTrace();
System.exit(-1);
}
}
}

View file

@ -0,0 +1,12 @@
package brainwine.bootstrap;
import java.util.jar.Attributes;
public class Constants {
public static final Attributes.Name BOOT_CLASS_KEY = new Attributes.Name("Dist-Boot-Class");
public static final Attributes.Name CLASS_PATH_KEY = new Attributes.Name("Dist-Class-Path");
public static final String LICENSE_PATH = "META-INF/LICENSE";
public static final String LIBRARY_PATH = "META-INF/libraries";
public static final Class<?> MAIN_CLASS = Bootstrap.class;
}

View file

@ -0,0 +1,14 @@
package brainwine.build;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
public class DistributionPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(JavaPlugin.class);
project.getTasks().register("dist", DistributionTask.class, task -> task.dependsOn("build"));
}
}

View file

@ -0,0 +1,128 @@
package brainwine.build;
import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY;
import static brainwine.bootstrap.Constants.CLASS_PATH_KEY;
import static brainwine.bootstrap.Constants.LIBRARY_PATH;
import static brainwine.bootstrap.Constants.LICENSE_PATH;
import static brainwine.bootstrap.Constants.MAIN_CLASS;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.FileTree;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.initialization.IncludedBuild;
import org.gradle.api.invocation.Gradle;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.bundling.Jar;
public abstract class DistributionTask extends DefaultTask {
private final FileTree bootCodeTree;
@Input
public abstract Property<String> getMainClass();
@Input
@Optional
public abstract Property<String> getArchiveFileName();
@Input
@Optional
public abstract RegularFileProperty getLicenseFile();
@Inject
public DistributionTask(Gradle gradle) {
IncludedBuild build = gradle.getIncludedBuilds().stream().filter(x -> x.getName().equals("build-logic")).findFirst().get();
bootCodeTree = getProject().fileTree(new File(build.getProjectDir(), "build/classes/java/boot"));
}
@TaskAction
public void createDistributionArchive() throws IOException {
Configuration config = getProject().getConfigurations().getByName("runtimeClasspath");
Jar jarTask = (Jar)getProject().getTasks().getByName("jar");
String archiveFileName = getArchiveFileName().getOrElse(jarTask.getArchiveFileName().get());
File outputDirectory = new File(getProject().getBuildDir(), "dist");
outputDirectory.mkdirs();
File outputFile = new File(outputDirectory, archiveFileName);
// Fetch libraries
List<File> classpath = new ArrayList<>();
config.getResolvedConfiguration().getResolvedArtifacts().forEach(artifact -> classpath.add(artifact.getFile()));
jarTask.getOutputs().getFiles().forEach(classpath::add);
classpath.sort((a, b) -> a.getName().compareTo(b.getName())); // Guarantee file order
// Create jar manifest
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, MAIN_CLASS.getName());
manifest.getMainAttributes().put(BOOT_CLASS_KEY, getMainClass().get());
manifest.getMainAttributes().put(CLASS_PATH_KEY, String.join(";", classpath.stream().map(File::getName).collect(Collectors.toList())));
manifest.getMainAttributes().putValue("Multi-Release", "true");
// Create jar file
try(JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(outputFile))) {
// Add manifest
addJarManifest(outputStream, manifest);
// Add libraries
for(File file : classpath) {
addFileToJar(outputStream, file, String.format("%s/%s", LIBRARY_PATH, file.getName()));
}
// Add boot code
bootCodeTree.visit(details -> {
if(!details.isDirectory()) {
try {
addFileToJar(outputStream, details.getFile(), details.getPath());
} catch(IOException e) {
throw new GradleException(e.getMessage(), e);
}
}
});
// Add license
RegularFileProperty licenseFile = getLicenseFile();
if(licenseFile.isPresent()) {
addFileToJar(outputStream, licenseFile.get().getAsFile(), LICENSE_PATH);
}
}
}
private void addJarManifest(JarOutputStream outputStream, Manifest manifest) throws IOException {
JarEntry entry = new JarEntry(JarFile.MANIFEST_NAME);
entry.setTime(0);
outputStream.putNextEntry(entry);
manifest.write(outputStream);
outputStream.closeEntry();
}
private void addFileToJar(JarOutputStream outputStream, File file, String targetPath) throws IOException {
byte[] bytes = Files.readAllBytes(file.toPath());
JarEntry entry = new JarEntry(targetPath);
entry.setTime(0);
entry.setSize(bytes.length);
outputStream.putNextEntry(entry);
outputStream.write(bytes);
outputStream.closeEntry();
}
}

View file

@ -1,9 +1,9 @@
plugins {
id 'java'
id 'brainwine.distribution'
}
ext {
mainClass = 'brainwine.Bootstrap'
mainClass = 'brainwine.Main'
workingDirectory = 'run'
}
@ -15,26 +15,14 @@ dependencies {
implementation 'com.formdev:flatlaf-intellij-themes:3.0'
implementation 'com.formdev:flatlaf-extras:3.0'
implementation 'com.formdev:flatlaf:3.0'
implementation project(':api')
implementation project(':gameserver')
implementation project(':shared')
implementation project(':brainwine-api')
implementation project(':brainwine-gameserver')
implementation project(':brainwine-shared')
}
task dist(type: Jar) {
manifest {
attributes 'Multi-Release': 'true',
'Main-Class': project.ext.mainClass
}
from {
configurations.runtimeClasspath.collect {
it.isDirectory() ? it : zipTree(it)
}
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn configurations.runtimeClasspath
with jar
dist {
mainClass = project.ext.mainClass
licenseFile = file("${project.rootDir}/LICENSE.md")
}
task run(type: JavaExec) {

@ -1 +1 @@
Subproject commit ec2749d94b86dbbdefa52e0146ce278688e28370
Subproject commit 6052c53944e3d4469819e725c59914ea136b2007

View file

@ -24,11 +24,7 @@ dependencies {
implementation 'org.reflections:reflections:0.10.2'
implementation 'io.netty:netty-all:4.1.79.Final'
implementation 'org.mindrot:jbcrypt:0.4'
implementation project(':shared')
}
jar {
archiveBaseName = 'brainwine-gameserver'
implementation project(':brainwine-shared')
}
processResources.includeEmptyDirs = false

View file

@ -24,7 +24,7 @@ import org.yaml.snakeyaml.Yaml;
import com.fasterxml.jackson.core.JsonProcessingException;
import brainwine.gameserver.command.CommandManager;
import brainwine.gameserver.commands.CommandManager;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.entity.player.Skill;
import brainwine.gameserver.item.Item;

View file

@ -9,8 +9,8 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import brainwine.gameserver.achievements.AchievementManager;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.command.CommandManager;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.commands.CommandManager;
import brainwine.gameserver.entity.EntityRegistry;
import brainwine.gameserver.entity.player.NotificationType;
import brainwine.gameserver.entity.player.PlayerManager;

View file

@ -0,0 +1,117 @@
package brainwine.gameserver;
/**
* TODO all I'm doing here is moving the problem somewhere else.
*
* Entity names are sourced from: https://github.com/bytebin/deepworld-gameserver/blob/master/config/fake.yml
*/
public class Naming {
private static final String[] ZONE_FIRST_NAMES = {
"Malvern", "Tralee", "Horncastle", "Old", "Westwood",
"Citta", "Tadley", "Mossley", "West", "East",
"North", "South", "Wadpen", "Githam", "Soatnust",
"Highworth", "Creakynip", "Upper", "Lower", "Cannock",
"Dovercourt", "Limerick", "Pickering", "Glumshed", "Crusthack",
"Osyltyr", "Aberstaple", "New", "Stroud", "Crumclum",
"Crumsidle", "Bankswund", "Fiddletrast", "Bournpan", "St.",
"Funderbost", "Bexwoddly", "Pilkingheld", "Wittlepen", "Rabbitbleaker",
"Griffingumby", "Guilthead", "Bigglelund", "Bunnymold", "Rosesidle",
"Crushthorn", "Tanlyward", "Ahncrace", "Pilkingking", "Dingstrath",
"Axebury", "Ginglingtap", "Ballybibby", "Shadehoven"
};
private static final String[] ZONE_LAST_NAMES = {
"Falls", "Alloa", "Glen", "Way", "Dolente",
"Peak", "Heights", "Creek", "Banffshire", "Chagford",
"Gorge", "Valley", "Catacombs", "Depths", "Mines",
"Crickbridge", "Guildbost", "Pits", "Vaults", "Ruins",
"Dell", "Keep", "Chatterdin", "Scrimmance", "Gitwick",
"Ridge", "Alresford", "Place", "Bridge", "Glade",
"Mill", "Court", "Dooftory", "Hills", "Specklewint",
"Grove", "Aylesbury", "Wagwouth", "Russetcumby", "Point",
"Canyon", "Cranwarry", "Bluff", "Passage", "Crantippy",
"Kerbodome", "Dale", "Cemetery"
};
public static final String[] ENTITY_FIRST_NAMES = {
"Aaron", "Abby", "Abigale", "Abraham", "Ada", "Adella", "Agnes", "Alan",
"Albert", "Alexander", "Allie", "Almira", "Almyra", "Alonzo", "Alva", "Ambrose",
"Amelia", "Amon", "Amos", "Andrew", "Ann", "Annie", "Aquilla", "Archibald",
"Arnold", "Arrah", "Asa", "Augustus", "Barnabas", "Bartholomew", "Beatrice", "Becky",
"Benedict", "Benjamin", "Bennet", "Bernard", "Bernice", "Bertram", "Bess", "Bessie",
"Beth", "Betsy", "Buford", "Byron", "Calvin", "Charity", "Charles", "Charlotte",
"Chastity", "Christopher", "Claire", "Clarence", "Clement", "Clinton", "Cole", "Columbus",
"Commodore Perry", "Constance", "Cynthia", "Daniel", "David", "Dick", "Dorothy", "Edith",
"Edmund", "Edna", "Edward", "Edwin", "Edwina", "Eldon", "Eleanor", "Eli",
"Elijah", "Eliza", "Elizabeth", "Ella", "Ellie", "Elvira", "Emma", "Emmett",
"Enoch", "Esther", "Ethel", "Ettie", "Eudora", "Eva", "Ezekiel", "Ezra",
"Fanny", "Fidelia", "Flora", "Florence", "Frances", "Francis", "Franklin", "Frederick",
"Gabriel", "Garrett", "Geneve", "Genevieve", "George", "George", "Georgia", "Gertie",
"Gertrude", "Gideon", "Gilbert", "Ginny", "Gladys", "Grace", "Granville", "Hannah",
"Harland", "Harold", "Harrison", "Harvey", "Hattie", "Helen", "Helene", "Henrietta",
"Henry", "Hester", "Hettie", "Hiram", "Hope", "Horace", "Horatio", "Hortence",
"Hugh", "Isaac", "Isaac Newton", "Isabella", "Isaiah", "Israel", "Jacob", "James",
"Jane", "Jasper", "Jedediah", "Jefferson", "Jennie", "Jeptha", "Jessamine", "Jesse",
"Joel", "John Paul", "John Wesley", "Jonathan", "Joseph", "Josephine", "Josephus", "Joshua",
"Josiah", "Judith", "Julia", "Julian", "Juliet", "Julius", "Katherine", "Lafayette",
"Laura", "Lawrence", "Leah", "Leander", "Lenora", "Les", "Letitia", "Levi",
"Levi", "Lewis", "Lila", "Lilly", "Liza", "Lorena", "Lorraine", "Lottie",
"Louis", "Louisa", "Louise", "Lucas", "Lucas", "Lucian", "Lucian", "Lucius",
"Lucius", "Lucy", "Luke", "Luke", "Lulu", "Luther", "Luther", "Lydia",
"Mahulda", "Marcellus", "Margaret", "Mark", "Martha", "Martin", "Mary", "Mary Elizabeth",
"Mary Frances", "Masheck", "Matilda", "Matthew", "Maude", "Maurice", "Maxine", "Maxwell",
"Mercy", "Meriwether", "Meriwether Lewis", "Merrill", "Mildred", "Minerva", "Missouri", "Molly",
"Mordecai", "Morgan", "Morris", "Myrtle", "Nancy", "Natalie", "Nathaniel", "Ned",
"Nellie", "Nettie", "Newton", "Nicholas", "Nimrod", "Ninian", "Nora", "Obediah",
"Octavius", "Orpha", "Orville", "Oscar", "Owen", "Parthena", "Patrick", "Patrick Henry",
"Patsy", "Paul", "Paul", "Peggy", "Permelia", "Perry", "Peter", "Philomena",
"Phoebe", "Pleasant", "Polly", "Preshea", "Rachel", "Ralph", "Raymond", "Rebecca",
"Reuben", "Rhoda", "Richard", "Robert", "Robert Lee", "Roderick", "Rowena", "Rudolph",
"Rufina", "Rufus", "Ruth", "Sally", "Sam Houston", "Samantha", "Samuel", "Sarah",
"Sarah Ann", "Sarah Elizabeth", "Savannah", "Selina", "Seth", "Silas", "Simeon", "Simon",
"Sophronia", "Stanley", "Stella", "Stephen", "Thaddeus", "Theodore", "Theodosia", "Thomas",
"Timothy", "Ulysses", "Uriah", "Vertiline", "Victor", "Victoria", "Virginia", "Vivian",
"Walter", "Warren", "Washington", "Wilfred", "William", "Winnifred", "Zachariah", "Zebulon",
"Zedock", "Zona", "Zylphia"
};
public static final String[] ENTITY_LAST_NAMES = {
"Abraham", "Adams", "Alcorn", "Alderdice", "Angus", "Ashdown", "Ayre", "Backhaus",
"Baldwin", "Bamford", "Beaton", "Blackwood", "Blair", "Blewett", "Bornholdt", "Bowden",
"Burrows", "Cameron", "Carroll", "Clarke", "Claxton", "Collins", "Colson", "Connor",
"Conroy", "Cullen", "Cunningham", "Curd", "Curnow", "Cusack", "Dagon", "Dalton",
"Dawes", "Desmond", "Dewar", "Dickenson", "Donnell", "Drummond", "Dunstan", "English",
"Eveans", "Faraday", "Faulkner", "Fitzgerald", "Fitzpatrick", "Fletcher", "Foster", "Franklin",
"Fulton", "Gallagher", "Gibbons", "Gilmore", "Glover", "Goodfellow", "Goodwin", "Griffiths",
"Gullifer", "Hadley", "Haeffner", "Hanlon", "Harding", "Harris", "Holloway", "Hughes",
"Jarvis", "Jefferies", "Johnstone", "Kaylock", "Keane", "Kemp", "Kernaghan", "Kirby",
"Kirkland", "Knight", "LaFontaine", "Lawford", "Lawrence", "Lennox", "Longley", "Lonsdale",
"Luckett", "Lyons", "Macklin", "Madill", "Marsden", "Marshall", "Martin", "Mather",
"Mathieson", "Maunder", "McColl", "McDermott", "McGillicuddy", "McKenzie", "McLachlan", "McNeil",
"Meaklim", "Meighan", "Mellor", "Meyers", "Milsom", "Mitchell", "Mitchelson", "Moore",
"Morgan", "Morrison", "Mortimer", "Moulsdale", "Murphy", "Nelson", "Nolan", "Noonan",
"O'Keefe", "O'Sullivan", "Palmer", "Parnell", "Pattison", "Pettit", "Phillips", "Pinner",
"Porter", "Prosser", "Ramseyer", "Renton", "Rickard", "Riddington", "Roche", "Rowe",
"Russell", "Salisbury", "Saunders", "Sawyer", "Scanlan", "Scarborough", "Schwarer", "Sheary",
"Sheedy", "Shelton", "Shields", "Shinnick", "Skinner", "Sommer", "Spencer", "Stanbury",
"Stanton", "Storey", "Swaisbrick", "Thorley", "Thumpston", "Tichborne", "Tinning", "Tobin",
"Todd", "Trimble", "Twomey", "Upton", "Urwin", "Vandenburg", "Vinge", "Wakefield",
"Wakenshaw", "Walden", "Wallace", "Walton", "Warner", "Webb", "Whitehill", "Wickes",
"Wilberforce", "Wilkinson", "Wolstenholme", "Wright"
};
public static String getRandomZoneName() {
return getRandomName(ZONE_FIRST_NAMES, ZONE_LAST_NAMES);
}
public static String getRandomEntityName() {
return getRandomName(ENTITY_FIRST_NAMES, ENTITY_LAST_NAMES);
}
private static String getRandomName(String[] firstNames, String[] lastNames) {
String firstName = firstNames[(int)(Math.random() * firstNames.length)];
String lastName = lastNames[(int)(Math.random() * lastNames.length)];
return firstName + " " + lastName;
}
}

View file

@ -0,0 +1,42 @@
package brainwine.gameserver;
/**
* Model for synchronous timers.
*/
public class Timer<T> {
private T key;
private long time;
private Runnable action;
public Timer(T key, long delay, Runnable action) {
this.key = key;
this.time = System.currentTimeMillis() + delay;
this.action = action;
}
public boolean process() {
return process(false);
}
public boolean process(boolean force) {
if(force || System.currentTimeMillis() >= time) {
action.run();
return true;
}
return false;
}
public T getKey() {
return key;
}
public long getTime() {
return time;
}
public Runnable getAction() {
return action;
}
}

View file

@ -28,6 +28,8 @@ import brainwine.gameserver.util.MathUtils;
@Type(name = "ScavengingAchievement", value = ScavengingAchievement.class),
@Type(name = "DiscoveryAchievement", value = DiscoveryAchievement.class),
@Type(name = "SpawnerStoppageAchievement", value = SpawnerStoppageAchievement.class),
@Type(name = "UndertakerAchievement", value = UndertakerAchievement.class),
@Type(name = "TrappingAchievement", value = TrappingAchievement.class),
@Type(name = "Journeyman", value = JourneymanAchievement.class)
})
@JsonSerialize(using = AchievementSerializer.class)

View file

@ -0,0 +1,19 @@
package brainwine.gameserver.achievements;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import brainwine.gameserver.entity.player.Player;
public class TrappingAchievement extends Achievement {
@JsonCreator
public TrappingAchievement(@JacksonInject("title") String title) {
super(title);
}
@Override
public int getProgress(Player player) {
return player.getStatistics().getTotalTrappings();
}
}

View file

@ -0,0 +1,19 @@
package brainwine.gameserver.achievements;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import brainwine.gameserver.entity.player.Player;
public class UndertakerAchievement extends Achievement {
@JsonCreator
public UndertakerAchievement(@JacksonInject("title") String title) {
super(title);
}
@Override
public int getProgress(Player player) {
return player.getStatistics().getUndertakings();
}
}

View file

@ -0,0 +1,15 @@
package brainwine.gameserver.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandInfo {
public String name();
public String description();
public String[] aliases() default {};
}

View file

@ -1,24 +0,0 @@
package brainwine.gameserver.command;
public abstract class Command {
public abstract void execute(CommandExecutor executor, String[] args);
public abstract String getName();
public String[] getAliases() {
return null;
}
public String getDescription() {
return "No description for this command";
}
public String getUsage(CommandExecutor executor) {
return "/" + getName();
}
public boolean canExecute(CommandExecutor executor) {
return true;
}
}

View file

@ -1,182 +0,0 @@
package brainwine.gameserver.command;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import static brainwine.shared.LogMarkers.SERVER_MARKER;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import brainwine.gameserver.command.commands.AcidityCommand;
import brainwine.gameserver.command.commands.AdminCommand;
import brainwine.gameserver.command.commands.BanCommand;
import brainwine.gameserver.command.commands.BroadcastCommand;
import brainwine.gameserver.command.commands.EntityCommand;
import brainwine.gameserver.command.commands.ExperienceCommand;
import brainwine.gameserver.command.commands.ExportCommand;
import brainwine.gameserver.command.commands.GenerateZoneCommand;
import brainwine.gameserver.command.commands.GiveCommand;
import brainwine.gameserver.command.commands.HealthCommand;
import brainwine.gameserver.command.commands.HelpCommand;
import brainwine.gameserver.command.commands.ImportCommand;
import brainwine.gameserver.command.commands.KickCommand;
import brainwine.gameserver.command.commands.LevelCommand;
import brainwine.gameserver.command.commands.MuteCommand;
import brainwine.gameserver.command.commands.PlayerIdCommand;
import brainwine.gameserver.command.commands.PositionCommand;
import brainwine.gameserver.command.commands.PrefabListCommand;
import brainwine.gameserver.command.commands.RegisterCommand;
import brainwine.gameserver.command.commands.RickrollCommand;
import brainwine.gameserver.command.commands.SayCommand;
import brainwine.gameserver.command.commands.SeedCommand;
import brainwine.gameserver.command.commands.SettleLiquidsCommand;
import brainwine.gameserver.command.commands.SkillPointsCommand;
import brainwine.gameserver.command.commands.StopCommand;
import brainwine.gameserver.command.commands.TeleportCommand;
import brainwine.gameserver.command.commands.ThinkCommand;
import brainwine.gameserver.command.commands.TimeCommand;
import brainwine.gameserver.command.commands.UnbanCommand;
import brainwine.gameserver.command.commands.UnmuteCommand;
import brainwine.gameserver.command.commands.WeatherCommand;
import brainwine.gameserver.command.commands.ZoneIdCommand;
import brainwine.gameserver.entity.player.Player;
public class CommandManager {
public static final String CUSTOM_COMMAND_PREFIX = "!"; // TODO configurable
private static final Logger logger = LogManager.getLogger();
private static final Map<String, Command> commands = new HashMap<>();
private static final Map<String, Command> aliases = new HashMap<>();
private static boolean initialized = false;
public static void init() {
if(initialized) {
logger.warn(SERVER_MARKER, "CommandManager is already initialized - skipping!");
return;
}
registerCommands();
initialized = true;
}
private static void registerCommands() {
logger.info(SERVER_MARKER, "Registering commands ...");
registerCommand(new StopCommand());
registerCommand(new RegisterCommand());
registerCommand(new TeleportCommand());
registerCommand(new KickCommand());
registerCommand(new MuteCommand());
registerCommand(new UnmuteCommand());
registerCommand(new BanCommand());
registerCommand(new UnbanCommand());
registerCommand(new SayCommand());
registerCommand(new ThinkCommand());
registerCommand(new BroadcastCommand());
registerCommand(new PlayerIdCommand());
registerCommand(new ZoneIdCommand());
registerCommand(new AdminCommand());
registerCommand(new HelpCommand());
registerCommand(new GiveCommand());
registerCommand(new GenerateZoneCommand());
registerCommand(new SeedCommand());
registerCommand(new PrefabListCommand());
registerCommand(new ExportCommand());
registerCommand(new ImportCommand());
registerCommand(new PositionCommand());
registerCommand(new RickrollCommand());
registerCommand(new EntityCommand());
registerCommand(new HealthCommand());
registerCommand(new ExperienceCommand());
registerCommand(new LevelCommand());
registerCommand(new SkillPointsCommand());
registerCommand(new SettleLiquidsCommand());
registerCommand(new WeatherCommand());
registerCommand(new AcidityCommand());
registerCommand(new TimeCommand());
}
public static void executeCommand(CommandExecutor executor, String commandLine) {
if(commandLine.isEmpty()) {
return;
}
commandLine.trim().replaceAll(" +", " ");
String[] sections = commandLine.split(" ", 2);
if(sections.length == 0) {
return;
}
String name = sections[0];
String[] args = sections.length > 1 ? sections[1].split(" ") : new String[0];
executeCommand(executor, name, args);
}
public static void executeCommand(CommandExecutor executor, String commandName, String[] args) {
if(!(executor instanceof Player) && commandName.startsWith(CUSTOM_COMMAND_PREFIX) || commandName.startsWith("/")) {
commandName = commandName.substring(1);
}
Command command = getCommand(commandName, true);
if(command == null || !command.canExecute(executor)) {
executor.notify("Unknown command. Type '/help' for a list of commands.", SYSTEM);
return;
}
if(executor instanceof Player) {
Player player = (Player)executor;
logger.info(SERVER_MARKER, "{} used command '/{}'", player.getName(), commandName + (args.length == 0 ? "" : " " + String.join(" ", args)));
}
command.execute(executor, args);
}
public static void registerCommand(Command command) {
String name = command.getName();
if(commands.containsKey(name)) {
logger.warn(SERVER_MARKER, "Attempted to register duplicate command {} with name {}", command.getClass(), name);
return;
}
commands.put(name, command);
String[] aliases = command.getAliases();
if(aliases != null) {
for(String alias : aliases) {
if(commands.containsKey(alias) || CommandManager.aliases.containsKey(alias)) {
logger.warn(SERVER_MARKER, "Duplicate alias {} for command {}", alias, command.getClass());
continue;
}
CommandManager.aliases.put(alias, command);
}
}
}
public static Set<String> getCommandNames() {
Set<String> names = new HashSet<>();
names.addAll(commands.keySet());
names.addAll(aliases.keySet());
return names;
}
public static Command getCommand(String name) {
return getCommand(name, false);
}
public static Command getCommand(String name, boolean allowAlias) {
return commands.getOrDefault(name, allowAlias ? aliases.get(name) : null);
}
public static Collection<Command> getCommands() {
return Collections.unmodifiableCollection(commands.values());
}
}

View file

@ -1,55 +0,0 @@
package brainwine.gameserver.command.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
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)), SYSTEM);
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.SYSTEM);
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;
}
}

View file

@ -1,56 +0,0 @@
package brainwine.gameserver.command.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.server.messages.EventMessage;
public class RickrollCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
if(args.length < 1) {
executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM);
return;
}
Player player = GameServer.getInstance().getPlayerManager().getPlayer(args[0]);
if(player == null) {
executor.notify("This player does not exist.", SYSTEM);
return;
} else if(!player.isOnline()) {
executor.notify("This player is offline.", SYSTEM);
return;
} else if(!player.isV3()) {
executor.notify("Cannot open URLs on iOS clients.", SYSTEM);
return;
}
player.sendMessage(new EventMessage("openUrl", "https://www.youtube.com/watch?v=dQw4w9WgXcQ"));
executor.notify(String.format("Successfully rickrolled %s!", player.getName()), SYSTEM);
}
@Override
public String getName() {
return "rickroll";
}
@Override
public String getDescription() {
return "Makes a player hate you forever.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/rickroll <player>";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor.isAdmin();
}
}

View file

@ -1,33 +0,0 @@
package brainwine.gameserver.command.commands;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
public class StopCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
GameServer.getInstance().stopGracefully(); // YEET!!
}
@Override
public String getName() {
return "stop";
}
@Override
public String[] getAliases() {
return new String[] { "exit", "close", "shutdown" };
}
@Override
public String getDescription() {
return "Gracefully shuts down the server after the current tick.";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor.isAdmin();
}
}

View file

@ -0,0 +1,11 @@
package brainwine.gameserver.commands;
public abstract class Command {
public abstract void execute(CommandExecutor executor, String[] args);
public abstract String getUsage(CommandExecutor executor);
public boolean canExecute(CommandExecutor executor) {
return true;
}
}

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command;
package brainwine.gameserver.commands;
import brainwine.gameserver.entity.player.NotificationType;

View file

@ -0,0 +1,148 @@
package brainwine.gameserver.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import static brainwine.shared.LogMarkers.SERVER_MARKER;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.entity.player.Player;
@SuppressWarnings("unchecked")
public class CommandManager {
public static final String CUSTOM_COMMAND_PREFIX = "!"; // TODO configurable
private static final Logger logger = LogManager.getLogger();
private static final Map<String, Command> commands = new HashMap<>();
private static final Map<String, Command> aliases = new HashMap<>();
private static boolean initialized = false;
public static void init() {
if(initialized) {
logger.warn(SERVER_MARKER, "CommandManager is already initialized - skipping!");
return;
}
registerCommands();
initialized = true;
}
private static void registerCommands() {
logger.info(SERVER_MARKER, "Registering commands ...");
Reflections reflections = new Reflections("brainwine.gameserver.commands");
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(CommandInfo.class);
for(Class<?> clazz : classes) {
if(!Command.class.isAssignableFrom(clazz)) {
logger.warn(SERVER_MARKER, "Attempted to register non-command class {}", clazz.getSimpleName());
continue;
}
registerCommand((Class<? extends Command>)clazz);
}
}
public static void executeCommand(CommandExecutor executor, String commandLine) {
if(commandLine.isEmpty()) {
return;
}
commandLine.trim().replaceAll(" +", " ");
String[] sections = commandLine.split(" ", 2);
if(sections.length == 0) {
return;
}
String name = sections[0];
String[] args = sections.length > 1 ? sections[1].split(" ") : new String[0];
executeCommand(executor, name, args);
}
public static void executeCommand(CommandExecutor executor, String commandName, String[] args) {
if(!(executor instanceof Player) && commandName.startsWith(CUSTOM_COMMAND_PREFIX) || commandName.startsWith("/")) {
commandName = commandName.substring(1);
}
Command command = getCommand(commandName, true);
if(command == null || !command.canExecute(executor)) {
executor.notify("Unknown command. Type '/help' for a list of commands.", SYSTEM);
return;
}
if(executor instanceof Player) {
Player player = (Player)executor;
logger.info(SERVER_MARKER, "{} used command '/{}'", player.getName(), commandName + (args.length == 0 ? "" : " " + String.join(" ", args)));
}
command.execute(executor, args);
}
public static void registerCommand(Class<? extends Command> type) {
CommandInfo info = type.getAnnotation(CommandInfo.class);
if(info == null) {
logger.warn(SERVER_MARKER, "Cannot register command '{}' because it does not have the CommandInfo annotation", type.getSimpleName());
return;
}
String name = info.name();
String[] aliases = info.aliases();
if(commands.containsKey(name)) {
logger.warn(SERVER_MARKER, "Attempted to register duplicate command '{}' with name '{}'", type.getSimpleName(), name);
return;
}
Command command = null;
try {
command = type.getConstructor().newInstance();
} catch(ReflectiveOperationException e) {
logger.error("Failed to not instantiate command '{}'", type.getSimpleName(), e);
return;
}
commands.put(name, command);
if(aliases != null) {
for(String alias : aliases) {
if(commands.containsKey(alias) || CommandManager.aliases.containsKey(alias)) {
logger.warn(SERVER_MARKER, "Duplicate alias {} for command {}", alias, command.getClass());
continue;
}
CommandManager.aliases.put(alias, command);
}
}
}
public static Set<String> getCommandNames() {
Set<String> names = new HashSet<>();
names.addAll(commands.keySet());
names.addAll(aliases.keySet());
return names;
}
public static Command getCommand(String name) {
return getCommand(name, false);
}
public static Command getCommand(String name, boolean allowAlias) {
return commands.getOrDefault(name, allowAlias ? aliases.get(name) : null);
}
public static Collection<Command> getCommands() {
return Collections.unmodifiableCollection(commands.values());
}
}

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -8,16 +8,20 @@ import java.util.List;
import org.apache.commons.lang3.math.NumberUtils;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.command.CommandManager;
import brainwine.gameserver.annotations.CommandInfo;
@CommandInfo(name = "help", description = "Displays a list of commands.")
public class HelpCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
List<Command> commands = new ArrayList<>(CommandManager.getCommands());
commands.removeIf(command -> !command.canExecute(executor));
commands.sort((a, b) -> {
CommandInfo info1 = a.getClass().getAnnotation(CommandInfo.class);
CommandInfo info2 = b.getClass().getAnnotation(CommandInfo.class);
return info1.name().compareTo(info2.name());
});
int pageSize = 8;
int pageCount = (int)Math.ceil(commands.size() / (double)pageSize);
int page = 1;
@ -41,11 +45,12 @@ public class HelpCommand extends Command {
return;
}
executor.notify(String.format("========== Information about '/%s' ==========", command.getName()), SYSTEM);
executor.notify(String.format("Description: %s", command.getDescription()), SYSTEM);
CommandInfo info = command.getClass().getAnnotation(CommandInfo.class);
executor.notify(String.format("========== Information about '/%s' ==========", info.name()), SYSTEM);
executor.notify(String.format("Description: %s", info.description()), SYSTEM);
executor.notify(String.format("Usage: %s", command.getUsage(executor)), SYSTEM);
executor.notify(String.format("Aliases: %s", command.getAliases() == null ? "None :("
: Arrays.toString(command.getAliases())), SYSTEM);
executor.notify(String.format("Aliases: %s", info.aliases() == null ? "None :("
: Arrays.toString(info.aliases())), SYSTEM);
return;
}
}
@ -56,19 +61,10 @@ public class HelpCommand extends Command {
executor.notify(String.format("========== Command List (Page %s of %s) ==========", page, pageCount), SYSTEM);
for(Command command : commandsToDisplay) {
executor.notify(String.format("%s - %s", command.getUsage(executor), command.getDescription()), SYSTEM);
CommandInfo info = command.getClass().getAnnotation(CommandInfo.class);
executor.notify(String.format("%s - %s", command.getUsage(executor), info.description()), SYSTEM);
}
}
@Override
public String getName() {
return "help";
}
@Override
public String getDescription() {
return "Displays command information.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,16 +1,16 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands;
import org.apache.commons.validator.routines.EmailValidator;
import org.mindrot.jbcrypt.BCrypt;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.dialog.DialogHelper;
import brainwine.gameserver.entity.player.Player;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@CommandInfo(name = "register", description = "Shows a prompt with which you can register your account.")
public class RegisterCommand extends Command {
@Override
@ -55,13 +55,8 @@ public class RegisterCommand extends Command {
}
@Override
public String getName() {
return "register";
}
@Override
public String getDescription() {
return "Shows a prompt with which you can register your account.";
public String getUsage(CommandExecutor executor) {
return "/register";
}
@Override

View file

@ -1,12 +1,12 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.entity.player.ChatType;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "say", description = "Shows a speech bubble to nearby players.")
public class SayCommand extends Command {
@Override
@ -27,16 +27,6 @@ public class SayCommand extends Command {
player.getZone().sendChatMessage(player, text, ChatType.SPEECH);
}
@Override
public String getName() {
return "say";
}
@Override
public String getDescription() {
return "Shows a speech bubble to nearby players.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/say <message>";

View file

@ -1,12 +1,12 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.entity.player.ChatType;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "think", description = "Shows a thought bubble to nearby players.")
public class ThinkCommand extends Command {
@Override
@ -27,16 +27,6 @@ public class ThinkCommand extends Command {
player.getZone().sendChatMessage(player, text, ChatType.THOUGHT);
}
@Override
public String getName() {
return "think";
}
@Override
public String getDescription() {
return "Shows a thought bubble to nearby players.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/think <message>";

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "acidity", description = "Displays or changes the acidity in the current zone.")
public class AcidityCommand extends Command {
@Override
@ -35,16 +37,6 @@ public class AcidityCommand extends Command {
zone.setAcidity(value);
executor.notify(String.format("Acidity has been set to %s in %s.", value, zone.getName()), SYSTEM);
}
@Override
public String getName() {
return "acidity";
}
@Override
public String getDescription() {
return "Displays or changes the acidity in the current zone.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "admin", description = "Grants or revokes administrator rights.")
public class AdminCommand extends Command {
@Override
@ -38,16 +40,6 @@ public class AdminCommand extends Command {
executor.notify(String.format("Changed administrator status of player %s to %s", target.getName(), admin), SYSTEM);
}
@Override
public String getName() {
return "admin";
}
@Override
public String getDescription() {
return "Allows you to grant or revoke administrator rights.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/admin <player> [true|false]";

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -7,11 +7,13 @@ import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.util.DateTimeUtils;
@CommandInfo(name = "ban", description = "Bans a player from the server.")
public class BanCommand extends Command {
@Override
@ -58,21 +60,6 @@ public class BanCommand extends Command {
target.getName(), endDate.format(DateTimeFormatter.RFC_1123_DATE_TIME), reason), SYSTEM);
}
@Override
public String getName() {
return "ban";
}
@Override
public String[] getAliases() {
return new String[] { "banish" };
}
@Override
public String getDescription() {
return "Bans a player from the server.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/ban <player> <duration> [reason]";

View file

@ -1,13 +1,15 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.POPUP;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "broadcast", description = "Broadcasts a message to all online players.", aliases = "bc")
public class BroadcastCommand extends Command {
@Override
@ -26,21 +28,6 @@ public class BroadcastCommand extends Command {
executor.notify("Your message has been broadcasted.", POPUP);
}
@Override
public String getName() {
return "broadcast";
}
@Override
public String[] getAliases() {
return new String[] { "bc" };
}
@Override
public String getDescription() {
return "Broadcasts a message to all online players.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/broadcast <message>";

View file

@ -0,0 +1,39 @@
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.NotificationType;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "entity", description = "Spawns an entity at your current location.")
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)), SYSTEM);
return;
}
Player player = (Player)executor;
String type = args[0];
if(player.getZone().spawnEntity(type, player.getBlockX(), player.getBlockY(), true) == null) {
executor.notify(String.format("Entity type '%s' does not exist.", type), NotificationType.SYSTEM);
return;
}
}
@Override
public String getUsage(CommandExecutor executor) {
return "/entity <type>";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor.isAdmin() && executor instanceof Player;
}
}

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "experience", description = "Sets the experience of the target player.", aliases = { "xp", "exp" })
public class ExperienceCommand extends Command {
@Override
@ -53,21 +55,6 @@ public class ExperienceCommand extends Command {
target.setExperience(experience);
executor.notify(String.format("Successfully set %s's experience to %s.", target.getName(), experience), SYSTEM);
}
@Override
public String getName() {
return "experience";
}
@Override
public String[] getAliases() {
return new String[] { "xp", "exp" };
}
@Override
public String getDescription() {
return "Sets the experience of the target player.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -6,13 +6,15 @@ import java.util.Arrays;
import java.util.regex.Pattern;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.prefab.PrefabManager;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "export", description = "Exports a section of a zone to a prefab file.")
public class ExportCommand extends Command {
public static final Pattern PREFAB_NAME_PATTERN = Pattern.compile("\\w+(/\\w+)*");
@ -80,16 +82,6 @@ public class ExportCommand extends Command {
}
}
@Override
public String getName() {
return "export";
}
@Override
public String getDescription() {
return "Exports a section of a zone to a prefab file.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/export <x> <y> <width> <height> <name>";

View file

@ -1,14 +1,16 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.zone.Biome;
import brainwine.gameserver.zone.Zone;
import brainwine.gameserver.zone.gen.ZoneGenerator;
@CommandInfo(name = "genzone", description = "Asynchronously generates a new zone.", aliases = "generate")
public class GenerateZoneCommand extends Command {
public static final int MIN_WIDTH = 200;
@ -18,20 +20,20 @@ public class GenerateZoneCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
Biome biome = Biome.getRandomBiome();
int width = 2000;
int height = 600;
Biome biome = args.length > 0 ? Biome.fromName(args[0]) : Biome.getRandomBiome();
int width = biome == Biome.DEEP ? 1200 : 2000;
int height = biome == Biome.DEEP ? 1000 : 600;
int seed = (int)(Math.random() * Integer.MAX_VALUE);
if(args.length > 0 && args.length < 2) {
if(args.length > 1 && args.length < 3) {
executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM);
return;
}
if(args.length >= 2) {
if(args.length >= 3) {
try {
width = Integer.parseInt(args[0]);
height = Integer.parseInt(args[1]);
width = Integer.parseInt(args[1]);
height = Integer.parseInt(args[2]);
} catch(NumberFormatException e) {
executor.notify("Zone width and height must be valid numbers.", SYSTEM);
return;
@ -47,10 +49,6 @@ public class GenerateZoneCommand extends Command {
}
}
if(args.length >= 3) {
biome = Biome.fromName(args[2]);
}
ZoneGenerator generator = null;
if(args.length >= 4) {
@ -88,25 +86,10 @@ public class GenerateZoneCommand extends Command {
}
});
}
@Override
public String getName() {
return "genzone";
}
@Override
public String[] getAliases() {
return new String[] { "generate" };
}
@Override
public String getDescription() {
return "Asynchronously generates a new zone.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/genzone [<width> <height>] [biome] [generator] [seed]";
return "/genzone [biome] [<width> <height>] [generator] [seed]";
}
@Override

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -6,12 +6,14 @@ import java.util.ArrayList;
import java.util.List;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.ItemRegistry;
@CommandInfo(name = "give", description = "Give or take items from players.")
public class GiveCommand extends Command {
@Override
@ -35,7 +37,7 @@ public class GiveCommand extends Command {
title = "of every item";
for(Item item : ItemRegistry.getItems()) {
if(!item.isClothing() && !item.isAir()) {
if(!item.isAir()) {
items.add(item);
}
}
@ -84,16 +86,6 @@ public class GiveCommand extends Command {
executor.notify(String.format("Took %s %s from %s", -quantity, title, target.getName()), SYSTEM);
}
}
@Override
public String getName() {
return "give";
}
@Override
public String getDescription() {
return "Adds items to a player's inventory.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "health", description = "Sets the target player's health.", aliases = "hp")
public class HealthCommand extends Command {
@Override
@ -47,21 +49,6 @@ public class HealthCommand extends Command {
executor.notify(String.format("Set %s's health to %s", target.getName(), target.getHealth()), SYSTEM);
}
}
@Override
public String getName() {
return "health";
}
@Override
public String[] getAliases() {
return new String[] { "hp" };
}
@Override
public String getDescription() {
return "Sets a player's health.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,13 +1,15 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.prefab.Prefab;
@CommandInfo(name = "import", description = "Places a prefab at the specified location.")
public class ImportCommand extends Command {
@Override
@ -46,16 +48,6 @@ public class ImportCommand extends Command {
player.notify(String.format("Successfully imported '%s' @ [x: %s, y: %s, width: %s, height: %s]",
name, x, y, prefab.getWidth(), prefab.getHeight()), SYSTEM);
}
@Override
public String getName() {
return "import";
}
@Override
public String getDescription() {
return "Places a prefab at your or a specified location.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,14 +1,16 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import java.util.Arrays;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "kick", description = "Kicks a player from the server.")
public class KickCommand extends Command {
@Override
@ -38,16 +40,6 @@ public class KickCommand extends Command {
executor.notify("Kicked player " + player.getName() + " for '" + reason + "'", SYSTEM);
}
@Override
public String getName() {
return "kick";
}
@Override
public String getDescription() {
return "Kicks a player from the server.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/kick <player> [reason]";

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "level", description = "Sets the level of the target player.", aliases = { "lvl", "lv" })
public class LevelCommand extends Command {
@Override
@ -55,21 +57,6 @@ public class LevelCommand extends Command {
target.setLevel(level);
executor.notify(String.format("Successfully set %s's level to %s.", target.getName(), level), SYSTEM);
}
@Override
public String getName() {
return "level";
}
@Override
public String[] getAliases() {
return new String[] { "lvl", "lv" };
}
@Override
public String getDescription() {
return "Sets the level of the target player.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -7,11 +7,13 @@ import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.util.DateTimeUtils;
@CommandInfo(name = "mute", description = "Mutes a player, preventing them from chatting.", aliases = "silence")
public class MuteCommand extends Command {
@Override
@ -58,21 +60,6 @@ public class MuteCommand extends Command {
target.getName(), endDate.format(DateTimeFormatter.RFC_1123_DATE_TIME), reason), SYSTEM);
}
@Override
public String getName() {
return "mute";
}
@Override
public String[] getAliases() {
return new String[] { "silence", };
}
@Override
public String getDescription() {
return "Mutes a player, preventing them from chatting.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/mute <player> <duration> [reason]";

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "pid", description = "Displays the document id of a player.")
public class PlayerIdCommand extends Command {
@Override
@ -34,16 +36,6 @@ public class PlayerIdCommand extends Command {
executor.notify(target.getDocumentId(), SYSTEM);
}
@Override
public String getName() {
return "pid";
}
@Override
public String getDescription() {
return "Displays the document id of a player.";
}
@Override
public String getUsage(CommandExecutor executor) {
return String.format("/pid %s", executor instanceof Player ? "[player]" : "<player>");

View file

@ -1,11 +1,13 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "position", description = "Displays the coordinates of the block you are standing on.", aliases = { "pos", "feet", "coords", "location" })
public class PositionCommand extends Command {
@Override
@ -13,21 +15,6 @@ public class PositionCommand extends Command {
Player player = (Player)executor;
player.notify(String.format("X: %s Y: %s", (int)player.getX(), (int)player.getY() + 1), SYSTEM);
}
@Override
public String getName() {
return "pos";
}
@Override
public String[] getAliases() {
return new String[] { "position", "feet", "coords", "location" };
}
@Override
public String getDescription() {
return "Displays the coordinates of the block you are standing on.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
@ -8,10 +8,12 @@ import java.util.List;
import org.apache.commons.lang3.math.NumberUtils;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.prefab.Prefab;
@CommandInfo(name = "prefabs", description = "Displays a list of all prefabs.")
public class PrefabListCommand extends Command {
@Override
@ -34,21 +36,6 @@ public class PrefabListCommand extends Command {
executor.notify(prefab.getName(), SYSTEM);
}
}
@Override
public String getName() {
return "prefabs";
}
@Override
public String[] getAliases() {
return new String[] { "prefablist" };
}
@Override
public String getDescription() {
return "Displays a list of all prefabs.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,13 +1,15 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "seed", description = "Displays the seed of a zone.")
public class SeedCommand extends Command {
@Override
@ -35,16 +37,6 @@ public class SeedCommand extends Command {
executor.notify("Seed: " + target.getSeed(), SYSTEM);
}
@Override
public String getName() {
return "seed";
}
@Override
public String getDescription() {
return "Displays the seed of a zone.";
}
@Override
public String getUsage(CommandExecutor executor) {
return String.format("/seed %s", executor instanceof Player ? "[zone]" : "<zone>");

View file

@ -1,11 +1,13 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "settle", description = "Settles all liquids in all active chunks in the current zone. Warning - can cause lag!")
public class SettleLiquidsCommand extends Command {
@Override
@ -19,18 +21,8 @@ public class SettleLiquidsCommand extends Command {
}
@Override
public String getName() {
return "settleliquids";
}
@Override
public String[] getAliases() {
return new String[] {"settle"};
}
@Override
public String getDescription() {
return "Settles all liquids in all active chunks in the current zone. Warning - can cause lag!";
public String getUsage(CommandExecutor executor) {
return "/settle";
}
@Override

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "skillpoints", description = "Sets the skill points of the target player.", aliases = "points")
public class SkillPointsCommand extends Command {
@Override
@ -54,21 +56,6 @@ public class SkillPointsCommand extends Command {
target.notify(String.format("Your skill point count has been set to %s.", amount), SYSTEM);
executor.notify(String.format("Successfully set %s's skill point count to %s.", target.getName(), amount), SYSTEM);
}
@Override
public String getName() {
return "skillpoints";
}
@Override
public String[] getAliases() {
return new String[] { "points" };
}
@Override
public String getDescription() {
return "Sets the skill points of the target player.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -0,0 +1,25 @@
package brainwine.gameserver.commands.admin;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
@CommandInfo(name = "stop", description = "Gracefully shuts down the server.", aliases = { "exit", "close", "shutdown" })
public class StopCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
GameServer.getInstance().stopGracefully(); // YEET!!
}
@Override
public String getUsage(CommandExecutor executor) {
return "/stop";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor.isAdmin();
}
}

View file

@ -1,11 +1,13 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "teleport", description = "Teleports you to the specified position.", aliases = "tp")
public class TeleportCommand extends Command {
@Override
@ -35,21 +37,6 @@ public class TeleportCommand extends Command {
player.teleport(x, y);
}
@Override
public String getName() {
return "teleport";
}
@Override
public String[] getAliases() {
return new String[] { "tp" };
}
@Override
public String getDescription() {
return "Teleports you to the specified position.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/teleport <x> <y>";

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "time", description = "Displays or changes the time in the current zone.")
public class TimeCommand extends Command {
@Override
@ -45,16 +47,6 @@ public class TimeCommand extends Command {
zone.setTime(value);
executor.notify(String.format("Time has been set to %s in %s.", value, zone.getName()), SYSTEM);
}
@Override
public String getName() {
return "time";
}
@Override
public String getDescription() {
return "Displays or changes the time in the current zone.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "unban", description = "Unbans a player.", aliases = "pardon")
public class UnbanCommand extends Command {
@Override
@ -31,21 +33,6 @@ public class UnbanCommand extends Command {
target.unban(executor instanceof Player ? (Player)executor : null);
executor.notify(String.format("Player %s has been unbanned.", target.getName()), SYSTEM);
}
@Override
public String getName() {
return "unban";
}
@Override
public String[] getAliases() {
return new String[] { "pardon" };
}
@Override
public String getDescription() {
return "Unbans a player.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,12 +1,14 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
@CommandInfo(name = "unmute", description = "Unmutes a player.")
public class UnmuteCommand extends Command {
@Override
@ -31,16 +33,6 @@ public class UnmuteCommand extends Command {
target.unmute(executor instanceof Player ? (Player)executor : null);
executor.notify(String.format("Player %s has been unmuted.", target.getName()), SYSTEM);
}
@Override
public String getName() {
return "unmute";
}
@Override
public String getDescription() {
return "Unmutes a player.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,14 +1,16 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.zone.Biome;
import brainwine.gameserver.zone.WeatherManager;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "weather", description = "Displays or changes the weather in the current zone.")
public class WeatherCommand extends Command {
@Override
@ -32,16 +34,6 @@ public class WeatherCommand extends Command {
zone.getWeatherManager().createRandomRain(dry);
executor.notify(String.format("Weather has been %s in %s.", dry ? "cleared" : "made rainy", zone.getName()), SYSTEM);
}
@Override
public String getName() {
return "weather";
}
@Override
public String getDescription() {
return "Displays or changes the weather in the current zone.";
}
@Override
public String getUsage(CommandExecutor executor) {

View file

@ -1,13 +1,15 @@
package brainwine.gameserver.command.commands;
package brainwine.gameserver.commands.admin;
import static brainwine.gameserver.entity.player.NotificationType.SYSTEM;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.annotations.CommandInfo;
import brainwine.gameserver.commands.Command;
import brainwine.gameserver.commands.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.zone.Zone;
@CommandInfo(name = "zid", description = "Displays the document id of a zone.")
public class ZoneIdCommand extends Command {
@Override
@ -35,16 +37,6 @@ public class ZoneIdCommand extends Command {
executor.notify(target.getDocumentId(), SYSTEM);
}
@Override
public String getName() {
return "zid";
}
@Override
public String getDescription() {
return "Displays the document id of a zone.";
}
@Override
public String getUsage(CommandExecutor executor) {
return String.format("/zid %s", executor instanceof Player ? "[zone]" : "<zone>");

View file

@ -1,12 +1,15 @@
package brainwine.gameserver.dialog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonSetter;
@JsonInclude(Include.NON_DEFAULT)
@JsonIgnoreProperties(ignoreUnknown = true)
@ -15,6 +18,7 @@ public class Dialog {
private DialogType type = DialogType.STANDARD;
private DialogAlignment alignment = DialogAlignment.LEFT;
private List<DialogSection> sections = new ArrayList<>();
private Object actions;
private String title;
private String target;
@ -61,6 +65,29 @@ public class Dialog {
return sections;
}
@JsonSetter
private void setActions(Object actions) {
this.actions = actions;
}
public Dialog setActions(String actions) {
this.actions = actions;
return this;
}
public Dialog setActions(String... actions) {
return setActions(Arrays.asList(actions));
}
public Dialog setActions(Collection<String> actions) {
this.actions = actions;
return this;
}
public Object getActions() {
return actions;
}
public Dialog setTitle(String title) {
this.title = title;
return this;

View file

@ -3,13 +3,15 @@ package brainwine.gameserver.dialog;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import brainwine.gameserver.dialog.input.DialogInput;
import com.fasterxml.jackson.annotation.JsonProperty;
import brainwine.gameserver.util.Vector2i;
@JsonInclude(Include.NON_DEFAULT)
@JsonIgnoreProperties(ignoreUnknown = true)
@ -20,6 +22,7 @@ public class DialogSection {
private String text;
private String textColor;
private double textScale;
private Vector2i location;
private DialogInput input;
public DialogSection addItem(DialogListItem item) {
@ -75,6 +78,20 @@ public class DialogSection {
return textScale;
}
/**
* v2 clients only!
*/
public DialogSection setLocation(int x, int y) {
this.location = new Vector2i(x, y);
return this;
}
@JsonProperty("map")
@JsonFormat(shape = Shape.ARRAY)
public Vector2i getLocation() {
return location;
}
public DialogSection setInput(DialogInput input) {
this.input = input;
return this;

View file

@ -3,9 +3,12 @@ package brainwine.gameserver.dialog.input;
import java.util.Arrays;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DialogSelectInput extends DialogInput {
private Collection<String> options;
private int maxColumns;
public DialogSelectInput setOptions(String... options) {
return setOptions(Arrays.asList(options));
@ -19,4 +22,14 @@ public class DialogSelectInput extends DialogInput {
public Collection<String> getOptions() {
return options;
}
public DialogSelectInput setMaxColumns(int maxColumns) {
this.maxColumns = maxColumns;
return this;
}
@JsonProperty("max columns")
public int getMaxColumns() {
return maxColumns;
}
}

View file

@ -1,16 +1,24 @@
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 brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.item.DamageType;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.ItemUseType;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.server.messages.EffectMessage;
import brainwine.gameserver.server.messages.EntityChangeMessage;
import brainwine.gameserver.server.messages.EntityStatusMessage;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.util.MathUtils;
import brainwine.gameserver.zone.Block;
import brainwine.gameserver.zone.MetaBlock;
import brainwine.gameserver.zone.Zone;
public abstract class Entity {
@ -18,8 +26,11 @@ public abstract class Entity {
public static final float DEFAULT_HEALTH = 5;
public static final float POSITION_MODIFIER = 100F;
public static final int VELOCITY_MODIFIER = (int)POSITION_MODIFIER;
public static final int ATTACK_RETENTION_TIME = 2000;
public static final int ATTACK_INVINCIBLE_TIME = 333;
protected final Map<String, Object> properties = new HashMap<>();
protected final List<Player> trackers = new ArrayList<>();
protected final List<EntityAttack> recentAttacks = new ArrayList<>();
protected int type;
protected String name;
protected float health = DEFAULT_HEALTH;
@ -29,10 +40,18 @@ public abstract class Entity {
protected float y;
protected float velocityX;
protected float velocityY;
protected int blockX;
protected int blockY;
protected int lastBlockX;
protected int lastBlockY;
protected int targetX;
protected int targetY;
protected int sizeX = 1;
protected int sizeY = 1;
protected FacingDirection direction = FacingDirection.WEST;
protected int animation;
protected boolean invulnerable;
protected EntityAttack lastAttack; // Used for tracking in entity deaths -- do not use this for anything else!
protected long lastDamagedAt;
public Entity(Zone zone) {
@ -40,10 +59,16 @@ public abstract class Entity {
}
public void tick(float deltaTime) {
// Override
long now = System.currentTimeMillis();
// Update block position
updateBlockPosition();
// Clear expired recent attacks
recentAttacks.removeIf(attack -> now >= attack.getTime() + ATTACK_RETENTION_TIME);
}
public void die(Player killer) {
public void die(EntityAttack cause) {
// Override
}
@ -53,18 +78,93 @@ public abstract class Entity {
}
}
public void damage(float amount) {
damage(amount, null);
public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) {
attack(attacker, weapon, baseDamage, damageType, false);
}
public void damage(float amount, Player attacker) {
setHealth(health - amount);
if(health <= 0) {
die(attacker);
public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType, boolean trueDamage) {
// Ignore attack if entity is dead or invulnerable
if(isDead() || isInvulnerable()) {
return;
}
// Ignore attack if there is no damage to deal
if(baseDamage <= 0 || damageType == null || damageType == DamageType.NONE) {
return;
}
EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType);
recentAttacks.add(attack);
lastAttack = attack;
lastDamagedAt = System.currentTimeMillis();
// Kill entity if attacker is a player in god mode
if(attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode()) {
setHealth(0.0F);
return;
}
// Ignore multipliers if true damage should be dealt
if(trueDamage) {
setHealth(health - baseDamage);
return;
}
float attackMultiplier = attacker != null ? Math.max(0.0F, attacker.getAttackMultiplier(attack)) : 1.0F;
float defense = Math.max(0.0F, 1.0F - getDefense(attack));
float damage = baseDamage * attackMultiplier * defense;
setHealth(health - damage);
}
public float getAttackMultiplier(EntityAttack attack) {
return 1.0F; // Override
}
public float getDefense(EntityAttack attack) {
return 1.0F; // Override
}
public void spawnEffect(String type) {
spawnEffect(type, 1);
}
public void spawnEffect(String type, Object data) {
float effectX = x + sizeX / 2.0F;
float effectY = y + sizeY / 2.0F;
sendMessageToTrackers(new EffectMessage(effectX, effectY, type, data));
}
public void emote(String message) {
float effectX = x + sizeX / 2.0F;
float effectY = y - sizeY + 1;
sendMessageToTrackers(new EffectMessage(effectX, effectY, "emote", message));
}
public void updateBlockPosition() {
lastBlockX = blockX;
lastBlockY = blockY;
blockX = (int)x;
blockY = (int)y;
// Check if block position has changed
if(lastBlockX != blockX || lastBlockY != blockY) {
blockPositionChanged();
}
}
public void blockPositionChanged() {
// Check for touchplates
if(zone != null && zone.isChunkLoaded(blockX, blockY)) {
MetaBlock metaBlock = zone.getMetaBlock(blockX, blockY);
Block block = zone.getBlock(blockX, blockY);
Item item = block.getFrontItem();
int mod = block.getFrontMod();
// Trigger a switch interaction if the entity stepped on a touchplate
if(item.hasUse(ItemUseType.TRIGGER)) {
ItemUseType.SWITCH.getInteraction().interact(zone, this, blockX, blockY, Layer.FRONT, item, mod, metaBlock, null, null);
}
}
}
public boolean canSee(Entity other) {
@ -80,7 +180,7 @@ public abstract class Entity {
return inRange(other.getX(), other.getY(), range);
}
public boolean inRange(float x, float y, float range) {
public boolean inRange(float x, float y, double range) {
return MathUtils.inRange(this.x, this.y, x, y, range);
}
@ -128,6 +228,18 @@ public abstract class Entity {
return trackers;
}
public boolean wasAttackedRecently(Entity entity, int delay) {
return recentAttacks.stream().filter(attack -> attack.getAttacker() == entity && System.currentTimeMillis() < attack.getTime() + delay).findFirst().isPresent();
}
public EntityAttack getMostRecentAttack() {
return recentAttacks.isEmpty() ? null : recentAttacks.get(recentAttacks.size() - 1);
}
public List<EntityAttack> getRecentAttacks() {
return Collections.unmodifiableList(recentAttacks);
}
public void setId(int id) {
this.id = id;
}
@ -159,6 +271,12 @@ public abstract class Entity {
public void setHealth(float health) {
float maxHealth = getMaxHealth();
this.health = health < 0 ? 0 : health > maxHealth ? maxHealth : health;
if(this.health <= 0.0F) {
die(lastAttack);
}
lastAttack = null;
}
public float getHealth() {
@ -168,6 +286,7 @@ public abstract class Entity {
public void setPosition(float x, float y) {
this.x = x;
this.y = y;
updateBlockPosition();
}
public float getX() {
@ -204,6 +323,22 @@ public abstract class Entity {
return targetY;
}
public int getBlockX() {
return blockX;
}
public int getBlockY() {
return blockY;
}
public int getSizeX() {
return sizeX;
}
public int getSizeY() {
return sizeY;
}
public void setDirection(FacingDirection direction) {
this.direction = direction;
}
@ -220,6 +355,14 @@ public abstract class Entity {
return animation;
}
public void setInvulnerable(boolean invulnerable) {
this.invulnerable = invulnerable;
}
public boolean isInvulnerable() {
return invulnerable;
}
public void setZone(Zone zone) {
this.zone = zone;
}
@ -228,6 +371,10 @@ public abstract class Entity {
return zone;
}
public final boolean isPlayer() {
return this instanceof Player; // Not very OOP
}
/**
* @return A {@link Map} containing all the data necessary for use in {@link EntityStatusMessage}.
*/

View file

@ -0,0 +1,41 @@
package brainwine.gameserver.entity;
import brainwine.gameserver.item.DamageType;
import brainwine.gameserver.item.Item;
public class EntityAttack {
private final Entity attacker;
private final Item weapon;
private final float baseDamage;
private final DamageType damageType;
private final long time;
public EntityAttack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) {
this.attacker = attacker;
this.weapon = weapon;
this.baseDamage = baseDamage;
this.damageType = damageType;
this.time = System.currentTimeMillis();
}
public Entity getAttacker() {
return attacker;
}
public Item getWeapon() {
return weapon;
}
public float getBaseDamage() {
return baseDamage;
}
public DamageType getDamageType() {
return damageType;
}
public long getTime() {
return time;
}
}

View file

@ -29,6 +29,11 @@ public class EntityConfig {
private int experienceYield;
private float maxHealth = Entity.DEFAULT_HEALTH;
private float baseSpeed = 3;
private boolean character;
private boolean human;
private boolean named;
private boolean trappable;
private Item trappablePetItem;
private Vector2i size = new Vector2i(1, 1);
private EntityGroup group = EntityGroup.NONE;
private WeightedMap<EntityLoot> loot = new WeightedMap<>();
@ -79,6 +84,30 @@ public class EntityConfig {
return baseSpeed;
}
public boolean isCharacter() {
return character;
}
public boolean isHuman() {
return human;
}
public boolean isNamed() {
return named;
}
public boolean isTrappable() {
return trappable;
}
public boolean hasTrappablePetItem() {
return trappablePetItem != null && !trappablePetItem.isAir();
}
public Item getTrappablePetItem() {
return trappablePetItem;
}
@JsonSetter(nulls = Nulls.SKIP)
private void setSize(Vector2i size) {
this.size = size;

View file

@ -9,18 +9,20 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ThreadLocalRandom;
import brainwine.gameserver.behavior.SequenceBehavior;
import brainwine.gameserver.Naming;
import brainwine.gameserver.entity.Entity;
import brainwine.gameserver.entity.EntityAttack;
import brainwine.gameserver.entity.EntityConfig;
import brainwine.gameserver.entity.EntityLoot;
import brainwine.gameserver.entity.EntityRegistry;
import brainwine.gameserver.entity.FacingDirection;
import brainwine.gameserver.entity.npc.behavior.SequenceBehavior;
import brainwine.gameserver.entity.player.Appearance;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.item.DamageType;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.Layer;
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;
@ -28,13 +30,11 @@ import brainwine.gameserver.zone.Zone;
public class Npc extends Entity {
public static final int ATTACK_RETENTION_TIME = 2000;
public static final int ATTACK_INVINCIBLE_TIME = 333;
private final EntityConfig config;
private final String typeName;
private final float maxHealth;
private final float baseSpeed;
private final Vector2i size;
private final boolean persist;
private final WeightedMap<EntityLoot> loot;
private final WeightedMap<EntityLoot> placedLoot;
private final Map<Item, WeightedMap<EntityLoot>> lootByWeapon;
@ -43,7 +43,6 @@ public class Npc extends Entity {
private final List<String> animations;
private final SequenceBehavior behaviorTree;
private final Map<DamageType, Float> activeDefenses = new HashMap<>();
private final Map<Player, Pair<Item, Long>> recentAttacks = new HashMap<>();
private final List<Npc> children = new ArrayList<>();
private float speed;
private int moveX;
@ -52,6 +51,7 @@ public class Npc extends Entity {
private Vector2i mountBlock;
private Entity owner;
private Entity target;
private boolean artificial;
private long lastBehavedAt = System.currentTimeMillis();
private long lastTrackedAt = System.currentTimeMillis();
@ -100,12 +100,24 @@ public class Npc extends Entity {
properties.put("sl", slots);
}
// Generate random name
if(config.isNamed()) {
this.name = Naming.getRandomEntityName();
}
// Generate random appearance
if(config.isHuman()) {
properties.putAll(Appearance.getRandomAppearance());
}
this.config = config;
this.typeName = config.getName();
this.type = config.getType();
this.maxHealth = config.getMaxHealth();
this.baseSpeed = config.getBaseSpeed();
this.size = config.getSize();
this.persist = config.isCharacter();
this.sizeX = config.getSize().getX();
this.sizeY = config.getSize().getY();
this.loot = config.getLoot();
this.placedLoot = config.getPlacedLoot();
this.lootByWeapon = config.getLootByWeapon();
@ -120,11 +132,9 @@ public class Npc extends Entity {
@Override
public void tick(float deltaTime) {
super.tick(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;
@ -146,37 +156,17 @@ public class Npc extends Entity {
}
@Override
public void die(Player killer) {
// Grant loot & track kill
if(killer != null) {
if(!isPlayerPlaced()) {
// Track assists
for(Player attacker : recentAttacks.keySet()) {
if(attacker != killer) {
attacker.getStatistics().trackAssist(config);
}
}
killer.getStatistics().trackKill(config);
}
EntityLoot loot = getRandomLoot(killer);
if(loot != null) {
Item item = loot.getItem();
if(!item.isAir()) {
killer.getInventory().addItem(item, loot.getQuantity(), true);
}
}
}
public void die(EntityAttack cause) {
// Remove itself from the guard block metadata if it was guarding one
if(isGuard()) {
MetaBlock metaBlock = zone.getMetaBlock(guardBlock.getX(), guardBlock.getY());
if(metaBlock != null) {
MapHelper.getList(metaBlock.getMetadata(), "!", Collections.emptyList()).remove(typeName);
List<String> guards = MapHelper.getList(metaBlock.getMetadata(), "!");
if(guards != null) {
guards.remove(typeName);
}
}
}
@ -184,6 +174,41 @@ public class Npc extends Entity {
if(isMounted()) {
zone.updateBlock(mountBlock.getX(), mountBlock.getY(), Layer.FRONT, 0);
}
// Do nothing else if cause data isn't present
if(cause == null) {
return;
}
Entity killer = cause.getAttacker();
// Grant loot & track kill
if(!artificial && killer != null && killer.isPlayer()) {
Player player = (Player)killer;
if(!isPlayerPlaced()) {
// Track assists
for(EntityAttack recentAttack : recentAttacks) {
Entity attacker = recentAttack.getAttacker();
if(attacker != player && attacker.isPlayer()) {
((Player)attacker).getStatistics().trackAssist(config);
}
}
player.getStatistics().trackKill(config);
}
EntityLoot loot = getRandomLoot(player, cause.getWeapon());
if(loot != null) {
Item item = loot.getItem();
if(!item.isAir()) {
player.getInventory().addItem(item, loot.getQuantity(), true);
}
}
}
}
@Override
@ -191,6 +216,20 @@ public class Npc extends Entity {
return maxHealth;
}
@Override
public float getDefense(EntityAttack attack) {
Entity attacker = attack.getAttacker();
Player player = attacker != null && attacker.isPlayer() ? (Player)attacker : null;
// Full defense if block is mounted and is protected
if(isMounted() && zone.isBlockProtected(mountBlock.getX(), mountBlock.getY(), player)) {
return 1.0F;
}
// Otherwise, calculate defense
return getBaseDefense(attack.getDamageType()) + activeDefenses.getOrDefault(attack.getDamageType(), 0F);
}
@Override
public Map<String, Object> getStatusConfig() {
Map<String, Object> config = super.getStatusConfig();
@ -229,37 +268,6 @@ public class Npc extends Entity {
return config;
}
public Vector2i getSize() {
return size;
}
public void attack(Player attacker, Item weapon) {
// Prevent damage if this entity is mounted and its mount is protected
if(!attacker.isGodMode() && isMounted() && zone.isBlockProtected(mountBlock.getX(), mountBlock.getY(), attacker)) {
return;
}
Pair<Item, Long> recentAttack = recentAttacks.get(attacker);
long now = System.currentTimeMillis();
// Reject the attack if the player already attacked this entity recently
if(!attacker.isGodMode() && recentAttack != null && now < recentAttack.getLast() + ATTACK_INVINCIBLE_TIME) {
return;
}
float damage = attacker.isGodMode() ? 9999 : calculateDamage(weapon.getDamage(), weapon.getDamageType());
damage(damage, attacker);
recentAttacks.put(attacker, new Pair<>(weapon, now));
}
public float calculateDamage(float baseDamage, DamageType type) {
return baseDamage * (1 - getDefense(type));
}
public Collection<Pair<Item, Long>> getRecentAttacks() {
return Collections.unmodifiableCollection(recentAttacks.values());
}
public void setDefense(DamageType type, float amount) {
if(amount == 0) {
activeDefenses.remove(type);
@ -268,21 +276,11 @@ public class Npc extends Entity {
}
}
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 isTransient() {
return !isGuard() && !isMounted();
return !isGuard() && !isMounted() && !persist;
}
public EntityLoot getRandomLoot(Player awardee) {
Item weapon = awardee.getHeldItem();
public EntityLoot getRandomLoot(Player awardee, Item weapon) {
if(isOwnedBy(awardee)) {
return placedLoot.next();
} else if(lootByWeapon.containsKey(weapon)) {
@ -372,6 +370,18 @@ public class Npc extends Entity {
return target;
}
public void setArtificial(boolean artificial) {
this.artificial = artificial;
}
public boolean isArtificial() {
return artificial;
}
public boolean isPersistent() {
return persist;
}
public void setSpeed(float speed) {
this.speed = speed;
}
@ -403,15 +413,15 @@ public class Npc extends Entity {
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;
if(sizeX > 1) {
int additionalWidth = sizeX - 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;
if(sizeY > 1) {
int additionalHeight = sizeY - 1;
blocked = blocked || zone.isBlockSolid(tX, tY - additionalHeight)
|| (oX != 0 && zone.isBlockSolid(tX, y - additionalHeight))
|| (oY != 0 && zone.isBlockSolid(x, tY - additionalHeight));

View file

@ -0,0 +1,47 @@
package brainwine.gameserver.entity.npc;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import brainwine.gameserver.entity.EntityConfig;
/**
* Storage data for persistent non-player characters.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class NpcData {
private EntityConfig type;
private String name;
private int x;
private int y;
@JsonCreator
public NpcData(@JsonProperty(value = "type", required = true) EntityConfig type) {
this.type = type;
}
public NpcData(Npc npc) {
this.type = npc.getConfig();
this.name = npc.getName();
this.x = npc.getBlockX();
this.y = npc.getBlockY();
}
public EntityConfig getType() {
return type;
}
public String getName() {
return name;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior;
package brainwine.gameserver.entity.npc.behavior;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
@ -7,26 +7,26 @@ 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.DiggerBehavior;
import brainwine.gameserver.behavior.composed.FlyerBehavior;
import brainwine.gameserver.behavior.composed.WalkerBehavior;
import brainwine.gameserver.behavior.parts.ClimbBehavior;
import brainwine.gameserver.behavior.parts.DigBehavior;
import brainwine.gameserver.behavior.parts.EruptionAttackBehavior;
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.ReporterBehavior;
import brainwine.gameserver.behavior.parts.ShielderBehavior;
import brainwine.gameserver.behavior.parts.SpawnAttackBehavior;
import brainwine.gameserver.behavior.parts.TurnBehavior;
import brainwine.gameserver.behavior.parts.UnblockBehavior;
import brainwine.gameserver.behavior.parts.WalkBehavior;
import brainwine.gameserver.entity.npc.Npc;
import brainwine.gameserver.entity.npc.behavior.composed.CrawlerBehavior;
import brainwine.gameserver.entity.npc.behavior.composed.DiggerBehavior;
import brainwine.gameserver.entity.npc.behavior.composed.FlyerBehavior;
import brainwine.gameserver.entity.npc.behavior.composed.WalkerBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.EruptionAttackBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FlyBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FollowBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.RandomlyTargetBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.ReporterBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.ShielderBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.SpawnAttackBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.UnblockBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior;
/**
* Heavily based on Deepworld's original "rubyhave" (ha ha very punny) behavior system.

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior;
package brainwine.gameserver.entity.npc.behavior;
import static brainwine.shared.LogMarkers.SERVER_MARKER;

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior;
package brainwine.gameserver.entity.npc.behavior;
import java.util.Map;

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior;
package brainwine.gameserver.entity.npc.behavior;
import static brainwine.shared.LogMarkers.SERVER_MARKER;

View file

@ -1,17 +1,17 @@
package brainwine.gameserver.behavior.composed;
package brainwine.gameserver.entity.npc.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.entity.npc.behavior.SelectorBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior;
import brainwine.gameserver.util.MapHelper;
public class CrawlerBehavior extends SelectorBehavior {

View file

@ -1,17 +1,17 @@
package brainwine.gameserver.behavior.composed;
package brainwine.gameserver.entity.npc.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.DigBehavior;
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.entity.npc.behavior.SelectorBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior;
import brainwine.gameserver.util.MapHelper;
public class DiggerBehavior extends SelectorBehavior {

View file

@ -1,15 +1,15 @@
package brainwine.gameserver.behavior.composed;
package brainwine.gameserver.entity.npc.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.entity.npc.behavior.SelectorBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FlyBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior;
import brainwine.gameserver.util.MapHelper;
public class FlyerBehavior extends SelectorBehavior {

View file

@ -1,16 +1,16 @@
package brainwine.gameserver.behavior.composed;
package brainwine.gameserver.entity.npc.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.entity.npc.behavior.SelectorBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior;
import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior;
import brainwine.gameserver.util.MapHelper;
public class WalkerBehavior extends SelectorBehavior {

View file

@ -1,11 +1,11 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class ClimbBehavior extends Behavior {

View file

@ -1,10 +1,10 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
import brainwine.gameserver.zone.Zone;
public class DigBehavior extends Behavior {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import java.util.Map;
@ -6,10 +6,10 @@ 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.entity.npc.behavior.Behavior;
import brainwine.gameserver.server.messages.EntityStatusMessage;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.util.Vector2i;

View file

@ -1,11 +1,11 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import brainwine.gameserver.behavior.Behavior;
import brainwine.gameserver.entity.npc.Npc;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class FallBehavior extends Behavior {

View file

@ -1,12 +1,12 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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.entity.npc.behavior.Behavior;
import brainwine.gameserver.util.Vector2i;
public class FlyBehavior extends Behavior {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;

View file

@ -1,11 +1,11 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class FollowBehavior extends Behavior {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
@ -7,9 +7,9 @@ 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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
import brainwine.gameserver.util.Vector2i;
public class IdleBehavior extends Behavior {

View file

@ -1,10 +1,10 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
import brainwine.gameserver.entity.player.Player;
public class RandomlyTargetBehavior extends Behavior {
@ -31,7 +31,7 @@ public class RandomlyTargetBehavior extends Behavior {
if(!entity.hasTarget()) {
Player target = entity.getZone().getRandomPlayerInRange(entity.getX(), entity.getY(), range);
if(target != null && !target.isGodMode() && !target.isDead() && !entity.isOwnedBy(target) && (!blockable || entity.canSee(target))) {
if(target != null && !target.isGodMode() && !target.isStealthy() && !target.isDead() && !entity.isOwnedBy(target) && (!blockable || entity.canSee(target))) {
entity.setTarget(target);
targetLockedAt = now;
}

View file

@ -1,10 +1,10 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class ReporterBehavior extends Behavior {

View file

@ -1,7 +1,6 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
@ -9,11 +8,10 @@ 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.EntityAttack;
import brainwine.gameserver.entity.npc.Npc;
import brainwine.gameserver.entity.npc.behavior.Behavior;
import brainwine.gameserver.item.DamageType;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.util.Pair;
public class ShielderBehavior extends Behavior {
@ -32,11 +30,12 @@ public class ShielderBehavior extends Behavior {
@Override
public boolean behave() {
long now = System.currentTimeMillis();
Collection<Pair<Item, Long>> recentAttacks = entity.getRecentAttacks();
EntityAttack attack = entity.getMostRecentAttack();
if(!recentAttacks.isEmpty()) {
if(attack != null) {
lastAttackedAt = now;
DamageType type = recentAttacks.stream().findFirst().get().getFirst().getDamageType();
DamageType type = attack.getDamageType();
if(currentShield == null && now >= shieldStart + (recharge * 1000)) {
if(defenses.contains(type)) {
setShield(type);

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import java.util.Map;
@ -6,13 +6,12 @@ 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.entity.npc.behavior.Behavior;
import brainwine.gameserver.server.messages.EntityStatusMessage;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.util.Vector2i;
public class SpawnAttackBehavior extends Behavior {
@ -41,9 +40,8 @@ public class SpawnAttackBehavior extends Behavior {
if(npc) {
// Spawn child at parent's location
Vector2i size = entity.getSize();
int spawnX = (int)(entity.getX() + (size.getX() / 2F));
int spawnY = (int)(entity.getY() + (size.getY() / 2F));
int spawnX = (int)(entity.getX() + (entity.getSizeX() / 2.0F));
int spawnY = (int)(entity.getY() + (entity.getSizeX() / 2.0F));
Npc child = new Npc(entity.getZone(), entityConfig);
child.setOwner(entity);
entity.addChild(child);

View file

@ -1,11 +1,11 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class TurnBehavior extends Behavior {

View file

@ -1,4 +1,4 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
@ -6,9 +6,8 @@ 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;
import brainwine.gameserver.entity.npc.behavior.Behavior;
import brainwine.gameserver.zone.Zone;
public class UnblockBehavior extends Behavior {
@ -23,12 +22,11 @@ public class UnblockBehavior extends Behavior {
@Override
public boolean behave() {
Zone zone = entity.getZone();
Vector2i size = entity.getSize();
Random random = ThreadLocalRandom.current();
for(int i = 0; i < rate; i++) {
int x = (int)entity.getX() + random.nextInt(size.getX());
int y = (int)entity.getY() - random.nextInt(size.getY());
int x = (int)entity.getX() + random.nextInt(entity.getSizeX());
int y = (int)entity.getY() - random.nextInt(entity.getSizeY());
if(zone.isChunkLoaded(x, y) && zone.getBlock(x, y).getFrontItem().isDiggable()) {
zone.digBlock(x, y);

View file

@ -1,12 +1,12 @@
package brainwine.gameserver.behavior.parts;
package brainwine.gameserver.entity.npc.behavior.parts;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonCreator;
import brainwine.gameserver.behavior.Behavior;
import brainwine.gameserver.entity.FacingDirection;
import brainwine.gameserver.entity.npc.Npc;
import brainwine.gameserver.entity.npc.behavior.Behavior;
public class WalkBehavior extends Behavior {

View file

@ -0,0 +1,87 @@
package brainwine.gameserver.entity.player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import brainwine.gameserver.GameConfiguration;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.ItemRegistry;
import brainwine.gameserver.util.MapHelper;
/**
* Utility class for player appearance related stuff.
* Ghosts also have a random appearance, which is why it's here instead of in the player class.
*/
public class Appearance {
public static Map<String, Object> getRandomAppearance() {
return getRandomAppearance(null);
}
public static Map<String, Object> getRandomAppearance(Player player) {
Map<String, Object> appearance = new HashMap<>();
for(AppearanceSlot slot : AppearanceSlot.values()) {
// Skip if slot cannot be changed by players
if(!slot.isChangeable()) {
continue;
}
String category = slot.getCategory();
// Color handling
if(slot.isColor()) {
List<String> colors = getAvailableColors(slot, player);
// Change appearance to random color
if(!colors.isEmpty()) {
appearance.put(slot.getId(), colors.get((int)(Math.random() * colors.size())));
}
continue;
}
// Fetch list of items in this slot's category that the player owns
List<Item> items = ItemRegistry.getItemsByCategory(category).stream()
.filter(item -> item.isBase() || (player != null && player.getInventory().hasItem(item)))
.collect(Collectors.toList());
// Change appearance to random clothing item
if(!items.isEmpty()) {
appearance.put(slot.getId(), items.get((int)(Math.random() * items.size())).getCode());
}
}
return appearance;
}
public static List<String> getAvailableColors(AppearanceSlot slot) {
return getAvailableColors(null);
}
public static List<String> getAvailableColors(AppearanceSlot slot, Player player) {
List<String> colors = new ArrayList<>();
// Return empty list if slot is not valid
if(!slot.isColor()) {
return colors;
}
Map<String, Object> wardrobe = MapHelper.getMap(GameConfiguration.getBaseConfig(), "wardrobe", Collections.emptyMap());
String category = slot.getCategory();
// Add base colors
colors.addAll(MapHelper.getList(wardrobe, category, Collections.emptyList()));
// Add bonus colors
if(player != null && player.getInventory().hasItem(ItemRegistry.getItem("accessories/makeup"))) {
colors.addAll(MapHelper.getList(wardrobe, String.format("%s-bonus", category), Collections.emptyList()));
}
return colors;
}
}

View file

@ -0,0 +1,61 @@
package brainwine.gameserver.entity.player;
public enum AppearanceSlot {
SKIN_COLOR("c*", "skin-color", true),
HAIR_COLOR("h*", "hair-color", true),
HAIR("h", "hair", true),
FACIAL_HAIR("fh", "facialhair", true),
TOPS("t", "tops", true),
BOTTOMS("b", "bottoms", true),
FOOTWEAR("fw", "footwear", true),
HEADGEAR("hg", "headgear", true),
FACIAL_GEAR("fg", "facialgear", true),
FACIAL_GEAR_GLOW("fg*", "facialgear-glow"),
SUIT("u", "suit"),
TOPS_OVERLAY("to", "tops-overlay"),
TOPS_OVERLAY_GLOW("to*", "tops-overlay-glow"),
ARMS_OVERLAY("ao", "arms-overlay"),
LEGS_OVERLAY("lo", "legs-overlay"),
FOOTWEAR_OVERLAY("fo", "footwear-overlay");
private final String id;
private final String category;
private final boolean changeable;
private AppearanceSlot(String id, String category) {
this(id, category, false);
}
private AppearanceSlot(String id, String category, boolean changeable) {
this.id = id;
this.category = category;
this.changeable = changeable;
}
public static AppearanceSlot fromId(String id) {
for(AppearanceSlot value : values()) {
if(value.getId().equals(id)) {
return value;
}
}
return null;
}
public String getId() {
return id;
}
public String getCategory() {
return category;
}
public boolean isChangeable() {
return changeable;
}
public boolean isColor() {
return id.endsWith("*");
}
}

View file

@ -1,42 +0,0 @@
package brainwine.gameserver.entity.player;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum ClothingSlot {
HAIR("h"),
FACIAL_HAIR("fh"),
TOPS("t"),
BOTTOMS("b"),
FOOTWEAR("fw"),
HEADGEAR("hg"),
FACIAL_GEAR("fg"),
SUIT("u"),
TOPS_OVERLAY("to"),
ARMS_OVERLAY("ao"),
LEGS_OVERLAY("lo"),
FOOTWEAR_OVERLAY("fo");
private final String id;
private ClothingSlot(String id) {
this.id = id;
}
@JsonCreator
public static ClothingSlot fromId(String id) {
for(ClothingSlot value : values()) {
if(value.getId().equals(id)) {
return value;
}
}
return null;
}
@JsonValue
public String getId() {
return id;
}
}

View file

@ -1,32 +0,0 @@
package brainwine.gameserver.entity.player;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum ColorSlot {
SKIN_COLOR("c*"),
HAIR_COLOR("h*");
private final String id;
private ColorSlot(String id) {
this.id = id;
}
@JsonCreator
public static ColorSlot fromId(String id) {
for(ColorSlot value : values()) {
if(value.getId().equals(id)) {
return value;
}
}
return null;
}
@JsonValue
public String getId() {
return id;
}
}

View file

@ -0,0 +1,35 @@
package brainwine.gameserver.entity.player;
import java.time.OffsetDateTime;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
@JsonIncludeProperties({"new_name", "previous_name", "date"})
public class NameChange {
private String newName;
private String previousName;
private OffsetDateTime date;
@JsonCreator
private NameChange() {}
public NameChange(String newName, String previousName) {
this.newName = newName;
this.previousName = previousName;
date = OffsetDateTime.now();
}
public String getNewName() {
return newName;
}
public String getPreviousName() {
return previousName;
}
public OffsetDateTime getDate() {
return date;
}
}

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