Compare commits

...

4 Commits

Author SHA1 Message Date
ShiroSaki
3a39545e34 Refactor support for codex(aka archive) animal, weapon, reliquary 2022-05-23 02:46:50 -07:00
Luke H-W
e433b46ef5
Fix team command (#1035)
* Fix logic error in team command

* Sugar team command messages

Co-authored-by: AnimeGitB <AnimeGitB@bigblueball.in>
2022-05-23 02:45:03 -07:00
CCasusensa
0e2fc3850e Traditional Chinese | Translation Patch 2022-05-23 02:44:34 -07:00
Magix
d73c7f7233
Move commands & teleportation to the wiki. 2022-05-23 00:27:23 -04:00
23 changed files with 552 additions and 141 deletions

View File

@ -98,72 +98,11 @@ chmod +x gradlew
You can find the output jar in the root of the project folder.
## Commands
You might want to use this command (`java -jar grasscutter.jar -handbook`) in a cmd that is in the grasscutter folder. It will create a handbook file (GM Handbook.txt) where you can find the item IDs for stuff you want
You may want to use this command (`java -jar grasscutter.jar -gachamap`) to generate a mapping file for the gacha record subsystem. The file will be generated to `GRASSCUTTER_RESOURCE/gcstatic` folder. Otherwise you may only see number IDs in the gacha record page.
There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats. to run commands ingame, you need to add prefix `/` or `!` such as `/pos`
### Targeting
1. For commands that target a Player, you can specify a target UID with `@UID` as an argument in any position to the command.
2. If you message a valid command at another player (instead of at the "Server" virtual player), they will be the target for that command if you didn't set one above.
3. If none of the above, it will default to a persistent target player you previously set using the command `/target <UID>`.
4. If none of the above, you will be the target of the command. If you are entering the command from the Server console, **it will not work!**
Note that performing commands on other players will usually require different a permission to the base permission node. e.g. `player.give` becomes `player.give.others` if used on another player.
| Commands | Description | Alias | Targeting | Usage | Permission node |
| -------------- | ------------------------------------------------------------------------------------------------- | ------------------ | ------------- | --------------------------------------------------------------------------- | ------------------------- |
| account | Creates an account with the specified username, and the in-game UID if specified. | | Server only | account \<create\|delete> \<username> [UID] | |
| broadcast | Sends a message to all the players. | b | None | broadcast \<message> | server.broadcast |
| coop | Forces someone to join the world of others. | | Online Player | coop [host UID (default self)] | server.coop |
| changescene | Switch scenes by scene ID. | scene | Online Player | changescene \<scene id> | player.changescene |
| clear | Deletes all unequipped and unlocked lvl0 artifacts(art)/weapons(wp)/material(mat) from inventory. | | Online Player | clear \<all\|wp\|art\|mat> | player.clearinv |
| drop | Drops an item around you. | d dropitem | Online Player | drop \<itemID\|itemName> [amount] | server.drop |
| enterdungeon | Enter a dungeon by dungeon ID. | | Online Player | enterdungeon \<dungeon id> | player.enterdungeon |
| give | Gives item(s) to you or the specified player. | g item giveitem | Online Player | give \<itemId\|itemName> [amount] [level] [refinement] | player.give |
| giveall | Gives all items. | givea | Online Player | giveall [amount] | player.giveall |
| giveart | Gives the player a specified artifact. | gart | Online Player | giveart \<artifactId> \<mainPropId> [\<appendPropId>[,\<times>]]... [level] | player.giveart |
| givechar | Gives the player a specified character. | givec | Online Player | givechar \<avatarId> | player.givechar |
| godmode | Prevents you from taking damage. | | Online Player | godmode | player.godmode |
| heal | Heals all characters in your current team. | h | Online Player | heal | player.heal |
| help | Sends the help message or shows information about a specified command. | | None | help [command] | |
| kick | Kicks the specified player from the server. | k | Online Player | kick | server.kick |
| killall | Kills all entities in the current scene or specified scene of the corresponding player. | | Online Player | killall [sceneId] | server.killall |
| list | Lists online players. | | None | list | |
| permission | Grants or removes a permission for a user. | | Online Player | permission \<add\|remove> \<permission> | permission |
| position | Sends your current coordinates. | pos | Online Player | position | |
| reload | Reloads the server config. | | None | reload | server.reload |
| resetconst | Resets currently selected (or all) character(s) to C0. Relog to see proper effects. | resetconstellation | Online Player | resetconst [all] | player.resetconstellation |
| restart | Restarts the current session. | | None | restart | |
| sendmessage | Sends a message to a player as the server. | say | Online Player | say \<message> | server.sendmessage |
| setfetterlevel | Sets the friendship level for your currently selected character. | setfetterlvl | Online Player | setfetterlevel \<level> | player.setfetterlevel |
| setstats | Sets a stat for your currently selected character. | stats | Online Player | setstats \<stat> \<value> | player.setstats |
| setworldlevel | Sets your world level. Relog to see proper effects. | setworldlvl | Online Player | setworldlevel \<level> | player.setworldlevel |
| spawn | Spawns some entities around you. | | Online Player | spawn \<entityId> [amount] [level(monster only)] | server.spawn |
| stop | Stops the server. | | None | stop | server.stop |
| talent | Sets talent level for your currently selected character | | Online Player | talent \<talentID> \<value> | player.settalent |
| team | Add, remove, or swap avatars in your current team. Index start from 1. | | Online Player | team \<add\|remove\|set> [avatarId,...] [index|first|last|index-index,...] | player.team |
| teleport | Change the player's position. | tp | Online Player | teleport \<x> \<y> \<z> [sceneId] | player.teleport |
| tpall | Teleports all players in your world to your position. | | Online Player | tpall | player.tpall |
| unlocktower | Unlock the all floors of abyss. | ut | Online Player | ut | player.tower |
| weather | Changes the weather. | w | Online Player | weather \<weatherID> \<climateID> | player.weather |
### Bonus
- Teleporting
- When you want to teleport to somewhere, use the in-game marking function on Map.
- Mark a point on the map using the fish hook marking (the last one.)
- (Optional) rename the map marker to a number to override the default Y coordinate (height, default 300.)
- Confirm and close the map.
- You will see your character falling from a very high destination, exact location that you marked.
### Commands have moved to the [wiki](https://github.com/Grasscutters/Grasscutter/wiki/Commands)!
# Quick Troubleshooting
* If compiling wasn't successful, please check your JDK installation (JDK 17 and validated JDK's bin PATH variable)
* My client doesn't connect, doesn't login, 4206, etc... - Mostly your proxy daemon setup is *the issue*, if using
Fiddler make sure it running on another port except 8888
* Startup sequence: Mongodb > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Game
* Startup sequence: MongoDB > Grasscutter > Proxy daemon (mitmdump, fiddler, etc.) > Game

View File

@ -0,0 +1,16 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
message QueryCodexMonsterBeKilledNumReq {
enum CmdId {
option allow_alias = true;
ENET_CHANNEL_ID = 0;
NONE = 0;
ENET_IS_RELIABLE = 1;
IS_ALLOW_CLIENT = 1;
CMD_ID = 4201;
}
repeated uint32 codex_id_list = 1;
}

View File

@ -0,0 +1,18 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
message QueryCodexMonsterBeKilledNumRsp {
enum CmdId {
option allow_alias = true;
NONE = 0;
ENET_CHANNEL_ID = 0;
ENET_IS_RELIABLE = 1;
CMD_ID = 4210;
}
int32 retcode = 1;
repeated uint32 codex_id_list = 2;
repeated uint32 be_killed_num_list = 3;
repeated uint32 be_killed_num_empty_list = 4;
}

View File

@ -1,9 +1,7 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
@ -11,7 +9,6 @@ import java.util.List;
import java.util.ArrayList;
import java.util.HashSet;
import static emu.grasscutter.utils.Language.translate;
import static emu.grasscutter.Configuration.*;
@Command(label = "team", usage = "team <add|remove|set> [avatarId,...] [index|first|last|index-index,...]",
@ -22,7 +19,7 @@ public final class TeamCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.isEmpty()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.usage"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.usage");
return;
}
@ -40,8 +37,8 @@ public final class TeamCommand implements CommandHandler {
break;
default:
CommandHandler.sendMessage(sender, translate(sender, "commands.team.invalid_usage"));
CommandHandler.sendMessage(sender, translate(sender, "commands.team.usage"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
CommandHandler.sendTranslatedMessage(sender, "commands.team.usage");
return;
}
@ -51,8 +48,8 @@ public final class TeamCommand implements CommandHandler {
private boolean addCommand(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.invalid_usage"));
CommandHandler.sendMessage(sender, translate(sender, "commands.team.add_usage"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
CommandHandler.sendTranslatedMessage(sender, "commands.team.add_usage");
return false;
}
@ -62,7 +59,7 @@ public final class TeamCommand implements CommandHandler {
index = Integer.parseInt(args.get(2)) - 1;
if (index < 0) index = 0;
} catch (Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.invalid_index"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_index");
return false;
}
}
@ -71,28 +68,22 @@ public final class TeamCommand implements CommandHandler {
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
if (currentTeamAvatars.size() + avatarIds.length > GAME_OPTIONS.avatarLimits.singlePlayerTeam) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.add_too_much", GAME_OPTIONS.avatarLimits.singlePlayerTeam));
CommandHandler.sendTranslatedMessage(sender, "commands.team.add_too_much", GAME_OPTIONS.avatarLimits.singlePlayerTeam);
return false;
}
for (var avatarId: avatarIds) {
try {
int id = Integer.parseInt(avatarId);
var ret = addAvatar(sender, targetPlayer, id, index);
if (index > 0) ++index;
if (!ret) continue;
} catch (Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.failed_to_add_avatar", avatarId));
continue;
}
int id = Integer.parseInt(avatarId);
var success = addAvatar(sender, targetPlayer, id, index);
if (index > 0) ++index;
}
return true;
}
private boolean removeCommand(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.invalid_usage"));
CommandHandler.sendMessage(sender, translate(sender, "commands.team.remove_usage"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
CommandHandler.sendTranslatedMessage(sender, "commands.team.remove_usage");
return false;
}
@ -106,7 +97,7 @@ public final class TeamCommand implements CommandHandler {
// step 1: parse metaIndex to indexes
var subIndexes = transformToIndexes(metaIndex, avatarCount);
if (subIndexes == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.failed_to_parse_index", metaIndex));
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_to_parse_index", metaIndex);
continue;
}
@ -123,13 +114,13 @@ public final class TeamCommand implements CommandHandler {
// step 3: check if user remove all of the avatar
if (indexes.size() >= avatarCount) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.remove_too_much"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.remove_too_much");
return false;
}
// step 4: hint user for ignore index
if (!ignoreList.isEmpty()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.ignore_index", ignoreList));
CommandHandler.sendTranslatedMessage(sender, "commands.team.ignore_index", ignoreList);
}
// step 5: remove
@ -139,8 +130,8 @@ public final class TeamCommand implements CommandHandler {
private boolean setCommand(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 3) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.invalid_usage"));
CommandHandler.sendMessage(sender, translate(sender, "commands.team.set_usage"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
CommandHandler.sendTranslatedMessage(sender, "commands.team.set_usage");
return false;
}
@ -151,12 +142,12 @@ public final class TeamCommand implements CommandHandler {
index = Integer.parseInt(args.get(1)) - 1;
if (index < 0) index = 0;
} catch(Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.failed_to_parse_index", args.get(1)));
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_to_parse_index", args.get(1));
return false;
}
if (index + 1 > currentTeamAvatars.size()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.index_out_of_range"));
CommandHandler.sendTranslatedMessage(sender, "commands.team.index_out_of_range");
return false;
}
@ -164,7 +155,7 @@ public final class TeamCommand implements CommandHandler {
try {
avatarId = Integer.parseInt(args.get(2));
} catch(Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.failed_parse_avatar_id", args.get(2)));
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_parse_avatar_id", args.get(2));
return false;
}
if (avatarId < BASE_AVATARID) {
@ -172,12 +163,12 @@ public final class TeamCommand implements CommandHandler {
}
if (currentTeamAvatars.contains(avatarId)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.avatar_already_in_team", avatarId));
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_already_in_team", avatarId);
return false;
}
if (!targetPlayer.getAvatars().hasAvatar(avatarId)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.avatar_not_found", avatarId));
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId);
return false;
}
@ -191,11 +182,11 @@ public final class TeamCommand implements CommandHandler {
}
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
if (currentTeamAvatars.contains(avatarId)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.avatar_already_in_team", avatarId));
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_already_in_team", avatarId);
return false;
}
if (!sender.getAvatars().hasAvatar(avatarId)) {
CommandHandler.sendMessage(sender, translate(sender, "commands.team.avatar_not_found", avatarId));
if (!targetPlayer.getAvatars().hasAvatar(avatarId)) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId);
return false;
}
if (index < 0) {

View File

@ -64,8 +64,16 @@ public class GameData {
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexQuest> codexQuestMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexQuest> codexQuestIdMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexQuestData> codexQuestDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexQuestData> codexQuestDataIdMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexAnimalData> codexAnimalDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataIdMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataIdMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataIdMap = new Int2ObjectOpenHashMap<>();
private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
@ -294,9 +302,17 @@ public class GameData {
return fetters;
}
public static Int2ObjectMap<CodexQuest> getCodexQuestMap(){return codexQuestMap;}
public static Int2ObjectMap<CodexQuestData> getCodexQuestDataIdMap(){return codexQuestDataIdMap;}
public static Int2ObjectMap<CodexQuest> getCodexQuestIdMap(){return codexQuestIdMap;}
public static Int2ObjectMap<CodexAnimalData> getCodexAnimalDataMap(){return codexAnimalDataMap;}
public static Int2ObjectMap<CodexWeaponData> getCodexWeaponDataIdMap(){return codexWeaponDataIdMap;}
public static Int2ObjectMap<CodexMaterialData> getCodexMaterialDataIdMap(){return codexMaterialDataIdMap;}
public static Int2ObjectMap<CodexReliquaryData> getcodexReliquaryIdMap(){return codexReliquaryDataIdMap;}
public static ArrayList<CodexReliquaryData> getcodexReliquaryArrayList(){return codexReliquaryArrayList;}
public static Int2ObjectMap<WorldLevelData> getWorldLevelDataMap() {
return worldLevelDataMap;

View File

@ -0,0 +1,39 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = {"AnimalCodexExcelConfigData.json"})
public class CodexAnimalData extends GameResource {
private int Id;
private String Type;
private int DescribeId;
private int SortOrder;
private CodexAnimalUnlockCondition BAINKHIIMJE;
@Override
public int getId() {
return Id;
}
public String getType() {
return Type;
}
public int getDescribeId() {
return DescribeId;
}
public int getSortOrder() {
return SortOrder;
}
public CodexAnimalUnlockCondition getUnlockCondition() {
return BAINKHIIMJE;
}
public enum CodexAnimalUnlockCondition {
CODEX_COUNT_TYPE_KILL,
CODEX_COUNT_TYPE_CAPTURE
}
}

View File

@ -0,0 +1,29 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = {"MaterialCodexExcelConfigData.json"})
public class CodexMaterialData extends GameResource {
private int Id;
private int MaterialId;
private int SortOrder;
public int getSortOrder() {
return SortOrder;
}
public int getMaterialId() {
return MaterialId;
}
public int getId() {
return Id;
}
@Override
public void onLoad() {
GameData.getCodexMaterialDataIdMap().put(this.getMaterialId(), this);
}
}

View File

@ -1,12 +1,11 @@
package emu.grasscutter.data.def;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = {"QuestCodexExcelConfigData.json"}, loadPriority = ResourceType.LoadPriority.HIGH)
public class CodexQuest extends GameResource {
@ResourceType(name = {"QuestCodexExcelConfigData.json"})
public class CodexQuestData extends GameResource {
private int Id;
private int ParentQuestId;
private int ChapterId;
@ -36,7 +35,7 @@ public class CodexQuest extends GameResource {
@Override
public void onLoad() {
if(!this.getIsDisuse()) {
GameData.getCodexQuestIdMap().put(this.getParentQuestId(), this);
GameData.getCodexQuestDataIdMap().put(this.getParentQuestId(), this);
}
}
}

View File

@ -0,0 +1,60 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = {"ReliquaryCodexExcelConfigData.json"})
public class CodexReliquaryData extends GameResource {
private int Id;
private int SuitId;
private int Level;
private int CupId;
private int LeatherId;
private int CapId;
private int FlowerId;
private int SandId;
private int SortOrder;
public int getSortOrder() {
return SortOrder;
}
public int getId() {
return Id;
}
public int getSuitId() {
return SuitId;
}
public int getLevel() {
return Level;
}
public int getCupId() {
return CupId;
}
public int getLeatherId() {
return LeatherId;
}
public int getCapId() {
return CapId;
}
public int getFlowerId() {
return FlowerId;
}
public int getSandId() {
return SandId;
}
@Override
public void onLoad() {
GameData.getcodexReliquaryArrayList().add(this);
GameData.getcodexReliquaryIdMap().put(getSuitId(), this);
}
}

View File

@ -0,0 +1,29 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = {"WeaponCodexExcelConfigData.json"})
public class CodexWeaponData extends GameResource {
private int Id;
private int WeaponId;
private int SortOrder;
public int getSortOrder() {
return SortOrder;
}
public int getWeaponId() {
return WeaponId;
}
public int getId() {
return Id;
}
@Override
public void onLoad() {
GameData.getCodexWeaponDataIdMap().put(this.getWeaponId(), this);
}
}

View File

@ -240,6 +240,7 @@ public class Inventory implements Iterable<GameItem> {
}
private synchronized void putItem(GameItem item, InventoryTab tab) {
getPlayer().getCodex().checkAddedItem(item);
// Set owner and guid FIRST!
item.setOwner(getPlayer());
// Put in item store

View File

@ -2,7 +2,6 @@ package emu.grasscutter.game.player;
import dev.morphia.annotations.*;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.PlayerLevelData;
import emu.grasscutter.database.DatabaseHelper;
@ -30,8 +29,6 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.managers.MapMarkManager.*;
@ -80,6 +77,7 @@ public class Player {
private Position pos;
private Position rotation;
private PlayerBirthday birthday;
private PlayerCodex codex;
private Map<Integer, Integer> properties;
private Set<Integer> nameCardList;
@ -189,6 +187,7 @@ public class Player {
this.birthday = new PlayerBirthday();
this.rewardedLevels = new HashSet<>();
this.moonCardGetTimes = new HashSet<>();
this.codex = new PlayerCodex();
this.shopLimit = new ArrayList<>();
this.expeditionInfo = new HashMap<>();
@ -209,6 +208,7 @@ public class Player {
this.signature = "";
this.teamManager = new TeamManager(this);
this.birthday = new PlayerBirthday();
this.codex = new PlayerCodex();
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
@ -758,7 +758,6 @@ public class Player {
return expeditionInfo.get(avaterGuid);
}
public List<ShopLimit> getShopLimit() {
return shopLimit;
}
@ -984,6 +983,8 @@ public class Player {
return this.birthday.getDay() > 0;
}
public PlayerCodex getCodex(){ return this.codex; }
public Set<Integer> getRewardedLevels() {
return rewardedLevels;
}
@ -1159,6 +1160,7 @@ public class Player {
@PostLoad
private void onLoad() {
this.getCodex().setPlayer(this);
this.getTeamManager().setPlayer(this);
this.getTowerManager().setPlayer(this);
}
@ -1229,7 +1231,6 @@ public class Player {
session.send(new PacketFinishedParentQuestNotify(this));
session.send(new PacketQuestListNotify(this));
session.send(new PacketCodexDataFullNotify(this));
session.send(new PacketServerCondMeetQuestListUpdateNotify(this));
session.send(new PacketAllWidgetDataNotify(this));
session.send(new PacketWidgetGadgetAllDataNotify());
session.send(new PacketPlayerHomeCompInfoNotify(this));

View File

@ -0,0 +1,160 @@
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.CodexAnimalData;
import emu.grasscutter.data.def.CodexReliquaryData;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
import java.util.*;
@Entity
public class PlayerCodex {
@Transient private Player player;
//itemId is not codexId!
private Set<Integer> unlockedWeapon;
private Map<Integer, Integer> unlockedAnimal;
private Set<Integer> unlockedMaterial;
private Set<Integer> unlockedBook;
private Set<Integer> unlockedTip;
private Set<Integer> unlockedView;
private Set<Integer> unlockedReliquary;
private Set<Integer> unlockedReliquarySuitCodex;
public PlayerCodex(){
this.unlockedWeapon = new HashSet<>();
this.unlockedAnimal = new HashMap<>();
this.unlockedMaterial = new HashSet<>();
this.unlockedBook = new HashSet<>();
this.unlockedTip = new HashSet<>();
this.unlockedView = new HashSet<>();
this.unlockedReliquary = new HashSet<>();
this.unlockedReliquarySuitCodex = new HashSet<>();
}
public void setPlayer(Player player) {
this.player = player;
}
public void checkAddedItem(GameItem item){
ItemType type = item.getItemData().getItemType();
if (type == ItemType.ITEM_WEAPON){
if(!getUnlockedWeapon().contains(item.getItemId())){
getUnlockedWeapon().add(item.getItemId());
var codexItem = GameData.getCodexWeaponDataIdMap().get(item.getItemId());
if(codexItem != null){
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(2, codexItem.getId()));
}
}
}
else if(type == ItemType.ITEM_MATERIAL){
if( item.getItemData().getMaterialType() == MaterialType.MATERIAL_FOOD ||
item.getItemData().getMaterialType() == MaterialType.MATERIAL_WIDGET||
item.getItemData().getMaterialType() == MaterialType.MATERIAL_EXCHANGE||
item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR_MATERIAL||
item.getItemData().getMaterialType() == MaterialType.MATERIAL_NOTICE_ADD_HP){
if (!getUnlockedMaterial().contains(item.getItemId())) {
var codexMaterial = GameData.getCodexMaterialDataIdMap().get(item.getItemId());
if (codexMaterial != null) {
getUnlockedMaterial().add(item.getItemId());
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(4, codexMaterial.getId()));
}
}
}
}
else if(type == ItemType.ITEM_RELIQUARY) {
if(!getUnlockedReliquary().contains(item.getItemId())){
getUnlockedReliquary().add(item.getItemId());
checkUnlockedSuits(item);
}
}
}
public void checkAnimal(GameEntity target, CodexAnimalData.CodexAnimalUnlockCondition condition){
if(target.getEntityType() == 2){
var monsterId = target.getSpawnEntry().getMonsterId();
var codexAnimal = GameData.getCodexAnimalDataMap().get(monsterId);
if(!getUnlockedAnimal().containsKey(monsterId)) {
if (codexAnimal != null) {
if(codexAnimal.getUnlockCondition() == condition){
getUnlockedAnimal().put(monsterId, 1);
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(3, monsterId));
}
}
}else{
getUnlockedAnimal().put(monsterId, getUnlockedAnimal().get(monsterId) + 1);
player.save();
}
}
}
public void checkUnlockedSuits(GameItem item){
int reliquaryId = item.getItemId();
Optional<CodexReliquaryData> excelReliquarySuitList = GameData.getcodexReliquaryArrayList().stream().filter(
x -> x.getCupId() == reliquaryId
|| x.getLeatherId() == reliquaryId
|| x.getCapId() == reliquaryId
|| x.getFlowerId() == reliquaryId
|| x.getSandId() == reliquaryId
).findFirst();
if(excelReliquarySuitList.isPresent()) {
var excelReliquarySuit = excelReliquarySuitList.get();
if(!getUnlockedReliquarySuitCodex().contains(excelReliquarySuit.getId())){
if(
getUnlockedReliquary().contains(excelReliquarySuit.getCupId()) &&
getUnlockedReliquary().contains(excelReliquarySuit.getLeatherId()) &&
getUnlockedReliquary().contains(excelReliquarySuit.getCapId()) &&
getUnlockedReliquary().contains(excelReliquarySuit.getFlowerId()) &&
getUnlockedReliquary().contains(excelReliquarySuit.getSandId())
){
getUnlockedReliquarySuitCodex().add(excelReliquarySuit.getId());
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(8, excelReliquarySuit.getId()));
}
}
}
}
public Set<Integer> getUnlockedWeapon() {
return unlockedWeapon;
}
public Map<Integer, Integer> getUnlockedAnimal() {
return unlockedAnimal;
}
public Set<Integer> getUnlockedMaterial() {
return unlockedMaterial;
}
public Set<Integer> getUnlockedBook() {
return unlockedBook;
}
public Set<Integer> getUnlockedTip() {
return unlockedTip;
}
public Set<Integer> getUnlockedView() {
return unlockedView;
}
public Set<Integer> getUnlockedReliquary() {
return unlockedReliquary;
}
public Set<Integer> getUnlockedReliquarySuitCodex() {
return unlockedReliquarySuitCodex;
}
}

View File

@ -121,7 +121,6 @@ public class QuestManager {
mainQuest.save();
// Send packet
getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(quest));
getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest));
return quest;

View File

@ -2,10 +2,7 @@ package emu.grasscutter.game.world;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.def.DungeonData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.data.def.WorldLevelData;
import emu.grasscutter.data.def.*;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.dungeons.DungeonSettleListener;
import emu.grasscutter.game.entity.*;
@ -389,6 +386,9 @@ public class Scene {
}
public void killEntity(GameEntity target, int attackerId) {
for (Player player : this.getPlayers()) {
player.getCodex().checkAnimal(target, CodexAnimalData.CodexAnimalUnlockCondition.CODEX_COUNT_TYPE_KILL);
}
// Packet
this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));

View File

@ -0,0 +1,20 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.QueryCodexMonsterBeKilledNumReqOuterClass;
import emu.grasscutter.net.proto.QueryCodexMonsterBeKilledNumReqOuterClass.QueryCodexMonsterBeKilledNumReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketQueryCodexMonsterBeKilledNumRsp;
@Opcodes(PacketOpcodes.QueryCodexMonsterBeKilledNumReq)
public class HandlerQueryCodexMonsterBeKilledNumReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
QueryCodexMonsterBeKilledNumReq req = QueryCodexMonsterBeKilledNumReq.parseFrom(payload);
session.send(new PacketQueryCodexMonsterBeKilledNumRsp(session.getPlayer(), req.getCodexIdListList()));
}
}

View File

@ -1,17 +1,13 @@
package emu.grasscutter.server.packet.send;
import java.util.Collections;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CodexDataFullNotifyOuterClass.CodexDataFullNotify;
import emu.grasscutter.net.proto.CodexTypeDataOuterClass.CodexTypeData;
import emu.grasscutter.net.proto.CodexTypeOuterClass;
import emu.grasscutter.server.game.GameSession;
public class PacketCodexDataFullNotify extends BasePacket {
public PacketCodexDataFullNotify(Player player) {
@ -21,6 +17,22 @@ public class PacketCodexDataFullNotify extends BasePacket {
CodexTypeData.Builder questTypeData = CodexTypeData.newBuilder()
.setTypeValue(1);
//Weapons
CodexTypeData.Builder weaponTypeData = CodexTypeData.newBuilder()
.setTypeValue(2);
//Animals
CodexTypeData.Builder animalTypeData = CodexTypeData.newBuilder()
.setTypeValue(3);
//Materials
CodexTypeData.Builder materialTypeData = CodexTypeData.newBuilder()
.setTypeValue(4);
//Books
CodexTypeData.Builder bookTypeData = CodexTypeData.newBuilder()
.setTypeValue(5);
//Tips
CodexTypeData.Builder pushTipsTypeData = CodexTypeData.newBuilder()
.setTypeValue(6);
@ -29,25 +41,53 @@ public class PacketCodexDataFullNotify extends BasePacket {
CodexTypeData.Builder viewTypeData = CodexTypeData.newBuilder()
.setTypeValue(7);
//Weapons
CodexTypeData.Builder weaponTypeData = CodexTypeData.newBuilder()
.setTypeValue(2);
//Reliquary
CodexTypeData.Builder reliquaryData = CodexTypeData.newBuilder()
.setTypeValue(8);
player.getQuestManager().forEachMainQuest(mainQuest -> {
if(mainQuest.isFinished()){
var codexQuest = GameData.getCodexQuestIdMap().get(mainQuest.getParentQuestId());
var codexQuest = GameData.getCodexQuestDataIdMap().get(mainQuest.getParentQuestId());
if(codexQuest != null){
questTypeData.addCodexIdList(codexQuest.getId()).addAllHaveViewedList(Collections.singleton(true));
}
}
});
player.getCodex().getUnlockedWeapon().forEach(weapon -> {
var codexWeapon = GameData.getCodexWeaponDataIdMap().get(weapon);
if(codexWeapon != null){
weaponTypeData.addCodexIdList(codexWeapon.getId()).addAllHaveViewedList(Collections.singleton(true));
}
});
player.getCodex().getUnlockedAnimal().forEach((animal, amount) -> {
var codexAnimal = GameData.getCodexAnimalDataMap().get(animal);
if(codexAnimal != null){
animalTypeData.addCodexIdList(codexAnimal.getId()).addAllHaveViewedList(Collections.singleton(true));
}
});
player.getCodex().getUnlockedMaterial().forEach(material -> {
var codexMaterial = GameData.getCodexMaterialDataIdMap().get(material);
if(codexMaterial != null){
materialTypeData.addCodexIdList(codexMaterial.getId()).addAllHaveViewedList(Collections.singleton(true));
}
});
player.getCodex().getUnlockedReliquarySuitCodex().forEach(reliquarySuit -> {
reliquaryData.addCodexIdList(reliquarySuit).addAllHaveViewedList(Collections.singleton(true));
});
CodexDataFullNotify.Builder proto = CodexDataFullNotify.newBuilder()
.addTypeDataList(questTypeData.build())
.addTypeDataList(weaponTypeData)
.addTypeDataList(animalTypeData)
.addTypeDataList(materialTypeData)
.addTypeDataList(bookTypeData)
.addTypeDataList(pushTipsTypeData.build())
.addTypeDataList(viewTypeData.build())
.addTypeDataList(weaponTypeData);
.addTypeDataList(reliquaryData);
this.setData(proto);
}

View File

@ -1,21 +1,15 @@
package emu.grasscutter.server.packet.send;
import java.util.Collections;
import java.util.List;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CodexDataUpdateNotifyOuterClass.CodexDataUpdateNotify;
import emu.grasscutter.server.game.GameSession;
public class PacketCodexDataUpdateNotify extends BasePacket {
public PacketCodexDataUpdateNotify(GameMainQuest quest) {
super(PacketOpcodes.CodexDataUpdateNotify, true);
var codexQuest = GameData.getCodexQuestIdMap().get(quest.getParentQuestId());
var codexQuest = GameData.getCodexQuestDataIdMap().get(quest.getParentQuestId());
if(codexQuest != null){
CodexDataUpdateNotify proto = CodexDataUpdateNotify.newBuilder()
.setTypeValue(1)
@ -24,4 +18,13 @@ public class PacketCodexDataUpdateNotify extends BasePacket {
this.setData(proto);
}
}
public PacketCodexDataUpdateNotify(int typeValue, int codexId){
super(PacketOpcodes.CodexDataUpdateNotify, true);
CodexDataUpdateNotify proto = CodexDataUpdateNotify.newBuilder()
.setTypeValue(typeValue)
.setId(codexId)
.build();
this.setData(proto);
}
}

View File

@ -0,0 +1,26 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.QueryCodexMonsterBeKilledNumRspOuterClass.QueryCodexMonsterBeKilledNumRsp;
import java.util.List;
public class PacketQueryCodexMonsterBeKilledNumRsp extends BasePacket {
public PacketQueryCodexMonsterBeKilledNumRsp(Player player, List<Integer> codexList) {
super(PacketOpcodes.QueryCodexMonsterBeKilledNumRsp);
QueryCodexMonsterBeKilledNumRsp.Builder proto = QueryCodexMonsterBeKilledNumRsp.newBuilder();
codexList.forEach(animal -> {
if(player.getCodex().getUnlockedAnimal().containsKey(animal)){
proto.addCodexIdList(animal)
.addBeKilledNumList(player.getCodex().getUnlockedAnimal().get(animal))
.addBeKilledNumEmptyList(0);
}
});
this.setData(proto);
}
}

View File

@ -12,13 +12,15 @@ public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket {
super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify);
ServerCondMeetQuestListUpdateNotify.Builder proto = ServerCondMeetQuestListUpdateNotify.newBuilder();
/*
player.getQuestManager().forEachQuest(quest -> {
if (quest.getState().getValue() <= 2) {
proto.addAddQuestIdList(quest.getQuestId());
}
});
*/
this.setData(proto);
}
@ -26,9 +28,9 @@ public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket {
super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify);
ServerCondMeetQuestListUpdateNotify proto = ServerCondMeetQuestListUpdateNotify.newBuilder()
.addAddQuestIdList(quest.getQuestId())
//.addAddQuestIdList(quest.getQuestId())
.build();
this.setData(proto);
}
}

View File

@ -159,7 +159,7 @@
"description": "Gives the player a specified artifact"
},
"giveChar": {
"usage": "Usage: givechar <player> <itemID|itemName> [amount]",
"usage": "Usage: givechar <player> <itemID|itemName> [level]",
"given": "Given %s with level %s to %s.",
"invalid_avatar_id": "Invalid avatar ID.",
"invalid_avatar_level": "Invalid avatar level.",

View File

@ -163,7 +163,7 @@
"description": "给予指定圣遗物"
},
"giveChar": {
"usage": "用法givechar <玩家> <角色ID|角色名> [数量]",
"usage": "用法givechar <玩家> <角色ID|角色名> [等级]",
"given": "已将角色 %s [等级 %s] 给与 %s。",
"invalid_avatar_id": "无效的角色ID。",
"invalid_avatar_level": "无效的角色等级。",

View File

@ -37,8 +37,9 @@
"session_key_error": "對話密鑰不符。",
"username_error": "未找到此用戶名。",
"username_create_error": "未找到用戶名,建立失敗。",
"server_max_player_limit": "服務器在綫人數已滿"
}
"server_max_player_limit": "服務器在線人數已滿"
},
"router_error": "[Dispatch] 無法附加到路由上。"
},
"status": {
"free_software": "Grasscutter 是免費開源軟體。如果你已經付錢了那你可能被騙了。主頁https://github.com/Grasscutters/Grasscutter",
@ -158,7 +159,7 @@
"description": "給予指定聖遺物。"
},
"giveChar": {
"usage": "用法givechar <player> <itemId|itemName> [amount]",
"usage": "用法givechar <player> <itemId|itemName> [level]",
"given": "已將 %s 等級 %s 給予 %s。",
"invalid_avatar_id": "無效的角色ID。",
"invalid_avatar_level": "無效的角色等級。.",
@ -396,5 +397,27 @@
"available_four_stars": "可獲得的4星物品",
"available_three_stars": "可獲得的3星物品"
}
},
"documentation": {
"handbook": {
"title": "GM手冊",
"title_commands": "指令",
"title_avatars": "角色",
"title_items": "道具",
"title_scenes": "場景",
"title_monsters": "怪物",
"header_id": "ID",
"header_command": "指令",
"header_description": "描述",
"header_avatar": "角色",
"header_item": "物品",
"header_scene": "場景",
"header_monster": "怪物"
},
"index": {
"title": "文件",
"handbook": "GM手冊",
"gacha_mapping": "祈願物品映射到JSON上"
}
}
}