Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90395f71e9 | ||
|
|
b6c3f2c1e0 | ||
|
|
e8e46c8e46 | ||
|
|
d021f12ad0 | ||
|
|
29d347ec03 | ||
|
|
460d737d3d | ||
|
|
7591ce29ff | ||
|
|
39bbf19d9d | ||
|
|
df239d8166 | ||
|
|
bf9c16ccc8 | ||
|
|
b5d6d7bac4 | ||
|
|
3b55f16b24 | ||
|
|
5ef254b75f | ||
|
|
5e5f36153e | ||
|
|
e77879c657 | ||
|
|
e3068be160 | ||
|
|
867bee1c5a | ||
|
|
893923cc56 | ||
|
|
38c944e6c1 |
No files matched your search
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
//for jar packaging see https://github.com/Protonull/BasicMinestomServer/blob/master/build.gradle.kts
|
//for jar packaging see https://github.com/Protonull/BasicMinestomServer/blob/master/build.gradle.kts
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
|||||||
Vendored
+82
-82
@@ -1,82 +1,82 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem Copyright 2015 the original author or authors.
|
@rem Copyright 2015 the original author or authors.
|
||||||
@rem
|
@rem
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@rem you may not use this file except in compliance with the License.
|
@rem you may not use this file except in compliance with the License.
|
||||||
@rem You may obtain a copy of the License at
|
@rem You may obtain a copy of the License at
|
||||||
@rem
|
@rem
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
@rem
|
@rem
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem gradlew startup script for Windows
|
@rem gradlew startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
setlocal EnableExtensions
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@rem This is normally unused
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo. 1>&2
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo. 1>&2
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute gradlew
|
@rem Execute gradlew
|
||||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
@rem which allows us to clear the local environment before executing the java command
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:exitWithErrorLevel
|
:exitWithErrorLevel
|
||||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
||||||
|
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
|
import net.minestom.server.network.packet.server.play.ParticlePacket;
|
||||||
|
import net.minestom.server.particle.Particle;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class Cursor extends Entity {
|
||||||
|
private boolean cursorActive = false;
|
||||||
|
private final int reach;
|
||||||
|
|
||||||
|
public Cursor(int reach) {
|
||||||
|
super(EntityType.ARMOR_STAND);
|
||||||
|
this.setBoundingBox(0,0,0);
|
||||||
|
this.reach = reach;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCursorPosition(Player player) {
|
||||||
|
Point targetPosition = player.getTargetBlockPosition(this.reach);
|
||||||
|
this.moveCursorTo(targetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveCursorTo(@Nullable Point newTargetBlockPosition) {
|
||||||
|
this.setCursorEnabled(true);
|
||||||
|
if(newTargetBlockPosition == null) {
|
||||||
|
this.setCursorEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!this.getInstance().getBlock(newTargetBlockPosition).equals(Block.BLACK_WOOL)) this.setCursorEnabled(false);
|
||||||
|
Material holdingMaterial = this.getInstance().getPlayerHandMaterial();
|
||||||
|
if(!this.getInstance().getGame().getAvailableTowers().containsKey(holdingMaterial)) this.setCursorEnabled(false);
|
||||||
|
this.teleport(new Pos(newTargetBlockPosition.add(0.5,1,0.5)));
|
||||||
|
this.showRange(this.getInstance().getGame().getAvailableTowers().get(holdingMaterial));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCursorEnabled(boolean enabled) {
|
||||||
|
this.cursorActive = enabled;
|
||||||
|
this.setInvisible(!enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRange(@Nullable Class<? extends Tower> towerClass) {
|
||||||
|
if(towerClass == null) return;
|
||||||
|
if(!this.isCursorActive()) return;
|
||||||
|
try {
|
||||||
|
int range = towerClass.getConstructor().newInstance().getRange();
|
||||||
|
Collection<SendablePacket> particles = new ArrayList<>();
|
||||||
|
double circumference = 2 * Math.PI * range;
|
||||||
|
int count = (int) (circumference * 1.5);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
double radians = ((2 * Math.PI) / count) * i;
|
||||||
|
Vec relativePosition = new Vec(Math.sin(radians)*range, 0, Math.cos(radians)*range);
|
||||||
|
ParticlePacket particle = new ParticlePacket(Particle.COMPOSTER, this.position.add(relativePosition), Pos.ZERO, 0, 1);
|
||||||
|
particles.add(particle);
|
||||||
|
}
|
||||||
|
this.getInstance().getPlayer().sendPackets(particles);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TowerdefenseRoom getInstance() {
|
||||||
|
return (TowerdefenseRoom) super.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCursorActive() {
|
||||||
|
return this.cursorActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getTargetBlock() {
|
||||||
|
return this.getInstance().getBlock(this.getTargetBlockPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pos getTargetBlockPosition() {
|
||||||
|
return this.getPosition().sub(0, 0.5, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+145
-13
@@ -2,40 +2,159 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
|||||||
|
|
||||||
import eu.mhsl.minenet.minigames.instance.Dimension;
|
import eu.mhsl.minenet.minigames.instance.Dimension;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies.WaveGenerator;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.*;
|
||||||
import eu.mhsl.minenet.minigames.score.LastWinsScore;
|
import eu.mhsl.minenet.minigames.score.LastWinsScore;
|
||||||
|
import net.kyori.adventure.sound.Sound;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.EntityType;
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
|
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import net.minestom.server.sound.SoundEvent;
|
||||||
|
import net.minestom.server.timer.Task;
|
||||||
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class Towerdefense extends StatelessGame {
|
public class Towerdefense extends StatelessGame {
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
|
private final AbsoluteBlockBatch mazeBatch = new AbsoluteBlockBatch();
|
||||||
private final List<Pos> mazePath = new ArrayList<>();
|
private final List<Pos> mazePath = new ArrayList<>();
|
||||||
private final List<TowerdefenseRoom> instances = new ArrayList<>();
|
private final List<TowerdefenseRoom> instances = new ArrayList<>();
|
||||||
|
private final Set<Player> spectators = new HashSet<>();
|
||||||
|
private final WaveGenerator waveGenerator;
|
||||||
|
private Task waveTask;
|
||||||
|
public static final Component SWITCHER_NAME = Component.text("Spieler wechseln", NamedTextColor.AQUA);
|
||||||
|
private final Map<Material, Class<? extends Tower>> availableTowers = Map.of(
|
||||||
|
Material.SKELETON_SPAWN_EGG, SkeletonTower.class,
|
||||||
|
Material.ZOMBIE_SPAWN_EGG, ZombieTower.class,
|
||||||
|
Material.BLAZE_SPAWN_EGG, BlazeTower.class,
|
||||||
|
Material.ENDERMAN_SPAWN_EGG, EndermanTower.class,
|
||||||
|
Material.PIGLIN_BRUTE_SPAWN_EGG, BruteTower.class,
|
||||||
|
Material.WITHER_SPAWN_EGG, WitherTower.class,
|
||||||
|
Material.ENDER_DRAGON_SPAWN_EGG, EnderDragonTower.class
|
||||||
|
);
|
||||||
|
private final Map<Class<? extends Tower>, Integer> prices = Map.of(
|
||||||
|
ZombieTower.class, 4,
|
||||||
|
SkeletonTower.class, 3,
|
||||||
|
BlazeTower.class, 8,
|
||||||
|
EndermanTower.class, 20,
|
||||||
|
BruteTower.class, 27,
|
||||||
|
WitherTower.class, 40,
|
||||||
|
EnderDragonTower.class, 200
|
||||||
|
);
|
||||||
|
private static final int pathLength = 10;
|
||||||
|
|
||||||
public Towerdefense() {
|
public Towerdefense() {
|
||||||
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
super(Dimension.NETHER.key, "Towerdefense", new LastWinsScore());
|
||||||
|
|
||||||
this.setGenerator(new MazeGenerator());
|
this.setGenerator(new MazeGenerator());
|
||||||
this.generateMaze();
|
this.generateMaze();
|
||||||
|
|
||||||
|
this.waveGenerator = new WaveGenerator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void checkAbandoned() {
|
||||||
|
this.scheduleNextTick((instance) -> {
|
||||||
|
boolean noAlive = this.instances.stream().allMatch(room -> room.getPlayers().isEmpty());
|
||||||
|
boolean noSpectators = this.spectators.stream().noneMatch(Player::isOnline);
|
||||||
|
if(noAlive && this.getPlayers().isEmpty() && noSpectators) {
|
||||||
|
this.waveTask.cancel();
|
||||||
|
this.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
this.getPlayers().forEach(player -> {
|
||||||
|
TowerdefenseRoom newRoom = new TowerdefenseRoom(player, this);
|
||||||
|
this.instances.add(newRoom);
|
||||||
|
player.setInstance(newRoom, new Pos(0, 1, 0)).thenRun(() -> this.welcome(player));
|
||||||
|
});
|
||||||
|
this.waveTask = MinecraftServer.getSchedulerManager().scheduleTask(
|
||||||
|
this.waveGenerator::startNextWave,
|
||||||
|
TaskSchedule.seconds(8),
|
||||||
|
TaskSchedule.seconds(15)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void welcome(Player player) {
|
||||||
|
player.showTitle(Title.title(
|
||||||
|
Component.text("Tower Defense", NamedTextColor.GOLD),
|
||||||
|
Component.text("Verteidige dich gegen die Mobs!", NamedTextColor.YELLOW),
|
||||||
|
Title.Times.times(Duration.ofMillis(400), Duration.ofSeconds(3), Duration.ofMillis(600))
|
||||||
|
));
|
||||||
|
player.playSound(Sound.sound(SoundEvent.UI_TOAST_CHALLENGE_COMPLETE, Sound.Source.MASTER, 1f, 1f));
|
||||||
|
player.sendMessage(Component.text("Die erste Welle kommt in 8 Sekunden...", NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TowerdefenseRoom> getAliveInstances() {
|
||||||
|
synchronized (this.instances) {
|
||||||
|
return new ArrayList<>(this.instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeInstance(TowerdefenseRoom room) {
|
||||||
|
synchronized (this.instances) {
|
||||||
|
this.instances.remove(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Player> getSpectators() {
|
||||||
|
return this.spectators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginSpectating(Player player) {
|
||||||
|
this.spectators.add(player);
|
||||||
|
ItemStack switcher = ItemStack.of(Material.COMPASS).withCustomName(SWITCHER_NAME);
|
||||||
|
player.getInventory().clear();
|
||||||
|
player.getInventory().setItemStack(4, switcher);
|
||||||
|
player.sendMessage(Component.text("Du bist gestorben! Nutze den Kompass, um andere Spieler zu beobachten.", NamedTextColor.RED));
|
||||||
|
this.cycleSpectator(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cycleSpectator(Player player) {
|
||||||
|
List<TowerdefenseRoom> alive = this.getAliveInstances();
|
||||||
|
if(alive.isEmpty()) {
|
||||||
|
player.sendMessage(Component.text("Kein Spieler mehr am Leben.", NamedTextColor.GRAY));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TowerdefenseRoom current = alive.stream()
|
||||||
|
.filter(r -> r == player.getInstance())
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
int nextIdx = current == null ? 0 : (alive.indexOf(current) + 1) % alive.size();
|
||||||
|
TowerdefenseRoom target = alive.get(nextIdx);
|
||||||
|
player.setInstance(target, new Pos(0, 2, 0)).thenRun(() -> {
|
||||||
|
player.setGameMode(net.minestom.server.entity.GameMode.SPECTATOR);
|
||||||
|
player.sendActionBar(Component.text("Du beobachtest: " + target.getPlayer().getUsername(), NamedTextColor.AQUA));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateMaze() {
|
private void generateMaze() {
|
||||||
Pos position = new Pos(0, 0, 0);
|
Pos position = new Pos(0, 0, 0);
|
||||||
this.addMazePosition(position, Block.GREEN_WOOL);
|
this.addMazePosition(position, Block.GREEN_WOOL);
|
||||||
|
position = position.add(0,0,2);
|
||||||
|
|
||||||
List<Integer> previousDirections = new ArrayList<>();
|
List<Integer> previousDirections = new ArrayList<>();
|
||||||
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
|
int direction = 1; // 0 -> right; 1 -> straight; 2 -> left
|
||||||
for(int i = 0; i < 9; i++) {
|
for (int i = 0; i < pathLength; i++) {
|
||||||
for(int j = 0; j < 3; j++) {
|
for (int j = 0; j < 9; j++) {
|
||||||
position = position.add(direction - 1, 0, direction % 2);
|
position = position.add(direction-1,0,direction%2);
|
||||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +167,17 @@ public class Towerdefense extends StatelessGame {
|
|||||||
previousDirections.add(direction);
|
previousDirections.add(direction);
|
||||||
}
|
}
|
||||||
this.addMazePosition(position, Block.WHITE_WOOL);
|
this.addMazePosition(position, Block.WHITE_WOOL);
|
||||||
this.addMazePosition(position.add(0, 0, 1), Block.WHITE_WOOL);
|
this.addMazePosition(position.add(0,0,3), Block.WHITE_WOOL);
|
||||||
this.addMazePosition(position.add(0, 0, 2), Block.RED_WOOL);
|
this.addMazePosition(position.add(0,0,6), Block.RED_WOOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMazePosition(Pos position, Block pathBlock) {
|
private void addMazePosition(Pos position, Block pathBlock) {
|
||||||
this.mazeBatch.setBlock(position, pathBlock);
|
for (int i = 0; i < 3; i++) {
|
||||||
this.mazePath.add(position.add(0.5, 1, 0.5));
|
for (int j = 0; j < 3; j++) {
|
||||||
|
this.mazeBatch.setBlock(position.add(i-1,0,j-1), pathBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mazePath.add(position.add(0.5,1,0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbsoluteBlockBatch getMazeBatch() {
|
public AbsoluteBlockBatch getMazeBatch() {
|
||||||
@@ -65,12 +188,21 @@ public class Towerdefense extends StatelessGame {
|
|||||||
return this.mazePath;
|
return this.mazePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TowerdefenseRoom> getInstances() {
|
||||||
|
return this.instances;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onPlayerJoin(Player p) {
|
protected boolean onPlayerJoin(Player p) {
|
||||||
TowerdefenseRoom newRoom = new TowerdefenseRoom(p, this);
|
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> p.teleport(new Pos((long) -this.getPlayers().size(), 1, 0)));
|
||||||
this.instances.add(newRoom);
|
return super.onPlayerJoin(p);
|
||||||
p.setInstance(newRoom);
|
}
|
||||||
newRoom.startWave(List.of(EntityType.ENDERMAN, EntityType.BLAZE, EntityType.PLAYER, EntityType.HORSE, EntityType.ARMOR_STAND, EntityType.SKELETON));
|
|
||||||
return false;
|
public Map<Class<? extends Tower>, Integer> getPrices() {
|
||||||
|
return this.prices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Material, Class<? extends Tower>> getAvailableTowers() {
|
||||||
|
return this.availableTowers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+252
-29
@@ -1,67 +1,290 @@
|
|||||||
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
|
||||||
|
|
||||||
import eu.mhsl.minenet.minigames.instance.Dimension;
|
import eu.mhsl.minenet.minigames.instance.Dimension;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies.GroupFactory;
|
||||||
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
|
||||||
import eu.mhsl.minenet.minigames.util.BatchUtil;
|
import eu.mhsl.minenet.minigames.util.BatchUtil;
|
||||||
|
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.entity.EntityCreature;
|
import net.minestom.server.collision.Aerodynamics;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.GameMode;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.*;
|
||||||
import net.minestom.server.entity.attribute.Attribute;
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
import net.minestom.server.entity.damage.DamageType;
|
||||||
|
import net.minestom.server.event.entity.EntityDeathEvent;
|
||||||
|
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
|
||||||
|
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
|
||||||
|
import net.minestom.server.event.inventory.InventoryPreClickEvent;
|
||||||
|
import net.minestom.server.event.item.ItemDropEvent;
|
||||||
|
import net.minestom.server.event.item.PlayerBeginItemUseEvent;
|
||||||
|
import net.minestom.server.event.player.*;
|
||||||
import net.minestom.server.instance.InstanceContainer;
|
import net.minestom.server.instance.InstanceContainer;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import net.minestom.server.item.ItemAnimation;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.item.Material;
|
import net.minestom.server.item.Material;
|
||||||
import net.minestom.server.timer.TaskSchedule;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TowerdefenseRoom extends InstanceContainer {
|
public class TowerdefenseRoom extends InstanceContainer {
|
||||||
|
private final static int reach = 30;
|
||||||
|
private long lastPlayerAttack = 0;
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private final Towerdefense game;
|
private final Towerdefense game;
|
||||||
|
private final List<EntityCreature> enemies = new ArrayList<>();
|
||||||
|
private final List<Tower> towers = new ArrayList<>();
|
||||||
|
private final Cursor cursor;
|
||||||
|
private int money = 0;
|
||||||
|
private boolean dead = false;
|
||||||
|
|
||||||
public TowerdefenseRoom(Player player, Towerdefense game) {
|
public TowerdefenseRoom(Player player, Towerdefense game) {
|
||||||
this(UUID.randomUUID(), player, game);
|
super(UUID.randomUUID(), Dimension.OVERWORLD.key);
|
||||||
}
|
|
||||||
|
|
||||||
// eindeutiger Dimension-Name pro Instanz, siehe MineNetInstance
|
|
||||||
private TowerdefenseRoom(UUID uuid, Player player, Towerdefense game) {
|
|
||||||
super(uuid, Dimension.OVERWORLD.key, net.kyori.adventure.key.Key.key("minenet", "instance-" + uuid));
|
|
||||||
MinecraftServer.getInstanceManager().registerInstance(this);
|
MinecraftServer.getInstanceManager().registerInstance(this);
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.player.setGameMode(GameMode.ADVENTURE);
|
this.player.setGameMode(GameMode.ADVENTURE);
|
||||||
this.player.setAllowFlying(true);
|
this.player.setAllowFlying(true);
|
||||||
this.player.getInventory().setItemStack(0, ItemStack.of(Material.ARMOR_STAND));
|
this.addMoney(0);
|
||||||
|
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach);
|
||||||
|
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach);
|
||||||
|
|
||||||
|
this.player.getInventory().addItemStack(
|
||||||
|
ItemStack.of(Material.BOW).withCustomName(Component.text("Schießen", TextColor.color(255, 180, 0)))
|
||||||
|
);
|
||||||
|
this.player.getInventory().addItemStack(
|
||||||
|
ItemStack.of(Material.BARRIER).withCustomName(Component.text("Löschen", TextColor.color(255,0,0)))
|
||||||
|
);
|
||||||
|
this.game.getAvailableTowers().entrySet().stream()
|
||||||
|
.sorted(java.util.Comparator.comparingInt(e -> this.game.getPrices().get(e.getValue())))
|
||||||
|
.forEach(entry -> {
|
||||||
|
int price = this.game.getPrices().get(entry.getValue());
|
||||||
|
this.player.getInventory().addItemStack(ItemStack.of(entry.getKey()).withMaxStackSize(price).withAmount(price));
|
||||||
|
});
|
||||||
|
|
||||||
this.setGenerator(new MazeGenerator());
|
this.setGenerator(new MazeGenerator());
|
||||||
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {
|
BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});
|
||||||
|
|
||||||
|
this.cursor = new Cursor(reach);
|
||||||
|
this.cursor.setInstance(this);
|
||||||
|
this.cursor.setBoundingBox(0,0,0);
|
||||||
|
|
||||||
|
this.eventNode()
|
||||||
|
.addListener(EntityDeathEvent.class, event -> {
|
||||||
|
if(!(event.getEntity() instanceof EntityCreature enemy)) return;
|
||||||
|
this.enemies.remove(enemy);
|
||||||
|
})
|
||||||
|
.addListener(PlayerTickEvent.class, event -> {
|
||||||
|
if(event.getPlayer() != this.player || this.dead) return;
|
||||||
|
this.cursor.updateCursorPosition(this.player);
|
||||||
|
})
|
||||||
|
.addListener(PlayerUseItemEvent.class, event -> this.handleUse(event.getPlayer()))
|
||||||
|
.addListener(PlayerUseItemOnBlockEvent.class, event -> this.handleUse(event.getPlayer()))
|
||||||
|
.addListener(PlayerEntityInteractEvent.class, event -> this.handleUse(event.getPlayer()))
|
||||||
|
.addListener(PlayerHandAnimationEvent.class, event -> this.handleUse(event.getPlayer()))
|
||||||
|
.addListener(ItemDropEvent.class, CommonEventHandles::cancel)
|
||||||
|
.addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel)
|
||||||
|
.addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel)
|
||||||
|
.addListener(PlayerBeginItemUseEvent.class, event -> {
|
||||||
|
if(event.getAnimation().equals(ItemAnimation.BOW)) event.setCancelled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUse(Player eventPlayer) {
|
||||||
|
if(this.game.getSpectators().contains(eventPlayer)) {
|
||||||
|
if(eventPlayer.getItemInMainHand().material() == Material.COMPASS) {
|
||||||
|
this.game.cycleSpectator(eventPlayer);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(eventPlayer != this.player || this.dead) return;
|
||||||
|
this.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useItem() {
|
||||||
|
Material itemInHand = this.player.getItemInMainHand().material();
|
||||||
|
if(itemInHand.equals(Material.BOW)) {
|
||||||
|
this.playerAttack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(itemInHand.equals(Material.BARRIER)) {
|
||||||
|
this.removeTower();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.placeTower();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playerAttack() {
|
||||||
|
if(this.lastPlayerAttack > System.currentTimeMillis()-200) return;
|
||||||
|
Player p = this.player;
|
||||||
|
this.lastPlayerAttack = System.currentTimeMillis();
|
||||||
|
EntityProjectile projectile = new EntityProjectile(p, EntityType.SPECTRAL_ARROW);
|
||||||
|
projectile.setNoGravity(true);
|
||||||
|
projectile.setAerodynamics(new Aerodynamics(0, 0.99, 0.99));
|
||||||
|
projectile.setView(p.getPosition().yaw(), p.getPosition().pitch());
|
||||||
|
|
||||||
|
Pos spawn = p.getPosition().add(0, p.getEyeHeight() - 0.2, 0);
|
||||||
|
projectile.scheduleRemove(Duration.ofSeconds(5));
|
||||||
|
projectile.eventNode()
|
||||||
|
.addListener(ProjectileCollideWithEntityEvent.class, hitEvent -> {
|
||||||
|
if(hitEvent.getTarget() == projectile.getShooter()) return;
|
||||||
|
try {
|
||||||
|
if(hitEvent.getTarget() instanceof EntityCreature target
|
||||||
|
&& !target.isDead()
|
||||||
|
&& this.enemies.contains(target)) {
|
||||||
|
target.damage(DamageType.PLAYER_ATTACK, 1.2f);
|
||||||
|
if(target.isDead()) this.addMoney(Math.max(1, (int) target.getAttribute(Attribute.MAX_HEALTH).getBaseValue() / 3));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
projectile.remove();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove());
|
||||||
|
projectile.setInstance(this, spawn).thenRun(() -> {
|
||||||
|
Vec projectileVelocity = p.getPosition().direction().normalize().mul(60);
|
||||||
|
projectile.setVelocity(projectileVelocity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startWave(List<EntityType> entities) {
|
public void addMoney(int amount) {
|
||||||
int counter = 0;
|
this.money += amount;
|
||||||
for(EntityType entityType : entities) {
|
this.player.setLevel(this.money);
|
||||||
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
|
||||||
this.addEntity(new EntityCreature(entityType));
|
|
||||||
return TaskSchedule.stop();
|
|
||||||
}, TaskSchedule.millis(800L * counter));
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEntity(EntityCreature entity) {
|
public Material getPlayerHandMaterial() {
|
||||||
entity.setInstance(this, this.game.getMazePath().getFirst());
|
return this.player.getItemInMainHand().material();
|
||||||
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.15);
|
|
||||||
entity.getNavigator().setPathTo(this.game.getMazePath().get(1), 0.7, () -> this.changeEntityGoal(entity, 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeEntityGoal(EntityCreature entity, int positionIndex) {
|
private synchronized void placeTower() {
|
||||||
if(positionIndex == this.game.getMazePath().size() - 1) {
|
if(!this.canPlaceActiveTower()) {
|
||||||
|
if(this.cursor.isCursorActive() && !this.enoughMoneyForActiveTower()) {
|
||||||
|
this.player.sendActionBar(Component.text("Nicht genug Geld!", TextColor.color(255,0,0)));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entity.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex + 1), 0.7, () -> this.changeEntityGoal(entity, positionIndex + 1));
|
try {
|
||||||
|
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||||
|
Tower tower = this.game.getAvailableTowers().entrySet().stream()
|
||||||
|
.filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow()
|
||||||
|
.getValue()
|
||||||
|
.getConstructor()
|
||||||
|
.newInstance();
|
||||||
|
this.setBlock(this.cursor.getTargetBlockPosition(), Block.BLUE_WOOL);
|
||||||
|
tower.setInstance(this, this.cursor.getPosition());
|
||||||
|
this.towers.add(tower);
|
||||||
|
tower.startShooting();
|
||||||
|
this.addMoney(-this.game.getPrices().get(tower.getClass()));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
this.cursor.updateCursorPosition(this.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeTower() {
|
||||||
|
Tower tower = null;
|
||||||
|
Entity entity = this.player.getLineOfSightEntity(reach, e -> e instanceof Tower);
|
||||||
|
if(entity instanceof Tower t && this.towers.contains(t)) {
|
||||||
|
tower = t;
|
||||||
|
} else if(this.cursor.getTargetBlockPosition() != null) {
|
||||||
|
Pos target = new Pos(this.cursor.getTargetBlockPosition()).add(0.5, 0.5, 0.5);
|
||||||
|
tower = this.towers.stream()
|
||||||
|
.filter(t -> t.getPosition().distanceSquared(target) <= 4)
|
||||||
|
.min(java.util.Comparator.comparingDouble(t -> t.getPosition().distanceSquared(target)))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
if(tower == null) return;
|
||||||
|
|
||||||
|
this.setBlock(tower.getPosition().sub(0, 0.5, 0), Block.BLACK_WOOL);
|
||||||
|
this.addMoney(tower.getSellingPrice(this.game.getPrices().get(tower.getClass())));
|
||||||
|
this.towers.remove(tower);
|
||||||
|
tower.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canPlaceActiveTower() {
|
||||||
|
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||||
|
return this.canPlaceTower(this.game.getAvailableTowers().get(usedMaterial));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) {
|
||||||
|
if(towerClass == null) return false;
|
||||||
|
return this.cursor.isCursorActive() && this.enoughMoney(towerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enoughMoneyForActiveTower() {
|
||||||
|
Material usedMaterial = this.player.getItemInMainHand().material();
|
||||||
|
return this.enoughMoney(this.game.getAvailableTowers().get(usedMaterial));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enoughMoney(@Nullable Class<? extends Tower> towerClass) {
|
||||||
|
if(towerClass == null) return true;
|
||||||
|
return this.money >= this.game.getPrices().get(towerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startWave(List<GroupFactory> enemyGroups) {
|
||||||
|
enemyGroups.forEach(groupFactory -> groupFactory.summonGroup(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEnemy(EntityCreature enemy) {
|
||||||
|
enemy.setInstance(this, this.game.getMazePath().getFirst());
|
||||||
|
this.enemies.add(enemy);
|
||||||
|
this.changeEnemyGoal(enemy, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void changeEnemyGoal(EntityCreature enemy, int positionIndex) {
|
||||||
|
if(positionIndex == this.game.getMazePath().size()-1) {
|
||||||
|
this.enemies.remove(enemy);
|
||||||
|
enemy.remove();
|
||||||
|
if(this.dead) return;
|
||||||
|
float damage = (float) Math.ceil(enemy.getHealth()/10);
|
||||||
|
if(this.player.getHealth() - damage <= 0) {
|
||||||
|
this.handleDeath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.player.damage(DamageType.PLAYER_ATTACK, damage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDeath() {
|
||||||
|
this.dead = true;
|
||||||
|
this.game.removeInstance(this);
|
||||||
|
new ArrayList<>(this.enemies).forEach(Entity::remove);
|
||||||
|
this.enemies.clear();
|
||||||
|
new ArrayList<>(this.towers).forEach(Entity::remove);
|
||||||
|
this.towers.clear();
|
||||||
|
this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue());
|
||||||
|
this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue());
|
||||||
|
this.player.heal();
|
||||||
|
this.game.getScore().insertResult(this.player);
|
||||||
|
if(!this.game.getAliveInstances().isEmpty()) {
|
||||||
|
this.game.beginSpectating(this.player);
|
||||||
|
} else {
|
||||||
|
this.player.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDead() {
|
||||||
|
return this.dead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Towerdefense getGame() {
|
||||||
|
return this.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EntityCreature> getEnemies() {
|
||||||
|
return this.enemies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return this.player;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
|
||||||
|
public record EnemyFactory(EntityType entityType, float health, double speed) {
|
||||||
|
/**
|
||||||
|
* Factory for a tower defense enemy.
|
||||||
|
* @param entityType type of enemy
|
||||||
|
* @param health base health (between 0 and 1024, default 10)
|
||||||
|
* @param speed walk speed (default 0.1)
|
||||||
|
*/
|
||||||
|
public EnemyFactory {
|
||||||
|
if(health > 1024 || health <= 0) throw new IllegalArgumentException("Enemy health has to be between 0 and 1024");
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnemyFactory(EntityType entityType) {
|
||||||
|
this(entityType, 10, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityCreature buildEntity() {
|
||||||
|
EntityCreature entity = new EntityCreature(this.entityType);
|
||||||
|
entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(this.speed);
|
||||||
|
entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(this.health);
|
||||||
|
entity.setHealth(this.health);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStrength() {
|
||||||
|
return (int) Math.floor(this.health * this.speed * 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
|
||||||
|
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
|
|
||||||
|
public record GroupFactory(EnemyFactory enemyFactory, int count, long delay) {
|
||||||
|
public void summonGroup(TowerdefenseRoom instance) {
|
||||||
|
for (int i = 0; i < this.count; i++) {
|
||||||
|
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||||
|
instance.addEnemy(this.enemyFactory.buildEntity());
|
||||||
|
return TaskSchedule.stop();
|
||||||
|
}, TaskSchedule.millis(this.delay*i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies;
|
||||||
|
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.Towerdefense;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class WaveGenerator {
|
||||||
|
final Towerdefense game;
|
||||||
|
int currentLevel;
|
||||||
|
Random random = new Random();
|
||||||
|
private final Map<EnemyFactory, Integer> availableEnemies = Map.of(
|
||||||
|
new EnemyFactory(EntityType.PIG, 2, 0.1), new EnemyFactory(EntityType.PIG, 2, 0.1).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.VILLAGER, 6, 0.1), new EnemyFactory(EntityType.VILLAGER, 6, 0.1).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.SHEEP, 4, 0.2), new EnemyFactory(EntityType.SHEEP, 4, 0.2).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.COW, 10, 0.1), new EnemyFactory(EntityType.COW, 10, 0.1).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.CAMEL, 10, 0.2), new EnemyFactory(EntityType.CAMEL, 10, 0.2).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.ARMADILLO, 20, 0.4), new EnemyFactory(EntityType.ARMADILLO, 20, 0.3).getStrength(),
|
||||||
|
new EnemyFactory(EntityType.CHICKEN, 8, 0.5), new EnemyFactory(EntityType.CHICKEN, 8, 0.6).getStrength()
|
||||||
|
);
|
||||||
|
|
||||||
|
public WaveGenerator(Towerdefense game) {
|
||||||
|
this(game, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaveGenerator(Towerdefense game, int startLevel) {
|
||||||
|
this.game = game;
|
||||||
|
this.currentLevel = startLevel - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MAX_ENEMIES_PER_WAVE = 60;
|
||||||
|
private static final int MAX_ALIVE_PER_ROOM = 150;
|
||||||
|
|
||||||
|
public void startNextWave() {
|
||||||
|
this.currentLevel += 1;
|
||||||
|
if(this.game.getAliveInstances().isEmpty()) return;
|
||||||
|
List<EnemyFactory> enemyList = this.chooseEnemies();
|
||||||
|
if(enemyList.isEmpty()) return;
|
||||||
|
Collections.shuffle(enemyList);
|
||||||
|
|
||||||
|
int averageDelay = Math.min(20000 / enemyList.size(), 1500);
|
||||||
|
AtomicInteger delay = new AtomicInteger(0);
|
||||||
|
for (EnemyFactory enemy : enemyList) {
|
||||||
|
delay.addAndGet(averageDelay);
|
||||||
|
MinecraftServer.getSchedulerManager().scheduleTask(
|
||||||
|
() -> this.spawnEnemy(enemy),
|
||||||
|
TaskSchedule.millis(delay.get() + this.random.nextInt(averageDelay-200, averageDelay+200)),
|
||||||
|
TaskSchedule.stop()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnEnemy(EnemyFactory enemy) {
|
||||||
|
float hpMultiplier = this.getHealthMultiplier();
|
||||||
|
this.game.getAliveInstances().forEach(instance -> {
|
||||||
|
if(instance.isDead()) return;
|
||||||
|
if(instance.getEnemies().size() >= MAX_ALIVE_PER_ROOM) return;
|
||||||
|
try {
|
||||||
|
EntityCreature entity = enemy.buildEntity();
|
||||||
|
if(hpMultiplier > 1f) {
|
||||||
|
float scaledHp = (float) Math.min(1024, entity.getAttribute(net.minestom.server.entity.attribute.Attribute.MAX_HEALTH).getBaseValue() * hpMultiplier);
|
||||||
|
entity.getAttribute(net.minestom.server.entity.attribute.Attribute.MAX_HEALTH).setBaseValue(scaledHp);
|
||||||
|
entity.setHealth(scaledHp);
|
||||||
|
}
|
||||||
|
instance.addEnemy(entity);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getHealthMultiplier() {
|
||||||
|
// Levels 1-3: unchanged. Then +12% HP per level, capped so tower
|
||||||
|
// damage still matters at very high levels.
|
||||||
|
return Math.min(5f, 1f + Math.max(0, this.currentLevel - 3) * 0.12f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentLevelStrength() {
|
||||||
|
// Linear scaling (was 0.5 * 2^level — exponential blow-up).
|
||||||
|
int alive = Math.max(1, this.game.getAliveInstances().size());
|
||||||
|
return (int) (15 + this.currentLevel * 6 * Math.sqrt(alive));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EnemyFactory> chooseEnemies() {
|
||||||
|
int strength = this.getCurrentLevelStrength();
|
||||||
|
final List<EnemyFactory> usedEnemies = new ArrayList<>();
|
||||||
|
|
||||||
|
while(strength > 0 && usedEnemies.size() < MAX_ENEMIES_PER_WAVE) {
|
||||||
|
int finalStrength = strength;
|
||||||
|
Optional<Map.Entry<EnemyFactory, Integer>> chosenEnemy = this.availableEnemies.entrySet().stream()
|
||||||
|
.filter(e -> e.getValue() <= finalStrength)
|
||||||
|
.sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2))
|
||||||
|
.findAny();
|
||||||
|
if(chosenEnemy.isEmpty()) break;
|
||||||
|
usedEnemies.add(chosenEnemy.get().getKey());
|
||||||
|
strength -= chosenEnemy.get().getKey().getStrength();
|
||||||
|
}
|
||||||
|
|
||||||
|
return usedEnemies;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
|
||||||
|
public class BlazeTower extends ShootingTower {
|
||||||
|
public BlazeTower() {
|
||||||
|
super(EntityType.BLAZE, 2, 2, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.swingMainHand();
|
||||||
|
this.shootProjectile(enemy, EntityType.FIREBALL, 2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
|
||||||
|
public class BruteTower extends Tower {
|
||||||
|
public BruteTower() {
|
||||||
|
super(EntityType.PIGLIN_BRUTE, 20, 2, 4);
|
||||||
|
this.setItemInMainHand(ItemStack.of(Material.GOLDEN_AXE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.swingMainHand();
|
||||||
|
this.causeDamage(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
|
||||||
|
public class EnderDragonTower extends ShootingTower{
|
||||||
|
public EnderDragonTower() {
|
||||||
|
super(EntityType.ENDER_DRAGON, 20, 3, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.shootProjectile(enemy, EntityType.DRAGON_FIREBALL, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProjectileHit(EntityCreature enemy) {
|
||||||
|
super.onProjectileHit(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
|
||||||
|
public class EndermanTower extends Tower {
|
||||||
|
public EndermanTower() {
|
||||||
|
super(EntityType.ENDERMAN, 12, 1, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.swingMainHand();
|
||||||
|
this.causeDamage(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.collision.Aerodynamics;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityProjectile;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
|
||||||
|
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public abstract class ShootingTower extends Tower {
|
||||||
|
public ShootingTower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
|
||||||
|
super(entityType, damage, attacksPerSecond, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void shootProjectile(EntityCreature enemy, EntityType projectileType, double power, double spread) {
|
||||||
|
EntityProjectile projectile = new EntityProjectile(this, projectileType);
|
||||||
|
projectile.setView(this.getPosition().yaw(), this.getPosition().pitch());
|
||||||
|
projectile.setNoGravity(true);
|
||||||
|
projectile.setAerodynamics(new Aerodynamics(0, 1, 1));
|
||||||
|
Pos startingPoint = this.getPosition().add(0, this.getEyeHeight(), 0);
|
||||||
|
|
||||||
|
double enemySpeed = enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() / 0.05;
|
||||||
|
Point enemyGoal = enemy.getNavigator().getGoalPosition();
|
||||||
|
if(enemyGoal == null) enemyGoal = enemy.getPosition();
|
||||||
|
Pos enemyPosition = enemy.getPosition();
|
||||||
|
Vec enemyMovement = Vec.fromPoint(enemyGoal.sub(enemyPosition)).normalize().mul(enemySpeed);
|
||||||
|
|
||||||
|
// just an approximation, not calculated correctly:
|
||||||
|
double projectileSpeed = 20 * power;
|
||||||
|
double enemyTowerDistance = startingPoint.distance(enemyPosition);
|
||||||
|
double estimatedFlightTime = (enemyTowerDistance / projectileSpeed);
|
||||||
|
Pos targetPosition = enemyPosition.add(enemyMovement.mul(estimatedFlightTime)).withY(enemyPosition.y()+enemy.getEyeHeight());
|
||||||
|
|
||||||
|
projectile.shoot(targetPosition, power, spread);
|
||||||
|
projectile.scheduleRemove(Duration.ofSeconds(5));
|
||||||
|
projectile.setInstance(this.getInstance(), startingPoint);
|
||||||
|
projectile.eventNode()
|
||||||
|
.addListener(ProjectileCollideWithEntityEvent.class, event -> {
|
||||||
|
if(event.getTarget() == projectile.getShooter()) return;
|
||||||
|
try {
|
||||||
|
if(event.getTarget() instanceof EntityCreature target
|
||||||
|
&& !target.isDead()
|
||||||
|
&& this.getRoomInstance().getEnemies().contains(target)) {
|
||||||
|
this.causeDamage(target);
|
||||||
|
this.onProjectileHit(target);
|
||||||
|
if(target.isDead()) target.setFlyingWithElytra(true);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
projectile.remove();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addListener(ProjectileCollideWithBlockEvent.class, event -> projectile.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onProjectileHit(EntityCreature enemy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
|
||||||
|
public class SkeletonTower extends ShootingTower {
|
||||||
|
public SkeletonTower() {
|
||||||
|
super(EntityType.SKELETON, 3, 2.5, 12);
|
||||||
|
this.setItemInMainHand(ItemStack.of(Material.BOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.swingMainHand();
|
||||||
|
this.shootProjectile(enemy, EntityType.ARROW, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProjectileHit(EntityCreature enemy) {
|
||||||
|
super.onProjectileHit(enemy);
|
||||||
|
// enemy.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(enemy.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue()*0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.entity.*;
|
||||||
|
import net.minestom.server.entity.attribute.Attribute;
|
||||||
|
import net.minestom.server.entity.damage.DamageType;
|
||||||
|
import net.minestom.server.timer.Task;
|
||||||
|
import net.minestom.server.timer.TaskSchedule;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public abstract class Tower extends EntityCreature {
|
||||||
|
public enum Priority {
|
||||||
|
FIRST(Type.PATH_DISTANCE),
|
||||||
|
LAST(Type.PATH_DISTANCE),
|
||||||
|
STRONG(Type.HEALTH),
|
||||||
|
WEAK(Type.HEALTH),
|
||||||
|
CLOSE(Type.TOWER_DISTANCE),
|
||||||
|
FAR(Type.TOWER_DISTANCE);
|
||||||
|
|
||||||
|
final Type type;
|
||||||
|
Priority(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
PATH_DISTANCE,
|
||||||
|
HEALTH,
|
||||||
|
TOWER_DISTANCE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static int damageDivider = 10;
|
||||||
|
private Priority priority = Priority.FIRST;
|
||||||
|
protected float damage;
|
||||||
|
protected int range;
|
||||||
|
protected double attacksPerSecond;
|
||||||
|
protected TaskSchedule attackDelay;
|
||||||
|
private @Nullable Task attackTask;
|
||||||
|
private float sellingPriceMultiplier = 1;
|
||||||
|
|
||||||
|
public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
|
||||||
|
super(entityType);
|
||||||
|
this.damage = (float) damage / damageDivider;
|
||||||
|
this.range = range;
|
||||||
|
this.attacksPerSecond = attacksPerSecond;
|
||||||
|
this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startShooting() {
|
||||||
|
this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
|
||||||
|
EntityCreature nextEnemy = this.getNextEnemy();
|
||||||
|
if(nextEnemy == null) return this.attackDelay;
|
||||||
|
this.lookAt(nextEnemy);
|
||||||
|
this.attack(nextEnemy);
|
||||||
|
return this.attackDelay;
|
||||||
|
}, TaskSchedule.immediate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void remove(boolean permanent) {
|
||||||
|
if(this.attackTask != null) this.attackTask.cancel();
|
||||||
|
super.remove(permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable EntityCreature getNextEnemy() {
|
||||||
|
List<EntityCreature> enemies = this.getSortedEnemies();
|
||||||
|
if(enemies.isEmpty()) return null;
|
||||||
|
|
||||||
|
return switch (this.priority) {
|
||||||
|
case LAST, STRONG, FAR -> enemies.getLast();
|
||||||
|
case FIRST, WEAK, CLOSE -> enemies.getFirst();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EntityCreature> getSortedEnemies() {
|
||||||
|
Stream<EntityCreature> enemyStream = this.getRoomInstance().getEnemies().stream().parallel()
|
||||||
|
.filter(enemy -> !enemy.isDead())
|
||||||
|
.filter(enemy -> enemy.getPosition().distance(this.getPosition()) <= this.range);
|
||||||
|
return switch (this.priority.getType()) {
|
||||||
|
case PATH_DISTANCE -> {
|
||||||
|
List<Pos> path = this.getRoomInstance().getGame().getMazePath();
|
||||||
|
yield enemyStream
|
||||||
|
.sorted((o1, o2) -> Integer.compare(
|
||||||
|
pathProgress(o1, path),
|
||||||
|
pathProgress(o2, path)
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
case TOWER_DISTANCE -> enemyStream
|
||||||
|
.sorted((o1, o2) -> Double.compare(
|
||||||
|
o1.getPosition().distance(this.getPosition()),
|
||||||
|
o2.getPosition().distance(this.getPosition())
|
||||||
|
)).toList();
|
||||||
|
case HEALTH -> enemyStream
|
||||||
|
.sorted((o1, o2) -> Float.compare(o1.getHealth(), o2.getHealth()))
|
||||||
|
.toList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int pathProgress(EntityCreature enemy, List<Pos> path) {
|
||||||
|
// Highest index == furthest along the path. Returned as negative so a
|
||||||
|
// natural ascending sort puts the most-advanced enemy first.
|
||||||
|
double bestDist = Double.MAX_VALUE;
|
||||||
|
int bestIdx = 0;
|
||||||
|
for(int i = 0; i < path.size(); i++) {
|
||||||
|
double d = enemy.getPosition().distanceSquared(path.get(i));
|
||||||
|
if(d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
bestIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -bestIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TowerdefenseRoom getRoomInstance() {
|
||||||
|
return (TowerdefenseRoom) this.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSellingPrice(int buyPrice) {
|
||||||
|
return (int) (this.sellingPriceMultiplier * buyPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue() {
|
||||||
|
return this.damage * this.attacksPerSecond * this.range * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRange() {
|
||||||
|
return this.range;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void causeDamage(EntityCreature enemy) {
|
||||||
|
this.causeDamage(enemy, this.damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void causeDamage(EntityCreature enemy, float amount) {
|
||||||
|
enemy.damage(DamageType.PLAYER_ATTACK, amount);
|
||||||
|
if(enemy.isDead()) this.getRoomInstance().addMoney(Math.max(1, (int) enemy.getAttribute(Attribute.MAX_HEALTH).getBaseValue() / 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void attack(EntityCreature enemy);
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
|
||||||
|
public class WitherTower extends ShootingTower {
|
||||||
|
public WitherTower() {
|
||||||
|
super(EntityType.WITHER, 18, 4, 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.shootProjectile(enemy, EntityType.WITHER_SKULL, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProjectileHit(EntityCreature enemy) {
|
||||||
|
super.onProjectileHit(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
|
||||||
|
public class ZombieTower extends Tower {
|
||||||
|
public ZombieTower() {
|
||||||
|
super(EntityType.ZOMBIE, 7, 1, 4);
|
||||||
|
this.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attack(EntityCreature enemy) {
|
||||||
|
this.swingMainHand();
|
||||||
|
this.causeDamage(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,4 +20,5 @@ api:
|
|||||||
|
|
||||||
admins:
|
admins:
|
||||||
- minetec
|
- minetec
|
||||||
- 28Pupsi28
|
- 28Pupsi28
|
||||||
|
- jannis_mg
|
||||||
Reference in new issue
Block a user