Add !character command

This commit is contained in:
Melledy
2025-11-17 07:25:18 -08:00
parent 7c95448e4e
commit 334c8dc12e
10 changed files with 206 additions and 33 deletions

View File

@@ -3,6 +3,7 @@ package emu.nebula.command;
import java.util.List; import java.util.List;
import emu.nebula.Nebula; import emu.nebula.Nebula;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.player.Player; import emu.nebula.game.player.Player;
import emu.nebula.util.Utils; import emu.nebula.util.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -21,9 +22,9 @@ public class CommandArgs {
private int targetUid; private int targetUid;
private int amount; private int amount;
private int level = -1; private int level = -1;
private int rank = -1; private int advance = -1;
private int promotion = -1; private int talent = -1;
private int stage = -1; private int skill = -1;
private Int2IntMap map; private Int2IntMap map;
private ObjectSet<String> flags; private ObjectSet<String> flags;
@@ -50,17 +51,14 @@ public class CommandArgs {
} else if (arg.startsWith("lv")) { // Level } else if (arg.startsWith("lv")) { // Level
this.level = Utils.parseSafeInt(arg.substring(2)); this.level = Utils.parseSafeInt(arg.substring(2));
it.remove(); it.remove();
} else if (arg.startsWith("r")) { // Rank } else if (arg.startsWith("a")) { // Advance
this.rank = Utils.parseSafeInt(arg.substring(1)); this.advance = Utils.parseSafeInt(arg.substring(1));
it.remove(); it.remove();
} else if (arg.startsWith("e")) { // Eidolons } else if (arg.startsWith("t")) { // Talents
this.rank = Utils.parseSafeInt(arg.substring(1)); this.talent = Utils.parseSafeInt(arg.substring(1));
it.remove(); it.remove();
} else if (arg.startsWith("p")) { // Promotion } else if (arg.startsWith("s")) { // Skill
this.promotion = Utils.parseSafeInt(arg.substring(1)); this.skill = Utils.parseSafeInt(arg.substring(1));
it.remove();
} else if (arg.startsWith("s")) { // Stage or Superimposition
this.stage = Utils.parseSafeInt(arg.substring(1));
it.remove(); it.remove();
} }
} else if (arg.startsWith("-")) { // Flag } else if (arg.startsWith("-")) { // Flag
@@ -127,4 +125,67 @@ public class CommandArgs {
return this.flags.contains(flag); return this.flags.contains(flag);
} }
// Utility commands
/**
* Changes the properties of an character based on the arguments provided
* @param character The targeted character to change
* @return A boolean of whether or not any changes were made to the character
*/
public boolean setProperties(GameCharacter character) {
boolean hasChanged = false;
// Try to set level
if (this.getLevel() > 0 && character.getLevel() != this.getLevel()) {
character.setLevel(Math.min(this.getLevel(), 90));
character.setAdvance(Utils.getMinAdvanceForLevel(character.getLevel()));
hasChanged = true;
}
// Try to set advance (ascension level)
if (this.getAdvance() >= 0 && character.getAdvance() != this.getAdvance()) {
character.setAdvance(Math.min(this.getAdvance(), 8));
hasChanged = true;
}
// Try to set skill trees
if (this.getSkill() > 0) {
int skill = Math.min(this.getSkill(), 10);
for (int i = 0; i < 4; i++) {
int s = character.getSkills()[i];
if (s != skill) {
character.getSkills()[i] = skill;
hasChanged = true;
}
}
}
// Try to set talents
if (this.getTalent() >= 0) {
// Clear talents first
character.getTalents().clear();
// Calculate how many talent stars we want to set
int talent = Math.min(this.getTalent(), 5);
for (int i = 0; i < talent; i++) {
// Get bitset offset
int offset = i * 16;
// First 10 sub nodes of a talent star
for (int x = 1; x <= 10; x++) {
character.getTalents().setBit(offset + x);
}
// Final sub node of a talent star
character.getTalents().setBit(offset + 16);
}
hasChanged = true;
}
return hasChanged;
}
} }

View File

@@ -0,0 +1,87 @@
package emu.nebula.command.commands;
import emu.nebula.util.Utils;
import emu.nebula.game.character.GameCharacter;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PubilcGm.Chars;
import java.util.ArrayList;
import java.util.HashSet;
import emu.nebula.command.Command;
import emu.nebula.command.CommandArgs;
import emu.nebula.command.CommandHandler;
@Command(
label = "character",
aliases = {"c", "char"},
permission = "player.character",
requireTarget = true,
desc = "!c [all | {characterId}] lv(level) a(ascension) s(skill level) t(talent level)"
)
public class CharacterCommand implements CommandHandler {
@Override
public void execute(CommandArgs args) {
// Init
var player = args.getTarget();
var characters = new HashSet<GameCharacter>();
// Parse args
for (String arg : args.getList()) {
// Lowercase
arg = arg.toLowerCase();
// Handle all characters
if (arg.equals("all")) {
characters.addAll(player.getCharacters().getCharacterCollection());
continue;
}
// Parse char id
int charId = Utils.parseSafeInt(arg);
var character = player.getCharacters().getCharacterById(charId);
if (character == null) {
continue;
}
characters.add(character);
}
// Sanity check
if (characters.isEmpty()) {
return;
}
// List of modified characters that we send to the client for updates
var modified = new ArrayList<GameCharacter>();
// Modify characters
for (var character : characters) {
// Apply changes
boolean changed = args.setProperties(character);
if (changed) {
// Save to database
character.save();
// Add to modified list
modified.add(character);
}
}
if (modified.isEmpty()) {
return;
}
// Encode and send
var proto = Chars.newInstance();
for (var character : modified) {
proto.addList(character.toProto());
}
player.addNextPackage(NetMsgId.chars_final_notify, proto);
}
}

View File

@@ -16,7 +16,7 @@ import us.hebi.quickbuf.RepeatedInt;
@Getter @Getter
@Entity(useDiscriminator = false) @Entity(useDiscriminator = false)
public class CharacterContact { public class CharacterContact {
private transient Character character; private transient GameCharacter character;
private boolean top; private boolean top;
private long triggerTime; private long triggerTime;
@@ -27,7 +27,7 @@ public class CharacterContact {
} }
public CharacterContact(Character character) { public CharacterContact(GameCharacter character) {
this.character = character; this.character = character;
this.chats = new HashMap<>(); this.chats = new HashMap<>();
this.triggerTime = character.getCreateTime(); this.triggerTime = character.getCreateTime();
@@ -45,7 +45,7 @@ public class CharacterContact {
} }
} }
public void setCharacter(Character character) { public void setCharacter(GameCharacter character) {
this.character = character; this.character = character;
} }

View File

@@ -15,7 +15,7 @@ public class CharacterGemPreset {
} }
public CharacterGemPreset(Character character) { public CharacterGemPreset(GameCharacter character) {
this.gems = new int[] {-1, -1, -1}; this.gems = new int[] {-1, -1, -1};
} }

View File

@@ -16,7 +16,7 @@ import lombok.Getter;
@Getter @Getter
public class CharacterStorage extends PlayerManager { public class CharacterStorage extends PlayerManager {
private final Int2ObjectMap<Character> characters; private final Int2ObjectMap<GameCharacter> characters;
private final Int2ObjectMap<GameDisc> discs; private final Int2ObjectMap<GameDisc> discs;
public CharacterStorage(Player player) { public CharacterStorage(Player player) {
@@ -28,7 +28,7 @@ public class CharacterStorage extends PlayerManager {
// Characters // Characters
public Character getCharacterById(int id) { public GameCharacter getCharacterById(int id) {
if (id <= 0) { if (id <= 0) {
return null; return null;
} }
@@ -40,7 +40,7 @@ public class CharacterStorage extends PlayerManager {
return this.characters.containsKey(id); return this.characters.containsKey(id);
} }
public Character addCharacter(int charId) { public GameCharacter addCharacter(int charId) {
// Sanity check to make sure we dont have this character already // Sanity check to make sure we dont have this character already
if (this.hasCharacter(charId)) { if (this.hasCharacter(charId)) {
return null; return null;
@@ -49,14 +49,14 @@ public class CharacterStorage extends PlayerManager {
return this.addCharacter(GameData.getCharacterDataTable().get(charId)); return this.addCharacter(GameData.getCharacterDataTable().get(charId));
} }
private Character addCharacter(CharacterDef data) { private GameCharacter addCharacter(CharacterDef data) {
// Sanity check to make sure we dont have this character already // Sanity check to make sure we dont have this character already
if (this.hasCharacter(data.getId())) { if (this.hasCharacter(data.getId())) {
return null; return null;
} }
// Create character // Create character
var character = new Character(this.getPlayer(), data); var character = new GameCharacter(this.getPlayer(), data);
// Save to database // Save to database
character.save(); character.save();
@@ -66,7 +66,7 @@ public class CharacterStorage extends PlayerManager {
return character; return character;
} }
public Collection<Character> getCharacterCollection() { public Collection<GameCharacter> getCharacterCollection() {
return this.getCharacters().values(); return this.getCharacters().values();
} }
@@ -169,7 +169,7 @@ public class CharacterStorage extends PlayerManager {
public void loadFromDatabase() { public void loadFromDatabase() {
var db = Nebula.getGameDatabase(); var db = Nebula.getGameDatabase();
db.getObjects(Character.class, "playerUid", getPlayerUid()).forEach(character -> { db.getObjects(GameCharacter.class, "playerUid", getPlayerUid()).forEach(character -> {
// Get data // Get data
var data = GameData.getCharacterDataTable().get(character.getCharId()); var data = GameData.getCharacterDataTable().get(character.getCharId());

View File

@@ -40,7 +40,7 @@ import us.hebi.quickbuf.RepeatedInt;
@Getter @Getter
@Entity(value = "characters", useDiscriminator = false) @Entity(value = "characters", useDiscriminator = false)
public class Character implements GameDatabaseObject { public class GameCharacter implements GameDatabaseObject {
@Id @Id
private ObjectId uid; private ObjectId uid;
@Indexed @Indexed
@@ -65,15 +65,15 @@ public class Character implements GameDatabaseObject {
private CharacterContact contact; private CharacterContact contact;
@Deprecated // Morphia only! @Deprecated // Morphia only!
public Character() { public GameCharacter() {
} }
public Character(Player player, int charId) { public GameCharacter(Player player, int charId) {
this(player, GameData.getCharacterDataTable().get(charId)); this(player, GameData.getCharacterDataTable().get(charId));
} }
public Character(Player player, CharacterDef data) { public GameCharacter(Player player, CharacterDef data) {
this.player = player; this.player = player;
this.playerUid = player.getUid(); this.playerUid = player.getUid();
this.charId = data.getId(); this.charId = data.getId();
@@ -110,6 +110,14 @@ public class Character implements GameDatabaseObject {
} }
} }
public void setLevel(int level) {
this.level = level;
}
public void setAdvance(int advance) {
this.advance = advance;
}
public int getMaxGainableExp() { public int getMaxGainableExp() {
if (this.getLevel() >= this.getMaxLevel()) { if (this.getLevel() >= this.getMaxLevel()) {
return 0; return 0;

View File

@@ -12,7 +12,7 @@ import emu.nebula.game.GameContextModule;
import emu.nebula.game.account.Account; import emu.nebula.game.account.Account;
import emu.nebula.game.agent.AgentManager; import emu.nebula.game.agent.AgentManager;
import emu.nebula.game.battlepass.BattlePass; import emu.nebula.game.battlepass.BattlePass;
import emu.nebula.game.character.Character; import emu.nebula.game.character.GameCharacter;
import emu.nebula.game.character.GameDisc; import emu.nebula.game.character.GameDisc;
import emu.nebula.game.formation.FormationManager; import emu.nebula.game.formation.FormationManager;
import emu.nebula.game.friends.Friendship; import emu.nebula.game.friends.Friendship;
@@ -167,7 +167,7 @@ public class PlayerModule extends GameContextModule {
var datastore = Nebula.getGameDatabase().getDatastore(); var datastore = Nebula.getGameDatabase().getDatastore();
// Delete data from collections // Delete data from collections
datastore.getCollection(Character.class).deleteMany(multiFilter); datastore.getCollection(GameCharacter.class).deleteMany(multiFilter);
datastore.getCollection(GameDisc.class).deleteMany(multiFilter); datastore.getCollection(GameDisc.class).deleteMany(multiFilter);
datastore.getCollection(GameItem.class).deleteMany(multiFilter); datastore.getCollection(GameItem.class).deleteMany(multiFilter);
datastore.getCollection(GameResource.class).deleteMany(multiFilter); datastore.getCollection(GameResource.class).deleteMany(multiFilter);

View File

@@ -11,7 +11,7 @@ import dev.morphia.annotations.Id;
import emu.nebula.database.GameDatabaseObject; import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.player.Player; import emu.nebula.game.player.Player;
import emu.nebula.game.tower.StarTowerBuild; import emu.nebula.game.tower.StarTowerBuild;
import emu.nebula.game.character.Character; import emu.nebula.game.character.GameCharacter;
import emu.nebula.proto.Public.HonorInfo; import emu.nebula.proto.Public.HonorInfo;
import emu.nebula.proto.ScoreBossRank.ScoreBossRankChar; import emu.nebula.proto.ScoreBossRank.ScoreBossRankChar;
import emu.nebula.proto.ScoreBossRank.ScoreBossRankData; import emu.nebula.proto.ScoreBossRank.ScoreBossRankData;
@@ -155,7 +155,7 @@ public class ScoreBossRankEntry implements GameDatabaseObject {
} }
public ScoreBossCharEntry(Character character) { public ScoreBossCharEntry(GameCharacter character) {
this.id = character.getCharId(); this.id = character.getCharId();
this.level = character.getLevel(); this.level = character.getLevel();
} }

View File

@@ -3,7 +3,9 @@ package emu.nebula.server.handlers;
import emu.nebula.net.NetHandler; import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId; import emu.nebula.net.NetMsgId;
import emu.nebula.proto.PlayerSignatureEdit.PlayerSignatureEditReq; import emu.nebula.proto.PlayerSignatureEdit.PlayerSignatureEditReq;
import emu.nebula.proto.Public.Error;
import emu.nebula.net.HandlerId; import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession; import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.player_signature_edit_req) @HandlerId(NetMsgId.player_signature_edit_req)
@@ -13,7 +15,22 @@ public class HandlerPlayerSignatureEdit extends NetHandler {
public byte[] handle(GameSession session, byte[] message) throws Exception { public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request // Parse request
var req = PlayerSignatureEditReq.parseFrom(message); var req = PlayerSignatureEditReq.parseFrom(message);
var signature = req.getSignature();
if (signature == null) {
return session.encodeMsg(NetMsgId.player_signature_edit_failed_ack);
}
// Check if we need to handle a command
if (signature.charAt(0) == '!' || signature.charAt(0) == '/') {
Nebula.getCommandManager().invoke(session.getPlayer(), signature);
return session.encodeMsg(
NetMsgId.player_signature_edit_failed_ack,
Error.newInstance().setCode(119902).addArguments("\nCommand Success")
);
}
// Edit signature
session.getPlayer().editSignature(req.getSignature()); session.getPlayer().editSignature(req.getSignature());
// Send response // Send response

View File

@@ -86,8 +86,8 @@ public class Utils {
return Math.floorDiv(System.currentTimeMillis(), 1000); return Math.floorDiv(System.currentTimeMillis(), 1000);
} }
public static int getMinPromotionForLevel(int level) { public static int getMinAdvanceForLevel(int level) {
return Math.max(Math.min((int) ((level - 11) / 10D), 6), 0); return Math.max(Math.min((int) ((level - 1) / 10D), 8), 0);
} }
/** /**