Commit ccdce684 authored by Akka's avatar Akka
Browse files

Merge branch 'tower' of https://github.com/Akka0/Grasscutter into tower

parents d468edcf 219a8508
package emu.grasscutter.game.managers.StaminaManager;
public interface AfterUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New Stamina value.
*/
void onAfterUpdateStamina(String reason, int newStamina);
}
package emu.grasscutter.game.managers.StaminaManager;
public interface BeforeUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
* @param reason Why updating stamina.
* @param newStamina New ABSOLUTE stamina value.
* @return true if you want to cancel this update, otherwise false.
*/
int onBeforeUpdateStamina(String reason, int newStamina);
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
* @param reason Why updating stamina.
* @param consumption ConsumptionType and RELATIVE stamina change amount.
* @return true if you want to cancel this update, otherwise false.
*/
Consumption onBeforeUpdateStamina(String reason, Consumption consumption);
}
\ No newline at end of file
...@@ -10,10 +10,10 @@ public enum ConsumptionType { ...@@ -10,10 +10,10 @@ public enum ConsumptionType {
SPRINT(-1800), SPRINT(-1800),
DASH(-360), DASH(-360),
FLY(-60), FLY(-60),
SWIM_DASH_START(-200), SWIM_DASH_START(-20),
SWIM_DASH(-200), SWIM_DASH(-204),
SWIMMING(-80), SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height.
FIGHT(0), FIGHT(0), // See StaminaManager.getFightConsumption()
// restore // restore
STANDBY(500), STANDBY(500),
......
# Stamina Manager
---
## UpdateStamina
```java
// will use consumption.consumptionType as reason
public int updateStaminaRelative(GameSession session, Consumption consumption);
```
```java
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina)
```
---
## Pause and Resume
```java
public void startSustainedStaminaHandler()
```
```java
public void stopSustainedStaminaHandler()
```
---
## Stamina change listeners and intercepting
### BeforeUpdateStaminaListener
```java
import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener;
// Listener sample: plugin disable CLIMB_JUMP stamina cost.
private class MyClass implements BeforeUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener.
public MyClass() {
getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this);
}
@Override
public boolean onBeforeUpdateStamina(String reason, int newStamina) {
// do not intercept this update
return false;
}
@Override
public boolean onBeforeUpdateStamina(String reason, Consumption consumption) {
// Try to intercept if this update is CLIMB_JUMP
if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) {
return true;
}
// If it is not CLIMB_JUMP, do not intercept.
return false;
}
}
```
### AfterUpdateStaminaListener
```java
import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener;
// Listener sample: plugin listens for changes already made.
private class MyClass implements AfterUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener.
public MyClass() {
registerAfterUpdateStaminaListener("myClass", this);
}
@Override
public void onAfterUpdateStamina(String reason, int newStamina) {
// ...
}
}
```
\ No newline at end of file
...@@ -49,22 +49,23 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { ...@@ -49,22 +49,23 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity);
// TODO: handle MOTION_FIGHT landing // TODO: handle MOTION_FIGHT landing which has a different damage factor
// For plunge attacks, LAND_SPEED is always -30 and is not useful. // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful.
// May need the height when starting plunge attack. // May need the height when starting plunge attack.
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets.
// Cache land speed for later use.
if (motionState == MotionState.MOTION_LAND_SPEED) {
cachedLandingSpeed = motionInfo.getSpeed().getY();
cachedLandingTimeMillisecond = System.currentTimeMillis();
monitorLandingEvent = true;
}
if (monitorLandingEvent) { if (monitorLandingEvent) {
if (motionState == MotionState.MOTION_FALL_ON_GROUND) { if (motionState == MotionState.MOTION_FALL_ON_GROUND) {
monitorLandingEvent = false; monitorLandingEvent = false;
handleFallOnGround(session, entity, motionState); handleFallOnGround(session, entity, motionState);
} }
} }
if (motionState == MotionState.MOTION_LAND_SPEED) {
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packet. Cache land speed for later use.
cachedLandingSpeed = motionInfo.getSpeed().getY();
cachedLandingTimeMillisecond = System.currentTimeMillis();
monitorLandingEvent = true;
}
} }
break; break;
default: default:
...@@ -84,33 +85,42 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { ...@@ -84,33 +85,42 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
} }
private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) {
// If not received immediately after MOTION_LAND_SPEED, discard this packet. // People have reported that after plunge attack (client sends a FIGHT instead of FALL_ON_GROUND) they will die
// if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping again.
// A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet.
// 200ms seems to be a reasonable delay.
int maxDelay = 200; int maxDelay = 200;
long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond;
Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : "")); Grasscutter.getLogger().trace("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : ""));
if (actualDelay > maxDelay) { if (actualDelay > maxDelay) {
return; return;
} }
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float damage = 0; float damageFactor = 0;
if (cachedLandingSpeed < -23.5) { if (cachedLandingSpeed < -23.5) {
damage = (float) (maxHP * 0.33); damageFactor = 0.33f;
} }
if (cachedLandingSpeed < -25) { if (cachedLandingSpeed < -25) {
damage = (float) (maxHP * 0.5); damageFactor = 0.5f;
} }
if (cachedLandingSpeed < -26.5) { if (cachedLandingSpeed < -26.5) {
damage = (float) (maxHP * 0.66); damageFactor = 0.66f;
} }
if (cachedLandingSpeed < -28) { if (cachedLandingSpeed < -28) {
damage = (maxHP * 1); damageFactor = 1f;
} }
float damage = maxHP * damageFactor;
float newHP = currentHP - damage; float newHP = currentHP - damage;
if (newHP < 0) { if (newHP < 0) {
newHP = 0; newHP = 0;
} }
Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\t" + "\tDamage: " + damage + "\tnewHP: " + newHP); if (damageFactor > 0) {
Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\tLandingSpeed: " + cachedLandingSpeed +
"\tDamageFactor: " + damageFactor + "\tDamage: " + damage + "\tNewHP: " + newHP);
} else {
Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage");
}
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
if (newHP == 0) { if (newHP == 0) {
......
...@@ -95,17 +95,20 @@ ...@@ -95,17 +95,20 @@
"create": "已建立账号,UID 为 %s 。", "create": "已建立账号,UID 为 %s 。",
"delete": "账号已刪除。", "delete": "账号已刪除。",
"no_account": "账号不存在。", "no_account": "账号不存在。",
"command_usage": "用法:account <create|delete> <username> [uid]" "command_usage": "用法:account <create|delete> <username> [uid]",
"description": "创建或删除账号。"
}, },
"broadcast": { "broadcast": {
"command_usage": "用法:broadcast <消息>", "command_usage": "用法:broadcast <消息>",
"message_sent": "公告已发送。" "message_sent": "公告已发送。",
"description": "向所有玩家发送公告。"
}, },
"changescene": { "changescene": {
"usage": "用法:changescene <scene id>", "usage": "用法:changescene <scene id>",
"already_in_scene": "你已经在这个秘境中了。", "already_in_scene": "你已经在这个秘境中了。",
"success": "已切换至秘境 %s.", "success": "已切换至秘境 %s.",
"exists_error": "此秘境不存在。" "exists_error": "此秘境不存在。",
"description": "切换指定秘境。"
}, },
"clear": { "clear": {
"command_usage": "用法: clear <all|wp|art|mat>", "command_usage": "用法: clear <all|wp|art|mat>",
...@@ -115,35 +118,41 @@ ...@@ -115,35 +118,41 @@
"furniture": "已将 %s 的尘歌壶家具清空。", "furniture": "已将 %s 的尘歌壶家具清空。",
"displays": "已清除 %s 的显示。", "displays": "已清除 %s 的显示。",
"virtuals": "已将 %s 的所有货币和经验值清空。", "virtuals": "已将 %s 的所有货币和经验值清空。",
"everything": "已将 %s 的所有物品清空。" "everything": "已将 %s 的所有物品清空。",
"description": "从您的背包中删除所有未装备且已解锁的物品,包括稀有物品。"
}, },
"coop": { "coop": {
"usage": "用法:coop <playerId> <target playerId>", "usage": "用法:coop <playerId> <target playerId>",
"success": "已强制召唤 %s 到 %s的世界" "success": "已强制召唤 %s 到 %s的世界",
"description": "强制召唤指定用户到他人的世界。"
}, },
"enter_dungeon": { "enter_dungeon": {
"usage": "用法:enterdungeon <dungeon id>", "usage": "用法:enterdungeon <dungeon id>",
"changed": "已进入秘境 %s", "changed": "已进入秘境 %s",
"not_found_error": "此秘境不存在。", "not_found_error": "此秘境不存在。",
"in_dungeon_error": "你已经在秘境中了。" "in_dungeon_error": "你已经在秘境中了。",
"description": "进入指定秘境。"
}, },
"giveAll": { "giveAll": {
"usage": "用法:giveall [player] [amount]", "usage": "用法:giveall [player] [amount]",
"started": "正在给予全部物品...", "started": "正在给予全部物品...",
"success": "已给予全部物品。", "success": "已给予全部物品。",
"invalid_amount_or_playerId": "无效的数量/玩家ID。" "invalid_amount_or_playerId": "无效的数量/玩家ID。",
"description": "给予所有物品。"
}, },
"giveArtifact": { "giveArtifact": {
"usage": "用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", "usage": "用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]",
"id_error": "无效的圣遗物ID。", "id_error": "无效的圣遗物ID。",
"success": "已将 %s 给予 %s。" "success": "已将 %s 给予 %s。",
"description": "给予指定圣遗物。"
}, },
"giveChar": { "giveChar": {
"usage": "用法:givechar <player> <itemId|itemName> [amount]", "usage": "用法:givechar <player> <itemId|itemName> [amount]",
"given": "给予角色 %s 等级 %s 向UID %s.", "given": "给予角色 %s 等级 %s 向UID %s.",
"invalid_avatar_id": "无效的角色ID。", "invalid_avatar_id": "无效的角色ID。",
"invalid_avatar_level": "无效的角色等級。.", "invalid_avatar_level": "无效的角色等級。.",
"invalid_avatar_or_player_id": "无效的角色ID/玩家ID。" "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。",
"description": "给予指定角色。"
}, },
"give": { "give": {
"usage": "用法:give <player> <itemId|itemName> [amount] [level] [refinement]", "usage": "用法:give <player> <itemId|itemName> [amount] [level] [refinement]",
...@@ -151,29 +160,36 @@ ...@@ -151,29 +160,36 @@
"refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。", "refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。",
"given": "已将 %s 个 %s 给予 %s。", "given": "已将 %s 个 %s 给予 %s。",
"given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s",
"given_level": "已将 %s 等级 %s %s 个给予UID %s" "given_level": "已将 %s 等级 %s %s 个给予UID %s",
"description": "给予指定物品。"
}, },
"godmode": { "godmode": {
"success": "上帝模式已被设置为 %s 。 [用户:%s]" "success": "上帝模式已被设置为 %s 。 [用户:%s]",
"description": "防止你受到伤害。"
}, },
"heal": { "heal": {
"success": "所有角色已被治疗。" "success": "所有角色已被治疗。",
"description": "治疗所选队伍的角色。"
}, },
"kick": { "kick": {
"player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出",
"server_kick_player": "正在踢出玩家 [%s:%s]" "server_kick_player": "正在踢出玩家 [%s:%s]",
"description": "从服务器内踢出指定玩家。"
}, },
"kill": { "kill": {
"usage": "用法:killall [playerUid] [sceneId]", "usage": "用法:killall [playerUid] [sceneId]",
"scene_not_found_in_player_world": "未在玩家世界中找到此场景", "scene_not_found_in_player_world": "未在玩家世界中找到此场景",
"kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]" "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]",
"description": "杀死所有怪物"
}, },
"killCharacter": { "killCharacter": {
"usage": "用法:/killcharacter [playerId]", "usage": "用法:/killcharacter [playerId]",
"success": "已杀死 %s 目前使用的角色。" "success": "已杀死 %s 目前使用的角色。",
"description": "杀死目前使用的角色"
}, },
"list": { "list": {
"success": "目前在线人数:%s" "success": "目前在线人数:%s",
"description": "查看所有玩家"
}, },
"permission": { "permission": {
"usage": "用法:permission <add|remove> <username> <permission>", "usage": "用法:permission <add|remove> <username> <permission>",
...@@ -181,21 +197,26 @@ ...@@ -181,21 +197,26 @@
"has_error": "此玩家已拥有此权限!", "has_error": "此玩家已拥有此权限!",
"remove": "权限已移除。", "remove": "权限已移除。",
"not_have_error": "此玩家未拥有权限!", "not_have_error": "此玩家未拥有权限!",
"account_error": "账号不存在!" "account_error": "账号不存在!",
"description": "给予或移除指定玩家的权限。"
}, },
"position": { "position": {
"success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d" "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d",
"description": "获取所在位置。"
}, },
"reload": { "reload": {
"reload_start": "正在重载配置文件和数据。", "reload_start": "正在重载配置文件和数据。",
"reload_done": "重载完毕。" "reload_done": "重载完毕。",
"description": "重载配置文件和数据。"
}, },
"resetConst": { "resetConst": {
"reset_all": "重置所有角色的命座。", "reset_all": "重置所有角色的命座。",
"success": "已重置 %s 的命座,重新登录后将会生效。" "success": "已重置 %s 的命座,重新登录后将会生效。",
"description": "重置当前角色的命之座,执行命令后需重新登录以生效。"
}, },
"resetShopLimit": { "resetShopLimit": {
"usage": "用法:/resetshop <player id>" "usage": "用法:/resetshop <player id>",
"description": "重置所选玩家的商店刷新时间。"
}, },
"sendMail": { "sendMail": {
"usage": "用法:give [player] <itemId|itemName> [amount]", "usage": "用法:give [player] <itemId|itemName> [amount]",
...@@ -217,17 +238,20 @@ ...@@ -217,17 +238,20 @@
"message": "<正文>", "message": "<正文>",
"sender": "<发件人>", "sender": "<发件人>",
"arguments": "<itemId|itemName|finish> [数量] [等级]", "arguments": "<itemId|itemName|finish> [数量] [等级]",
"error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。" "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。",
"description": "向指定用户发送邮件。 此命令的用法可根据附加的参数而变化。"
}, },
"sendMessage": { "sendMessage": {
"usage": "用法:sendmessage <player> <message>", "usage": "用法:sendmessage <player> <message>",
"success": "消息已发送。" "success": "消息已发送。",
"description": "向指定玩家发送消息"
}, },
"setFetterLevel": { "setFetterLevel": {
"usage": "用法:setfetterlevel <level>", "usage": "用法:setfetterlevel <level>",
"range_error": "好感度等级必须在 0 到 10 之间。", "range_error": "好感度等级必须在 0 到 10 之间。",
"fetter_set_level": "好感度已设置为 %s 级", "fetter_set_level": "好感度已设置为 %s 级",
"level_error": "无效的好感度等级。" "level_error": "无效的好感度等级。",
"description": "设置当前角色的好感度等级。"
}, },
"setStats": { "setStats": {
"usage_console": "用法:setstats|stats @<UID> <stat> <value>", "usage_console": "用法:setstats|stats @<UID> <stat> <value>",
...@@ -238,20 +262,24 @@ ...@@ -238,20 +262,24 @@
"player_error": "玩家不存在或已离线。", "player_error": "玩家不存在或已离线。",
"set_self": "%s 已经设置为 %s。", "set_self": "%s 已经设置为 %s。",
"set_for_uid": "%s 的使用者 %s 更改为 %s。", "set_for_uid": "%s 的使用者 %s 更改为 %s。",
"set_max_hp": "最大生命值更改为 %s。" "set_max_hp": "最大生命值更改为 %s。",
"description": "设置当前角色的属性。"
}, },
"setWorldLevel": { "setWorldLevel": {
"usage": "用法:setworldlevel <level>", "usage": "用法:setworldlevel <level>",
"value_error": "世界等级必须设置在0-8之间。", "value_error": "世界等级必须设置在0-8之间。",
"success": "已将世界等级设为%s。", "success": "已将世界等级设为%s。",
"invalid_world_level": "无效的世界等级。" "invalid_world_level": "无效的世界等级。",
"description": "设置世界等级,执行命令后需重新登录以生效。"
}, },
"spawn": { "spawn": {
"usage": "用法:spawn <entityId> [amount] [level(仅限怪物]", "usage": "用法:spawn <entityId> [amount] [level(仅限怪物]",
"success": "已生成 %s 个 %s。" "success": "已生成 %s 个 %s。",
"description": "在你附近生成一个生物。"
}, },
"stop": { "stop": {
"success": "正在关闭服务器..." "success": "正在关闭服务器...",
"description": "停止服务器"
}, },
"talent": { "talent": {
"usage_1": "设置天赋等级:/talent set <talentID> <value>", "usage_1": "设置天赋等级:/talent set <talentID> <value>",
...@@ -267,32 +295,41 @@ ...@@ -267,32 +295,41 @@
"invalid_level": "无效的天赋等级。", "invalid_level": "无效的天赋等级。",
"normal_attack_id": "普通攻击的 ID 为 %s。", "normal_attack_id": "普通攻击的 ID 为 %s。",
"e_skill_id": "元素战技ID %s。", "e_skill_id": "元素战技ID %s。",
"q_skill_id": "元素爆发ID %s。" "q_skill_id": "元素爆发ID %s。",
"description": "设置当前角色的天赋等级。"
}, },
"teleportAll": { "teleportAll": {
"success": "已将全部玩家传送到你的位置", "success": "已将全部玩家传送到你的位置",
"error": "命令仅限处于多人游戏状态下使用。" "error": "命令仅限处于多人游戏状态下使用。",
"description": "将你世界中的所有玩家传送到你所在的位置。"
}, },
"teleport": { "teleport": {
"usage_server": "用法:/tp @<player id> <x> <y> <z> [scene id]", "usage_server": "用法:/tp @<player id> <x> <y> <z> [scene id]",
"usage": "用法:/tp [@<player id>] <x> <y> <z> [scene id]", "usage": "用法:/tp [@<player id>] <x> <y> <z> [scene id]",
"specify_player_id": "你必须指定一个玩家ID。", "specify_player_id": "你必须指定一个玩家ID。",
"invalid_position": "无效的位置。", "invalid_position": "无效的位置。",
"success": "传送 %s 到坐标 %s,%s,%s,场景为 %s" "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s",
"description": "改变指定玩家的位置。"
}, },
"weather": { "weather": {
"usage": "用法:weather <weatherId> [climateId]", "usage": "用法:weather <weatherId> [climateId]",
"success": "已将当前天气设定为 %s,气候为 %s。", "success": "已将当前天气设定为 %s,气候为 %s。",
"invalid_id": "无效的天气ID。" "invalid_id": "无效的天气ID。",
"description": "改变天气"
}, },
"drop": { "drop": {
"command_usage": "用法:drop <itemId|itemName> [amount]", "command_usage": "用法:drop <itemId|itemName> [amount]",
"success": "已将 %s x %s 丟在附近。" "success": "已将 %s x %s 丟在附近。",
"description": "在你附近丢一个物品。"
}, },
"help": { "help": {
"usage": "用法:", "usage": "用法:",
"aliases": "別名:", "aliases": "別名:",
"available_commands": "可用指令:" "available_commands": "可用指令:",
"description": "发送帮助信息或显示指定命令的信息。"
},
"restart": {
"description": "重新启动服务器。"
} }
} }
} }
<Configuration> <Configuration>
<variable name="LOG_LEVEL" value="${LOG_LEVEL:-INFO}" />
<appender name="STDOUT" class="emu.grasscutter.utils.JlineLogbackAppender"> <appender name="STDOUT" class="emu.grasscutter.utils.JlineLogbackAppender">
<encoder> <encoder>
<pattern>[%d{HH:mm:ss}] [%highlight(%level)] %msg%n</pattern> <pattern>[%d{HH:mm:ss}] [%highlight(%level)] %msg%n</pattern>
...@@ -14,7 +16,10 @@ ...@@ -14,7 +16,10 @@
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n</pattern> <pattern>%d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n</pattern>
</encoder> </encoder>
</appender> </appender>
<logger name="org.reflections" level="OFF"/>
<logger name="org.reflections" level="OFF" />
<logger name="emu.grasscutter" level="${LOG_LEVEL}" />
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
<appender-ref ref="FILE" /> <appender-ref ref="FILE" />
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment