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
8e99cb4f
Commit
8e99cb4f
authored
May 07, 2022
by
gentlespoon
Committed by
Melledy
May 07, 2022
Browse files
More reliable stamina calculation
by separately handling immediate one-time cost and cost over time.
parent
43c27c46
Changes
5
Show whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java
View file @
8e99cb4f
...
@@ -7,22 +7,31 @@ import emu.grasscutter.game.player.Player;
...
@@ -7,22 +7,31 @@ import emu.grasscutter.game.player.Player;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.LifeState
;
import
emu.grasscutter.game.props.LifeState
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.net.proto.EntityMoveInfoOuterClass
;
import
emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo
;
import
emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify
;
import
emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo
;
import
emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo
;
import
emu.grasscutter.net.proto.MotionStateOuterClass.MotionState
;
import
emu.grasscutter.net.proto.MotionStateOuterClass.MotionState
;
import
emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType
;
import
emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType
;
import
emu.grasscutter.net.proto.VectorOuterClass
;
import
emu.grasscutter.net.proto.VectorOuterClass
.Vector
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.*
;
import
emu.grasscutter.server.packet.send.*
;
import
emu.grasscutter.utils.Position
;
import
emu.grasscutter.utils.Position
;
import
org.jetbrains.annotations.NotNull
;
import
java.lang.Math
;
import
java.lang.Math
;
import
java.util.*
;
import
java.util.*
;
public
class
MovementManager
{
public
class
MovementManager
{
private
final
Player
player
;
public
HashMap
<
String
,
HashSet
<
MotionState
>>
MotionStatesCategorized
=
new
HashMap
<>();
private
HashMap
<
String
,
HashSet
<
MotionState
>>
MotionStatesCategorized
=
new
HashMap
<>();
private
Position
currentCoordinates
=
new
Position
(
0
,
0
,
0
);
private
Position
previousCoordinates
=
new
Position
(
0
,
0
,
0
);
private
MotionState
currentState
=
MotionState
.
MOTION_STANDBY
;
private
MotionState
previousState
=
MotionState
.
MOTION_STANDBY
;
private
Timer
sustainedStaminaHandlerTimer
;
private
GameSession
cachedSession
=
null
;
private
GameEntity
cachedEntity
=
null
;
private
int
staminaRecoverDelay
=
0
;
private
boolean
isInSkillMove
=
false
;
private
enum
ConsumptionType
{
private
enum
ConsumptionType
{
None
(
0
),
None
(
0
),
...
@@ -31,8 +40,8 @@ public class MovementManager {
...
@@ -31,8 +40,8 @@ public class MovementManager {
CLIMB_START
(-
500
),
CLIMB_START
(-
500
),
CLIMBING
(-
150
),
CLIMBING
(-
150
),
CLIMB_JUMP
(-
2500
),
CLIMB_JUMP
(-
2500
),
DASH
(-
1800
),
SPRINT
(-
1800
),
SPRINT
(-
360
),
DASH
(-
360
),
FLY
(-
60
),
FLY
(-
60
),
SWIM_DASH_START
(-
200
),
SWIM_DASH_START
(-
200
),
SWIM_DASH
(-
200
),
SWIM_DASH
(-
200
),
...
@@ -47,6 +56,7 @@ public class MovementManager {
...
@@ -47,6 +56,7 @@ public class MovementManager {
POWERED_FLY
(
500
);
POWERED_FLY
(
500
);
public
final
int
amount
;
public
final
int
amount
;
ConsumptionType
(
int
amount
)
{
ConsumptionType
(
int
amount
)
{
this
.
amount
=
amount
;
this
.
amount
=
amount
;
}
}
...
@@ -55,33 +65,26 @@ public class MovementManager {
...
@@ -55,33 +65,26 @@ public class MovementManager {
private
class
Consumption
{
private
class
Consumption
{
public
ConsumptionType
consumptionType
;
public
ConsumptionType
consumptionType
;
public
int
amount
;
public
int
amount
;
public
Consumption
(
ConsumptionType
ct
,
int
a
)
{
public
Consumption
(
ConsumptionType
ct
,
int
a
)
{
consumptionType
=
ct
;
consumptionType
=
ct
;
amount
=
a
;
amount
=
a
;
}
}
public
Consumption
(
ConsumptionType
ct
)
{
public
Consumption
(
ConsumptionType
ct
)
{
this
(
ct
,
ct
.
amount
);
this
(
ct
,
ct
.
amount
);
}
}
}
}
private
MotionState
previousState
=
MotionState
.
MOTION_STANDBY
;
public
boolean
getIsInSkillMove
()
{
private
MotionState
currentState
=
MotionState
.
MOTION_STANDBY
;
return
isInSkillMove
;
private
Position
previousCoordinates
=
new
Position
(
0
,
0
,
0
);
}
private
Position
currentCoordinates
=
new
Position
(
0
,
0
,
0
);
private
final
Player
player
;
private
float
landSpeed
=
0
;
public
void
setIsInSkillMove
(
boolean
b
)
{
private
long
landTimeMillisecond
=
0
;
isInSkillMove
=
b
;
private
Timer
movementManagerTickTimer
;
}
private
GameSession
cachedSession
=
null
;
private
GameEntity
cachedEntity
=
null
;
private
int
staminaRecoverDelay
=
0
;
private
int
skillCaster
=
0
;
private
int
skillCasting
=
0
;
public
MovementManager
(
Player
player
)
{
public
MovementManager
(
Player
player
)
{
previousCoordinates
.
add
(
new
Position
(
0
,
0
,
0
));
this
.
player
=
player
;
this
.
player
=
player
;
MotionStatesCategorized
.
put
(
"SWIM"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionStatesCategorized
.
put
(
"SWIM"
,
new
HashSet
<>(
Arrays
.
asList
(
...
@@ -131,250 +134,223 @@ public class MovementManager {
...
@@ -131,250 +134,223 @@ public class MovementManager {
MotionStatesCategorized
.
put
(
"FIGHT"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionStatesCategorized
.
put
(
"FIGHT"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_FIGHT
MotionState
.
MOTION_FIGHT
)));
)));
}
public
void
handle
(
GameSession
session
,
EntityMoveInfoOuterClass
.
EntityMoveInfo
moveInfo
,
GameEntity
entity
)
{
if
(
movementManagerTickTimer
==
null
)
{
movementManagerTickTimer
=
new
Timer
();
movementManagerTickTimer
.
scheduleAtFixedRate
(
new
MotionManagerTick
(),
0
,
200
);
}
// cache info for later use in tick
cachedSession
=
session
;
cachedEntity
=
entity
;
MotionInfo
motionInfo
=
moveInfo
.
getMotionInfo
();
moveEntity
(
entity
,
moveInfo
);
VectorOuterClass
.
Vector
posVector
=
motionInfo
.
getPos
();
Position
newPos
=
new
Position
(
posVector
.
getX
(),
posVector
.
getY
(),
posVector
.
getZ
());;
if
(
newPos
.
getX
()
!=
0
&&
newPos
.
getY
()
!=
0
&&
newPos
.
getZ
()
!=
0
)
{
currentCoordinates
=
newPos
;
}
currentState
=
motionInfo
.
getState
();
Grasscutter
.
getLogger
().
debug
(
""
+
currentState
+
"\t"
+
(
moveInfo
.
getIsReliable
()
?
"reliable"
:
""
));
handleFallOnGround
(
motionInfo
);
}
public
void
resetTimer
()
{
Grasscutter
.
getLogger
().
debug
(
"MovementManager ticker stopped"
);
movementManagerTickTimer
.
cancel
();
movementManagerTickTimer
=
null
;
}
private
void
moveEntity
(
GameEntity
entity
,
EntityMoveInfoOuterClass
.
EntityMoveInfo
moveInfo
)
{
entity
.
getPosition
().
set
(
moveInfo
.
getMotionInfo
().
getPos
());
entity
.
getRotation
().
set
(
moveInfo
.
getMotionInfo
().
getRot
());
entity
.
setLastMoveSceneTimeMs
(
moveInfo
.
getSceneTime
());
entity
.
setLastMoveReliableSeq
(
moveInfo
.
getReliableSeq
());
entity
.
setMotionState
(
moveInfo
.
getMotionInfo
().
getState
());
}
}
private
boolean
isPlayerMoving
()
{
private
boolean
isPlayerMoving
()
{
float
diffX
=
currentCoordinates
.
getX
()
-
previousCoordinates
.
getX
();
float
diffX
=
currentCoordinates
.
getX
()
-
previousCoordinates
.
getX
();
float
diffY
=
currentCoordinates
.
getY
()
-
previousCoordinates
.
getY
();
float
diffY
=
currentCoordinates
.
getY
()
-
previousCoordinates
.
getY
();
float
diffZ
=
currentCoordinates
.
getZ
()
-
previousCoordinates
.
getZ
();
float
diffZ
=
currentCoordinates
.
getZ
()
-
previousCoordinates
.
getZ
();
// Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ);
Grasscutter
.
getLogger
().
debug
(
"isPlayerMoving: "
+
previousCoordinates
+
", "
+
currentCoordinates
+
return
Math
.
abs
(
diffX
)
>
0.2
||
Math
.
abs
(
diffY
)
>
0.1
||
Math
.
abs
(
diffZ
)
>
0.2
;
", "
+
diffX
+
", "
+
diffY
+
", "
+
diffZ
);
}
return
Math
.
abs
(
diffX
)
>
0.3
||
Math
.
abs
(
diffY
)
>
0.2
||
Math
.
abs
(
diffZ
)
>
0.3
;
private
int
getCurrentStamina
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
}
}
private
int
getMaximumStamina
()
{
// Returns new stamina and sends PlayerPropNotify
return
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
public
int
updateStamina
(
GameSession
session
,
Consumption
consumption
)
{
}
int
currentStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
if
(
consumption
.
amount
==
0
)
{
// Returns new stamina
public
int
updateStamina
(
GameSession
session
,
int
amount
)
{
int
currentStamina
=
session
.
getPlayer
().
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
if
(
amount
==
0
)
{
return
currentStamina
;
return
currentStamina
;
}
}
int
playerMaxStamina
=
session
.
getPlayer
().
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
int
playerMaxStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
int
newStamina
=
currentStamina
+
amount
;
Grasscutter
.
getLogger
().
debug
(
currentStamina
+
"/"
+
playerMaxStamina
+
"\t"
+
currentState
+
"\t"
+
(
isPlayerMoving
()
?
"moving"
:
" "
)
+
"\t("
+
consumption
.
consumptionType
+
","
+
consumption
.
amount
+
")"
);
int
newStamina
=
currentStamina
+
consumption
.
amount
;
if
(
newStamina
<
0
)
{
if
(
newStamina
<
0
)
{
newStamina
=
0
;
newStamina
=
0
;
}
}
if
(
newStamina
>
playerMaxStamina
)
{
if
(
newStamina
>
playerMaxStamina
)
{
newStamina
=
playerMaxStamina
;
newStamina
=
playerMaxStamina
;
}
}
session
.
getP
layer
()
.
setProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
,
newStamina
);
p
layer
.
setProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
,
newStamina
);
session
.
send
(
new
PacketPlayerPropNotify
(
player
,
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
));
session
.
send
(
new
PacketPlayerPropNotify
(
player
,
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
));
return
newStamina
;
return
newStamina
;
}
}
private
void
handleFallOnGround
(
@NotNull
MotionInfo
motionInfo
)
{
// Kills avatar, removes entity and sends notification.
MotionState
state
=
motionInfo
.
getState
();
// TODO: Probably move this to Avatar class? since other components may also need to kill avatar.
// land speed and fall on ground event arrive in different packets
public
void
killAvatar
(
GameSession
session
,
GameEntity
entity
,
PlayerDieType
dieType
)
{
// cache land speed
session
.
send
(
new
PacketAvatarLifeStateChangeNotify
(
player
.
getTeamManager
().
getCurrentAvatarEntity
().
getAvatar
(),
if
(
state
==
MotionState
.
MOTION_LAND_SPEED
)
{
LifeState
.
LIFE_DEAD
,
dieType
));
landSpeed
=
motionInfo
.
getSpeed
().
getY
();
session
.
send
(
new
PacketLifeStateChangeNotify
(
entity
,
LifeState
.
LIFE_DEAD
,
dieType
));
landTimeMillisecond
=
System
.
currentTimeMillis
();
entity
.
setFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
,
0
);
entity
.
getWorld
().
broadcastPacket
(
new
PacketEntityFightPropUpdateNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
entity
.
getWorld
().
broadcastPacket
(
new
PacketLifeStateChangeNotify
(
0
,
entity
,
LifeState
.
LIFE_DEAD
));
player
.
getScene
().
removeEntity
(
entity
);
((
EntityAvatar
)
entity
).
onDeath
(
dieType
,
0
);
}
}
if
(
state
==
MotionState
.
MOTION_FALL_ON_GROUND
)
{
// if not received immediately after MOTION_LAND_SPEED, discard this packet.
public
void
startSustainedStaminaHandler
()
{
// TODO: Test in high latency.
if
(
sustainedStaminaHandlerTimer
==
null
)
{
int
maxDelay
=
200
;
sustainedStaminaHandlerTimer
=
new
Timer
();
if
((
System
.
currentTimeMillis
()
-
landTimeMillisecond
)
>
maxDelay
)
{
sustainedStaminaHandlerTimer
.
scheduleAtFixedRate
(
new
SustainedStaminaHandler
(),
0
,
200
);
Grasscutter
.
getLogger
().
debug
(
"MOTION_FALL_ON_GROUND received after "
+
maxDelay
+
"ms, discard."
);
Grasscutter
.
getLogger
().
debug
(
"[MovementManager] SustainedStaminaHandlerTimer started"
);
return
;
}
}
float
currentHP
=
cachedEntity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
);
float
maxHP
=
cachedEntity
.
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
);
public
void
stopSustainedStaminaHandler
()
{
Grasscutter
.
getLogger
().
debug
(
"[MovementManager] SustainedStaminaHandlerTimer stopped"
);
sustainedStaminaHandlerTimer
.
cancel
();
sustainedStaminaHandlerTimer
=
null
;
}
}
if
(
landSpeed
<
-
26.5
)
{
damage
=
(
float
)(
maxHP
*
0.66
);
// Handlers
// External trigger handler
public
void
handleEvtDoSkillSuccNotify
(
GameSession
session
,
EvtDoSkillSuccNotify
notify
)
{
handleImmediateStamina
(
session
,
notify
);
}
}
if
(
landSpeed
<
-
28
)
{
damage
=
(
maxHP
*
1
);
public
void
handleCombatInvocationsNotify
(
GameSession
session
,
EntityMoveInfo
moveInfo
,
GameEntity
entity
)
{
// cache info for later use in SustainedStaminaHandler tick
cachedSession
=
session
;
cachedEntity
=
entity
;
MotionInfo
motionInfo
=
moveInfo
.
getMotionInfo
();
MotionState
motionState
=
motionInfo
.
getState
();
boolean
isReliable
=
moveInfo
.
getIsReliable
();
Grasscutter
.
getLogger
().
trace
(
""
+
motionState
+
"\t"
+
(
isReliable
?
"reliable"
:
""
));
if
(
isReliable
)
{
currentState
=
motionState
;
Vector
posVector
=
motionInfo
.
getPos
();
Position
newPos
=
new
Position
(
posVector
.
getX
(),
posVector
.
getY
(),
posVector
.
getZ
());
if
(
newPos
.
getX
()
!=
0
&&
newPos
.
getY
()
!=
0
&&
newPos
.
getZ
()
!=
0
)
{
currentCoordinates
=
newPos
;
}
}
float
newHP
=
currentHP
-
damage
;
if
(
newHP
<
0
)
{
newHP
=
0
;
}
}
Grasscutter
.
getLogger
().
debug
(
"Max: "
+
maxHP
+
"\tCurr: "
+
currentHP
+
"\tDamage: "
+
damage
+
"\tnewHP: "
+
newHP
);
startSustainedStaminaHandler
();
cachedEntity
.
setFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
,
newHP
);
handleImmediateStamina
(
session
,
motionInfo
,
motionState
,
entity
);
cachedEntity
.
getWorld
().
broadcastPacket
(
new
PacketEntityFightPropUpdateNotify
(
cachedEntity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
if
(
newHP
==
0
)
{
killAvatar
(
cachedSession
,
cachedEntity
,
PlayerDieType
.
PLAYER_DIE_FALL
);
}
}
landSpeed
=
0
;
// Internal handler
private
void
handleImmediateStamina
(
GameSession
session
,
MotionInfo
motionInfo
,
MotionState
motionState
,
GameEntity
entity
)
{
switch
(
motionState
)
{
case
MOTION_DASH_BEFORE_SHAKE:
if
(
previousState
!=
MotionState
.
MOTION_DASH_BEFORE_SHAKE
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
SPRINT
));
}
}
break
;
case
MOTION_CLIMB_JUMP:
if
(
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
CLIMB_JUMP
));
}
}
break
;
private
void
handleDrowning
()
{
case
MOTION_SWIM_DASH:
int
stamina
=
getCurrentStamina
();
if
(
previousState
!=
MotionState
.
MOTION_SWIM_DASH
)
{
if
(
stamina
<
10
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
SWIM_DASH_START
));
boolean
isSwimming
=
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
(
currentState
);
Grasscutter
.
getLogger
().
debug
(
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
)
+
"/"
+
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
)
+
"\t"
+
currentState
+
"\t"
+
isSwimming
);
if
(
isSwimming
&&
currentState
!=
MotionState
.
MOTION_SWIM_IDLE
)
{
killAvatar
(
cachedSession
,
cachedEntity
,
PlayerDieType
.
PLAYER_DIE_DRAWN
);
}
}
break
;
}
}
}
}
public
void
killAvatar
(
GameSession
session
,
GameEntity
entity
,
PlayerDieType
dieType
)
{
private
void
handleImmediateStamina
(
GameSession
session
,
EvtDoSkillSuccNotify
notify
)
{
cachedSession
.
send
(
new
PacketAvatarLifeStateChangeNotify
(
Consumption
consumption
=
getFightConsumption
(
notify
.
getSkillId
());
cachedSession
.
getPlayer
().
getTeamManager
().
getCurrentAvatarEntity
().
getAvatar
(),
updateStamina
(
session
,
consumption
);
LifeState
.
LIFE_DEAD
,
dieType
));
cachedSession
.
send
(
new
PacketLifeStateChangeNotify
(
cachedEntity
,
LifeState
.
LIFE_DEAD
,
dieType
));
cachedEntity
.
setFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
,
0
);
cachedEntity
.
getWorld
().
broadcastPacket
(
new
PacketEntityFightPropUpdateNotify
(
cachedEntity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
entity
.
getWorld
().
broadcastPacket
(
new
PacketLifeStateChangeNotify
(
0
,
entity
,
LifeState
.
LIFE_DEAD
));
session
.
getPlayer
().
getScene
().
removeEntity
(
entity
);
((
EntityAvatar
)
entity
).
onDeath
(
dieType
,
0
);
}
}
private
class
MotionManagerTick
extends
TimerTask
private
class
SustainedStaminaHandler
extends
TimerTask
{
{
public
void
run
()
{
public
void
run
()
{
if
(
Grasscutter
.
getConfig
().
OpenStamina
)
{
if
(
Grasscutter
.
getConfig
().
OpenStamina
)
{
boolean
moving
=
isPlayerMoving
();
boolean
moving
=
isPlayerMoving
();
if
(
moving
||
(
getCurrentStamina
()
<
getMaximumStamina
()))
{
int
currentStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
// Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina");
int
maxStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
if
(
moving
||
(
currentStamina
<
maxStamina
))
{
Grasscutter
.
getLogger
().
debug
(
"Player moving: "
+
moving
+
", stamina full: "
+
(
currentStamina
>=
maxStamina
)
+
", recalculate stamina"
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(!
isInSkillMove
)
{
// TODO: refactor these conditions.
if
(
MotionStatesCategorized
.
get
(
"CLIMB"
).
contains
(
currentState
))
{
if
(
MotionStatesCategorized
.
get
(
"CLIMB"
).
contains
(
currentState
))
{
consumption
=
getClimbConsumption
();
consumption
=
getClimb
Sustained
Consumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
((
currentState
)))
{
}
else
if
(
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
((
currentState
)))
{
consumption
=
getSwimConsumptions
();
consumption
=
getSwim
Sustained
Consumptions
();
}
else
if
(
MotionStatesCategorized
.
get
(
"RUN"
).
contains
(
currentState
))
{
}
else
if
(
MotionStatesCategorized
.
get
(
"RUN"
).
contains
(
currentState
))
{
consumption
=
getRunWalkDashConsumption
();
consumption
=
getRunWalkDash
Sustained
Consumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"FLY"
).
contains
(
currentState
))
{
}
else
if
(
MotionStatesCategorized
.
get
(
"FLY"
).
contains
(
currentState
))
{
consumption
=
getFlyConsumption
();
consumption
=
getFly
Sustained
Consumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"STANDBY"
).
contains
(
currentState
))
{
}
else
if
(
MotionStatesCategorized
.
get
(
"STANDBY"
).
contains
(
currentState
))
{
consumption
=
getStandConsumption
();
consumption
=
getStandSustainedConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"FIGHT"
).
contains
(
currentState
))
{
}
consumption
=
getFightConsumption
();
}
}
// delay 2 seconds before start recovering - as official server does.
if
(
cachedSession
!=
null
)
{
if
(
cachedSession
!=
null
)
{
if
(
consumption
.
amount
<
0
)
{
if
(
consumption
.
amount
<
0
)
{
staminaRecoverDelay
=
0
;
staminaRecoverDelay
=
0
;
}
}
if
(
consumption
.
amount
>
0
&&
consumption
.
consumptionType
!=
ConsumptionType
.
POWERED_FLY
)
{
if
(
consumption
.
amount
>
0
&&
consumption
.
consumptionType
!=
ConsumptionType
.
POWERED_FLY
)
{
// For POWERED_FLY recover immediately - things like Amber's gliding exam may require this.
if
(
staminaRecoverDelay
<
10
)
{
if
(
staminaRecoverDelay
<
10
)
{
// For others recover after 2 seconds (10 ticks) - as official server does.
staminaRecoverDelay
++;
staminaRecoverDelay
++;
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
}
}
}
}
// Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")");
updateStamina
(
cachedSession
,
consumption
);
updateStamina
(
cachedSession
,
consumption
.
amount
);
}
}
// tick triggered
handleDrowning
();
handleDrowning
();
}
}
}
}
previousState
=
currentState
;
previousState
=
currentState
;
previousCoordinates
=
new
Position
(
currentCoordinates
.
getX
(),
previousCoordinates
=
new
Position
(
currentCoordinates
.
getY
(),
currentCoordinates
.
getZ
());;
currentCoordinates
.
getX
(),
currentCoordinates
.
getY
(),
currentCoordinates
.
getZ
()
);
}
}
private
void
handleDrowning
()
{
int
stamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
if
(
stamina
<
10
)
{
boolean
isSwimming
=
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
(
currentState
);
Grasscutter
.
getLogger
().
debug
(
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
)
+
"/"
+
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
)
+
"\t"
+
currentState
+
"\t"
+
isSwimming
);
if
(
isSwimming
&&
currentState
!=
MotionState
.
MOTION_SWIM_IDLE
)
{
killAvatar
(
cachedSession
,
cachedEntity
,
PlayerDieType
.
PLAYER_DIE_DRAWN
);
}
}
}
}
}
// Consumption Calculators
private
Consumption
get
Climb
Consumption
()
{
private
Consumption
get
Fight
Consumption
(
int
skillCasting
)
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_CLIMB
)
{
HashMap
<
Integer
,
Integer
>
fightingCost
=
new
HashMap
<>()
{{
put
(
10013
,
-
1000
);
// Kamisato Ayaka
put
(
10413
,
-
1000
);
// Mona
}};
if
(
fightingCost
.
containsKey
(
skillCasting
))
{
consumption
=
new
Consumption
(
ConsumptionType
.
FIGHT
,
fightingCost
.
get
(
skillCasting
));
}
return
consumption
;
}
private
Consumption
getClimbSustainedConsumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_CLIMB
&&
isPlayerMoving
())
{
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMBING
);
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMBING
);
if
(
previousState
!=
MotionState
.
MOTION_CLIMB
&&
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
if
(
previousState
!=
MotionState
.
MOTION_CLIMB
&&
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMB_START
);
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMB_START
);
}
}
if
(!
isPlayerMoving
())
{
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
}
}
if
(
currentState
==
MotionState
.
MOTION_CLIMB_JUMP
)
{
if
(
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMB_JUMP
);
}
}
}
return
consumption
;
return
consumption
;
}
}
private
Consumption
getSwimConsumptions
()
{
private
Consumption
getSwim
Sustained
Consumptions
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_SWIM_MOVE
)
{
if
(
currentState
==
MotionState
.
MOTION_SWIM_MOVE
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SWIMMING
);
consumption
=
new
Consumption
(
ConsumptionType
.
SWIMMING
);
}
}
if
(
currentState
==
MotionState
.
MOTION_SWIM_DASH
)
{
if
(
currentState
==
MotionState
.
MOTION_SWIM_DASH
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SWIM_DASH_START
);
if
(
previousState
==
MotionState
.
MOTION_SWIM_DASH
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SWIM_DASH
);
consumption
=
new
Consumption
(
ConsumptionType
.
SWIM_DASH
);
}
}
}
return
consumption
;
return
consumption
;
}
}
private
Consumption
getRunWalkDashConsumption
()
{
private
Consumption
getRunWalkDash
Sustained
Consumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_DASH_BEFORE_SHAKE
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
DASH
);
if
(
previousState
==
MotionState
.
MOTION_DASH_BEFORE_SHAKE
)
{
// only charge once
consumption
=
new
Consumption
(
ConsumptionType
.
SPRINT
);
}
}
if
(
currentState
==
MotionState
.
MOTION_DASH
)
{
if
(
currentState
==
MotionState
.
MOTION_DASH
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SPRINT
);
consumption
=
new
Consumption
(
ConsumptionType
.
DASH
);
}
}
if
(
currentState
==
MotionState
.
MOTION_RUN
)
{
if
(
currentState
==
MotionState
.
MOTION_RUN
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
RUN
);
consumption
=
new
Consumption
(
ConsumptionType
.
RUN
);
...
@@ -385,22 +361,21 @@ public class MovementManager {
...
@@ -385,22 +361,21 @@ public class MovementManager {
return
consumption
;
return
consumption
;
}
}
private
Consumption
getFlyConsumption
()
{
private
Consumption
getFly
Sustained
Consumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
FLY
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
FLY
);
HashMap
<
Integer
,
Float
>
glidingCostReduction
=
new
HashMap
<>()
{{
HashMap
<
Integer
,
Float
>
glidingCostReduction
=
new
HashMap
<>()
{{
put
(
212301
,
0.8f
);
// Amber
put
(
212301
,
0.8f
);
// Amber
put
(
222301
,
0.8f
);
// Venti
put
(
222301
,
0.8f
);
// Venti
}};
}};
float
reduction
=
1
;
float
reduction
=
1
;
for
(
EntityAvatar
entity:
cachedSession
.
getPlayer
().
getTeamManager
().
getActiveTeam
())
{
for
(
EntityAvatar
entity
:
cachedSession
.
getPlayer
().
getTeamManager
().
getActiveTeam
())
{
for
(
int
skillId:
entity
.
getAvatar
().
getProudSkillList
())
{
for
(
int
skillId
:
entity
.
getAvatar
().
getProudSkillList
())
{
if
(
glidingCostReduction
.
containsKey
(
skillId
))
{
if
(
glidingCostReduction
.
containsKey
(
skillId
))
{
reduction
=
glidingCostReduction
.
get
(
skillId
);
reduction
=
glidingCostReduction
.
get
(
skillId
);
}
}
}
}
}
}
consumption
.
amount
*=
reduction
;
consumption
.
amount
*=
reduction
;
// POWERED_FLY, e.g. wind tunnel
// POWERED_FLY, e.g. wind tunnel
if
(
currentState
==
MotionState
.
MOTION_POWERED_FLY
)
{
if
(
currentState
==
MotionState
.
MOTION_POWERED_FLY
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
POWERED_FLY
);
consumption
=
new
Consumption
(
ConsumptionType
.
POWERED_FLY
);
...
@@ -408,7 +383,7 @@ public class MovementManager {
...
@@ -408,7 +383,7 @@ public class MovementManager {
return
consumption
;
return
consumption
;
}
}
private
Consumption
getStandConsumption
()
{
private
Consumption
getStand
Sustained
Consumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_STANDBY
)
{
if
(
currentState
==
MotionState
.
MOTION_STANDBY
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
STANDBY
);
consumption
=
new
Consumption
(
ConsumptionType
.
STANDBY
);
...
@@ -418,25 +393,4 @@ public class MovementManager {
...
@@ -418,25 +393,4 @@ public class MovementManager {
}
}
return
consumption
;
return
consumption
;
}
}
private
Consumption
getFightConsumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
HashMap
<
Integer
,
Integer
>
fightingCost
=
new
HashMap
<>()
{{
put
(
10013
,
-
1000
);
// Kamisato Ayaka
put
(
10413
,
-
1000
);
// Mona
}};
if
(
fightingCost
.
containsKey
(
skillCasting
))
{
consumption
=
new
Consumption
(
ConsumptionType
.
FIGHT
,
fightingCost
.
get
(
skillCasting
));
// only handle once, so reset.
skillCasting
=
0
;
skillCaster
=
0
;
}
return
consumption
;
}
public
void
notifySkill
(
int
caster
,
int
skillId
)
{
skillCaster
=
caster
;
skillCasting
=
skillId
;
}
}
}
src/main/java/emu/grasscutter/game/player/Player.java
View file @
8e99cb4f
...
@@ -1152,7 +1152,7 @@ public class Player {
...
@@ -1152,7 +1152,7 @@ public class Player {
public
void
onLogout
()
{
public
void
onLogout
()
{
// stop stamina calculation
// stop stamina calculation
getMovementManager
().
resetTim
er
();
getMovementManager
().
stopSustainedStaminaHandl
er
();
// force to leave the dungeon
// force to leave the dungeon
if
(
getScene
().
getSceneType
()
==
SceneType
.
SCENE_DUNGEON
)
{
if
(
getScene
().
getSceneType
()
==
SceneType
.
SCENE_DUNGEON
)
{
...
...
src/main/java/emu/grasscutter/game/player/TeamManager.java
View file @
8e99cb4f
...
@@ -557,7 +557,7 @@ public class TeamManager {
...
@@ -557,7 +557,7 @@ public class TeamManager {
// return;
// return;
// }
// }
// }
// }
player
.
getMovementManager
().
resetTim
er
();
// prevent drowning immediately after respawn
player
.
getMovementManager
().
stopSustainedStaminaHandl
er
();
// prevent drowning immediately after respawn
// Revive all team members
// Revive all team members
for
(
EntityAvatar
entity
:
getActiveTeam
())
{
for
(
EntityAvatar
entity
:
getActiveTeam
())
{
...
...
src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java
View file @
8e99cb4f
package
emu.grasscutter.server.packet.recv
;
package
emu.grasscutter.server.packet.recv
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify
;
import
emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify
;
...
@@ -8,11 +10,21 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
...
@@ -8,11 +10,21 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import
emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo
;
import
emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo
;
import
emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo
;
import
emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo
;
import
emu.grasscutter.net.proto.MotionStateOuterClass.MotionState
;
import
emu.grasscutter.net.proto.PlayerDieTypeOuterClass
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify
;
import
java.util.HashMap
;
@Opcodes
(
PacketOpcodes
.
CombatInvocationsNotify
)
@Opcodes
(
PacketOpcodes
.
CombatInvocationsNotify
)
public
class
HandlerCombatInvocationsNotify
extends
PacketHandler
{
public
class
HandlerCombatInvocationsNotify
extends
PacketHandler
{
private
float
cachedLandingSpeed
=
0
;
private
long
cachedLandingTimeMillisecond
=
0
;
private
boolean
monitorLandingEvent
=
false
;
@Override
@Override
public
void
handle
(
GameSession
session
,
byte
[]
header
,
byte
[]
payload
)
throws
Exception
{
public
void
handle
(
GameSession
session
,
byte
[]
header
,
byte
[]
payload
)
throws
Exception
{
CombatInvocationsNotify
notif
=
CombatInvocationsNotify
.
parseFrom
(
payload
);
CombatInvocationsNotify
notif
=
CombatInvocationsNotify
.
parseFrom
(
payload
);
...
@@ -28,7 +40,33 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
...
@@ -28,7 +40,33 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
EntityMoveInfo
moveInfo
=
EntityMoveInfo
.
parseFrom
(
entry
.
getCombatData
());
EntityMoveInfo
moveInfo
=
EntityMoveInfo
.
parseFrom
(
entry
.
getCombatData
());
GameEntity
entity
=
session
.
getPlayer
().
getScene
().
getEntityById
(
moveInfo
.
getEntityId
());
GameEntity
entity
=
session
.
getPlayer
().
getScene
().
getEntityById
(
moveInfo
.
getEntityId
());
if
(
entity
!=
null
)
{
if
(
entity
!=
null
)
{
session
.
getPlayer
().
getMovementManager
().
handle
(
session
,
moveInfo
,
entity
);
// Move player
MotionInfo
motionInfo
=
moveInfo
.
getMotionInfo
();
entity
.
getPosition
().
set
(
motionInfo
.
getPos
());
entity
.
getRotation
().
set
(
motionInfo
.
getRot
());
entity
.
setLastMoveSceneTimeMs
(
moveInfo
.
getSceneTime
());
entity
.
setLastMoveReliableSeq
(
moveInfo
.
getReliableSeq
());
MotionState
motionState
=
motionInfo
.
getState
();
entity
.
setMotionState
(
motionState
);
session
.
getPlayer
().
getMovementManager
().
handleCombatInvocationsNotify
(
session
,
moveInfo
,
entity
);
// TODO: handle MOTION_FIGHT landing
// For plunge attacks, LAND_SPEED is always -30 and is not useful.
// May need the height when starting plunge attack.
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
;
break
;
default
:
default
:
...
@@ -47,5 +85,39 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
...
@@ -47,5 +85,39 @@ 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.
int
maxDelay
=
200
;
long
actualDelay
=
System
.
currentTimeMillis
()
-
cachedLandingTimeMillisecond
;
Grasscutter
.
getLogger
().
debug
(
"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
;
if
(
cachedLandingSpeed
<
-
23.5
)
{
damage
=
(
float
)
(
maxHP
*
0.33
);
}
if
(
cachedLandingSpeed
<
-
25
)
{
damage
=
(
float
)
(
maxHP
*
0.5
);
}
if
(
cachedLandingSpeed
<
-
26.5
)
{
damage
=
(
float
)
(
maxHP
*
0.66
);
}
if
(
cachedLandingSpeed
<
-
28
)
{
damage
=
(
maxHP
*
1
);
}
float
newHP
=
currentHP
-
damage
;
if
(
newHP
<
0
)
{
newHP
=
0
;
}
Grasscutter
.
getLogger
().
debug
(
currentHP
+
"/"
+
maxHP
+
"\t"
+
"\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
)
{
session
.
getPlayer
().
getMovementManager
().
killAvatar
(
session
,
entity
,
PlayerDieTypeOuterClass
.
PlayerDieType
.
PLAYER_DIE_FALL
);
}
cachedLandingSpeed
=
0
;
}
}
}
src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java
View file @
8e99cb4f
package
emu.grasscutter.server.packet.recv
;
package
emu.grasscutter.server.packet.recv
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
...
@@ -15,10 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
...
@@ -15,10 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
EvtDoSkillSuccNotify
notify
=
EvtDoSkillSuccNotify
.
parseFrom
(
payload
);
EvtDoSkillSuccNotify
notify
=
EvtDoSkillSuccNotify
.
parseFrom
(
payload
);
// TODO: Will be used for deducting stamina for charged skills.
// TODO: Will be used for deducting stamina for charged skills.
int
caster
=
notify
.
getCasterId
();
session
.
getPlayer
().
getMovementManager
().
handleEvtDoSkillSuccNotify
(
session
,
notify
);
int
skillId
=
notify
.
getSkillId
();
session
.
getPlayer
().
getMovementManager
().
notifySkill
(
caster
,
skillId
);
}
}
}
}
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