Skip to content

Commit

Permalink
feat: add towerpreset command
Browse files Browse the repository at this point in the history
  • Loading branch information
ZakShearman committed Sep 29, 2024
1 parent ab3a866 commit 26e3f5d
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package pink.zak.minestom.towerdefence.command;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonWriter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.ArgumentLiteral;
import net.minestom.server.command.builder.arguments.ArgumentWord;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pink.zak.minestom.towerdefence.game.GameHandler;
import pink.zak.minestom.towerdefence.model.preset.TowerPreset;
import pink.zak.minestom.towerdefence.model.tower.TowerManager;
import pink.zak.minestom.towerdefence.model.tower.placed.PlacedTower;
import pink.zak.minestom.towerdefence.model.user.GameUser;
import pink.zak.minestom.towerdefence.storage.TowerStorage;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

public class TowerPresetCommand extends Command {
private static final Logger LOGGER = LoggerFactory.getLogger(TowerPresetCommand.class);
private static final Gson GSON = new Gson();

private final @NotNull GameHandler gameHandler;
private final @NotNull TowerManager towerManager;
private final @NotNull TowerStorage towerStorage;

public TowerPresetCommand(@NotNull GameHandler gameHandler, @NotNull TowerStorage towerStorage) {
super("towerpreset");
this.gameHandler = gameHandler;
this.towerManager = gameHandler.getTowerManager();
this.towerStorage = towerStorage;

ArgumentLiteral saveArg = new ArgumentLiteral("save");
ArgumentLiteral loadArg = new ArgumentLiteral("load");
ArgumentWord presetIdArg = new ArgumentWord("presetId");

this.addSyntax(this::executeSave, saveArg, presetIdArg);
this.addSyntax(this::executeLoad, loadArg, presetIdArg);
}

private void executeSave(CommandSender sender, CommandContext context) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You must be a player to use this command.");
return;
}

String fileName = context.get("presetId") + ".json";
Path path = Path.of("towerPresets", fileName);
if (Files.exists(path)) {
sender.sendMessage("Overwriting existing tower preset...");
} else {
try {
Files.createDirectories(path.getParent());
Files.createFile(path);
} catch (Exception e) {
LOGGER.error("Failed to create tower preset file", e);
sender.sendMessage("Failed to create tower preset file.");
return;
}
}

GameUser user = this.gameHandler.getGameUser(player);
Set<PlacedTower<?>> towers = this.gameHandler.getTowerManager().getTowers(user.getTeam());
TowerPreset preset = new TowerPreset(towers);

// Save the tower preset
try (JsonWriter writer = GSON.newJsonWriter(Files.newBufferedWriter(path))) {
GSON.toJson(preset.toJson(), writer);
sender.sendMessage("Tower preset saved.");
} catch (Exception e) {
LOGGER.error("Failed to save tower preset", e);
sender.sendMessage("Failed to save tower preset.");
}
}

private void executeLoad(CommandSender sender, CommandContext context) {
if (!(sender instanceof Player player)) {
sender.sendMessage("You must be a player to use this command.");
return;
}

String presetName = context.get("presetId");
String fileName = presetName + ".json";
Path path = Path.of("towerPresets", fileName);
if (!Files.exists(path)) {
sender.sendMessage("Tower preset does not exist.");
return;
}

JsonArray json;
try {
json = GSON.fromJson(Files.newBufferedReader(path), JsonArray.class);
} catch (Exception e) {
LOGGER.error("Failed to load tower preset", e);
sender.sendMessage("Failed to load tower preset.");
return;
}

TowerPreset preset = new TowerPreset(json);
GameUser user = this.gameHandler.getGameUser(player);

this.towerManager.removeAllTowers(user.getTeam());
preset.placeTowers(this.towerStorage, this.towerManager, user);
sender.sendMessage(Component.text("Loaded preset %s with %s towers.".formatted(presetName, preset.getTowers().size()), NamedTextColor.GREEN));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import pink.zak.minestom.towerdefence.actionbar.ActionBarHandler;
import pink.zak.minestom.towerdefence.agones.GameStateManager;
import pink.zak.minestom.towerdefence.api.event.game.CastleDamageEvent;
import pink.zak.minestom.towerdefence.command.TowerPresetCommand;
import pink.zak.minestom.towerdefence.enums.GameState;
import pink.zak.minestom.towerdefence.enums.Team;
import pink.zak.minestom.towerdefence.game.handlers.NecromancerDamageListener;
Expand Down Expand Up @@ -132,6 +133,9 @@ public void start() {
.schedule();
}

MinecraftServer.getCommandManager().register(new TowerPresetCommand(this, this.module.getTowerStorage()));
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> player.refreshCommands());

/*EnemyMob enemyMob = this.plugin.getMobStorage().getEnemyMob(EntityType.LLAMA);
EnemyMobLevel enemyMobLevel = enemyMob.level(1);
QueuedEnemyMob queuedEnemyMob = new QueuedEnemyMob(enemyMob,enemyMobLevel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package pink.zak.minestom.towerdefence.model.preset;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minestom.server.coordinate.Vec;
import org.apache.commons.collections4.set.UnmodifiableSet;
import org.jetbrains.annotations.NotNull;
import pink.zak.minestom.towerdefence.enums.TowerType;
import pink.zak.minestom.towerdefence.model.tower.TowerManager;
import pink.zak.minestom.towerdefence.model.tower.TowerPlaceResult;
import pink.zak.minestom.towerdefence.model.tower.config.TowerLevel;
import pink.zak.minestom.towerdefence.model.tower.placed.PlacedTower;
import pink.zak.minestom.towerdefence.model.user.GameUser;
import pink.zak.minestom.towerdefence.storage.TowerStorage;

import java.util.HashSet;
import java.util.Set;

public class TowerPreset {
private final Set<@NotNull Tower> towers;

public TowerPreset(@NotNull JsonArray json) {
Set<Tower> towers = new HashSet<>();
for (int i = 0; i < json.size(); i++) {
JsonObject jsonObject = json.get(i).getAsJsonObject();
towers.add(new Tower(jsonObject));
}

this.towers = UnmodifiableSet.unmodifiableSet(towers);
}

public TowerPreset(@NotNull Set<PlacedTower<? extends TowerLevel>> placedTowers) {
Set<Tower> towers = new HashSet<>();
for (PlacedTower<? extends TowerLevel> placedTower : placedTowers) {
towers.add(new Tower(placedTower));
}

this.towers = UnmodifiableSet.unmodifiableSet(towers);
}

public void placeTowers(@NotNull TowerStorage storage, @NotNull TowerManager manager, @NotNull GameUser user) {
for (Tower tower : this.towers) {
tower.place(storage, manager, user);
}
}

public Set<Tower> getTowers() {
return this.towers;
}

public @NotNull JsonArray toJson() {
JsonArray jsonArray = new JsonArray();
for (Tower tower : this.towers) {
jsonArray.add(tower.toJson());
}

return jsonArray;
}

public static class Tower {
private final @NotNull TowerType towerType;
private final int level;
private final @NotNull Vec position;

public Tower(@NotNull JsonObject jsonObject) {
this.towerType = TowerType.valueOf(jsonObject.get("type").getAsString());
this.level = jsonObject.get("level").getAsInt();
this.position = new Vec(jsonObject.get("x").getAsDouble(), jsonObject.get("y").getAsDouble(), jsonObject.get("z").getAsDouble());
}

public Tower(@NotNull TowerType towerType, int level, @NotNull Vec position) {
this.towerType = towerType;
this.level = level;
this.position = position;
}

public Tower(@NotNull PlacedTower<? extends TowerLevel> placedTower) {
this(
placedTower.getConfiguration().getType(),
placedTower.getLevel().asInteger(),
Vec.fromPoint(placedTower.getBasePoint())
);
}

public void place(@NotNull TowerStorage storage, @NotNull TowerManager manager, @NotNull GameUser user) {
TowerPlaceResult result = manager.placeTower(storage.getTower(this.towerType), this.position, user, true);

if (!result.isSuccessful()) {
throw new IllegalStateException("Failed to place tower: " + result.failureReason());
}

if (this.level > 1) {
result.tower().upgrade(this.level, null);
}
}

public @NotNull JsonObject toJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("type", this.towerType.name());
jsonObject.addProperty("level", this.level);
jsonObject.addProperty("x", this.position.x());
jsonObject.addProperty("y", this.position.y());
jsonObject.addProperty("z", this.position.z());
return jsonObject;
}

public @NotNull TowerType getTowerType() {
return this.towerType;
}

public int getLevel() {
return this.level;
}

public @NotNull Vec getPosition() {
return this.position;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import pink.zak.minestom.towerdefence.model.tower.config.TowerLevel;
import pink.zak.minestom.towerdefence.model.tower.placed.PlacedTower;
import pink.zak.minestom.towerdefence.model.user.GameUser;
import pink.zak.minestom.towerdefence.utils.Result;
import pink.zak.minestom.towerdefence.world.TowerDefenceInstance;

import java.util.Collection;
Expand All @@ -40,18 +39,22 @@ public TowerManager(@NotNull TowerDefenceInstance instance, @NotNull GameHandler
this.gameHandler = gameHandler;
}

public @NotNull Result<TowerPlaceFailureReason> placeTower(@NotNull Tower tower, @NotNull Point position, @NotNull GameUser user) {
public @NotNull TowerPlaceResult placeTower(@NotNull Tower tower, @NotNull Point position, @NotNull GameUser user) {
return this.placeTower(tower, position, user, false);
}

public @NotNull TowerPlaceResult placeTower(@NotNull Tower tower, @NotNull Point position, @NotNull GameUser user, boolean ignoreCost) {
// check if the user can afford the tower
TowerLevel level = tower.getLevel(1);
int cost = level.getCost();
// Audiences.all().sendMessage(Component.text("%s tried to place tower %s at cost %s. Has %s coins".formatted(user.getPlayer().getUsername(), tower.getName(), cost, user.getCoins())));
if (user.getCoins() < cost) return Result.failure(TowerPlaceFailureReason.CAN_NOT_AFFORD);
if (!ignoreCost && user.getCoins() < cost) return new TowerPlaceResult(TowerPlaceFailureReason.CAN_NOT_AFFORD, null);

// check if the tower is in a valid position
if (!tower.isSpaceClear(this.instance, position)) return Result.failure(TowerPlaceFailureReason.AREA_NOT_CLEAR);
if (!tower.isSpaceClear(this.instance, position)) return new TowerPlaceResult(TowerPlaceFailureReason.AREA_NOT_CLEAR, null);

// place tower
PlacedTower<?> placedTower = PlacedTower.create(
PlacedTower<? extends TowerLevel> placedTower = PlacedTower.create(
this.gameHandler,
tower,
TOWER_ID_COUNTER.getAndIncrement(),
Expand All @@ -60,11 +63,11 @@ public TowerManager(@NotNull TowerDefenceInstance instance, @NotNull GameHandler
this.determineDirection(position, user.getTeam())
);
this.getTowers(user.getTeam()).add(placedTower);
user.updateCoins(current -> current - cost);
if (!ignoreCost) user.updateCoins(current -> current - cost);

MinecraftServer.getGlobalEventHandler().call(new PlayerTowerPlaceEvent(tower, placedTower, user));

return Result.success();
return new TowerPlaceResult(null, placedTower);
}

public void removeTower(@NotNull PlacedTower<?> tower) {
Expand All @@ -74,6 +77,11 @@ public void removeTower(@NotNull PlacedTower<?> tower) {
// todo: tower remove event
}

public void removeAllTowers(Team team) {
this.getTowers(team).forEach(PlacedTower::destroy);
this.getTowers(team).clear();
}

public @Nullable PlacedTower<?> getTower(@NotNull Team team, int id) {
return this.getTowers(team).stream()
.filter(tower -> tower.getId() == id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pink.zak.minestom.towerdefence.model.tower;

import org.jetbrains.annotations.UnknownNullability;
import pink.zak.minestom.towerdefence.model.tower.config.TowerLevel;
import pink.zak.minestom.towerdefence.model.tower.placed.PlacedTower;

public record TowerPlaceResult(@UnknownNullability TowerPlaceFailureReason failureReason,
@UnknownNullability PlacedTower<? extends TowerLevel> tower) {

public boolean isSuccessful() {
return this.failureReason == null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pink.zak.minestom.towerdefence.enums.TowerType;
import pink.zak.minestom.towerdefence.game.GameHandler;
import pink.zak.minestom.towerdefence.model.tower.TowerManager;
import pink.zak.minestom.towerdefence.model.tower.TowerPlaceFailureReason;
import pink.zak.minestom.towerdefence.model.tower.TowerPlaceResult;
import pink.zak.minestom.towerdefence.model.tower.config.Tower;
import pink.zak.minestom.towerdefence.model.tower.placed.PlacedTower;
import pink.zak.minestom.towerdefence.model.user.GameUser;
Expand All @@ -30,7 +30,6 @@
import pink.zak.minestom.towerdefence.ui.spawner.TroopSpawnerUI;
import pink.zak.minestom.towerdefence.ui.tower.TowerManagementUI;
import pink.zak.minestom.towerdefence.ui.tower.TowerPlaceUI;
import pink.zak.minestom.towerdefence.utils.Result;
import pink.zak.minestom.towerdefence.world.TowerDefenceInstance;

import java.util.Set;
Expand Down Expand Up @@ -135,10 +134,10 @@ private void handleItemUse(@NotNull Player p, @NotNull ItemStack usedItem) {
private void handleTowerPlaceClick(@NotNull TowerType towerType, @NotNull Player player, @NotNull GameUser gameUser, @NotNull Point clickedBlock) {
Tower tower = this.towerStorage.getTower(towerType);

Result<TowerPlaceFailureReason> result = (this.towerManager.placeTower(tower, clickedBlock.add(0.5, 0.5, 0.5), gameUser));
if (!(result instanceof Result.Failure<TowerPlaceFailureReason> failure)) return;
TowerPlaceResult result = (this.towerManager.placeTower(tower, clickedBlock.add(0.5, 0.5, 0.5), gameUser));
if (result.isSuccessful()) return;

player.sendMessage(Component.text(switch (failure.reason()) {
player.sendMessage(Component.text(switch (result.failureReason()) {
case CAN_NOT_AFFORD -> "You can not afford this tower.";
case AREA_NOT_CLEAR -> "The area is not clear.";
}, NamedTextColor.RED));
Expand Down

0 comments on commit 26e3f5d

Please sign in to comment.