mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-10 18:33:20 +08:00
commit
833ea1b791
1
.gitignore
vendored
1
.gitignore
vendored
@ -67,3 +67,4 @@ mongod.exe
|
||||
/*.sh
|
||||
language/
|
||||
languages/
|
||||
gacha_mappings.js
|
||||
|
@ -125,7 +125,7 @@ There is a dummy user named "Server" in every player's friends list that you can
|
||||
| kick | kick \<player> | 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 <add\|remove> \<username> \<permission> | * | Both side | Grants or removes a permission for a user. | |
|
||||
| permission | permission <add\|remove> \<UID> \<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 |
|
||||
|
@ -126,7 +126,7 @@ chmod +x gradlew
|
||||
| kick | kick \<uid> | server.kick | 均可使用 | 从服务器中踢出指定玩家 (WIP) | k |
|
||||
| killall | killall [uid] [场景ID] | server.killall | 均可使用 | 杀死指定玩家世界中所在或指定场景的全部生物 | |
|
||||
| list | list | | 均可使用 | 列出在线玩家 | |
|
||||
| permission | permission <add\|remove> <用户名> <权限节点> | * | 均可使用 | 添加或移除玩家的权限 | |
|
||||
| permission | permission <add\|remove> <UID> <权限节点> | * | 均可使用 | 添加或移除玩家的权限 | |
|
||||
| position | position | | 仅客户端 | 获取当前坐标 | pos |
|
||||
| reload | reload | server.reload | 均可使用 | 重载服务器配置 | |
|
||||
| resetconst | resetconst [all] | player.resetconstellation | 仅客户端 | 重置当前角色的命座,重新登录即可生效 | resetconstellation |
|
||||
|
@ -92,7 +92,7 @@
|
||||
<!-- This file could be generated automatically using `java -jar grasscutter.jar -gachamap` -->
|
||||
<!-- You can also modify the file manually to customize it -->
|
||||
<!-- Otherwise you may onle see number IDs in the gacha record -->
|
||||
<script type="text/javascript" src="/gcstatic/mappings.js"></script>
|
||||
<script type="text/javascript" src="/gacha/mappings"></script>
|
||||
<script>
|
||||
mappings['default'] = mappings['en-us']; // make en-us as default/fallback option
|
||||
</script>
|
||||
|
@ -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<type=\"browser\" text=\"Discord\" href=\"https://discord.gg/T5vZU6UyeG\"/> <type=\"browser\" text=\"GitHub\" href=\"https://github.com/Melledy/Grasscutter\"/>";
|
||||
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<type=\"browser\" text=\"Discord\" href=\"https://discord.gg/T5vZU6UyeG\"/>";
|
||||
public Mail.MailItem[] WelcomeMailItems = {
|
||||
new Mail.MailItem(13509, 1, 1),
|
||||
new Mail.MailItem(201, 10000, 1),
|
||||
|
@ -76,7 +76,7 @@ public final class Grasscutter {
|
||||
Tools.createGmHandbook(); return;
|
||||
}
|
||||
case "-gachamap" -> {
|
||||
Tools.createGachaMapping(); return;
|
||||
Tools.createGachaMapping("./gacha_mappings.js"); return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -76,8 +76,6 @@ public class GameData {
|
||||
private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
|
||||
private static final IntList scenePointIdList = new IntArrayList();
|
||||
|
||||
public static char EJWOA = 's';
|
||||
|
||||
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
|
||||
Int2ObjectMap<?> map = null;
|
||||
|
||||
|
@ -234,6 +234,4 @@ public final class DatabaseHelper {
|
||||
DeleteResult result = DatabaseManager.getDatastore().delete(mail);
|
||||
return result.wasAcknowledged();
|
||||
}
|
||||
|
||||
public static char AWJVN = 'e';
|
||||
}
|
||||
|
@ -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<Position> 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;
|
||||
}
|
||||
}
|
@ -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<AbilityInvokeEntry> 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;
|
||||
}
|
||||
|
@ -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 <p玩家> <角色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 <add|remove> <用户名> <权限名>";
|
||||
public String Permission_usage = "用法: permission <add|remove> <权限名>";
|
||||
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> <x> <y> <z> [场景ID]";
|
||||
public String Teleport_usage = "用法: /tp @<玩家ID,不指定则为你自己> <x> <y> <z> [场景ID]";
|
||||
public String Teleport_specify_player_id = "你必须指定一个玩家。";
|
||||
public String Teleport_usage_server = "用法: /tp <x> <y> <z> [场景ID]";
|
||||
public String Teleport_invalid_position = "无效的位置。";
|
||||
public String Teleport_message = "已将 {name} 传送到场景 {id} ,坐标 {x},{y},{z}";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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())));
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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<MotionState> CONSUME_STAMINA_LIST = Arrays.asList(consumeStaminaTypes);
|
||||
private static final Collection<MotionState> RESTORE_STAMINA_LIST = Arrays.asList(restoreStaminaTypes);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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<type=\"browser\" text=\"GitHub\" href=\"https://github.com/Melledy/Grasscutter\"/>";
|
||||
mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems));
|
||||
mailBuilder.mail.importance = 1;
|
||||
player.sendMail(mailBuilder.mail);
|
||||
|
@ -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<Long, String> map;
|
||||
@ -106,11 +106,7 @@ public final class Tools {
|
||||
|
||||
List<Integer> 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)) {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user