3 Commits

Author SHA1 Message Date
Melledy
aecea6ab03 Implement !build command for creating records 2025-12-01 13:33:34 -08:00
Melledy
e8e7df7d50 Fix incorrect element type for wind/aqua 2025-12-01 13:21:42 -08:00
Melledy
9188d3b53a Improve command arg handling 2025-12-01 13:16:24 -08:00
6 changed files with 262 additions and 22 deletions

View File

@@ -7,8 +7,8 @@ import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.player.Player;
import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import lombok.Getter;
@@ -53,6 +53,9 @@ public class CommandArgs {
} else if (arg.startsWith("lv")) { // Level
this.level = Utils.parseSafeInt(arg.substring(2));
it.remove();
} else if (arg.startsWith("lvl")) { // Level
this.level = Utils.parseSafeInt(arg.substring(3));
it.remove();
} else if (arg.startsWith("a")) { // Advance
this.advance = Utils.parseSafeInt(arg.substring(1));
it.remove();
@@ -76,7 +79,7 @@ public class CommandArgs {
int key = Integer.parseInt(split[0]);
int value = Integer.parseInt(split[1]);
if (this.map == null) this.map = new Int2IntOpenHashMap();
if (this.map == null) this.map = new Int2IntLinkedOpenHashMap();
this.map.put(key, value);
it.remove();

View File

@@ -0,0 +1,179 @@
package emu.nebula.command.commands;
import java.util.ArrayList;
import java.util.List;
import emu.nebula.command.Command;
import emu.nebula.command.CommandArgs;
import emu.nebula.command.CommandHandler;
import emu.nebula.data.GameData;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.player.Player;
import emu.nebula.game.tower.StarTowerBuild;
import emu.nebula.net.NetMsgId;
import emu.nebula.util.Utils;
import lombok.Getter;
@Command(
label = "build",
aliases = {"b", "record", "r"},
permission = "player.build",
requireTarget = true,
desc = "!build [char ids...] [disc ids...] [potential ids...] [melody ids...]"
)
public class BuildCommand implements CommandHandler {
@Override
public String execute(CommandArgs args) {
// Create record
var target = args.getTarget();
var builder = new StarTowerBuildData(target);
// Parse items
for (String arg : args.getList()) {
int id = Utils.parseSafeInt(arg);
int count = 1;
this.parseItem(builder, id, count);
}
if (args.getMap() != null) {
for (var entry : args.getMap().int2IntEntrySet()) {
int id = entry.getIntKey();
int count = entry.getIntValue();
this.parseItem(builder, id, count);
}
}
// Check if build is valid
if (builder.getCharacters().size() != 3) {
return "Record must have 3 different characters";
}
if (builder.getDiscs().size() < 3 || builder.getDiscs().size() > 6) {
return "Record must have 3-6 different discs";
}
// Create record
var build = builder.toBuild();
// Add to star tower manager
target.getStarTowerManager().getBuilds().put(build.getUid(), build);
// Send package to player
target.addNextPackage(NetMsgId.st_import_build_notify, build.toProto());
// Send result to player
return "Created record for " + target.getName() + " (This command make take time to update on the client)";
}
private void parseItem(StarTowerBuildData builder, int id, int count) {
// Get item data
var itemData = GameData.getItemDataTable().get(id);
if (itemData == null) {
return;
}
// Clamp
count = Math.max(count, 1);
// Parse by item id
switch (itemData.getItemSubType()) {
case Char -> {
var character = builder.getPlayer().getCharacters().getCharacterById(id);
if (character == null || !character.getData().isAvailable()) {
break;
}
builder.addCharacter(character);
}
case Disc -> {
var disc = builder.getPlayer().getCharacters().getDiscById(id);
if (disc == null || !disc.getData().isAvailable()) {
break;
}
builder.addDisc(disc);
}
case Potential, SpecificPotential -> {
var potentialData = GameData.getPotentialDataTable().get(id);
if (potentialData == null) break;
int level = Math.min(count, potentialData.getMaxLevel());
builder.getBuild().getPotentials().add(id, level);
}
case SubNoteSkill -> {
builder.getBuild().getSubNoteSkills().add(id, count);
}
default -> {
// Ignored
}
}
}
@Getter
private static class StarTowerBuildData {
private Player player;
private StarTowerBuild build;
private List<GameCharacter> characters;
private List<GameDisc> discs;
public StarTowerBuildData(Player player) {
this.player = player;
this.build = new StarTowerBuild(player);
this.characters = new ArrayList<>();
this.discs = new ArrayList<>();
}
public void addCharacter(GameCharacter character) {
if (this.characters.contains(character)) {
return;
}
this.characters.add(character);
}
public void addDisc(GameDisc disc) {
if (this.discs.contains(disc)) {
return;
}
this.discs.add(disc);
}
public StarTowerBuild toBuild() {
// Set characters and discs
build.setChars(this.getCharacters());
build.setDiscs(this.getDiscs());
// Clear character potential cache
build.getCharPots().clear();
for (int charId : build.getCharIds()) {
build.getCharPots().put(charId, 0);
}
// Add potentials to character potential cache
var it = build.getPotentials().iterator();
while (it.hasNext()) {
var potential = it.next();
var data = GameData.getPotentialDataTable().get(potential.getIntKey());
if (data == null || !build.getCharPots().containsKey(data.getCharId())) {
it.remove();
continue;
}
build.getCharPots().add(data.getCharId(), 1);
}
// Calculate score
build.calculateScore();
// Return build
return build;
}
}
}

View File

@@ -13,6 +13,8 @@ public class PotentialDef extends BaseDef {
private int MaxLevel;
private int[] BuildScore;
private String BriefDesc;
@Override
public int getId() {
return Id;

View File

@@ -7,10 +7,10 @@ import lombok.Getter;
@Getter
public enum ElementType {
INHERIT (0),
WIND (1, 90020),
AQUA (1, 90018),
FIRE (2, 90019),
EARTH (3, 90021),
AQUA (4, 90018),
WIND (4, 90020),
LIGHT (5, 90022),
DARK (6, 90023),
NONE (7);

View File

@@ -1,12 +1,17 @@
package emu.nebula.game.tower;
import java.util.List;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.proto.Public.ItemTpl;
import emu.nebula.proto.PublicStarTower.BuildPotential;
import emu.nebula.proto.PublicStarTower.StarTowerBuildBrief;
@@ -42,18 +47,23 @@ public class StarTowerBuild implements GameDatabaseObject {
}
public StarTowerBuild(StarTowerGame game) {
public StarTowerBuild(Player player) {
this.uid = Snowflake.newUid();
this.playerUid = game.getPlayer().getUid();
this.playerUid = player.getUid();
this.name = "";
this.charPots = new ItemParamMap();
this.potentials = new ItemParamMap();
this.subNoteSkills = new ItemParamMap();
}
public StarTowerBuild(StarTowerGame game) {
// Initialize basic variables
this(game.getPlayer());
// Characters
this.charIds = game.getChars().stream()
.filter(d -> d.getId() > 0)
.mapToInt(d -> d.getId())
.filter(c -> c.getId() > 0)
.mapToInt(c -> c.getId())
.toArray();
// Discs
@@ -85,7 +95,19 @@ public class StarTowerBuild implements GameDatabaseObject {
}
// Caclulate record score and cache it
this.score = this.calculateScore();
this.calculateScore();
}
public void setChars(List<GameCharacter> characters) {
this.charIds = characters.stream()
.mapToInt(c -> c.getCharId())
.toArray();
}
public void setDiscs(List<GameDisc> discs) {
this.discIds = discs.stream()
.mapToInt(d -> d.getDiscId())
.toArray();
}
public void setName(String newName) {
@@ -109,9 +131,9 @@ public class StarTowerBuild implements GameDatabaseObject {
// Score
private int calculateScore() {
// Init score
int score = 0;
public int calculateScore() {
// Clear score
this.score = 0;
// Potentials
for (var potential : this.getPotentials().int2IntEntrySet()) {
@@ -119,16 +141,16 @@ public class StarTowerBuild implements GameDatabaseObject {
if (data == null) continue;
int index = potential.getIntValue() - 1;
score += data.getBuildScore()[index];
this.score += data.getBuildScore()[index];
}
// Sub note skills
for (var item : this.getSubNoteSkills()) {
score += item.getIntValue() * 15;
this.score += item.getIntValue() * 15;
}
// Complete
return score;
return this.score;
}
// Proto

View File

@@ -17,12 +17,13 @@ import emu.nebula.data.GameData;
import emu.nebula.data.ResourceType;
import emu.nebula.data.resources.CharacterDef;
import emu.nebula.data.resources.ItemDef;
import emu.nebula.data.resources.PotentialDef;
import emu.nebula.game.inventory.ItemType;
public class Handbook {
public static void generate() {
// Temp vars
Map<String, String> languageKey = null;
List<Integer> list = null;
// Save to file
@@ -41,31 +42,64 @@ public class Handbook {
writer.println(System.lineSeparator());
writer.println("# Characters");
list = GameData.getCharacterDataTable().keySet().intStream().sorted().boxed().toList();
languageKey = loadLanguageKey(CharacterDef.class);
var characterLanguageKey = loadLanguageKey(CharacterDef.class);
for (int id : list) {
CharacterDef data = GameData.getCharacterDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
writer.print(languageKey.getOrDefault(data.getName(), data.getName()));
writer.print(characterLanguageKey.getOrDefault(data.getName(), data.getName()));
writer.print(" (");
writer.print(data.getElementType().toString());
writer.println(")");
}
// Dump characters
// Dump items
writer.println(System.lineSeparator());
writer.println("# Items");
list = GameData.getItemDataTable().keySet().intStream().sorted().boxed().toList();
languageKey = loadLanguageKey(ItemDef.class);
var itemLanguageKey = loadLanguageKey(ItemDef.class);
for (int id : list) {
ItemDef data = GameData.getItemDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
writer.print(languageKey.getOrDefault(data.getTitle(), data.getTitle()));
writer.print(itemLanguageKey.getOrDefault(data.getTitle(), data.getTitle()));
writer.print(" [");
writer.print(data.getItemType());
writer.println("]");
writer.print("]");
if (data.getItemType() == ItemType.Disc) {
var discData = GameData.getDiscDataTable().get(id);
if (discData != null) {
writer.print(" (");
writer.print(discData.getElementType().toString());
writer.print(")");
}
}
writer.println("");
}
// Dump potentials
writer.println(System.lineSeparator());
writer.println("# Potentials");
list = GameData.getPotentialDataTable().keySet().intStream().sorted().boxed().toList();
var potentialLanguageKey = loadLanguageKey(PotentialDef.class);
for (int id : list) {
PotentialDef data = GameData.getPotentialDataTable().get(id);
writer.print(data.getId());
writer.print(" : ");
CharacterDef charData = GameData.getCharacterDataTable().get(data.getCharId());
writer.print("[");
writer.print(characterLanguageKey.getOrDefault(charData.getName(), charData.getName()));
writer.print("] ");
ItemDef itemData = GameData.getItemDataTable().get(id);
writer.print(itemLanguageKey.getOrDefault(itemData.getTitle(), itemData.getTitle()));
writer.print(" - ");
writer.print(potentialLanguageKey.getOrDefault(data.getBriefDesc(), data.getBriefDesc()));
writer.println("");
}
} catch (IOException e) {
e.printStackTrace();