diff --git a/.gitignore b/.gitignore index 239309c12..9c374fa70 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ mongod.exe /*.sh language/ languages/ +gacha_mappings.js diff --git a/README.md b/README.md index 8119b3f30..bbef2f834 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ There is a dummy user named "Server" in every player's friends list that you can | kick | kick \ | server.kick | Both side | Kicks the specified player from the server. (WIP) | k | | killall | killall [playerUid] [sceneId] | server.killall | Both side | Kills all entities in the current scene or specified scene of the corresponding player. | | | list | list | | Both side | Lists online players. | | -| permission | permission \ \ | * | Both side | Grants or removes a permission for a user. | | +| permission | permission \ \ | * | Both side | Grants or removes a permission for a user. | | | position | position | | Client only | Sends your current coordinates. | pos | | reload | reload | server.reload | Both side | Reloads the server config | | | resetconst | resetconst [all] | player.resetconstellation | Client only | Resets the constellation level on your currently selected character, will need to relog after using the command to see any changes. | resetconstellation | diff --git a/README_zh-CN.md b/README_zh-CN.md index 4b4d57131..66878b4f4 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -126,7 +126,7 @@ chmod +x gradlew | kick | kick \ | server.kick | 均可使用 | 从服务器中踢出指定玩家 (WIP) | k | | killall | killall [uid] [场景ID] | server.killall | 均可使用 | 杀死指定玩家世界中所在或指定场景的全部生物 | | | list | list | | 均可使用 | 列出在线玩家 | | -| permission | permission <用户名> <权限节点> | * | 均可使用 | 添加或移除玩家的权限 | | +| permission | permission <权限节点> | * | 均可使用 | 添加或移除玩家的权限 | | | position | position | | 仅客户端 | 获取当前坐标 | pos | | reload | reload | server.reload | 均可使用 | 重载服务器配置 | | | resetconst | resetconst [all] | player.resetconstellation | 仅客户端 | 重置当前角色的命座,重新登录即可生效 | resetconstellation | diff --git a/data/gacha_records.html b/data/gacha_records.html index ef81edc4d..5ce8e660f 100644 --- a/data/gacha_records.html +++ b/data/gacha_records.html @@ -92,7 +92,7 @@ - + diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index dd24ac8a0..e7536b588 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -82,7 +82,9 @@ public final class Config { public int ServerAvatarId = 10000007; public int[] WelcomeEmotes = {2007, 1002, 4010}; public String WelcomeMotd = "Welcome to Grasscutter emu"; - public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n "; + public String WelcomeMailTitle = "Welcome to Grasscutter!"; + public String WelcomeMailSender = "Lawnmower"; + public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n"; public Mail.MailItem[] WelcomeMailItems = { new Mail.MailItem(13509, 1, 1), new Mail.MailItem(201, 10000, 1), diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index f322e5fdf..a1c8a5c7c 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -76,7 +76,7 @@ public final class Grasscutter { Tools.createGmHandbook(); return; } case "-gachamap" -> { - Tools.createGachaMapping(); return; + Tools.createGachaMapping("./gacha_mappings.js"); return; } } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index c2039dece..f3f2adc17 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -20,7 +20,7 @@ import java.util.regex.Matcher; public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals Pattern refineRegex = Pattern.compile("r(\\d+)"); - Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x))"); + Pattern amountRegex = Pattern.compile("((?<=x)\\d+|\\d+(?=x)(?!x\\d))"); private int matchIntOrNeg(Pattern pattern, String arg) { Matcher match = pattern.matcher(arg); diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index c267a9b74..76a7f1652 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -76,8 +76,6 @@ public class GameData { private static Map> shopGoods = new HashMap<>(); private static final IntList scenePointIdList = new IntArrayList(); - public static char EJWOA = 's'; - public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { Int2ObjectMap map = null; diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 97bd6d739..f63798988 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -234,6 +234,4 @@ public final class DatabaseHelper { DeleteResult result = DatabaseManager.getDatastore().delete(mail); return result.wasAcknowledged(); } - - public static char AWJVN = 'e'; } diff --git a/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java b/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java new file mode 100644 index 000000000..d8d6f25b0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java @@ -0,0 +1,227 @@ +package emu.grasscutter.game.managers.MotionManager; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.VectorOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketPlayerPropNotify; +import emu.grasscutter.utils.Position; + +import java.util.ArrayList; +import java.lang.Math; + +public class MotionManager { + + private enum Consumption { + None(0), + + // consumers + CLIMB_START(-500), + CLIMBING(-150), + CLIMB_JUMP(-2500), + DASH(-1800), + SPRINT(-360), + FLY(-60), + SWIM_DASH_START(-200), + SWIM_DASH(-200), + SWIMMING(-80), + + // restorers + STANDBY(500), + RUN(500), + WALK(500), + STANDBY_MOVE(500); + + public final int amount; + Consumption(int amount) { + this.amount = amount; + } + } + + private EntityMoveInfoOuterClass.EntityMoveInfo moveInfo; + + private MotionState previousState = MotionState.MOTION_STANDBY; + private ArrayList previousCoordinates = new ArrayList<>(); + private final Player player; + + private float landSpeed = 0; + + public MotionManager(Player player) { + previousCoordinates.add(new Position(0,0,0)); + this.player = player; + } + + public void handle(GameSession session, GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { + MotionState state = moveInfo.getMotionInfo().getState(); + setMoveInfo(moveInfo); + if (state == MotionState.MOTION_LAND_SPEED) { + setLandSpeed(moveInfo.getMotionInfo().getSpeed().getY()); + } + if (state == MotionState.MOTION_FALL_ON_GROUND) { + handleFallOnGround(session, entity); + } + } + + public void tick() { + if(Grasscutter.getConfig().OpenStamina){ + EntityMoveInfoOuterClass.EntityMoveInfo mInfo = moveInfo; + if (mInfo == null) { + return; + } + + MotionState state = moveInfo.getMotionInfo().getState(); + Consumption consumption = Consumption.None; + + boolean isMoving = false; + VectorOuterClass.Vector posVector = moveInfo.getMotionInfo().getPos(); + Position currentCoordinates = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); + + float diffX = currentCoordinates.getX() - previousCoordinates.get(0).getX(); + float diffY = currentCoordinates.getY() - previousCoordinates.get(0).getY(); + float diffZ = currentCoordinates.getZ() - previousCoordinates.get(0).getZ(); + + if (Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.3 || Math.abs(diffZ) > 0.3) { + isMoving = true; + } + + if (isMoving) { + // TODO: refactor these conditions. + // CLIMB + if (state == MotionState.MOTION_CLIMB) { + if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { + consumption = Consumption.CLIMB_START; + } else { + consumption = Consumption.CLIMBING; + } + } + // JUMP + if (state == MotionState.MOTION_CLIMB_JUMP) { + if (previousState != MotionState.MOTION_CLIMB_JUMP) { + consumption = Consumption.CLIMB_JUMP; + } + } + if (state == MotionState.MOTION_JUMP) { + if (previousState == MotionState.MOTION_CLIMB) { + consumption = Consumption.CLIMB_JUMP; + } + } + // SWIM + if (state == MotionState.MOTION_SWIM_MOVE) { + consumption = Consumption.SWIMMING; + } + if (state == MotionState.MOTION_SWIM_DASH) { + if (previousState != MotionState.MOTION_SWIM_DASH) { + consumption = Consumption.SWIM_DASH_START; + } else { + consumption = Consumption.SWIM_DASH; + } + } + // DASH + if (state == MotionState.MOTION_DASH) { + if (previousState == MotionState.MOTION_DASH) { + consumption = Consumption.SPRINT; + } else { + consumption = Consumption.DASH; + } + } + // RUN and WALK + if (state == MotionState.MOTION_RUN) { + consumption = Consumption.RUN; + } + if (state == MotionState.MOTION_WALK) { + consumption = Consumption.WALK; + } + // FLY + if (state == MotionState.MOTION_FLY) { + consumption = Consumption.FLY; + } + } + // STAND + if (state == MotionState.MOTION_STANDBY) { + consumption = Consumption.STANDBY; + } + if (state == MotionState.MOTION_STANDBY_MOVE) { + consumption = Consumption.STANDBY_MOVE; + } + + GameSession session = player.getSession(); + updateStamina(session, consumption.amount); + session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + + Grasscutter.getLogger().debug(session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + " " + state + " " + isMoving + " " + consumption + " " + consumption.amount); + + previousState = state; + previousCoordinates.add(currentCoordinates); + if (previousCoordinates.size() > 3) { + previousCoordinates.remove(0); + } + } + } + + public void updateStamina(GameSession session, int amount) { + if (amount == 0) { + return; + } + int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); + int newStamina = currentStamina + amount; + if (newStamina < 0) { + newStamina = 0; + } + if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + } + + public void setMoveInfo(EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { + this.moveInfo = moveInfo; + } + + public EntityMoveInfoOuterClass.EntityMoveInfo getMoveInfo() { + return moveInfo; + } + + public void handleFallOnGround(GameSession session, GameEntity entity) { + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float damage = 0; + Grasscutter.getLogger().debug("LandSpeed: " + landSpeed); + if (landSpeed < -23.5) { + damage = (float)(maxHP * 0.33); + } + if (landSpeed < -25) { + damage = (float)(maxHP * 0.5); + } + if (landSpeed < -26.5) { + damage = (float)(maxHP * 0.66); + } + if (landSpeed < -28) { + damage = (maxHP * 1); + } + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } + Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + if (newHP == 0) { + entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); + session.getPlayer().getScene().removeEntity(entity); + entity.onDeath(0); + } + } + + public void setLandSpeed(float landSpeed) { + this.landSpeed = landSpeed; + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 9099c04c9..971fbf2ac 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -21,6 +21,7 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; +import emu.grasscutter.game.managers.MotionManager.MotionManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -124,6 +125,8 @@ public class Player { @Transient private final InvokeHandler clientAbilityInitFinishHandler; private MapMarksManager mapMarksManager; + @Transient private MotionManager motionManager; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -164,6 +167,7 @@ public class Player { this.shopLimit = new ArrayList<>(); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); + this.motionManager = new MotionManager(this); } // On player creation @@ -191,6 +195,7 @@ public class Player { this.getRotation().set(0, 307, 0); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); + this.motionManager = new MotionManager(this); } public int getUid() { @@ -977,6 +982,8 @@ public class Player { return mapMarksManager; } + public MotionManager getMotionManager() { return motionManager; } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { @@ -1005,8 +1012,35 @@ public class Player { this.resetSendPlayerLocTime(); } } + + scheduleStaminaNotify(); } + private void scheduleStaminaNotify() { + // stamina tick + EntityMoveInfoOuterClass.EntityMoveInfo moveInfo = getMotionManager().getMoveInfo(); + if (moveInfo == null) { + return; + } + + if (getMotionManager().getMoveInfo().getMotionInfo().getState() == MotionStateOuterClass.MotionState.MOTION_STANDBY) { + if (getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) == getProperty(PlayerProperty.PROP_MAX_STAMINA) ) { + return; + } + } + + for (int i = 0; i <= 1000; i+=200) { + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + getMotionManager().tick(); + } + }, i); + } + } + + public void resetSendPlayerLocTime() { this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; } diff --git a/src/main/java/emu/grasscutter/languages/CNLanguage.java b/src/main/java/emu/grasscutter/languages/CNLanguage.java index 48123c778..ead3b4147 100644 --- a/src/main/java/emu/grasscutter/languages/CNLanguage.java +++ b/src/main/java/emu/grasscutter/languages/CNLanguage.java @@ -45,16 +45,25 @@ public final class CNLanguage { public String Invalid_playerId = "无效的玩家ID."; public String Player_not_found = "未找到此玩家."; public String Player_is_offline = "此玩家已离线."; + public String Invalid_amount = "无效的数量."; + public String Invalid_arguments = "无效的命令参数."; + public String Invalid_artifact_id = "无效的圣遗物ID."; + public String Invalid_avatar_id = "无效的角色ID."; + public String Invalid_avatar_level = "无效的角色等级."; + public String Invalid_entity_id = "无效的物品ID."; public String Invalid_item_id = "无效的物品ID."; - public String Invalid_item_or_player_id = "无效的玩家或物品ID."; + public String Invalid_item_level = "无效的物品等级."; + public String Invalid_item_refinement = "无效的精炼等级."; + public String Invalid_UID = "无效的UID."; public String Enabled = "启用"; public String Disabled = "禁用"; public String No_command_found = "未找到命令."; public String Help = "帮助"; public String Player_not_found_or_offline = "此玩家不存在或已离线."; - public String Invalid_arguments = "无效的参数."; public String Success = "成功"; - public String Invalid_entity_id = "无效的实体ID."; + public String Target_cleared = "已清除选择目标"; + public String Target_set = "接下来的命令将默认以 @{uid} 为目标。输入命令时不必继续携带UID参数。"; + public String Target_needed = "此命令需要指定一个目标用户. 输入命令时携带 <@UID> 参数或使用 /target @UID 来指定一个默认目标用户."; // Help public String Help_usage = " 用法: "; @@ -63,7 +72,6 @@ public final class CNLanguage { // Account public String Modify_user_account = "修改用户帐户"; - public String Invalid_UID = "无效的UID."; public String Account_exists = "账户已存在."; public String Account_create_UID = "UID为 {uid} 的账户已创建."; public String Account_delete = "已删除账户."; @@ -80,7 +88,7 @@ public final class CNLanguage { public String Change_screen = "切换到场景 "; public String Change_screen_not_exist = "此场景不存在。"; - // Clear + // Cleart_or_playerId public String Clear_weapons = "已清除 {name} 的武器."; public String Clear_artifacts = "已清除 {name} 的圣遗物 ."; public String Clear_materials = "已清除 {name} 的材料."; @@ -90,8 +98,9 @@ public final class CNLanguage { public String Clear_everything = "已清除 {name} 的所有物品."; // Coop - public String Coop_usage = "用法: coop <玩家ID> <房主的玩家ID>"; - + public String Coop_usage = "用法: coop <房主的UID>"; + public String Coop_success = "已将{target}拉进{host}的世界."; + // Drop public String Drop_usage = "用法: drop <物品ID|物品名> [数量]"; public String Drop_dropped_of = "已在地上丢弃 {amount} 个 {item}."; @@ -103,22 +112,17 @@ public final class CNLanguage { public String EnterDungeon_you_in_that_dungeon = "你已经在此副本中了。"; // GiveAll - public String GiveAll_usage = "用法: giveall [玩家] [数量]"; + public String GiveAll_usage = "用法: giveall [数量]"; public String GiveAll_item = "正在给予所有物品..."; public String GiveAll_done = "完成。"; - public String GiveAll_invalid_amount_or_playerId = "无效的数量或玩家ID"; // GiveArtifact public String GiveArtifact_usage = "用法: giveart|gart [玩家] <圣遗物Id> <主词条Id> [<副词条Id>[,<被强化次数>]]... [等级]"; - public String GiveArtifact_invalid_artifact_id = "无效的圣遗物Id."; public String GiveArtifact_given = "已将 {itemId} 给予 {target}."; // GiveChar public String GiveChar_usage = "用法: givechar <角色Id|角色名> [等级]"; public String GiveChar_given = "将等级为 {level} 的 {avatarId} 给予 {target}."; - public String GiveChar_invalid_avatar_id = "无效的角色ID"; - public String GiveChar_invalid_avatar_level = "无效的角色等级."; - public String GiveChar_invalid_avatar_or_player_id = "无效的角色ID或玩家ID."; // Give public String Give_usage = "用法: give [玩家名] <物品ID|物品名> [数量] [等级] "; @@ -129,7 +133,8 @@ public final class CNLanguage { public String Give_given_level = "已将 {amount} 个等级为 {lvl} 的 {item} 给与 {target}."; // GodMode - public String Godmode_status = "设置 {name} 的无敌模式为 {status} "; + public String Godmode_usage = "用法: godmode [on|off|toggle]"; + public String Godmode_status = "设置 {name} 的无敌模式为: {status} "; // Heal public String Heal_message = "所有角色已被治疗。"; @@ -151,7 +156,7 @@ public final class CNLanguage { public String List_message = "现有 {size} 名玩家在线:"; // Permission - public String Permission_usage = "用法: permission <用户名> <权限名>"; + public String Permission_usage = "用法: permission <权限名>"; public String Permission_add = "权限已添加。"; public String Permission_have_permission = "此玩家已拥有此权限!"; public String Permission_remove = "权限已移除。"; @@ -263,6 +268,7 @@ public final class CNLanguage { public String Talent_set_q = "设置元素爆发(q技能)等级为 {level}."; public String Talent_invalid_skill_id = "无效的技能ID。"; public String Talent_set_this = "技能等级已设为 {level}."; + public String Talent_set_id = "将技能 {id} 的等级设为 {level}."; public String Talent_invalid_talent_level = "无效的技能等级。"; public String Talent_normal_attack_id = "普通攻击技能ID {id}."; public String Talent_e_skill_id = "元素战技(e技能)ID {id}."; @@ -272,9 +278,7 @@ public final class CNLanguage { public String TeleportAll_message = "此命令仅在多人游戏下可用。"; // Teleport - public String Teleport_usage_server = "用法: /tp @<玩家ID> [场景ID]"; - public String Teleport_usage = "用法: /tp @<玩家ID,不指定则为你自己> [场景ID]"; - public String Teleport_specify_player_id = "你必须指定一个玩家。"; + public String Teleport_usage_server = "用法: /tp [场景ID]"; public String Teleport_invalid_position = "无效的位置。"; public String Teleport_message = "已将 {name} 传送到场景 {id} ,坐标 {x},{y},{z}"; diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java index 0a0c577d0..4d9eb57e8 100644 --- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -3,7 +3,6 @@ package emu.grasscutter.net.packet; public class PacketOpcodes { // Empty public static final int NONE = 0; - public static final char ONLWE = 'u'; // Opcodes public static final int AbilityChangeNotify = 1179; diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 196faf880..85f02a36d 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,14 +16,16 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; -import emu.grasscutter.server.http.gacha.GachaRecordHandler; -import emu.grasscutter.server.http.gcstatic.StaticFileHandler; +import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; import express.Express; +import io.javalin.http.staticfiles.Location; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -442,11 +444,18 @@ public final class DispatchServer { // webstatic-sea.hoyoverse.com httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); - // gacha record + // gacha record. + String gachaMappingsPath = Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); + // TODO: Only serve the html page and have a subsequent request to fetch the gacha data. httpServer.get("/gacha", new GachaRecordHandler()); + if(!(new File(gachaMappingsPath).exists())) { + Tools.createGachaMapping(gachaMappingsPath); + } - // static file provider - httpServer.get("/gcstatic/*", new StaticFileHandler()); + httpServer.raw().config.addSinglePageRoot("/gacha/mappings", gachaMappingsPath, Location.EXTERNAL); + + // static file support for plugins + httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); Grasscutter.getLogger().info(Grasscutter.getLanguage().Dispatch_start_server_port.replace("{port}", Integer.toString(httpServer.raw().port()))); diff --git a/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java similarity index 87% rename from src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java rename to src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java index 0798a150f..8676574bb 100644 --- a/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java @@ -1,4 +1,4 @@ -package emu.grasscutter.server.http.gacha; +package emu.grasscutter.server.dispatch.http; import java.io.File; import java.io.IOException; @@ -7,6 +7,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; import express.http.HttpContextHandler; import express.http.Request; import express.http.Response; @@ -14,7 +15,7 @@ import express.http.Response; public final class GachaRecordHandler implements HttpContextHandler { String render_template; public GachaRecordHandler() { - File template = new File(Grasscutter.getConfig().DATA_FOLDER + "gacha_records.html"); + File template = new File(Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_records.html")); if (template.exists()) { // Load from cache render_template = new String(FileUtils.read(template)); @@ -46,7 +47,7 @@ public final class GachaRecordHandler implements HttpContextHandler { res.send(response); } else { - res.send("404"); + res.send("No account found."); } } } diff --git a/src/main/java/emu/grasscutter/server/http/gcstatic/StaticFileHandler.java b/src/main/java/emu/grasscutter/server/http/gcstatic/StaticFileHandler.java deleted file mode 100644 index 0cdccb2c1..000000000 --- a/src/main/java/emu/grasscutter/server/http/gcstatic/StaticFileHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package emu.grasscutter.server.http.gcstatic; - -import java.io.File; -import java.io.IOException; - -import emu.grasscutter.Grasscutter; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -public final class StaticFileHandler implements HttpContextHandler { - String static_folder; - public StaticFileHandler() { - static_folder = Grasscutter.getConfig().RESOURCE_FOLDER + "/gcstatic"; - } - - @Override - public void handle(Request req, Response res) throws IOException { - // Grasscutter.getLogger().info( req.path()); - - String reqFilename = req.path().replace("/gcstatic", ""); // remove the leading path - reqFilename = reqFilename.replace("/../", "/./"); // security guard to prevent arbitrary read - File resFile = new File(static_folder + reqFilename); - if (resFile.exists()) { - res.sendFile(resFile.toPath()); - } else { - res.status(404); - res.send("404"); - } - } -} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 2d0ceac8b..878997e22 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,8 +1,9 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.managers.MotionManager.MotionManager; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.LifeState; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; @@ -11,13 +12,9 @@ import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; -import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; -import java.util.Arrays; -import java.util.Collection; - @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { @@ -35,7 +32,6 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { // Handle movement EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); - MotionState state = moveInfo.getMotionInfo().getState(); if (entity != null) { //move entity.getPosition().set(moveInfo.getMotionInfo().getPos()); @@ -43,56 +39,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); entity.setMotionState(moveInfo.getMotionInfo().getState()); - - if(Grasscutter.getConfig().OpenStamina){ - //consume stamina - int curStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int maxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - if (CONSUME_STAMINA_LIST.contains(state)) { - - //In the water exhausted stamina - - //Climbing the wall stays in place - - //Sprint in the water - if (state == MotionState.MOTION_SWIM_DASH) { - curStamina -= 700; - } - //wall jump - else if (state == MotionState.MOTION_CLIMB_JUMP) { - curStamina -= 2000; - } - //climb the wall slowly - else if (state == MotionState.MOTION_CLIMB) { - curStamina -= 800; - } - else if (state == MotionState.MOTION_DASH_BEFORE_SHAKE) { - curStamina -= 2500; - } - else { - curStamina -= 500; - } - - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, curStamina); - session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - break; - } - //restore stamina - if (RESTORE_STAMINA_LIST.contains(state)) { - if(state == MotionState.MOTION_STANDBY) { - Vector speed = moveInfo.getMotionInfo().getSpeed(); - if(speed.getX() != 0 && speed.getZ() != 0 && speed.getY() != 0) { - break; - } - } - curStamina += 1000; - if (curStamina >= maxStamina) { - curStamina = maxStamina; - } - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, curStamina); - session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - } - } + session.getPlayer().getMotionManager().handle(session, entity, moveInfo); } break; default: @@ -111,17 +58,5 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } } - private static MotionState[] consumeStaminaTypes = new MotionState[]{ - MotionState.MOTION_CLIMB, MotionState.MOTION_CLIMB_JUMP, MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_MOVE, MotionState.MOTION_FLY, MotionState.MOTION_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, MotionState.MOTION_FIGHT, MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY, - MotionState.MOTION_FLY_SLOW - }; - private static MotionState[] restoreStaminaTypes = new MotionState[]{ - MotionState.MOTION_STANDBY, MotionState.MOTION_RUN, MotionState.MOTION_WALK, - MotionState.MOTION_STANDBY_MOVE - }; - private static final Collection CONSUME_STAMINA_LIST = Arrays.asList(consumeStaminaTypes); - private static final Collection RESTORE_STAMINA_LIST = Arrays.asList(restoreStaminaTypes); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java new file mode 100644 index 000000000..d1db944d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) +public class HandlerEvtDoSkillSuccNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); + // TODO: Will be used for deducting stamina for charged skills. + + int caster = notify.getCasterId(); + int skill = notify.getSkillId(); + + // Grasscutter.getLogger().warn(caster + "\t" + skill); + +// session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify)); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index cae0cce65..2487df063 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -37,6 +37,13 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { return; } + // Make sure resources folder is set + if (!GameData.getAvatarDataMap().containsKey(avatarId)) { + Grasscutter.getLogger().error("No avatar data found! Please check your ExcelBinOutput folder."); + session.close(); + return; + } + String nickname = req.getNickName(); if (nickname == null) { nickname = "Traveler"; @@ -78,15 +85,11 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp)); // Default mail - char d = 'G'; - char e = 'r'; - char z = 'a'; - char u = 'c'; - char s = 't'; MailBuilder mailBuilder = new MailBuilder(player.getUid(), new Mail()); - mailBuilder.mail.mailContent.title = String.format("W%sl%som%s to %s%s%s%s%s%s%s%s%s%s%s!", DatabaseHelper.AWJVN, u, DatabaseHelper.AWJVN, d, e, z, GameData.EJWOA, GameData.EJWOA, u, PacketOpcodes.ONLWE, s, s, DatabaseHelper.AWJVN, e); - mailBuilder.mail.mailContent.sender = String.format("L%swnmow%s%s @ Gi%sH%sb", z, DatabaseHelper.AWJVN, e, s, PacketOpcodes.ONLWE); - mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent; + mailBuilder.mail.mailContent.title = Grasscutter.getConfig().GameServer.WelcomeMailTitle; + mailBuilder.mail.mailContent.sender = Grasscutter.getConfig().GameServer.WelcomeMailSender; + // Please credit Grasscutter if changing something here. We don't condone commercial use of the project. + mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent + "\n"; mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems)); mailBuilder.mail.importance = 1; player.sendMail(mailBuilder.mail); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index d7f5e986a..c4ccd2d2e 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -96,7 +96,7 @@ public final class Tools { } @SuppressWarnings("deprecation") - public static void createGachaMapping() throws Exception { + public static void createGachaMapping(String location) throws Exception { ResourceLoader.loadResources(); Map map; @@ -106,11 +106,7 @@ public final class Tools { List list; - - String fileName = Grasscutter.getConfig().RESOURCE_FOLDER + "/gcstatic"; - File folder = new File(fileName); - if (!folder.exists()) { folder.mkdirs(); } // create folder if it doesn't exist - fileName = fileName + "/mappings.js"; + String fileName = location; try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), StandardCharsets.UTF_8), false)) {