Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
ziqian zhang
Grasscutter
Commits
ccdce684
Commit
ccdce684
authored
May 08, 2022
by
Akka
Browse files
Merge branch 'tower' of
https://github.com/Akka0/Grasscutter
into tower
parents
d468edcf
219a8508
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java
0 → 100644
View file @
ccdce684
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
);
}
src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java
0 → 100644
View file @
ccdce684
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
src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java
View file @
ccdce684
...
...
@@ -10,10 +10,10 @@ public enum ConsumptionType {
SPRINT
(-
1800
),
DASH
(-
360
),
FLY
(-
60
),
SWIM_DASH_START
(-
20
0
),
SWIM_DASH
(-
20
0
),
SWIMMING
(-
80
),
FIGHT
(
0
),
SWIM_DASH_START
(-
20
),
SWIM_DASH
(-
20
4
),
SWIMMING
(-
80
),
// TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height.
FIGHT
(
0
),
// See StaminaManager.getFightConsumption()
// restore
STANDBY
(
500
),
...
...
src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md
0 → 100644
View file @
ccdce684
# 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
src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java
View file @
ccdce684
This diff is collapsed.
Click to expand it.
src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java
View file @
ccdce684
...
...
@@ -49,22 +49,23 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
session
.
getPlayer
().
getStaminaManager
().
handleCombatInvocationsNotify
(
session
,
moveInfo
,
entity
);
// TODO: handle MOTION_FIGHT landing
//
F
or plunge attacks, LAND_SPEED is always -30 and is not useful.
// May need the height when starting plunge attack.
// TODO: handle MOTION_FIGHT landing
which has a different damage factor
//
Also, f
or plunge attacks, LAND_SPEED is always -30 and is not useful.
//
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
(
motionState
==
MotionState
.
MOTION_FALL_ON_GROUND
)
{
monitorLandingEvent
=
false
;
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
;
default
:
...
...
@@ -84,33 +85,42 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
}
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
;
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
)
{
return
;
}
float
currentHP
=
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
);
float
maxHP
=
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
);
float
damage
=
0
;
float
damage
Factor
=
0
;
if
(
cachedLandingSpeed
<
-
23.5
)
{
damage
=
(
float
)
(
maxHP
*
0.33
)
;
damage
Factor
=
0.33
f
;
}
if
(
cachedLandingSpeed
<
-
25
)
{
damage
=
(
float
)
(
maxHP
*
0.5
)
;
damage
Factor
=
0.5
f
;
}
if
(
cachedLandingSpeed
<
-
26.5
)
{
damage
=
(
float
)
(
maxHP
*
0.66
)
;
damage
Factor
=
0.66
f
;
}
if
(
cachedLandingSpeed
<
-
28
)
{
damage
=
(
maxHP
*
1
)
;
damage
Factor
=
1
f
;
}
float
damage
=
maxHP
*
damageFactor
;
float
newHP
=
currentHP
-
damage
;
if
(
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
.
getWorld
().
broadcastPacket
(
new
PacketEntityFightPropUpdateNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
if
(
newHP
==
0
)
{
...
...
src/main/resources/languages/zh-CN.json
View file @
ccdce684
...
...
@@ -95,17 +95,20 @@
"create"
:
"已建立账号,UID 为 %s 。"
,
"delete"
:
"账号已刪除。"
,
"no_account"
:
"账号不存在。"
,
"command_usage"
:
"用法:account <create|delete> <username> [uid]"
"command_usage"
:
"用法:account <create|delete> <username> [uid]"
,
"description"
:
"创建或删除账号。"
},
"broadcast"
:
{
"command_usage"
:
"用法:broadcast <消息>"
,
"message_sent"
:
"公告已发送。"
"message_sent"
:
"公告已发送。"
,
"description"
:
"向所有玩家发送公告。"
},
"changescene"
:
{
"usage"
:
"用法:changescene <scene id>"
,
"already_in_scene"
:
"你已经在这个秘境中了。"
,
"success"
:
"已切换至秘境 %s."
,
"exists_error"
:
"此秘境不存在。"
"exists_error"
:
"此秘境不存在。"
,
"description"
:
"切换指定秘境。"
},
"clear"
:
{
"command_usage"
:
"用法: clear <all|wp|art|mat>"
,
...
...
@@ -115,35 +118,41 @@
"furniture"
:
"已将 %s 的尘歌壶家具清空。"
,
"displays"
:
"已清除 %s 的显示。"
,
"virtuals"
:
"已将 %s 的所有货币和经验值清空。"
,
"everything"
:
"已将 %s 的所有物品清空。"
"everything"
:
"已将 %s 的所有物品清空。"
,
"description"
:
"从您的背包中删除所有未装备且已解锁的物品,包括稀有物品。"
},
"coop"
:
{
"usage"
:
"用法:coop <playerId> <target playerId>"
,
"success"
:
"已强制召唤 %s 到 %s的世界"
"success"
:
"已强制召唤 %s 到 %s的世界"
,
"description"
:
"强制召唤指定用户到他人的世界。"
},
"enter_dungeon"
:
{
"usage"
:
"用法:enterdungeon <dungeon id>"
,
"changed"
:
"已进入秘境 %s"
,
"not_found_error"
:
"此秘境不存在。"
,
"in_dungeon_error"
:
"你已经在秘境中了。"
"in_dungeon_error"
:
"你已经在秘境中了。"
,
"description"
:
"进入指定秘境。"
},
"giveAll"
:
{
"usage"
:
"用法:giveall [player] [amount]"
,
"started"
:
"正在给予全部物品..."
,
"success"
:
"已给予全部物品。"
,
"invalid_amount_or_playerId"
:
"无效的数量/玩家ID。"
"invalid_amount_or_playerId"
:
"无效的数量/玩家ID。"
,
"description"
:
"给予所有物品。"
},
"giveArtifact"
:
{
"usage"
:
"用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]"
,
"id_error"
:
"无效的圣遗物ID。"
,
"success"
:
"已将 %s 给予 %s。"
"success"
:
"已将 %s 给予 %s。"
,
"description"
:
"给予指定圣遗物。"
},
"giveChar"
:
{
"usage"
:
"用法:givechar <player> <itemId|itemName> [amount]"
,
"given"
:
"给予角色 %s 等级 %s 向UID %s."
,
"invalid_avatar_id"
:
"无效的角色ID。"
,
"invalid_avatar_level"
:
"无效的角色等級。."
,
"invalid_avatar_or_player_id"
:
"无效的角色ID/玩家ID。"
"invalid_avatar_or_player_id"
:
"无效的角色ID/玩家ID。"
,
"description"
:
"给予指定角色。"
},
"give"
:
{
"usage"
:
"用法:give <player> <itemId|itemName> [amount] [level] [refinement]"
,
...
...
@@ -151,29 +160,36 @@
"refinement_must_between_1_and_5"
:
"精炼等阶必须在 1 到 5 之间。"
,
"given"
:
"已将 %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"
:
{
"success"
:
"上帝模式已被设置为 %s 。 [用户:%s]"
"success"
:
"上帝模式已被设置为 %s 。 [用户:%s]"
,
"description"
:
"防止你受到伤害。"
},
"heal"
:
{
"success"
:
"所有角色已被治疗。"
"success"
:
"所有角色已被治疗。"
,
"description"
:
"治疗所选队伍的角色。"
},
"kick"
:
{
"player_kick_player"
:
"玩家 [%s:%s] 已将 [%s:%s] 踢出"
,
"server_kick_player"
:
"正在踢出玩家 [%s:%s]"
"server_kick_player"
:
"正在踢出玩家 [%s:%s]"
,
"description"
:
"从服务器内踢出指定玩家。"
},
"kill"
:
{
"usage"
:
"用法:killall [playerUid] [sceneId]"
,
"scene_not_found_in_player_world"
:
"未在玩家世界中找到此场景"
,
"kill_monsters_in_scene"
:
"已杀死 %s 个怪物。 [场景ID: %s]"
"kill_monsters_in_scene"
:
"已杀死 %s 个怪物。 [场景ID: %s]"
,
"description"
:
"杀死所有怪物"
},
"killCharacter"
:
{
"usage"
:
"用法:/killcharacter [playerId]"
,
"success"
:
"已杀死 %s 目前使用的角色。"
"success"
:
"已杀死 %s 目前使用的角色。"
,
"description"
:
"杀死目前使用的角色"
},
"list"
:
{
"success"
:
"目前在线人数:%s"
"success"
:
"目前在线人数:%s"
,
"description"
:
"查看所有玩家"
},
"permission"
:
{
"usage"
:
"用法:permission <add|remove> <username> <permission>"
,
...
...
@@ -181,21 +197,26 @@
"has_error"
:
"此玩家已拥有此权限!"
,
"remove"
:
"权限已移除。"
,
"not_have_error"
:
"此玩家未拥有权限!"
,
"account_error"
:
"账号不存在!"
"account_error"
:
"账号不存在!"
,
"description"
:
"给予或移除指定玩家的权限。"
},
"position"
:
{
"success"
:
"坐标:%.3f, %.3f, %.3f
\n
场景ID:%d"
"success"
:
"坐标:%.3f, %.3f, %.3f
\n
场景ID:%d"
,
"description"
:
"获取所在位置。"
},
"reload"
:
{
"reload_start"
:
"正在重载配置文件和数据。"
,
"reload_done"
:
"重载完毕。"
"reload_done"
:
"重载完毕。"
,
"description"
:
"重载配置文件和数据。"
},
"resetConst"
:
{
"reset_all"
:
"重置所有角色的命座。"
,
"success"
:
"已重置 %s 的命座,重新登录后将会生效。"
"success"
:
"已重置 %s 的命座,重新登录后将会生效。"
,
"description"
:
"重置当前角色的命之座,执行命令后需重新登录以生效。"
},
"resetShopLimit"
:
{
"usage"
:
"用法:/resetshop <player id>"
"usage"
:
"用法:/resetshop <player id>"
,
"description"
:
"重置所选玩家的商店刷新时间。"
},
"sendMail"
:
{
"usage"
:
"用法:give [player] <itemId|itemName> [amount]"
,
...
...
@@ -217,17 +238,20 @@
"message"
:
"<正文>"
,
"sender"
:
"<发件人>"
,
"arguments"
:
"<itemId|itemName|finish> [数量] [等级]"
,
"error"
:
"错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。"
"error"
:
"错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。"
,
"description"
:
"向指定用户发送邮件。 此命令的用法可根据附加的参数而变化。"
},
"sendMessage"
:
{
"usage"
:
"用法:sendmessage <player> <message>"
,
"success"
:
"消息已发送。"
"success"
:
"消息已发送。"
,
"description"
:
"向指定玩家发送消息"
},
"setFetterLevel"
:
{
"usage"
:
"用法:setfetterlevel <level>"
,
"range_error"
:
"好感度等级必须在 0 到 10 之间。"
,
"fetter_set_level"
:
"好感度已设置为 %s 级"
,
"level_error"
:
"无效的好感度等级。"
"level_error"
:
"无效的好感度等级。"
,
"description"
:
"设置当前角色的好感度等级。"
},
"setStats"
:
{
"usage_console"
:
"用法:setstats|stats @<UID> <stat> <value>"
,
...
...
@@ -238,20 +262,24 @@
"player_error"
:
"玩家不存在或已离线。"
,
"set_self"
:
"%s 已经设置为 %s。"
,
"set_for_uid"
:
"%s 的使用者 %s 更改为 %s。"
,
"set_max_hp"
:
"最大生命值更改为 %s。"
"set_max_hp"
:
"最大生命值更改为 %s。"
,
"description"
:
"设置当前角色的属性。"
},
"setWorldLevel"
:
{
"usage"
:
"用法:setworldlevel <level>"
,
"value_error"
:
"世界等级必须设置在0-8之间。"
,
"success"
:
"已将世界等级设为%s。"
,
"invalid_world_level"
:
"无效的世界等级。"
"invalid_world_level"
:
"无效的世界等级。"
,
"description"
:
"设置世界等级,执行命令后需重新登录以生效。"
},
"spawn"
:
{
"usage"
:
"用法:spawn <entityId> [amount] [level(仅限怪物]"
,
"success"
:
"已生成 %s 个 %s。"
"success"
:
"已生成 %s 个 %s。"
,
"description"
:
"在你附近生成一个生物。"
},
"stop"
:
{
"success"
:
"正在关闭服务器..."
"success"
:
"正在关闭服务器..."
,
"description"
:
"停止服务器"
},
"talent"
:
{
"usage_1"
:
"设置天赋等级:/talent set <talentID> <value>"
,
...
...
@@ -267,32 +295,41 @@
"invalid_level"
:
"无效的天赋等级。"
,
"normal_attack_id"
:
"普通攻击的 ID 为 %s。"
,
"e_skill_id"
:
"元素战技ID %s。"
,
"q_skill_id"
:
"元素爆发ID %s。"
"q_skill_id"
:
"元素爆发ID %s。"
,
"description"
:
"设置当前角色的天赋等级。"
},
"teleportAll"
:
{
"success"
:
"已将全部玩家传送到你的位置"
,
"error"
:
"命令仅限处于多人游戏状态下使用。"
"error"
:
"命令仅限处于多人游戏状态下使用。"
,
"description"
:
"将你世界中的所有玩家传送到你所在的位置。"
},
"teleport"
:
{
"usage_server"
:
"用法:/tp @<player id> <x> <y> <z> [scene id]"
,
"usage"
:
"用法:/tp [@<player id>] <x> <y> <z> [scene id]"
,
"specify_player_id"
:
"你必须指定一个玩家ID。"
,
"invalid_position"
:
"无效的位置。"
,
"success"
:
"传送 %s 到坐标 %s,%s,%s,场景为 %s"
"success"
:
"传送 %s 到坐标 %s,%s,%s,场景为 %s"
,
"description"
:
"改变指定玩家的位置。"
},
"weather"
:
{
"usage"
:
"用法:weather <weatherId> [climateId]"
,
"success"
:
"已将当前天气设定为 %s,气候为 %s。"
,
"invalid_id"
:
"无效的天气ID。"
"invalid_id"
:
"无效的天气ID。"
,
"description"
:
"改变天气"
},
"drop"
:
{
"command_usage"
:
"用法:drop <itemId|itemName> [amount]"
,
"success"
:
"已将 %s x %s 丟在附近。"
"success"
:
"已将 %s x %s 丟在附近。"
,
"description"
:
"在你附近丢一个物品。"
},
"help"
:
{
"usage"
:
"用法:"
,
"aliases"
:
"別名:"
,
"available_commands"
:
"可用指令:"
"available_commands"
:
"可用指令:"
,
"description"
:
"发送帮助信息或显示指定命令的信息。"
},
"restart"
:
{
"description"
:
"重新启动服务器。"
}
}
}
src/main/resources/logback.xml
View file @
ccdce684
<Configuration>
<variable
name=
"LOG_LEVEL"
value=
"${LOG_LEVEL:-INFO}"
/>
<appender
name=
"STDOUT"
class=
"emu.grasscutter.utils.JlineLogbackAppender"
>
<encoder>
<pattern>
[%d{HH:mm:ss}] [%highlight(%level)] %msg%n
</pattern>
...
...
@@ -14,7 +16,10 @@
<pattern>
%d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n
</pattern>
</encoder>
</appender>
<logger
name=
"org.reflections"
level=
"OFF"
/>
<logger
name=
"org.reflections"
level=
"OFF"
/>
<logger
name=
"emu.grasscutter"
level=
"${LOG_LEVEL}"
/>
<root
level=
"INFO"
>
<appender-ref
ref=
"STDOUT"
/>
<appender-ref
ref=
"FILE"
/>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment