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
d7834852
Commit
d7834852
authored
May 08, 2022
by
gentlespoon
Committed by
Melledy
May 08, 2022
Browse files
Update StaminaManager
parent
a09723f0
Changes
6
Show whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java
0 → 100644
View file @
d7834852
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 @
d7834852
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 @
d7834852
...
...
@@ -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 @
d7834852
# 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 @
d7834852
...
...
@@ -33,13 +33,9 @@ public class StaminaManager {
private
GameSession
cachedSession
=
null
;
private
GameEntity
cachedEntity
=
null
;
private
int
staminaRecoverDelay
=
0
;
private
boolean
isInSkillMove
=
false
;
public
boolean
getIsInSkillMove
()
{
return
isInSkillMove
;
}
public
void
setIsInSkillMove
(
boolean
b
)
{
isInSkillMove
=
b
;
}
private
HashMap
<
String
,
BeforeUpdateStaminaListener
>
beforeUpdateStaminaListeners
=
new
HashMap
<>();
private
HashMap
<
String
,
AfterUpdateStaminaListener
>
afterUpdateStaminaListeners
=
new
HashMap
<>();
public
StaminaManager
(
Player
player
)
{
this
.
player
=
player
;
...
...
@@ -91,36 +87,116 @@ public class StaminaManager {
MotionStatesCategorized
.
put
(
"FIGHT"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_FIGHT
)));
MotionStatesCategorized
.
put
(
"SKIFF"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_SKIFF_BOARDING
,
MotionState
.
MOTION_SKIFF_NORMAL
,
MotionState
.
MOTION_SKIFF_DASH
,
MotionState
.
MOTION_SKIFF_POWERED_DASH
)));
}
// Listeners
public
boolean
registerBeforeUpdateStaminaListener
(
String
listenerName
,
BeforeUpdateStaminaListener
listener
)
{
if
(
beforeUpdateStaminaListeners
.
containsKey
(
listenerName
))
{
return
false
;
}
beforeUpdateStaminaListeners
.
put
(
listenerName
,
listener
);
return
true
;
}
public
boolean
unregisterBeforeUpdateStaminaListener
(
String
listenerName
)
{
if
(!
beforeUpdateStaminaListeners
.
containsKey
(
listenerName
))
{
return
false
;
}
beforeUpdateStaminaListeners
.
remove
(
listenerName
);
return
true
;
}
public
boolean
registerAfterUpdateStaminaListener
(
String
listenerName
,
AfterUpdateStaminaListener
listener
)
{
if
(
afterUpdateStaminaListeners
.
containsKey
(
listenerName
))
{
return
false
;
}
afterUpdateStaminaListeners
.
put
(
listenerName
,
listener
);
return
true
;
}
public
boolean
unregisterAfterUpdateStaminaListener
(
String
listenerName
)
{
if
(!
afterUpdateStaminaListeners
.
containsKey
(
listenerName
))
{
return
false
;
}
afterUpdateStaminaListeners
.
remove
(
listenerName
);
return
true
;
}
private
boolean
isPlayerMoving
()
{
float
diffX
=
currentCoordinates
.
getX
()
-
previousCoordinates
.
getX
();
float
diffY
=
currentCoordinates
.
getY
()
-
previousCoordinates
.
getY
();
float
diffZ
=
currentCoordinates
.
getZ
()
-
previousCoordinates
.
getZ
();
Grasscutter
.
getLogger
().
debug
(
"isPlayerMoving: "
+
previousCoordinates
+
", "
+
currentCoordinates
+
Grasscutter
.
getLogger
().
trace
(
"isPlayerMoving: "
+
previousCoordinates
+
", "
+
currentCoordinates
+
", "
+
diffX
+
", "
+
diffY
+
", "
+
diffZ
);
return
Math
.
abs
(
diffX
)
>
0.3
||
Math
.
abs
(
diffY
)
>
0.2
||
Math
.
abs
(
diffZ
)
>
0.3
;
}
// Returns new stamina and sends PlayerPropNotify
public
int
updateStamina
(
GameSession
session
,
Consumption
consumption
)
{
public
int
updateStaminaRelative
(
GameSession
session
,
Consumption
consumption
)
{
int
currentStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
if
(
consumption
.
amount
==
0
)
{
return
currentStamina
;
}
// notify will update
for
(
Map
.
Entry
<
String
,
BeforeUpdateStaminaListener
>
listener
:
beforeUpdateStaminaListeners
.
entrySet
())
{
Consumption
overriddenConsumption
=
listener
.
getValue
().
onBeforeUpdateStamina
(
consumption
.
consumptionType
.
toString
(),
consumption
);
if
((
overriddenConsumption
.
consumptionType
!=
consumption
.
consumptionType
)
&&
(
overriddenConsumption
.
amount
!=
consumption
.
amount
))
{
Grasscutter
.
getLogger
().
debug
(
"[StaminaManager] Stamina update relative("
+
consumption
.
consumptionType
.
toString
()
+
", "
+
consumption
.
amount
+
") overridden to relative("
+
consumption
.
consumptionType
.
toString
()
+
", "
+
consumption
.
amount
+
") by: "
+
listener
.
getKey
());
return
currentStamina
;
}
}
int
playerMaxStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
Grasscutter
.
getLogger
().
debug
(
currentStamina
+
"/"
+
playerMaxStamina
+
"\t"
+
currentState
+
"\t"
+
Grasscutter
.
getLogger
().
trace
(
currentStamina
+
"/"
+
playerMaxStamina
+
"\t"
+
currentState
+
"\t"
+
(
isPlayerMoving
()
?
"moving"
:
" "
)
+
"\t("
+
consumption
.
consumptionType
+
","
+
consumption
.
amount
+
")"
);
int
newStamina
=
currentStamina
+
consumption
.
amount
;
if
(
newStamina
<
0
)
{
newStamina
=
0
;
}
else
if
(
newStamina
>
playerMaxStamina
)
{
newStamina
=
playerMaxStamina
;
}
return
setStamina
(
session
,
consumption
.
consumptionType
.
toString
(),
newStamina
);
}
if
(
newStamina
>
playerMaxStamina
)
{
public
int
updateStaminaAbsolute
(
GameSession
session
,
String
reason
,
int
newStamina
)
{
int
currentStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
// notify will update
for
(
Map
.
Entry
<
String
,
BeforeUpdateStaminaListener
>
listener
:
beforeUpdateStaminaListeners
.
entrySet
())
{
int
overriddenNewStamina
=
listener
.
getValue
().
onBeforeUpdateStamina
(
reason
,
newStamina
);
if
(
overriddenNewStamina
!=
newStamina
)
{
Grasscutter
.
getLogger
().
debug
(
"[StaminaManager] Stamina update absolute("
+
reason
+
", "
+
newStamina
+
") overridden to absolute("
+
reason
+
", "
+
newStamina
+
") by: "
+
listener
.
getKey
());
return
currentStamina
;
}
}
int
playerMaxStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
if
(
newStamina
<
0
)
{
newStamina
=
0
;
}
else
if
(
newStamina
>
playerMaxStamina
)
{
newStamina
=
playerMaxStamina
;
}
return
setStamina
(
session
,
reason
,
newStamina
);
}
// Returns new stamina and sends PlayerPropNotify
public
int
setStamina
(
GameSession
session
,
String
reason
,
int
newStamina
)
{
// set stamina
player
.
setProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
,
newStamina
);
session
.
send
(
new
PacketPlayerPropNotify
(
player
,
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
));
// notify updated
for
(
Map
.
Entry
<
String
,
AfterUpdateStaminaListener
>
listener
:
afterUpdateStaminaListeners
.
entrySet
())
{
listener
.
getValue
().
onAfterUpdateStamina
(
reason
,
newStamina
);
}
return
newStamina
;
}
...
...
@@ -141,7 +217,7 @@ public class StaminaManager {
if
(!
player
.
isPaused
()
&&
sustainedStaminaHandlerTimer
==
null
)
{
sustainedStaminaHandlerTimer
=
new
Timer
();
sustainedStaminaHandlerTimer
.
scheduleAtFixedRate
(
new
SustainedStaminaHandler
(),
0
,
200
);
//
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started");
Grasscutter
.
getLogger
().
debug
(
"[MovementManager] SustainedStaminaHandlerTimer started"
);
}
}
...
...
@@ -149,7 +225,7 @@ public class StaminaManager {
if
(
sustainedStaminaHandlerTimer
!=
null
)
{
sustainedStaminaHandlerTimer
.
cancel
();
sustainedStaminaHandlerTimer
=
null
;
//
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped");
Grasscutter
.
getLogger
().
debug
(
"[MovementManager] SustainedStaminaHandlerTimer stopped"
);
}
}
...
...
@@ -188,17 +264,17 @@ public class StaminaManager {
switch
(
motionState
)
{
case
MOTION_DASH_BEFORE_SHAKE:
if
(
previousState
!=
MotionState
.
MOTION_DASH_BEFORE_SHAKE
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
SPRINT
));
updateStamina
Relative
(
session
,
new
Consumption
(
ConsumptionType
.
SPRINT
));
}
break
;
case
MOTION_CLIMB_JUMP:
if
(
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
CLIMB_JUMP
));
updateStamina
Relative
(
session
,
new
Consumption
(
ConsumptionType
.
CLIMB_JUMP
));
}
break
;
case
MOTION_SWIM_DASH:
if
(
previousState
!=
MotionState
.
MOTION_SWIM_DASH
)
{
updateStamina
(
session
,
new
Consumption
(
ConsumptionType
.
SWIM_DASH_START
));
updateStamina
Relative
(
session
,
new
Consumption
(
ConsumptionType
.
SWIM_DASH_START
));
}
break
;
}
...
...
@@ -206,7 +282,7 @@ public class StaminaManager {
private
void
handleImmediateStamina
(
GameSession
session
,
EvtDoSkillSuccNotify
notify
)
{
Consumption
consumption
=
getFightConsumption
(
notify
.
getSkillId
());
updateStamina
(
session
,
consumption
);
updateStamina
Relative
(
session
,
consumption
);
}
private
class
SustainedStaminaHandler
extends
TimerTask
{
...
...
@@ -216,10 +292,10 @@ public class StaminaManager {
int
currentStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
int
maxStamina
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
if
(
moving
||
(
currentStamina
<
maxStamina
))
{
Grasscutter
.
getLogger
().
debug
(
"Player moving: "
+
moving
+
", stamina full: "
+
Grasscutter
.
getLogger
().
trace
(
"Player moving: "
+
moving
+
", stamina full: "
+
(
currentStamina
>=
maxStamina
)
+
", recalculate stamina"
);
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(!
isInSkillMove
)
{
if
(
MotionStatesCategorized
.
get
(
"CLIMB"
).
contains
(
currentState
))
{
consumption
=
getClimbSustainedConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
((
currentState
)))
{
...
...
@@ -231,7 +307,15 @@ public class StaminaManager {
}
else
if
(
MotionStatesCategorized
.
get
(
"STANDBY"
).
contains
(
currentState
))
{
consumption
=
getStandSustainedConsumption
();
}
}
/*
TODO: Reductions that apply to all motion types:
Elemental Resonance
Wind: -15%
Skills
Diona E: -10% while shield lasts
Barbara E: -12% while lasts
*/
if
(
cachedSession
!=
null
)
{
if
(
consumption
.
amount
<
0
)
{
staminaRecoverDelay
=
0
;
...
...
@@ -241,12 +325,12 @@ public class StaminaManager {
if
(
staminaRecoverDelay
<
10
)
{
// For others recover after 2 seconds (10 ticks) - as official server does.
staminaRecoverDelay
++;
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
consumption
.
amount
=
0
;
Grasscutter
.
getLogger
().
trace
(
"[StaminaManager] Delaying recovery: "
+
staminaRecoverDelay
);
}
}
updateStamina
(
cachedSession
,
consumption
);
updateStamina
Relative
(
cachedSession
,
consumption
);
}
handleDrowning
();
}
}
previousState
=
currentState
;
...
...
@@ -261,10 +345,9 @@ public class StaminaManager {
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
)
{
Grasscutter
.
getLogger
().
trace
(
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
)
+
"/"
+
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
)
+
"\t"
+
currentState
);
if
(
currentState
!=
MotionState
.
MOTION_SWIM_IDLE
)
{
killAvatar
(
cachedSession
,
cachedEntity
,
PlayerDieType
.
PLAYER_DIE_DRAWN
);
}
}
...
...
@@ -272,7 +355,33 @@ public class StaminaManager {
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private
Consumption
getFightConsumption
(
int
skillCasting
)
{
/* TODO:
Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with
type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and
other info. Handling it here could be very complicated.
Charged attack
Default:
Polearm: (-2500)
Claymore: (-4000 per second, -800 each tick)
Catalyst: (-5000)
Talent:
Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0)
Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark.
This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0)
Constellations:
Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0)
Character Specific:
Keqing: (-2500)
Diluc: (Claymore * 0.5)
Talent Moving: (Those are skills too)
Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000)
Mona: (-1000 initial) (-1500 per second)
*/
// TODO: Currently only handling Ayaka and Mona's talent moving initial costs.
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
HashMap
<
Integer
,
Integer
>
fightingCost
=
new
HashMap
<>()
{{
put
(
10013
,
-
1000
);
// Kamisato Ayaka
...
...
@@ -292,10 +401,12 @@ public class StaminaManager {
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMB_START
);
}
}
// TODO: Foods
return
consumption
;
}
private
Consumption
getSwimSustainedConsumptions
()
{
handleDrowning
();
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_SWIM_MOVE
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SWIMMING
);
...
...
@@ -310,6 +421,7 @@ public class StaminaManager {
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_DASH
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
DASH
);
// TODO: Foods
}
if
(
currentState
==
MotionState
.
MOTION_RUN
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
RUN
);
...
...
@@ -321,7 +433,12 @@ public class StaminaManager {
}
private
Consumption
getFlySustainedConsumption
()
{
// POWERED_FLY, e.g. wind tunnel
if
(
currentState
==
MotionState
.
MOTION_POWERED_FLY
)
{
return
new
Consumption
(
ConsumptionType
.
POWERED_FLY
);
}
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
FLY
);
// Talent
HashMap
<
Integer
,
Float
>
glidingCostReduction
=
new
HashMap
<>()
{{
put
(
212301
,
0.8f
);
// Amber
put
(
222301
,
0.8f
);
// Venti
...
...
@@ -330,15 +447,15 @@ public class StaminaManager {
for
(
EntityAvatar
entity
:
cachedSession
.
getPlayer
().
getTeamManager
().
getActiveTeam
())
{
for
(
int
skillId
:
entity
.
getAvatar
().
getProudSkillList
())
{
if
(
glidingCostReduction
.
containsKey
(
skillId
))
{
reduction
=
glidingCostReduction
.
get
(
skillId
);
float
potentialLowerReduction
=
glidingCostReduction
.
get
(
skillId
);
if
(
potentialLowerReduction
<
reduction
)
{
reduction
=
potentialLowerReduction
;
}
}
}
consumption
.
amount
*=
reduction
;
// POWERED_FLY, e.g. wind tunnel
if
(
currentState
==
MotionState
.
MOTION_POWERED_FLY
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
POWERED_FLY
);
}
consumption
.
amount
*=
reduction
;
// TODO: Foods
return
consumption
;
}
...
...
src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java
View file @
d7834852
...
...
@@ -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.
// 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
)
{
...
...
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