SetPlayerPropReq Handler and player.setProperty() sanity check.

This commit is contained in:
gentlespoon 2022-05-04 23:16:17 -07:00 committed by Melledy
parent 37c2ee5e2c
commit 5c275c2e3c
4 changed files with 224 additions and 66 deletions

View File

@ -29,19 +29,16 @@ import emu.grasscutter.game.managers.MapMarkManager.*;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType; import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo; import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass;
import emu.grasscutter.server.event.player.PlayerJoinEvent; import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
@ -59,6 +56,9 @@ import java.util.concurrent.LinkedBlockingQueue;
@Entity(value = "players", useDiscriminator = false) @Entity(value = "players", useDiscriminator = false)
public class Player { public class Player {
@Transient private static int GlobalMaximumSpringVolume = 8500000;
@Transient private static int GlobalMaximumStamina = 24000;
@Id private int id; @Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId; @Indexed(options = @IndexOptions(unique = true)) private String accountId;
@ -395,12 +395,14 @@ public class Player {
return playerProfile; return playerProfile;
} }
// TODO: Based on the proto, property value could be int or float.
// Although there's no float value at this moment, our code should be prepared for float values.
public Map<Integer, Integer> getProperties() { public Map<Integer, Integer> getProperties() {
return properties; return properties;
} }
public void setProperty(PlayerProperty prop, int value) { public boolean setProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value); return setPropertyWithSanityCheck(prop, value);
} }
public int getProperty(PlayerProperty prop) { public int getProperty(PlayerProperty prop) {
@ -1107,4 +1109,102 @@ public class Player {
public void setMessageHandler(MessageHandler messageHandler) { public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
} }
private void saveSanityCheckedProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value);
}
private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value) {
if (prop == PlayerProperty.PROP_EXP) { // 1001
if (!(value >= 0)) { return false; }
} else if (prop == PlayerProperty.PROP_BREAK_LEVEL) { // 1002
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_VAL) { // 1003
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_SATIATION_PENALTY_TIME) { // 1004
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_LEVEL) { // 4001
if (!(value >= 0 && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002
if (!(value >= 0 && value <= GlobalMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003
int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_SPRING_AUTO_USE) { // 10004
if (!(value >= 0 && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT) { // 10005
if (!(value >= 0 && value <= 100)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_FLYABLE) { // 10006
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_WEATHER_LOCKED) { // 10007
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_GAME_TIME_LOCKED) { // 10008
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
if (!(value >= 0 && value <= GlobalMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) { // 10012
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEVEL) { // 10013
if (!(0 < value && value <= 90)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015
// see 10015
} else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016
// See 10015
} else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017
if (!(0 <= value && value <= 2)) { return false; }
} else if (prop == PlayerProperty.PROP_IS_MP_MODE_AVAILABLE) { // 10018
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL) { // 10019
if (!(0 <= value && value <= 8)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_RESIN) { // 10020
// Do not set 160 as a cap, because player can have more than 160 when they use fragile resin.
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HCOIN) { // 10022
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_SCOIN) { // 10023
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_ONLY_MP_WITH_PS_PLAYER) { // 10024
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_MCOIN) { // 10025
// see 10015
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_MCOIN) { // 10026
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_KEY) { // 10027
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_IS_HAS_FIRST_SHARE) { // 10028
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_FORGE_POINT) { // 10029
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_METER) { // 10035
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_TYPE) { // 10036
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_ID) { // 10037
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE) { // 10038
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_LIMIT) { // 10039
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_WORLD_LEVEL_ADJUST_CD) { // 10040
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM) { // 10041
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_PLAYER_HOME_COIN) { // 10042
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_WAIT_SUB_HOME_COIN) { // 10043
// TODO: implement sanity check
}
saveSanityCheckedProperty(prop, value);
return false;
}
} }

View File

@ -6,65 +6,67 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum PlayerProperty { public enum PlayerProperty {
PROP_EXP (1001), PROP_EXP (1001),
PROP_BREAK_LEVEL (1002), PROP_BREAK_LEVEL (1002),
PROP_SATIATION_VAL (1003), PROP_SATIATION_VAL (1003),
PROP_SATIATION_PENALTY_TIME (1004), PROP_SATIATION_PENALTY_TIME (1004),
PROP_LEVEL (4001), PROP_LEVEL (4001),
PROP_LAST_CHANGE_AVATAR_TIME (10001), PROP_LAST_CHANGE_AVATAR_TIME (10001),
PROP_MAX_SPRING_VOLUME (10002), PROP_MAX_SPRING_VOLUME (10002), // Maximum volume of the Statue of the Seven for the player [0, 8500000]
PROP_CUR_SPRING_VOLUME (10003), PROP_CUR_SPRING_VOLUME (10003), // Current volume of the Statue of the Seven [0, PROP_MAX_SPRING_VOLUME]
PROP_IS_SPRING_AUTO_USE (10004), PROP_IS_SPRING_AUTO_USE (10004), // Auto HP recovery when approaching the Statue of the Seven [0, 1]
PROP_SPRING_AUTO_USE_PERCENT (10005), PROP_SPRING_AUTO_USE_PERCENT (10005), // Auto HP recovery percentage [0, 100]
PROP_IS_FLYABLE (10006), PROP_IS_FLYABLE (10006), // Are you in a state that disables your flying ability? e.g. new player [0, 1]
PROP_IS_WEATHER_LOCKED (10007), PROP_IS_WEATHER_LOCKED (10007),
PROP_IS_GAME_TIME_LOCKED (10008), PROP_IS_GAME_TIME_LOCKED (10008),
PROP_IS_TRANSFERABLE (10009), PROP_IS_TRANSFERABLE (10009),
PROP_MAX_STAMINA (10010), PROP_MAX_STAMINA (10010), // Maximum stamina of the player (0 - 24000)
PROP_CUR_PERSIST_STAMINA (10011), PROP_CUR_PERSIST_STAMINA (10011), // Used stamina of the player (0 - PROP_MAX_STAMINA)
PROP_CUR_TEMPORARY_STAMINA (10012), PROP_CUR_TEMPORARY_STAMINA (10012),
PROP_PLAYER_LEVEL (10013), PROP_PLAYER_LEVEL (10013),
PROP_PLAYER_EXP (10014), PROP_PLAYER_EXP (10014),
PROP_PLAYER_HCOIN (10015), // Primogem PROP_PLAYER_HCOIN (10015), // Primogem (-inf, +inf)
PROP_PLAYER_SCOIN (10016), // Mora // It is known that Mihoyo will make Primogem negative in the cases that a player spends
PROP_PLAYER_MP_SETTING_TYPE (10017), // his gems and then got a money refund, so negative is allowed.
PROP_IS_MP_MODE_AVAILABLE (10018), PROP_PLAYER_SCOIN (10016), // Mora [0, +inf)
PROP_PLAYER_WORLD_LEVEL (10019), PROP_PLAYER_MP_SETTING_TYPE (10017), // Do you allow other players to join your game? [0=no 1=direct 2=approval]
PROP_PLAYER_RESIN (10020), PROP_IS_MP_MODE_AVAILABLE (10018), // Are you not in a quest or something that disables MP? [0, 1]
PROP_PLAYER_WAIT_SUB_HCOIN (10022), PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8]
PROP_PLAYER_WAIT_SUB_SCOIN (10023), PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf)
PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), PROP_PLAYER_WAIT_SUB_HCOIN (10022),
PROP_PLAYER_MCOIN (10025), // Genesis Crystal PROP_PLAYER_WAIT_SUB_SCOIN (10023),
PROP_PLAYER_WAIT_SUB_MCOIN (10026), PROP_IS_ONLY_MP_WITH_PS_PLAYER (10024), // Is only MP with PlayStation players? [0, 1]
PROP_PLAYER_LEGENDARY_KEY (10027), PROP_PLAYER_MCOIN (10025), // Genesis Crystal (-inf, +inf) see 10015
PROP_IS_HAS_FIRST_SHARE (10028), PROP_PLAYER_WAIT_SUB_MCOIN (10026),
PROP_PLAYER_FORGE_POINT (10029), PROP_PLAYER_LEGENDARY_KEY (10027),
PROP_CUR_CLIMATE_METER (10035), PROP_IS_HAS_FIRST_SHARE (10028),
PROP_CUR_CLIMATE_TYPE (10036), PROP_PLAYER_FORGE_POINT (10029),
PROP_CUR_CLIMATE_AREA_ID (10037), PROP_CUR_CLIMATE_METER (10035),
PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038), PROP_CUR_CLIMATE_TYPE (10036),
PROP_PLAYER_WORLD_LEVEL_LIMIT (10039), PROP_CUR_CLIMATE_AREA_ID (10037),
PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040), PROP_CUR_CLIMATE_AREA_CLIMATE_TYPE (10038),
PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041), PROP_PLAYER_WORLD_LEVEL_LIMIT (10039),
PROP_PLAYER_HOME_COIN (10042), PROP_PLAYER_WORLD_LEVEL_ADJUST_CD (10040),
PROP_PLAYER_WAIT_SUB_HOME_COIN (10043); PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM (10041),
PROP_PLAYER_HOME_COIN (10042), // Realm currency [0, +inf)
PROP_PLAYER_WAIT_SUB_HOME_COIN (10043);
private final int id; private final int id;
private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<PlayerProperty> map = new Int2ObjectOpenHashMap<>();
static { static {
Stream.of(values()).forEach(e -> map.put(e.getId(), e)); Stream.of(values()).forEach(e -> map.put(e.getId(), e));
} }
private PlayerProperty(int id) { PlayerProperty(int id) {
this.id = id; this.id = id;
} }
public int getId() { public int getId() {
return id; return id;
} }
public static PlayerProperty getPropById(int value) { public static PlayerProperty getPropById(int value) {
return map.getOrDefault(value, null); return map.getOrDefault(value, null);
} }
} }

View File

@ -0,0 +1,37 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
import emu.grasscutter.net.proto.SetPlayerPropReqOuterClass.SetPlayerPropReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSetPlayerNameRsp;
import emu.grasscutter.server.packet.send.PacketSetPlayerPropRsp;
import java.util.ArrayList;
import java.util.List;
@Opcodes(PacketOpcodes.SetPlayerPropReq)
public class HandlerSetPlayerPropReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
// Auto template
SetPlayerPropReq req = SetPlayerPropReq.parseFrom(payload);
Player player = session.getPlayer();
List<PropValue> propList = req.getPropListList();
for (int i = 0; i < propList.size(); i++) {
if (!player.setProperty(PlayerProperty.getPropById(propList.get(i).getType()), (int)propList.get(i).getVal())) {
session.send(new PacketSetPlayerPropRsp(1));
}
}
player.save();
session.send(new PacketSetPlayerPropRsp(0));
}
}

View File

@ -0,0 +1,19 @@
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.SetPlayerPropRspOuterClass;
import emu.grasscutter.net.proto.SetPlayerPropRspOuterClass.SetPlayerPropRsp;
public class PacketSetPlayerPropRsp extends BasePacket {
public PacketSetPlayerPropRsp(int retCode) {
super(PacketOpcodes.SetPlayerPropRsp);
SetPlayerPropRspOuterClass.SetPlayerPropRsp.Builder proto = SetPlayerPropRspOuterClass.SetPlayerPropRsp.newBuilder();
if (retCode != 0) {
proto.setRetcode(retCode);
}
this.setData(proto.build());
}
}