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
a8293102
Commit
a8293102
authored
Jun 07, 2022
by
Melledy
Committed by
GitHub
Jun 07, 2022
Browse files
Merge branch 'development' into stable
parents
304b9cb8
ecf7a81a
Changes
410
Hide whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManagerHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.ChatManager
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.server.game.GameServer
;
public
interface
ChatManagerHandler
{
GameServer
getServer
();
void
sendPrivateMessage
(
Player
player
,
int
targetUid
,
String
message
);
void
sendPrivateMessage
(
Player
player
,
int
targetUid
,
int
emote
);
void
sendTeamMessage
(
Player
player
,
int
channel
,
String
message
);
void
sendTeamMessage
(
Player
player
,
int
channel
,
int
icon
);
}
src/main/java/emu/grasscutter/game/managers/DeforestationManager/DeforestationManager.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.DeforestationManager
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
dev.morphia.annotations.Transient
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.game.entity.EntityItem
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.world.Scene
;
import
emu.grasscutter.net.proto.HitTreeNotifyOuterClass
;
import
emu.grasscutter.net.proto.VectorOuterClass
;
import
emu.grasscutter.utils.Position
;
public
class
DeforestationManager
{
final
static
int
RECORD_EXPIRED_SECONDS
=
60
*
5
;
// 5 min
final
static
int
RECORD_MAX_TIMES
=
3
;
// max number of wood
final
static
int
RECORD_MAX_TIMES_OTHER_HIT_TREE
=
10
;
// if hit 10 times other trees, reset wood
@Transient
private
final
Player
player
;
@Transient
private
final
ArrayList
<
HitTreeRecord
>
currentRecord
;
@Transient
private
final
static
HashMap
<
Integer
,
Integer
>
ColliderTypeToWoodItemID
=
new
HashMap
<>();
static
{
/* define wood types which reflected to item id*/
ColliderTypeToWoodItemID
.
put
(
1
,
101301
);
ColliderTypeToWoodItemID
.
put
(
2
,
101302
);
ColliderTypeToWoodItemID
.
put
(
3
,
101303
);
ColliderTypeToWoodItemID
.
put
(
4
,
101304
);
ColliderTypeToWoodItemID
.
put
(
5
,
101305
);
ColliderTypeToWoodItemID
.
put
(
6
,
101306
);
ColliderTypeToWoodItemID
.
put
(
7
,
101307
);
ColliderTypeToWoodItemID
.
put
(
8
,
101308
);
ColliderTypeToWoodItemID
.
put
(
9
,
101309
);
ColliderTypeToWoodItemID
.
put
(
10
,
101310
);
ColliderTypeToWoodItemID
.
put
(
11
,
101311
);
ColliderTypeToWoodItemID
.
put
(
12
,
101312
);
}
public
DeforestationManager
(
Player
player
){
this
.
player
=
player
;
this
.
currentRecord
=
new
ArrayList
<>();
}
public
void
resetWood
(){
synchronized
(
currentRecord
)
{
currentRecord
.
clear
();
}
}
public
void
onDeforestationInvoke
(
HitTreeNotifyOuterClass
.
HitTreeNotify
hit
){
synchronized
(
currentRecord
)
{
//Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord);
VectorOuterClass
.
Vector
hitPosition
=
hit
.
getHitPostion
();
int
woodType
=
hit
.
getWoodType
();
if
(
ColliderTypeToWoodItemID
.
containsKey
(
woodType
))
{
// is a available wood type
Scene
scene
=
player
.
getScene
();
int
itemId
=
ColliderTypeToWoodItemID
.
get
(
woodType
);
int
positionHash
=
hitPosition
.
hashCode
();
HitTreeRecord
record
=
searchRecord
(
positionHash
);
if
(
record
==
null
)
{
record
=
new
HitTreeRecord
(
positionHash
);
}
else
{
currentRecord
.
remove
(
record
);
// move it to last position
}
currentRecord
.
add
(
record
);
if
(
currentRecord
.
size
()>
RECORD_MAX_TIMES_OTHER_HIT_TREE
){
currentRecord
.
remove
(
0
);
}
if
(
record
.
record
())
{
EntityItem
entity
=
new
EntityItem
(
scene
,
null
,
GameData
.
getItemDataMap
().
get
(
itemId
),
new
Position
(
hitPosition
.
getX
(),
hitPosition
.
getY
(),
hitPosition
.
getZ
()),
1
,
false
);
scene
.
addEntity
(
entity
);
}
//record.record()=false : too many wood they have deforested, no more wood dropped!
}
else
{
Grasscutter
.
getLogger
().
warn
(
"No wood type {} found."
,
woodType
);
}
}
// unknown wood type
}
private
HitTreeRecord
searchRecord
(
int
id
){
for
(
HitTreeRecord
record
:
currentRecord
)
{
if
(
record
.
getUnique
()
==
id
)
{
return
record
;
}
}
return
null
;
}
}
src/main/java/emu/grasscutter/game/managers/DeforestationManager/HitTreeRecord.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.DeforestationManager
;
public
class
HitTreeRecord
{
private
final
int
unique
;
private
short
count
;
// hit this tree times
private
long
time
;
// last available hitting time
HitTreeRecord
(
int
unique
){
this
.
count
=
0
;
this
.
time
=
0
;
this
.
unique
=
unique
;
}
/**
* reset hit time
*/
private
void
resetTime
(){
this
.
time
=
System
.
currentTimeMillis
();
}
/**
* commit hit behavior
*/
public
boolean
record
(){
if
(
this
.
count
<
DeforestationManager
.
RECORD_MAX_TIMES
)
{
this
.
count
++;
resetTime
();
return
true
;
}
// check expired
boolean
isWaiting
=
System
.
currentTimeMillis
()
-
this
.
time
<
DeforestationManager
.
RECORD_EXPIRED_SECONDS
*
1000L
;
if
(
isWaiting
){
return
false
;
}
else
{
this
.
count
=
1
;
resetTime
();
return
true
;
}
}
/**
* get unique id
*/
public
int
getUnique
(){
return
unique
;
}
@Override
public
String
toString
()
{
return
"HitTreeRecord{"
+
"unique="
+
unique
+
", count="
+
count
+
", time="
+
time
+
'}'
;
}
}
src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.EnergyManager
;
import
java.util.List
;
public
class
EnergyDropEntry
{
private
int
dropId
;
private
List
<
EnergyDropInfo
>
dropList
;
public
int
getDropId
()
{
return
this
.
dropId
;
}
public
List
<
EnergyDropInfo
>
getDropList
()
{
return
this
.
dropList
;
}
}
src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.EnergyManager
;
public
class
EnergyDropInfo
{
private
int
ballId
;
private
int
count
;
public
int
getBallId
()
{
return
this
.
ballId
;
}
public
int
getCount
()
{
return
this
.
count
;
}
}
src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.EnergyManager
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.DataLoader
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.data.excels.AvatarSkillDepotData
;
import
emu.grasscutter.data.excels.ItemData
;
import
emu.grasscutter.data.excels.MonsterData.HpDrops
;
import
emu.grasscutter.game.avatar.Avatar
;
import
emu.grasscutter.game.entity.EntityAvatar
;
import
emu.grasscutter.game.entity.EntityClientGadget
;
import
emu.grasscutter.game.entity.EntityItem
;
import
emu.grasscutter.game.entity.EntityMonster
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.inventory.GameItem
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.props.ElementType
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.MonsterType
;
import
emu.grasscutter.game.props.WeaponType
;
import
emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall
;
import
emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier
;
import
emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry
;
import
emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult
;
import
emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo
;
import
emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.utils.Position
;
import
it.unimi.dsi.fastutil.ints.Int2IntMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
;
import
static
emu
.
grasscutter
.
Configuration
.
GAME_OPTIONS
;
import
java.io.InputStreamReader
;
import
java.io.Reader
;
import
java.util.Collection
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.concurrent.ThreadLocalRandom
;
import
static
java
.
util
.
Map
.
entry
;
import
com.google.gson.reflect.TypeToken
;
import
com.google.protobuf.InvalidProtocolBufferException
;
public
class
EnergyManager
{
private
final
Player
player
;
private
final
Map
<
EntityAvatar
,
Integer
>
avatarNormalProbabilities
;
// energyUsage for each player
private
Boolean
energyUsage
;
private
final
static
Int2ObjectMap
<
List
<
EnergyDropInfo
>>
energyDropData
=
new
Int2ObjectOpenHashMap
<>();
private
final
static
Int2ObjectMap
<
List
<
SkillParticleGenerationInfo
>>
skillParticleGenerationData
=
new
Int2ObjectOpenHashMap
<>();
public
EnergyManager
(
Player
player
)
{
this
.
player
=
player
;
this
.
avatarNormalProbabilities
=
new
HashMap
<>();
this
.
energyUsage
=
GAME_OPTIONS
.
energyUsage
;
}
public
Player
getPlayer
()
{
return
this
.
player
;
}
public
static
void
initialize
()
{
// Read the data we need for monster energy drops.
try
(
Reader
fileReader
=
new
InputStreamReader
(
DataLoader
.
load
(
"EnergyDrop.json"
)))
{
List
<
EnergyDropEntry
>
energyDropList
=
Grasscutter
.
getGsonFactory
().
fromJson
(
fileReader
,
TypeToken
.
getParameterized
(
Collection
.
class
,
EnergyDropEntry
.
class
).
getType
());
for
(
EnergyDropEntry
entry
:
energyDropList
)
{
energyDropData
.
put
(
entry
.
getDropId
(),
entry
.
getDropList
());
}
Grasscutter
.
getLogger
().
info
(
"Energy drop data successfully loaded."
);
}
catch
(
Exception
ex
)
{
Grasscutter
.
getLogger
().
error
(
"Unable to load energy drop data."
,
ex
);
}
// Read the data for particle generation from skills
try
(
Reader
fileReader
=
new
InputStreamReader
(
DataLoader
.
load
(
"SkillParticleGeneration.json"
)))
{
List
<
SkillParticleGenerationEntry
>
skillParticleGenerationList
=
Grasscutter
.
getGsonFactory
().
fromJson
(
fileReader
,
TypeToken
.
getParameterized
(
Collection
.
class
,
SkillParticleGenerationEntry
.
class
).
getType
());
for
(
SkillParticleGenerationEntry
entry
:
skillParticleGenerationList
)
{
skillParticleGenerationData
.
put
(
entry
.
getAvatarId
(),
entry
.
getAmountList
());
}
Grasscutter
.
getLogger
().
info
(
"Skill particle generation data successfully loaded."
);
}
catch
(
Exception
ex
)
{
Grasscutter
.
getLogger
().
error
(
"Unable to load skill particle generation data data."
,
ex
);
}
}
/**********
Particle creation for elemental skills.
**********/
private
int
getBallCountForAvatar
(
int
avatarId
)
{
// We default to two particles.
int
count
=
2
;
// If we don't have any data for this avatar, stop.
if
(!
skillParticleGenerationData
.
containsKey
(
avatarId
))
{
Grasscutter
.
getLogger
().
warn
(
"No particle generation data for avatarId {} found."
,
avatarId
);
}
// If we do have data, roll for how many particles we should generate.
else
{
int
roll
=
ThreadLocalRandom
.
current
().
nextInt
(
0
,
100
);
int
percentageStack
=
0
;
for
(
SkillParticleGenerationInfo
info
:
skillParticleGenerationData
.
get
(
avatarId
))
{
int
chance
=
info
.
getChance
();
percentageStack
+=
chance
;
if
(
roll
<
percentageStack
)
{
count
=
info
.
getValue
();
break
;
}
}
}
// Done.
return
count
;
}
private
int
getBallIdForElement
(
ElementType
element
)
{
// If we have no element, we default to an elementless particle.
if
(
element
==
null
)
{
return
2024
;
}
// Otherwise, we determin the particle's ID based on the element.
return
switch
(
element
)
{
case
Fire
->
2017
;
case
Water
->
2018
;
case
Grass
->
2019
;
case
Electric
->
2020
;
case
Wind
->
2021
;
case
Ice
->
2022
;
case
Rock
->
2023
;
default
->
2024
;
};
}
public
void
handleGenerateElemBall
(
AbilityInvokeEntry
invoke
)
throws
InvalidProtocolBufferException
{
// ToDo:
// This is also called when a weapon like Favonius Warbow etc. creates energy through its passive.
// We are not handling this correctly at the moment.
// Get action info.
AbilityActionGenerateElemBall
action
=
AbilityActionGenerateElemBall
.
parseFrom
(
invoke
.
getAbilityData
());
if
(
action
==
null
)
{
return
;
}
// Default to an elementless particle.
int
itemId
=
2024
;
// Generate 2 particles by default.
int
amount
=
2
;
// Try to get the casting avatar from the player's party.
Optional
<
EntityAvatar
>
avatarEntity
=
getCastingAvatarEntityForEnergy
(
invoke
.
getEntityId
());
// Bug: invokes twice sometimes, Ayato, Keqing
// ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin)
if
(
avatarEntity
.
isPresent
())
{
Avatar
avatar
=
avatarEntity
.
get
().
getAvatar
();
if
(
avatar
!=
null
)
{
int
avatarId
=
avatar
.
getAvatarId
();
AvatarSkillDepotData
skillDepotData
=
avatar
.
getSkillDepot
();
// Determine how many particles we need to create for this avatar.
amount
=
this
.
getBallCountForAvatar
(
avatarId
);
// Determine the avatar's element, and based on that the ID of the
// particles we have to generate.
if
(
skillDepotData
!=
null
)
{
ElementType
element
=
skillDepotData
.
getElementType
();
itemId
=
getBallIdForElement
(
element
);
}
}
}
// Generate the particles.
for
(
int
i
=
0
;
i
<
amount
;
i
++)
{
generateElemBall
(
itemId
,
new
Position
(
action
.
getPos
()),
1
);
}
}
/**********
Pickup of elemental particles and orbs.
**********/
public
void
handlePickupElemBall
(
GameItem
elemBall
)
{
// Check if the item is indeed an energy particle/orb.
if
(
elemBall
.
getItemId
()
<
2001
||
elemBall
.
getItemId
()
>
2024
)
{
return
;
}
// Determine the base amount of energy given by the particle/orb.
// Particles have a base amount of 1.0, and orbs a base amount of 3.0.
float
baseEnergy
=
(
elemBall
.
getItemId
()
<=
2008
)
?
3.0f
:
1.0f
;
// Add energy to every team member.
for
(
int
i
=
0
;
i
<
this
.
player
.
getTeamManager
().
getActiveTeam
().
size
();
i
++)
{
EntityAvatar
entity
=
this
.
player
.
getTeamManager
().
getActiveTeam
().
get
(
i
);
// On-field vs off-field multiplier.
// The on-field character gets no penalty.
// Off-field characters get a penalty depending on the team size, as follows:
// - 2 character team: 0.8
// - 3 character team: 0.7
// - 4 character team: 0.6
// - etc.
// We set a lower bound of 0.1 here, to avoid gaining no or negative energy.
float
offFieldPenalty
=
(
this
.
player
.
getTeamManager
().
getCurrentCharacterIndex
()
==
i
)
?
1.0f
:
1.0f
-
this
.
player
.
getTeamManager
().
getActiveTeam
().
size
()
*
0.1f
;
offFieldPenalty
=
Math
.
max
(
offFieldPenalty
,
0.1f
);
// Same element/neutral bonus.
// Same-element characters get a bonus of *3, while different-element characters get no bonus at all.
// For neutral particles/orbs, the multiplier is always *2.
if
(
entity
.
getAvatar
().
getSkillDepot
()
==
null
)
{
continue
;
}
ElementType
avatarElement
=
entity
.
getAvatar
().
getSkillDepot
().
getElementType
();
ElementType
ballElement
=
switch
(
elemBall
.
getItemId
())
{
case
2001
,
2017
->
ElementType
.
Fire
;
case
2002
,
2018
->
ElementType
.
Water
;
case
2003
,
2019
->
ElementType
.
Grass
;
case
2004
,
2020
->
ElementType
.
Electric
;
case
2005
,
2021
->
ElementType
.
Wind
;
case
2006
,
2022
->
ElementType
.
Ice
;
case
2007
,
2023
->
ElementType
.
Rock
;
default
->
null
;
};
float
elementBonus
=
(
ballElement
==
null
)
?
2.0f
:
(
avatarElement
==
ballElement
)
?
3.0f
:
1.0f
;
// Add the energy.
entity
.
addEnergy
(
baseEnergy
*
elementBonus
*
offFieldPenalty
*
elemBall
.
getCount
(),
PropChangeReason
.
PROP_CHANGE_REASON_ENERGY_BALL
);
}
}
/**********
Energy generation for NAs/CAs.
**********/
private
void
generateEnergyForNormalAndCharged
(
EntityAvatar
avatar
)
{
// This logic is based on the descriptions given in
// https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks
// https://library.keqingmains.com/combat-mechanics/energy#auto-attacking
// Those descriptions are lacking in some information, so this implementation most likely
// does not fully replicate the behavior of the official server. Open questions:
// - Does the probability for a character reset after some time?
// - Does the probability for a character reset when switching them out?
// - Does this really count every individual hit separately?
// Get the avatar's weapon type.
WeaponType
weaponType
=
avatar
.
getAvatar
().
getAvatarData
().
getWeaponType
();
// Check if we already have probability data for this avatar. If not, insert it.
if
(!
this
.
avatarNormalProbabilities
.
containsKey
(
avatar
))
{
this
.
avatarNormalProbabilities
.
put
(
avatar
,
weaponType
.
getEnergyGainInitialProbability
());
}
// Roll for energy.
int
currentProbability
=
this
.
avatarNormalProbabilities
.
get
(
avatar
);
int
roll
=
ThreadLocalRandom
.
current
().
nextInt
(
0
,
100
);
// If the player wins the roll, we increase the avatar's energy and reset the probability.
if
(
roll
<
currentProbability
)
{
avatar
.
addEnergy
(
1.0f
,
PropChangeReason
.
PROP_CHANGE_REASON_ABILITY
,
true
);
this
.
avatarNormalProbabilities
.
put
(
avatar
,
weaponType
.
getEnergyGainInitialProbability
());
}
// Otherwise, we increase the probability for the next hit.
else
{
this
.
avatarNormalProbabilities
.
put
(
avatar
,
currentProbability
+
weaponType
.
getEnergyGainIncreaseProbability
());
}
}
public
void
handleAttackHit
(
EvtBeingHitInfo
hitInfo
)
{
// Get the attack result.
AttackResult
attackRes
=
hitInfo
.
getAttackResult
();
// Make sure the attack was performed by the currently active avatar. If not, we ignore the hit.
Optional
<
EntityAvatar
>
attackerEntity
=
this
.
getCastingAvatarEntityForEnergy
(
attackRes
.
getAttackerId
());
if
(
attackerEntity
.
isEmpty
()
||
this
.
player
.
getTeamManager
().
getCurrentAvatarEntity
().
getId
()
!=
attackerEntity
.
get
().
getId
())
{
return
;
}
// Make sure the target is an actual enemy.
GameEntity
targetEntity
=
this
.
player
.
getScene
().
getEntityById
(
attackRes
.
getDefenseId
());
if
(!(
targetEntity
instanceof
EntityMonster
))
{
return
;
}
EntityMonster
targetMonster
=
(
EntityMonster
)
targetEntity
;
MonsterType
targetType
=
targetMonster
.
getMonsterData
().
getType
();
if
(
targetType
!=
MonsterType
.
MONSTER_ORDINARY
&&
targetType
!=
MonsterType
.
MONSTER_BOSS
)
{
return
;
}
// Get the ability that caused this hit.
AbilityIdentifier
ability
=
attackRes
.
getAbilityIdentifier
();
// Make sure there is no actual "ability" associated with the hit. For now, this is how we
// identify normal and charged attacks. Note that this is not completely accurate:
// - Many character's charged attacks have an ability associated with them. This means that,
// for now, we don't identify charged attacks reliably.
// - There might also be some cases where we incorrectly identify something as a normal or
// charged attack that is not (Diluc's E?).
// - Catalyst normal attacks have an ability, so we don't handle those for now.
// ToDo: Fix all of that.
if
(
ability
!=
AbilityIdentifier
.
getDefaultInstance
())
{
return
;
}
// Handle the energy generation.
this
.
generateEnergyForNormalAndCharged
(
attackerEntity
.
get
());
}
/**********
Energy logic related to using skills.
**********/
private
void
handleBurstCast
(
Avatar
avatar
,
int
skillId
)
{
// Don't do anything if energy usage is disabled.
if
(!
GAME_OPTIONS
.
energyUsage
||
!
this
.
energyUsage
)
{
return
;
}
// If the cast skill was a burst, consume energy.
if
(
avatar
.
getSkillDepot
()
!=
null
&&
skillId
==
avatar
.
getSkillDepot
().
getEnergySkill
())
{
avatar
.
getAsEntity
().
clearEnergy
(
PropChangeReason
.
PROP_CHANGE_REASON_ABILITY
);
}
}
public
void
handleEvtDoSkillSuccNotify
(
GameSession
session
,
int
skillId
,
int
casterId
)
{
// Determine the entity that has cast the skill. Cancel if we can't find that avatar.
Optional
<
EntityAvatar
>
caster
=
this
.
player
.
getTeamManager
().
getActiveTeam
().
stream
()
.
filter
(
character
->
character
.
getId
()
==
casterId
)
.
findFirst
();
if
(
caster
.
isEmpty
())
{
return
;
}
Avatar
avatar
=
caster
.
get
().
getAvatar
();
// Handle elemental burst.
this
.
handleBurstCast
(
avatar
,
skillId
);
}
/**********
Monster energy drops.
**********/
private
void
generateElemBallDrops
(
EntityMonster
monster
,
int
dropId
)
{
// Generate all drops specified for the given drop id.
if
(!
energyDropData
.
containsKey
(
dropId
))
{
Grasscutter
.
getLogger
().
warn
(
"No drop data for dropId {} found."
,
dropId
);
return
;
}
for
(
EnergyDropInfo
info
:
energyDropData
.
get
(
dropId
))
{
this
.
generateElemBall
(
info
.
getBallId
(),
monster
.
getPosition
(),
info
.
getCount
());
}
}
public
void
handleMonsterEnergyDrop
(
EntityMonster
monster
,
float
hpBeforeDamage
,
float
hpAfterDamage
)
{
// Make sure this is actually a monster.
// Note that some wildlife also has that type, like boars or birds.
MonsterType
type
=
monster
.
getMonsterData
().
getType
();
if
(
type
!=
MonsterType
.
MONSTER_ORDINARY
&&
type
!=
MonsterType
.
MONSTER_BOSS
)
{
return
;
}
// Calculate the HP tresholds for before and after the damage was taken.
float
maxHp
=
monster
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
);
float
thresholdBefore
=
hpBeforeDamage
/
maxHp
;
float
thresholdAfter
=
hpAfterDamage
/
maxHp
;
// Determine the thresholds the monster has passed, and generate drops based on that.
for
(
HpDrops
drop
:
monster
.
getMonsterData
().
getHpDrops
())
{
if
(
drop
.
getDropId
()
==
0
)
{
continue
;
}
float
threshold
=
drop
.
getHpPercent
()
/
100.0f
;
if
(
threshold
<
thresholdBefore
&&
threshold
>=
thresholdAfter
)
{
generateElemBallDrops
(
monster
,
drop
.
getDropId
());
}
}
// Handle kill drops.
if
(
hpAfterDamage
<=
0
&&
monster
.
getMonsterData
().
getKillDropId
()
!=
0
)
{
generateElemBallDrops
(
monster
,
monster
.
getMonsterData
().
getKillDropId
());
}
}
/**********
Utility.
**********/
private
void
generateElemBall
(
int
ballId
,
Position
position
,
int
count
)
{
// Generate a particle/orb with the specified parameters.
ItemData
itemData
=
GameData
.
getItemDataMap
().
get
(
ballId
);
if
(
itemData
==
null
)
{
return
;
}
EntityItem
energyBall
=
new
EntityItem
(
this
.
getPlayer
().
getScene
(),
this
.
getPlayer
(),
itemData
,
position
,
count
);
this
.
getPlayer
().
getScene
().
addEntity
(
energyBall
);
}
private
Optional
<
EntityAvatar
>
getCastingAvatarEntityForEnergy
(
int
invokeEntityId
)
{
// To determine the avatar that has cast the skill that caused the energy particle to be generated,
// we have to look at the entity that has invoked the ability. This can either be that avatar directly,
// or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar
// that cast the skill.
// Try to get the invoking entity from the scene.
GameEntity
entity
=
player
.
getScene
().
getEntityById
(
invokeEntityId
);
// Determine the ID of the entity that originally cast this skill. If the scene entity is null,
// or not an `EntityClientGadget`, we assume that we are directly looking at the casting avatar
// (the null case will happen if the avatar was switched out between casting the skill and the
// particle being generated). If the scene entity is an `EntityClientGadget`, we need to find the
// ID of the original owner of that gadget.
int
avatarEntityId
=
(!(
entity
instanceof
EntityClientGadget
))
?
invokeEntityId
:
((
EntityClientGadget
)
entity
).
getOriginalOwnerEntityId
();
// Finally, find the avatar entity in the player's team.
return
player
.
getTeamManager
().
getActiveTeam
()
.
stream
()
.
filter
(
character
->
character
.
getId
()
==
avatarEntityId
)
.
findFirst
();
}
public
Boolean
getEnergyUsage
()
{
return
energyUsage
;
}
public
void
setEnergyUsage
(
Boolean
energyUsage
)
{
this
.
energyUsage
=
energyUsage
;
}
}
\ No newline at end of file
src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationEntry.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.EnergyManager
;
import
java.util.List
;
public
class
SkillParticleGenerationEntry
{
private
int
avatarId
;
private
List
<
SkillParticleGenerationInfo
>
amountList
;
public
int
getAvatarId
()
{
return
this
.
avatarId
;
}
public
List
<
SkillParticleGenerationInfo
>
getAmountList
()
{
return
this
.
amountList
;
}
}
src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationInfo.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.EnergyManager
;
public
class
SkillParticleGenerationInfo
{
private
int
value
;
private
int
chance
;
public
int
getValue
()
{
return
this
.
value
;
}
public
int
getChance
()
{
return
this
.
chance
;
}
}
src/main/java/emu/grasscutter/game/managers/InsectCaptureManager.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.data.excels.EnvAnimalGatherConfigData
;
import
emu.grasscutter.data.excels.ItemData
;
import
emu.grasscutter.game.entity.EntityMonster
;
import
emu.grasscutter.game.entity.EntityVehicle
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.inventory.GameItem
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.props.ActionReason
;
import
emu.grasscutter.net.proto.VisionTypeOuterClass
;
public
record
InsectCaptureManager
(
Player
player
)
{
public
void
arrestSmallCreature
(
GameEntity
entity
)
{
//System.out.println("arrestSmallCreature!");
EnvAnimalGatherConfigData
gather
;
int
thingId
;
if
(
entity
instanceof
EntityMonster
monster
)
{
thingId
=
monster
.
getMonsterData
().
getId
();
gather
=
GameData
.
getEnvAnimalGatherConfigDataMap
().
get
(
thingId
);
}
else
if
(
entity
instanceof
EntityVehicle
gadget
)
{
thingId
=
gadget
.
getGadgetId
();
gather
=
GameData
.
getEnvAnimalGatherConfigDataMap
().
get
(
thingId
);
}
else
{
return
;
}
if
(
gather
==
null
)
{
Grasscutter
.
getLogger
().
warn
(
"monster/gather(id={}) couldn't be caught."
,
thingId
);
return
;
}
String
type
=
gather
.
getEntityType
();
if
((
type
.
equals
(
"Monster"
)
&&
entity
instanceof
EntityMonster
)
||
(
type
.
equals
(
"Gadget"
)
&&
entity
instanceof
EntityVehicle
))
{
EnvAnimalGatherConfigData
.
GatherItem
gatherItem
=
gather
.
gatherItem
();
ItemData
data
=
GameData
.
getItemDataMap
().
get
(
gatherItem
.
getId
());
GameItem
item
=
new
GameItem
(
data
,
gatherItem
.
getCount
());
player
.
getInventory
().
addItem
(
item
,
ActionReason
.
SubfieldDrop
);
entity
.
getScene
().
removeEntity
(
entity
,
VisionTypeOuterClass
.
VisionType
.
VISION_TYPE_REMOVE
);
}
else
{
Grasscutter
.
getLogger
().
warn
(
"monster/gather(id={}) has a wrong type."
,
thingId
);
}
}
}
src/main/java/emu/grasscutter/game/managers/InventoryManager.java
View file @
a8293102
package
emu.grasscutter.game.managers
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.data.binout.OpenConfigEntry
;
import
emu.grasscutter.data.binout.OpenConfigEntry.SkillPointModifier
;
import
emu.grasscutter.data.common.ItemParamData
;
import
emu.grasscutter.data.custom.OpenConfigEntry
;
import
emu.grasscutter.data.custom.OpenConfigEntry.SkillPointModifier
;
import
emu.grasscutter.data.def.AvatarPromoteData
;
import
emu.grasscutter.data.def.AvatarSkillData
;
import
emu.grasscutter.data.def.AvatarSkillDepotData
;
import
emu.grasscutter.data.def.ItemData
;
import
emu.grasscutter.data.def.WeaponPromoteData
;
import
emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens
;
import
emu.grasscutter.data.def.AvatarTalentData
;
import
emu.grasscutter.data.def.ProudSkillData
;
import
emu.grasscutter.data.excels.AvatarPromoteData
;
import
emu.grasscutter.data.excels.AvatarSkillData
;
import
emu.grasscutter.data.excels.AvatarSkillDepotData
;
import
emu.grasscutter.data.excels.AvatarTalentData
;
import
emu.grasscutter.data.excels.ItemData
;
import
emu.grasscutter.data.excels.ProudSkillData
;
import
emu.grasscutter.data.excels.WeaponPromoteData
;
import
emu.grasscutter.data.excels.AvatarSkillDepotData.InherentProudSkillOpens
;
import
emu.grasscutter.game.avatar.Avatar
;
import
emu.grasscutter.game.inventory.GameItem
;
import
emu.grasscutter.game.inventory.ItemType
;
...
...
@@ -27,6 +29,7 @@ import emu.grasscutter.game.shop.ShopChestBatchUseTable;
import
emu.grasscutter.game.shop.ShopChestTable
;
import
emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam
;
import
emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo
;
import
emu.grasscutter.server.packet.send.PacketForgeFormulaDataNotify
;
import
emu.grasscutter.server.game.GameServer
;
import
emu.grasscutter.server.packet.send.*
;
import
emu.grasscutter.utils.Utils
;
...
...
@@ -38,6 +41,8 @@ public class InventoryManager {
private
final
static
int
RELIC_MATERIAL_1
=
105002
;
// Sanctifying Unction
private
final
static
int
RELIC_MATERIAL_2
=
105003
;
// Sanctifying Essence
private
final
static
int
RELIC_MATERIAL_EXP_1
=
2500
;
// Sanctifying Unction
private
final
static
int
RELIC_MATERIAL_EXP_2
=
10000
;
// Sanctifying Essence
private
final
static
int
WEAPON_ORE_1
=
104011
;
// Enhancement Ore
private
final
static
int
WEAPON_ORE_2
=
104012
;
// Fine Enhancement Ore
...
...
@@ -85,6 +90,7 @@ public class InventoryManager {
int
moraCost
=
0
;
int
expGain
=
0
;
List
<
GameItem
>
foodRelics
=
new
ArrayList
<
GameItem
>();
for
(
long
guid
:
foodRelicList
)
{
// Add to delete queue
GameItem
food
=
player
.
getInventory
().
getItemByGuid
(
guid
);
...
...
@@ -96,23 +102,21 @@ public class InventoryManager {
expGain
+=
food
.
getItemData
().
getBaseConvExp
();
// Feeding artifact with exp already
if
(
food
.
getTotalExp
()
>
0
)
{
expGain
+=
(
int
)
Math
.
floor
(
food
.
getTotalExp
()
*
.
8
f
)
;
expGain
+=
(
food
.
getTotalExp
()
*
4
)
/
5
;
}
foodRelics
.
add
(
food
);
}
List
<
ItemParamData
>
payList
=
new
ArrayList
<
ItemParamData
>();
for
(
ItemParam
itemParam
:
list
)
{
GameItem
food
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
itemParam
.
getItemId
());
if
(
food
==
null
||
food
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_RELIQUARY_MATERIAL
)
{
continue
;
}
int
amount
=
Math
.
min
(
food
.
getCount
(),
itemParam
.
getCount
());
int
gain
=
0
;
if
(
food
.
getItemId
()
==
RELIC_MATERIAL_2
)
{
gain
=
10000
*
amount
;
}
else
if
(
food
.
getItemId
()
==
RELIC_MATERIAL_1
)
{
gain
=
2500
*
amount
;
}
int
amount
=
itemParam
.
getCount
();
// Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
int
gain
=
amount
*
switch
(
itemParam
.
getItemId
())
{
case
RELIC_MATERIAL_1
->
RELIC_MATERIAL_EXP_1
;
case
RELIC_MATERIAL_2
->
RELIC_MATERIAL_EXP_2
;
default
->
0
;
};
expGain
+=
gain
;
moraCost
+=
gain
;
payList
.
add
(
new
ItemParamData
(
itemParam
.
getItemId
(),
itemParam
.
getCount
()));
}
// Make sure exp gain is valid
...
...
@@ -120,28 +124,14 @@ public class InventoryManager {
return
;
}
// Check mora
if
(
player
.
getMora
()
<
moraCost
)
{
// Confirm payment of materials and mora (assume food relics are payable afterwards)
payList
.
add
(
new
ItemParamData
(
202
,
moraCost
));
if
(!
player
.
getInventory
().
payItems
(
payList
.
toArray
(
new
ItemParamData
[
0
])))
{
return
;
}
player
.
setMora
(
player
.
getMora
()
-
moraCost
);
// Consume food items
for
(
long
guid
:
foodRelicList
)
{
GameItem
food
=
player
.
getInventory
().
getItemByGuid
(
guid
);
if
(
food
==
null
||
!
food
.
isDestroyable
())
{
continue
;
}
player
.
getInventory
().
removeItem
(
food
);
}
for
(
ItemParam
itemParam
:
list
)
{
GameItem
food
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
itemParam
.
getItemId
());
if
(
food
==
null
||
food
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_RELIQUARY_MATERIAL
)
{
continue
;
}
int
amount
=
Math
.
min
(
food
.
getCount
(),
itemParam
.
getCount
());
player
.
getInventory
().
removeItem
(
food
,
amount
);
}
// Consume food relics
player
.
getInventory
().
removeItems
(
foodRelics
);
// Implement random rate boost
int
rate
=
1
;
...
...
@@ -231,22 +221,16 @@ public class InventoryManager {
}
expGain
+=
food
.
getItemData
().
getWeaponBaseExp
();
if
(
food
.
getTotalExp
()
>
0
)
{
expGain
+=
(
int
)
Math
.
floor
(
food
.
getTotalExp
()
*
.
8
f
)
;
expGain
+=
(
food
.
getTotalExp
()
*
4
)
/
5
;
}
}
for
(
ItemParam
param
:
itemParamList
)
{
GameItem
food
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
param
.
getItemId
());
if
(
food
==
null
||
food
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_WEAPON_EXP_STONE
)
{
continue
;
}
int
amount
=
Math
.
min
(
param
.
getCount
(),
food
.
getCount
());
if
(
food
.
getItemId
()
==
WEAPON_ORE_3
)
{
expGain
+=
10000
*
amount
;
}
else
if
(
food
.
getItemId
()
==
WEAPON_ORE_2
)
{
expGain
+=
2000
*
amount
;
}
else
if
(
food
.
getItemId
()
==
WEAPON_ORE_1
)
{
expGain
+=
400
*
amount
;
}
expGain
+=
param
.
getCount
()
*
switch
(
param
.
getItemId
())
{
case
WEAPON_ORE_1
->
WEAPON_ORE_EXP_1
;
case
WEAPON_ORE_2
->
WEAPON_ORE_EXP_2
;
case
WEAPON_ORE_3
->
WEAPON_ORE_EXP_3
;
default
->
0
;
};
}
// Try
...
...
@@ -288,65 +272,45 @@ public class InventoryManager {
}
// Get exp gain
int
expGain
=
0
,
moraCost
=
0
;
int
expGain
=
0
,
expGainFree
=
0
;
List
<
GameItem
>
foodWeapons
=
new
ArrayList
<
GameItem
>();
for
(
long
guid
:
foodWeaponGuidList
)
{
GameItem
food
=
player
.
getInventory
().
getItemByGuid
(
guid
);
if
(
food
==
null
||
!
food
.
isDestroyable
())
{
continue
;
}
expGain
+=
food
.
getItemData
().
getWeaponBaseExp
();
moraCost
+=
(
int
)
Math
.
floor
(
food
.
getItemData
().
getWeaponBaseExp
()
*
.
1
f
);
if
(
food
.
getTotalExp
()
>
0
)
{
expGain
+=
(
int
)
Math
.
floor
(
food
.
getTotalExp
()
*
.
8
f
);
expGain
Free
+=
(
food
.
getTotalExp
()
*
4
)
/
5
;
// No tax :D
}
foodWeapons
.
add
(
food
);
}
List
<
ItemParamData
>
payList
=
new
ArrayList
<
ItemParamData
>();
for
(
ItemParam
param
:
itemParamList
)
{
GameItem
food
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
param
.
getItemId
());
if
(
food
==
null
||
food
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_WEAPON_EXP_STONE
)
{
continue
;
}
int
amount
=
Math
.
min
(
param
.
getCount
(),
food
.
getCount
());
int
gain
=
0
;
if
(
food
.
getItemId
()
==
WEAPON_ORE_3
)
{
gain
=
10000
*
amount
;
}
else
if
(
food
.
getItemId
()
==
WEAPON_ORE_2
)
{
gain
=
2000
*
amount
;
}
else
if
(
food
.
getItemId
()
==
WEAPON_ORE_1
)
{
gain
=
400
*
amount
;
}
int
amount
=
param
.
getCount
();
// Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
int
gain
=
amount
*
switch
(
param
.
getItemId
())
{
case
WEAPON_ORE_1
->
WEAPON_ORE_EXP_1
;
case
WEAPON_ORE_2
->
WEAPON_ORE_EXP_2
;
case
WEAPON_ORE_3
->
WEAPON_ORE_EXP_3
;
default
->
0
;
};
expGain
+=
gain
;
moraCost
+=
(
int
)
Math
.
floor
(
gain
*
.
1
f
);
payList
.
add
(
new
ItemParamData
(
param
.
getItemId
(),
amount
)
);
}
// Make sure exp gain is valid
int
moraCost
=
expGain
/
10
;
expGain
+=
expGainFree
;
if
(
expGain
<=
0
)
{
return
;
}
// Mora check
if
(
player
.
getMora
()
>=
moraCost
)
{
player
.
setMora
(
player
.
getMora
()
-
moraCost
);
}
else
{
// Confirm payment of materials and mora (assume food weapons are payable afterwards)
payList
.
add
(
new
ItemParamData
(
202
,
moraCost
));
if
(!
player
.
getInventory
().
payItems
(
payList
.
toArray
(
new
ItemParamData
[
0
])))
{
return
;
}
// Consume weapon/items used to feed
for
(
long
guid
:
foodWeaponGuidList
)
{
GameItem
food
=
player
.
getInventory
().
getItemByGuid
(
guid
);
if
(
food
==
null
||
!
food
.
isDestroyable
())
{
continue
;
}
player
.
getInventory
().
removeItem
(
food
);
}
for
(
ItemParam
param
:
itemParamList
)
{
GameItem
food
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
param
.
getItemId
());
if
(
food
==
null
||
food
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_WEAPON_EXP_STONE
)
{
continue
;
}
int
amount
=
Math
.
min
(
param
.
getCount
(),
food
.
getCount
());
player
.
getInventory
().
removeItem
(
food
,
amount
);
}
player
.
getInventory
().
removeItems
(
foodWeapons
);
// Level up
int
maxLevel
=
promoteData
.
getUnlockMaxLevel
();
...
...
@@ -393,7 +357,7 @@ public class InventoryManager {
player
.
sendPacket
(
new
PacketWeaponUpgradeRsp
(
weapon
,
oldLevel
,
leftovers
));
}
private
List
<
ItemParam
>
getLeftoverOres
(
floa
t
leftover
)
{
private
List
<
ItemParam
>
getLeftoverOres
(
in
t
leftover
)
{
List
<
ItemParam
>
leftoverOreList
=
new
ArrayList
<>(
3
);
if
(
leftover
<
WEAPON_ORE_EXP_1
)
{
...
...
@@ -401,11 +365,11 @@ public class InventoryManager {
}
// Get leftovers
int
ore3
=
(
int
)
Math
.
floor
(
leftover
/
WEAPON_ORE_EXP_3
)
;
int
ore3
=
leftover
/
WEAPON_ORE_EXP_3
;
leftover
=
leftover
%
WEAPON_ORE_EXP_3
;
int
ore2
=
(
int
)
Math
.
floor
(
leftover
/
WEAPON_ORE_EXP_2
)
;
int
ore2
=
leftover
/
WEAPON_ORE_EXP_2
;
leftover
=
leftover
%
WEAPON_ORE_EXP_2
;
int
ore1
=
(
int
)
Math
.
floor
(
leftover
/
WEAPON_ORE_EXP_1
)
;
int
ore1
=
leftover
/
WEAPON_ORE_EXP_1
;
if
(
ore3
>
0
)
{
leftoverOreList
.
add
(
ItemParam
.
newBuilder
().
setItemId
(
WEAPON_ORE_3
).
setCount
(
ore3
).
build
());
...
...
@@ -496,27 +460,16 @@ public class InventoryManager {
return
;
}
// Make sure player has promote items
for
(
ItemParamData
cost
:
nextPromoteData
.
getCostItems
())
{
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
if
(
feedItem
==
null
||
feedItem
.
getCount
()
<
cost
.
getCount
())
{
return
;
}
// Pay materials and mora if possible
ItemParamData
[]
costs
=
nextPromoteData
.
getCostItems
();
// Can this be null?
if
(
nextPromoteData
.
getCoinCost
()
>
0
)
{
costs
=
Arrays
.
copyOf
(
costs
,
costs
.
length
+
1
);
costs
[
costs
.
length
-
1
]
=
new
ItemParamData
(
202
,
nextPromoteData
.
getCoinCost
());
}
// Mora check
if
(
player
.
getMora
()
>=
nextPromoteData
.
getCoinCost
())
{
player
.
setMora
(
player
.
getMora
()
-
nextPromoteData
.
getCoinCost
());
}
else
{
if
(!
player
.
getInventory
().
payItems
(
costs
))
{
return
;
}
// Consume promote filler items
for
(
ItemParamData
cost
:
nextPromoteData
.
getCostItems
())
{
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
player
.
getInventory
().
removeItem
(
feedItem
,
cost
.
getCount
());
}
int
oldPromoteLevel
=
weapon
.
getPromoteLevel
();
weapon
.
setPromoteLevel
(
nextPromoteLevel
);
weapon
.
save
();
...
...
@@ -552,27 +505,16 @@ public class InventoryManager {
return
;
}
// Make sure player has cost items
for
(
ItemParamData
cost
:
nextPromoteData
.
getCostItems
())
{
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
if
(
feedItem
==
null
||
feedItem
.
getCount
()
<
cost
.
getCount
())
{
return
;
}
// Pay materials and mora if possible
ItemParamData
[]
costs
=
nextPromoteData
.
getCostItems
();
// Can this be null?
if
(
nextPromoteData
.
getCoinCost
()
>
0
)
{
costs
=
Arrays
.
copyOf
(
costs
,
costs
.
length
+
1
);
costs
[
costs
.
length
-
1
]
=
new
ItemParamData
(
202
,
nextPromoteData
.
getCoinCost
());
}
// Mora check
if
(
player
.
getMora
()
>=
nextPromoteData
.
getCoinCost
())
{
player
.
setMora
(
player
.
getMora
()
-
nextPromoteData
.
getCoinCost
());
}
else
{
if
(!
player
.
getInventory
().
payItems
(
costs
))
{
return
;
}
// Consume promote filler items
for
(
ItemParamData
cost
:
nextPromoteData
.
getCostItems
())
{
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
player
.
getInventory
().
removeItem
(
feedItem
,
cost
.
getCount
());
}
// Update promote level
avatar
.
setPromoteLevel
(
nextPromoteLevel
);
...
...
@@ -616,35 +558,26 @@ public class InventoryManager {
return
;
}
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
itemId
);
if
(
feedItem
==
null
||
feedItem
.
getItemData
().
getMaterialType
()
!=
MaterialType
.
MATERIAL_EXP_FRUIT
||
feedItem
.
getCount
()
<
count
)
{
return
;
}
// Calc exp
int
expGain
=
0
,
moraCost
=
0
;
// TODO clean up
if
(
itemId
==
AVATAR_BOOK_3
)
{
expGain
=
AVATAR_BOOK_EXP_3
*
count
;
}
else
if
(
itemId
==
AVATAR_BOOK_2
)
{
expGain
=
AVATAR_BOOK_EXP_2
*
count
;
}
else
if
(
itemId
==
AVATAR_BOOK_1
)
{
expGain
=
AVATAR_BOOK_EXP_1
*
count
;
int
expGain
=
switch
(
itemId
)
{
case
AVATAR_BOOK_1
->
AVATAR_BOOK_EXP_1
*
count
;
case
AVATAR_BOOK_2
->
AVATAR_BOOK_EXP_2
*
count
;
case
AVATAR_BOOK_3
->
AVATAR_BOOK_EXP_3
*
count
;
default
->
0
;
};
// Sanity check
if
(
expGain
<=
0
)
{
return
;
}
moraCost
=
(
int
)
Math
.
floor
(
expGain
*
.
2
f
);
// Mora check
if
(
player
.
getMora
()
>=
moraCost
)
{
player
.
setMora
(
player
.
getMora
()
-
moraCost
);
}
else
{
// Payment check
int
moraCost
=
expGain
/
5
;
ItemParamData
[]
costItems
=
new
ItemParamData
[]
{
new
ItemParamData
(
itemId
,
count
),
new
ItemParamData
(
202
,
moraCost
)};
if
(!
player
.
getInventory
().
payItems
(
costItems
))
{
return
;
}
// Consume items
player
.
getInventory
().
removeItem
(
feedItem
,
count
);
// Level up
upgradeAvatar
(
player
,
avatar
,
promoteData
,
expGain
);
}
...
...
@@ -764,33 +697,15 @@ public class InventoryManager {
return
;
}
// Make sure player has cost items
for
(
ItemParamData
cost
:
proudSkill
.
getCostItems
())
{
if
(
cost
.
getId
()
==
0
)
{
continue
;
}
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
if
(
feedItem
==
null
||
feedItem
.
getCount
()
<
cost
.
getCount
())
{
return
;
}
// Pay materials and mora if possible
List
<
ItemParamData
>
costs
=
new
ArrayList
<
ItemParamData
>(
proudSkill
.
getCostItems
());
// Can this be null?
if
(
proudSkill
.
getCoinCost
()
>
0
)
{
costs
.
add
(
new
ItemParamData
(
202
,
proudSkill
.
getCoinCost
()));
}
// Mora check
if
(
player
.
getMora
()
>=
proudSkill
.
getCoinCost
())
{
player
.
setMora
(
player
.
getMora
()
-
proudSkill
.
getCoinCost
());
}
else
{
if
(!
player
.
getInventory
().
payItems
(
costs
.
toArray
(
new
ItemParamData
[
0
])))
{
return
;
}
// Consume promote filler items
for
(
ItemParamData
cost
:
proudSkill
.
getCostItems
())
{
if
(
cost
.
getId
()
==
0
)
{
continue
;
}
GameItem
feedItem
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
cost
.
getId
());
player
.
getInventory
().
removeItem
(
feedItem
,
cost
.
getCount
());
}
// Upgrade skill
avatar
.
getSkillLevelMap
().
put
(
skillId
,
nextLevel
);
avatar
.
save
();
...
...
@@ -822,14 +737,11 @@ public class InventoryManager {
return
;
}
GameItem
cost
Item
=
player
.
getInventory
().
getInventoryTab
(
ItemType
.
ITEM_MATERIAL
).
getItemById
(
talentData
.
getMainCostItemId
());
if
(
costItem
==
null
||
costItem
.
getCount
()
<
talentData
.
getMainCostItem
Count
(
))
{
// Pay
co
n
st
ellation item if possible
if
(
!
player
.
getInventory
().
payItem
(
talentData
.
getMainCostItem
Id
(),
1
))
{
return
;
}
// Consume item
player
.
getInventory
().
removeItem
(
costItem
,
talentData
.
getMainCostItemCount
());
// Apply + recalc
avatar
.
getTalentIdList
().
add
(
talentData
.
getId
());
avatar
.
setCoreProudSkillLevel
(
currentTalentLevel
+
1
);
...
...
@@ -932,6 +844,25 @@ public class InventoryManager {
used
=
player
.
getTeamManager
().
healAvatar
(
target
,
SatiationParams
[
0
],
SatiationParams
[
1
])
?
1
:
0
;
}
break
;
case
MATERIAL_CONSUME:
// Make sure we have usage data for this material.
if
(
useItem
.
getItemData
().
getItemUse
()
==
null
)
{
break
;
}
// Handle forging blueprints.
if
(
useItem
.
getItemData
().
getItemUse
().
get
(
0
).
getUseOp
().
equals
(
"ITEM_USE_UNLOCK_FORGE"
))
{
// Determine the forging item we should unlock.
int
forgeId
=
Integer
.
parseInt
(
useItem
.
getItemData
().
getItemUse
().
get
(
0
).
getUseParam
().
get
(
0
));
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
player
.
sendPacket
(
new
PacketForgeFormulaDataNotify
(
forgeId
));
player
.
getUnlockedForgingBlueprints
().
add
(
forgeId
);
// Use up the blueprint item.
used
=
1
;
}
break
;
case
MATERIAL_CHEST:
List
<
ShopChestTable
>
shopChestTableList
=
player
.
getServer
().
getShopManager
().
getShopChestData
();
List
<
GameItem
>
rewardItemList
=
new
ArrayList
<>();
...
...
src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java
View file @
a8293102
package
emu.grasscutter.game.managers.MapMarkManager
;
import
dev.morphia.annotations.Entity
;
import
emu.grasscutter.net.proto.MapMarkFromTypeOuterClass
;
import
emu.grasscutter.net.proto.MapMarkPointOuterClass
;
import
emu.grasscutter.net.proto.MapMarkPointTypeOuterClass
;
import
emu.grasscutter.net.proto.MapMarkFromTypeOuterClass
.MapMarkFromType
;
import
emu.grasscutter.net.proto.MapMarkPointOuterClass
.MapMarkPoint
;
import
emu.grasscutter.net.proto.MapMarkPointTypeOuterClass
.MapMarkPointType
;
import
emu.grasscutter.utils.Position
;
@Entity
...
...
@@ -11,63 +11,56 @@ public class MapMark {
private
int
sceneId
;
private
String
name
;
private
Position
position
;
private
MapMarkPointTypeOuterClass
.
MapMarkPointType
pointType
;
private
int
monsterId
=
0
;
private
MapMarkFromTypeOuterClass
.
MapMarkFromType
fromType
;
private
int
questId
=
7
;
public
MapMark
(
Position
position
,
MapMarkPointTypeOuterClass
.
MapMarkPointType
type
)
{
this
.
position
=
position
;
private
MapMarkPointType
mapMarkPointType
;
private
int
monsterId
;
private
MapMarkFromType
mapMarkFromType
;
private
int
questId
;
@Deprecated
// Morhpia
public
MapMark
()
{
this
.
mapMarkPointType
=
MapMarkPointType
.
MAP_MARK_POINT_TYPE_MONSTER
;
this
.
mapMarkFromType
=
MapMarkFromType
.
MAP_MARK_FROM_TYPE_MONSTER
;
}
public
MapMark
(
MapMarkPoint
OuterClass
.
MapMarkPoint
mapMarkPoint
)
{
public
MapMark
(
MapMarkPoint
mapMarkPoint
)
{
this
.
sceneId
=
mapMarkPoint
.
getSceneId
();
this
.
name
=
mapMarkPoint
.
getName
();
this
.
position
=
new
Position
(
mapMarkPoint
.
getPos
().
getX
(),
mapMarkPoint
.
getPos
().
getY
(),
mapMarkPoint
.
getPos
().
getZ
());
this
.
pointType
=
mapMarkPoint
.
getPointType
();
this
.
position
=
new
Position
(
mapMarkPoint
.
getPos
().
getX
(),
mapMarkPoint
.
getPos
().
getY
(),
mapMarkPoint
.
getPos
().
getZ
()
);
this
.
mapMarkPointType
=
mapMarkPoint
.
getPointType
();
this
.
monsterId
=
mapMarkPoint
.
getMonsterId
();
this
.
f
romType
=
mapMarkPoint
.
getFromType
();
this
.
mapMarkF
romType
=
mapMarkPoint
.
getFromType
();
this
.
questId
=
mapMarkPoint
.
getQuestId
();
}
public
int
getSceneId
()
{
return
this
.
sceneId
;
}
public
String
getName
()
{
return
this
.
name
;
}
public
Position
getPosition
()
{
return
this
.
position
;
}
public
MapMarkPointTypeOuterClass
.
MapMarkPointType
getMapMarkPointType
()
{
return
this
.
p
ointType
;
public
MapMarkPointType
getMapMarkPointType
()
{
return
this
.
mapMarkP
ointType
;
}
public
void
setMapMarkPointType
(
MapMarkPointTypeOuterClass
.
MapMarkPointType
pointType
)
{
this
.
pointType
=
pointType
;
}
public
int
getMonsterId
()
{
return
this
.
monsterId
;
}
public
void
setMonsterId
(
int
monsterId
)
{
this
.
monsterId
=
monsterId
;
}
public
MapMarkFromTypeOuterClass
.
MapMarkFromType
getMapMarkFromType
()
{
return
this
.
fromType
;
public
MapMarkFromType
getMapMarkFromType
()
{
return
this
.
mapMarkFromType
;
}
public
int
getQuestId
()
{
return
this
.
questId
;
}
public
void
setQuestId
(
int
questId
)
{
this
.
questId
=
questId
;
}
}
}
\ No newline at end of file
src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java
View file @
a8293102
package
emu.grasscutter.game.managers.MapMarkManager
;
import
dev.morphia.annotations.Entity
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType
;
import
emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq
;
import
emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation
;
import
emu.grasscutter.server.packet.send.PacketMarkMapRsp
;
import
emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify
;
import
emu.grasscutter.utils.Position
;
import
java.util.HashMap
;
@Entity
public
class
MapMarksManager
{
static
final
int
mapMarkMaxCount
=
150
;
public
static
final
int
mapMarkMaxCount
=
150
;
private
HashMap
<
String
,
MapMark
>
mapMarks
;
private
final
Player
player
;
public
MapMarksManager
()
{
mapMarks
=
new
HashMap
<
String
,
MapMark
>();
public
MapMarksManager
(
Player
player
)
{
this
.
player
=
player
;
this
.
mapMarks
=
player
.
getMapMarks
();
if
(
this
.
mapMarks
==
null
)
{
this
.
mapMarks
=
new
HashMap
<>();
}
}
public
MapMarksManager
(
HashMap
<
String
,
MapMark
>
mapMarks
)
{
this
.
mapMarks
=
mapMarks
;
public
void
handleMapMarkReq
(
MarkMapReq
req
)
{
Operation
op
=
req
.
getOp
();
switch
(
op
)
{
case
OPERATION_ADD
->
{
MapMark
createMark
=
new
MapMark
(
req
.
getMark
());
// keep teleporting functionality on fishhook mark.
if
(
createMark
.
getMapMarkPointType
()
==
MapMarkPointType
.
MAP_MARK_POINT_TYPE_FISH_POOL
)
{
teleport
(
player
,
createMark
);
return
;
}
addMapMark
(
createMark
);
}
case
OPERATION_MOD
->
{
MapMark
oldMark
=
new
MapMark
(
req
.
getOld
());
removeMapMark
(
oldMark
.
getPosition
());
MapMark
newMark
=
new
MapMark
(
req
.
getMark
());
addMapMark
(
newMark
);
}
case
OPERATION_DEL
->
{
MapMark
deleteMark
=
new
MapMark
(
req
.
getMark
());
removeMapMark
(
deleteMark
.
getPosition
());
}
}
if
(
op
!=
Operation
.
OPERATION_GET
)
{
saveMapMarks
();
}
player
.
getSession
().
send
(
new
PacketMarkMapRsp
(
getMapMarks
()));
}
public
HashMap
<
String
,
MapMark
>
get
All
MapMarks
()
{
public
HashMap
<
String
,
MapMark
>
getMapMarks
()
{
return
mapMarks
;
}
public
MapMark
getMapMark
(
Position
position
)
{
String
key
=
getMapMarkKey
(
position
);
if
(
mapMarks
.
containsKey
(
key
))
{
return
mapMarks
.
get
(
key
);
}
else
{
return
null
;
}
}
public
String
getMapMarkKey
(
Position
position
)
{
return
"x"
+
(
int
)
position
.
getX
()+
"z"
+
(
int
)
position
.
getZ
();
}
public
boolean
removeMapMark
(
Position
position
)
{
String
key
=
getMapMarkKey
(
position
);
if
(
mapMarks
.
containsKey
(
key
))
{
mapMarks
.
remove
(
key
);
return
true
;
}
return
false
;
public
void
removeMapMark
(
Position
position
)
{
mapMarks
.
remove
(
getMapMarkKey
(
position
));
}
public
boolean
addMapMark
(
MapMark
mapMark
)
{
public
void
addMapMark
(
MapMark
mapMark
)
{
if
(
mapMarks
.
size
()
<
mapMarkMaxCount
)
{
if
(!
mapMarks
.
containsKey
(
mapMark
.
getPosition
()))
{
mapMarks
.
put
(
getMapMarkKey
(
mapMark
.
getPosition
()),
mapMark
);
return
true
;
}
mapMarks
.
put
(
getMapMarkKey
(
mapMark
.
getPosition
()),
mapMark
);
}
return
false
;
}
public
void
setMapMarks
(
HashMap
<
String
,
MapMark
>
mapMarks
)
{
this
.
mapMarks
=
mapMarks
;
private
void
saveMapMarks
()
{
player
.
setMapMarks
(
mapMarks
);
player
.
save
();
}
private
void
teleport
(
Player
player
,
MapMark
mapMark
)
{
float
y
;
try
{
y
=
(
float
)
Integer
.
parseInt
(
mapMark
.
getName
());
}
catch
(
Exception
e
)
{
y
=
300
;
}
Position
pos
=
mapMark
.
getPosition
();
player
.
getPos
().
set
(
pos
.
getX
(),
y
,
pos
.
getZ
());
if
(
mapMark
.
getSceneId
()
!=
player
.
getSceneId
())
{
player
.
getWorld
().
transferPlayerToScene
(
player
,
mapMark
.
getSceneId
(),
player
.
getPos
());
}
player
.
getScene
().
broadcastPacket
(
new
PacketSceneEntityAppearNotify
(
player
));
}
}
src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java
deleted
100644 → 0
View file @
304b9cb8
package
emu.grasscutter.game.managers.MovementManager
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.game.entity.EntityAvatar
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.LifeState
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.net.proto.EntityMoveInfoOuterClass
;
import
emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo
;
import
emu.grasscutter.net.proto.MotionStateOuterClass.MotionState
;
import
emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType
;
import
emu.grasscutter.net.proto.VectorOuterClass
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.*
;
import
emu.grasscutter.utils.Position
;
import
org.jetbrains.annotations.NotNull
;
import
java.lang.Math
;
import
java.util.*
;
public
class
MovementManager
{
public
HashMap
<
String
,
HashSet
<
MotionState
>>
MotionStatesCategorized
=
new
HashMap
<>();
private
enum
ConsumptionType
{
None
(
0
),
// consume
CLIMB_START
(-
500
),
CLIMBING
(-
150
),
CLIMB_JUMP
(-
2500
),
DASH
(-
1800
),
SPRINT
(-
360
),
FLY
(-
60
),
SWIM_DASH_START
(-
200
),
SWIM_DASH
(-
200
),
SWIMMING
(-
80
),
FIGHT
(
0
),
// restore
STANDBY
(
500
),
RUN
(
500
),
WALK
(
500
),
STANDBY_MOVE
(
500
),
POWERED_FLY
(
500
);
public
final
int
amount
;
ConsumptionType
(
int
amount
)
{
this
.
amount
=
amount
;
}
}
private
class
Consumption
{
public
ConsumptionType
consumptionType
;
public
int
amount
;
public
Consumption
(
ConsumptionType
ct
,
int
a
)
{
consumptionType
=
ct
;
amount
=
a
;
}
public
Consumption
(
ConsumptionType
ct
)
{
this
(
ct
,
ct
.
amount
);
}
}
private
MotionState
previousState
=
MotionState
.
MOTION_STANDBY
;
private
MotionState
currentState
=
MotionState
.
MOTION_STANDBY
;
private
Position
previousCoordinates
=
new
Position
(
0
,
0
,
0
);
private
Position
currentCoordinates
=
new
Position
(
0
,
0
,
0
);
private
final
Player
player
;
private
float
landSpeed
=
0
;
private
long
landTimeMillisecond
=
0
;
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
)
{
previousCoordinates
.
add
(
new
Position
(
0
,
0
,
0
));
this
.
player
=
player
;
MotionStatesCategorized
.
put
(
"SWIM"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_SWIM_MOVE
,
MotionState
.
MOTION_SWIM_IDLE
,
MotionState
.
MOTION_SWIM_DASH
,
MotionState
.
MOTION_SWIM_JUMP
)));
MotionStatesCategorized
.
put
(
"STANDBY"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_STANDBY
,
MotionState
.
MOTION_STANDBY_MOVE
,
MotionState
.
MOTION_DANGER_STANDBY
,
MotionState
.
MOTION_DANGER_STANDBY_MOVE
,
MotionState
.
MOTION_LADDER_TO_STANDBY
,
MotionState
.
MOTION_JUMP_UP_WALL_FOR_STANDBY
)));
MotionStatesCategorized
.
put
(
"CLIMB"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_CLIMB
,
MotionState
.
MOTION_CLIMB_JUMP
,
MotionState
.
MOTION_STANDBY_TO_CLIMB
,
MotionState
.
MOTION_LADDER_IDLE
,
MotionState
.
MOTION_LADDER_MOVE
,
MotionState
.
MOTION_LADDER_SLIP
,
MotionState
.
MOTION_STANDBY_TO_LADDER
)));
MotionStatesCategorized
.
put
(
"FLY"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_FLY
,
MotionState
.
MOTION_FLY_IDLE
,
MotionState
.
MOTION_FLY_SLOW
,
MotionState
.
MOTION_FLY_FAST
,
MotionState
.
MOTION_POWERED_FLY
)));
MotionStatesCategorized
.
put
(
"RUN"
,
new
HashSet
<>(
Arrays
.
asList
(
MotionState
.
MOTION_DASH
,
MotionState
.
MOTION_DANGER_DASH
,
MotionState
.
MOTION_DASH_BEFORE_SHAKE
,
MotionState
.
MOTION_RUN
,
MotionState
.
MOTION_DANGER_RUN
,
MotionState
.
MOTION_WALK
,
MotionState
.
MOTION_DANGER_WALK
)));
MotionStatesCategorized
.
put
(
"FIGHT"
,
new
HashSet
<>(
Arrays
.
asList
(
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
()
{
float
diffX
=
currentCoordinates
.
getX
()
-
previousCoordinates
.
getX
();
float
diffY
=
currentCoordinates
.
getY
()
-
previousCoordinates
.
getY
();
float
diffZ
=
currentCoordinates
.
getZ
()
-
previousCoordinates
.
getZ
();
// Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ);
return
Math
.
abs
(
diffX
)
>
0.2
||
Math
.
abs
(
diffY
)
>
0.1
||
Math
.
abs
(
diffZ
)
>
0.2
;
}
private
int
getCurrentStamina
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
);
}
private
int
getMaximumStamina
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
}
// 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
;
}
int
playerMaxStamina
=
session
.
getPlayer
().
getProperty
(
PlayerProperty
.
PROP_MAX_STAMINA
);
int
newStamina
=
currentStamina
+
amount
;
if
(
newStamina
<
0
)
{
newStamina
=
0
;
}
if
(
newStamina
>
playerMaxStamina
)
{
newStamina
=
playerMaxStamina
;
}
session
.
getPlayer
().
setProperty
(
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
,
newStamina
);
session
.
send
(
new
PacketPlayerPropNotify
(
player
,
PlayerProperty
.
PROP_CUR_PERSIST_STAMINA
));
return
newStamina
;
}
private
void
handleFallOnGround
(
@NotNull
MotionInfo
motionInfo
)
{
MotionState
state
=
motionInfo
.
getState
();
// land speed and fall on ground event arrive in different packets
// cache land speed
if
(
state
==
MotionState
.
MOTION_LAND_SPEED
)
{
landSpeed
=
motionInfo
.
getSpeed
().
getY
();
landTimeMillisecond
=
System
.
currentTimeMillis
();
}
if
(
state
==
MotionState
.
MOTION_FALL_ON_GROUND
)
{
// if not received immediately after MOTION_LAND_SPEED, discard this packet.
// TODO: Test in high latency.
int
maxDelay
=
200
;
if
((
System
.
currentTimeMillis
()
-
landTimeMillisecond
)
>
maxDelay
)
{
Grasscutter
.
getLogger
().
debug
(
"MOTION_FALL_ON_GROUND received after "
+
maxDelay
+
"ms, discard."
);
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
);
}
if
(
landSpeed
<
-
26.5
)
{
damage
=
(
float
)(
maxHP
*
0.66
);
}
if
(
landSpeed
<
-
28
)
{
damage
=
(
maxHP
*
1
);
}
float
newHP
=
currentHP
-
damage
;
if
(
newHP
<
0
)
{
newHP
=
0
;
}
Grasscutter
.
getLogger
().
debug
(
"Max: "
+
maxHP
+
"\tCurr: "
+
currentHP
+
"\tDamage: "
+
damage
+
"\tnewHP: "
+
newHP
);
cachedEntity
.
setFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
,
newHP
);
cachedEntity
.
getWorld
().
broadcastPacket
(
new
PacketEntityFightPropUpdateNotify
(
cachedEntity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
if
(
newHP
==
0
)
{
killAvatar
(
cachedSession
,
cachedEntity
,
PlayerDieType
.
PLAYER_DIE_FALL
);
}
landSpeed
=
0
;
}
}
private
void
handleDrowning
()
{
int
stamina
=
getCurrentStamina
();
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
);
}
}
}
public
void
killAvatar
(
GameSession
session
,
GameEntity
entity
,
PlayerDieType
dieType
)
{
cachedSession
.
send
(
new
PacketAvatarLifeStateChangeNotify
(
cachedSession
.
getPlayer
().
getTeamManager
().
getCurrentAvatarEntity
().
getAvatar
(),
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
{
public
void
run
()
{
if
(
Grasscutter
.
getConfig
().
OpenStamina
)
{
boolean
moving
=
isPlayerMoving
();
if
(
moving
||
(
getCurrentStamina
()
<
getMaximumStamina
()))
{
// Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina");
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
// TODO: refactor these conditions.
if
(
MotionStatesCategorized
.
get
(
"CLIMB"
).
contains
(
currentState
))
{
consumption
=
getClimbConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"SWIM"
).
contains
((
currentState
)))
{
consumption
=
getSwimConsumptions
();
}
else
if
(
MotionStatesCategorized
.
get
(
"RUN"
).
contains
(
currentState
))
{
consumption
=
getRunWalkDashConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"FLY"
).
contains
(
currentState
))
{
consumption
=
getFlyConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"STANDBY"
).
contains
(
currentState
))
{
consumption
=
getStandConsumption
();
}
else
if
(
MotionStatesCategorized
.
get
(
"FIGHT"
).
contains
(
currentState
))
{
consumption
=
getFightConsumption
();
}
// delay 2 seconds before start recovering - as official server does.
if
(
cachedSession
!=
null
)
{
if
(
consumption
.
amount
<
0
)
{
staminaRecoverDelay
=
0
;
}
if
(
consumption
.
amount
>
0
&&
consumption
.
consumptionType
!=
ConsumptionType
.
POWERED_FLY
)
{
if
(
staminaRecoverDelay
<
10
)
{
staminaRecoverDelay
++;
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
}
}
// Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")");
updateStamina
(
cachedSession
,
consumption
.
amount
);
}
// tick triggered
handleDrowning
();
}
}
previousState
=
currentState
;
previousCoordinates
=
new
Position
(
currentCoordinates
.
getX
(),
currentCoordinates
.
getY
(),
currentCoordinates
.
getZ
());;
}
}
private
Consumption
getClimbConsumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_CLIMB
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
CLIMBING
);
if
(
previousState
!=
MotionState
.
MOTION_CLIMB
&&
previousState
!=
MotionState
.
MOTION_CLIMB_JUMP
)
{
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
;
}
private
Consumption
getSwimConsumptions
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_SWIM_MOVE
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SWIMMING
);
}
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
);
}
}
return
consumption
;
}
private
Consumption
getRunWalkDashConsumption
()
{
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
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
SPRINT
);
}
if
(
currentState
==
MotionState
.
MOTION_RUN
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
RUN
);
}
if
(
currentState
==
MotionState
.
MOTION_WALK
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
WALK
);
}
return
consumption
;
}
private
Consumption
getFlyConsumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
FLY
);
HashMap
<
Integer
,
Float
>
glidingCostReduction
=
new
HashMap
<>()
{{
put
(
212301
,
0.8f
);
// Amber
put
(
222301
,
0.8f
);
// Venti
}};
float
reduction
=
1
;
for
(
EntityAvatar
entity:
cachedSession
.
getPlayer
().
getTeamManager
().
getActiveTeam
())
{
for
(
int
skillId:
entity
.
getAvatar
().
getProudSkillList
())
{
if
(
glidingCostReduction
.
containsKey
(
skillId
))
{
reduction
=
glidingCostReduction
.
get
(
skillId
);
}
}
}
consumption
.
amount
*=
reduction
;
// POWERED_FLY, e.g. wind tunnel
if
(
currentState
==
MotionState
.
MOTION_POWERED_FLY
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
POWERED_FLY
);
}
return
consumption
;
}
private
Consumption
getStandConsumption
()
{
Consumption
consumption
=
new
Consumption
(
ConsumptionType
.
None
);
if
(
currentState
==
MotionState
.
MOTION_STANDBY
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
STANDBY
);
}
if
(
currentState
==
MotionState
.
MOTION_STANDBY_MOVE
)
{
consumption
=
new
Consumption
(
ConsumptionType
.
STANDBY_MOVE
);
}
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/managers/MultiplayerManager.java
View file @
a8293102
...
...
@@ -27,7 +27,7 @@ public class MultiplayerManager {
public
void
applyEnterMp
(
Player
player
,
int
targetUid
)
{
Player
target
=
getServer
().
getPlayerByUid
(
targetUid
);
if
(
target
==
null
)
{
player
.
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
targetUid
,
""
,
false
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
PLAYER_CANNOT_ENTER_MP
));
player
.
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
targetUid
,
""
,
false
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
REASON_
PLAYER_CANNOT_ENTER_MP
));
return
;
}
...
...
@@ -72,12 +72,12 @@ public class MultiplayerManager {
// Sanity checks - Dont let the requesting player join if they are already in multiplayer
if
(
requester
.
getWorld
().
isMultiplayer
())
{
request
.
getRequester
().
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
hostPlayer
,
false
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
PLAYER_CANNOT_ENTER_MP
));
request
.
getRequester
().
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
hostPlayer
,
false
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
REASON_
PLAYER_CANNOT_ENTER_MP
));
return
;
}
// Response packet
request
.
getRequester
().
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
hostPlayer
,
isAgreed
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
PLAYER_JUDGE
));
request
.
getRequester
().
sendPacket
(
new
PacketPlayerApplyEnterMpResultNotify
(
hostPlayer
,
isAgreed
,
PlayerApplyEnterMpResultNotifyOuterClass
.
PlayerApplyEnterMpResultNotify
.
Reason
.
REASON_
PLAYER_JUDGE
));
// Declined
if
(!
isAgreed
)
{
...
...
@@ -93,7 +93,7 @@ public class MultiplayerManager {
world
.
addPlayer
(
hostPlayer
);
// Rejoin packet
hostPlayer
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
hostPlayer
,
hostPlayer
,
EnterType
.
ENTER_SELF
,
EnterReason
.
HostFromSingleToMp
,
hostPlayer
.
getScene
().
getId
(),
hostPlayer
.
getPos
()));
hostPlayer
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
hostPlayer
,
hostPlayer
,
EnterType
.
ENTER_
TYPE_
SELF
,
EnterReason
.
HostFromSingleToMp
,
hostPlayer
.
getScene
().
getId
(),
hostPlayer
.
getPos
()));
}
// Set scene pos and id of requester to the host player's
...
...
@@ -105,7 +105,7 @@ public class MultiplayerManager {
hostPlayer
.
getWorld
().
addPlayer
(
requester
);
// Packet
requester
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
requester
,
hostPlayer
,
EnterType
.
ENTER_OTHER
,
EnterReason
.
TeamJoin
,
hostPlayer
.
getScene
().
getId
(),
hostPlayer
.
getPos
()));
requester
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
requester
,
hostPlayer
,
EnterType
.
ENTER_
TYPE_
OTHER
,
EnterReason
.
TeamJoin
,
hostPlayer
.
getScene
().
getId
(),
hostPlayer
.
getPos
()));
}
public
boolean
leaveCoop
(
Player
player
)
{
...
...
@@ -126,7 +126,7 @@ public class MultiplayerManager {
world
.
addPlayer
(
player
);
// Packet
player
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
player
,
EnterType
.
ENTER_SELF
,
EnterReason
.
TeamBack
,
player
.
getScene
().
getId
(),
player
.
getPos
()));
player
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
player
,
EnterType
.
ENTER_
TYPE_
SELF
,
EnterReason
.
TeamBack
,
player
.
getScene
().
getId
(),
player
.
getPos
()));
return
true
;
}
...
...
@@ -153,7 +153,7 @@ public class MultiplayerManager {
World
world
=
new
World
(
victim
);
world
.
addPlayer
(
victim
);
victim
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
victim
,
EnterType
.
ENTER_SELF
,
EnterReason
.
TeamKick
,
victim
.
getScene
().
getId
(),
victim
.
getPos
()));
victim
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
victim
,
EnterType
.
ENTER_
TYPE_
SELF
,
EnterReason
.
TeamKick
,
victim
.
getScene
().
getId
(),
victim
.
getPos
()));
return
true
;
}
}
src/main/java/emu/grasscutter/game/managers/SotSManager.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers
;
import
ch.qos.logback.classic.Logger
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.game.entity.EntityAvatar
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason
;
import
emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify
;
import
emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify
;
import
java.util.List
;
import
java.util.Timer
;
import
java.util.TimerTask
;
// Statue of the Seven Manager
public
class
SotSManager
{
// NOTE: Spring volume balance *1 = fight prop HP *100
private
final
Player
player
;
private
final
Logger
logger
=
Grasscutter
.
getLogger
();
private
Timer
autoRecoverTimer
;
private
final
boolean
enablePriorityHealing
=
false
;
public
final
static
int
GlobalMaximumSpringVolume
=
8500000
;
public
SotSManager
(
Player
player
)
{
this
.
player
=
player
;
}
public
boolean
getIsAutoRecoveryEnabled
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_IS_SPRING_AUTO_USE
)
==
1
;
}
public
void
setIsAutoRecoveryEnabled
(
boolean
enabled
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_IS_SPRING_AUTO_USE
,
enabled
?
1
:
0
);
player
.
save
();
}
public
int
getAutoRecoveryPercentage
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_SPRING_AUTO_USE_PERCENT
);
}
public
void
setAutoRecoveryPercentage
(
int
percentage
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_SPRING_AUTO_USE_PERCENT
,
percentage
);
player
.
save
();
}
public
long
getLastUsed
()
{
return
player
.
getSpringLastUsed
();
}
public
void
setLastUsed
()
{
player
.
setSpringLastUsed
(
System
.
currentTimeMillis
()
/
1000
);
player
.
save
();
}
public
int
getMaxVolume
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_SPRING_VOLUME
);
}
public
void
setMaxVolume
(
int
volume
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_MAX_SPRING_VOLUME
,
volume
);
player
.
save
();
}
public
int
getCurrentVolume
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
);
}
public
void
setCurrentVolume
(
int
volume
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
,
volume
);
setLastUsed
();
player
.
save
();
}
public
void
handleEnterTransPointRegionNotify
()
{
logger
.
trace
(
"Player entered statue region"
);
autoRevive
();
if
(
autoRecoverTimer
==
null
)
{
autoRecoverTimer
=
new
Timer
();
autoRecoverTimer
.
schedule
(
new
AutoRecoverTimerTick
(),
2500
,
15000
);
}
}
public
void
handleExitTransPointRegionNotify
()
{
logger
.
trace
(
"Player left statue region"
);
if
(
autoRecoverTimer
!=
null
)
{
autoRecoverTimer
.
cancel
();
autoRecoverTimer
=
null
;
}
}
// autoRevive automatically revives all team members.
public
void
autoRevive
()
{
player
.
getTeamManager
().
getActiveTeam
().
forEach
(
entity
->
{
boolean
isAlive
=
entity
.
isAlive
();
if
(
isAlive
)
{
return
;
}
logger
.
trace
(
"Reviving avatar "
+
entity
.
getAvatar
().
getAvatarData
().
getName
());
player
.
getTeamManager
().
reviveAvatar
(
entity
.
getAvatar
());
player
.
getTeamManager
().
healAvatar
(
entity
.
getAvatar
(),
30
,
0
);
});
}
private
class
AutoRecoverTimerTick
extends
TimerTask
{
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level.
public
void
run
()
{
refillSpringVolume
();
logger
.
trace
(
"isAutoRecoveryEnabled: "
+
getIsAutoRecoveryEnabled
()
+
"\tautoRecoverPercentage: "
+
getAutoRecoveryPercentage
());
if
(
getIsAutoRecoveryEnabled
())
{
List
<
EntityAvatar
>
activeTeam
=
player
.
getTeamManager
().
getActiveTeam
();
// When the statue does not have enough remaining volume:
// Enhanced experience: Enable priority healing
// The current active character will get healed first, then sequential.
// Vanilla experience: Disable priority healing
// Sequential healing based on character index.
int
priorityIndex
=
enablePriorityHealing
?
player
.
getTeamManager
().
getCurrentCharacterIndex
()
:
-
1
;
if
(
priorityIndex
>=
0
)
{
checkAndHealAvatar
(
activeTeam
.
get
(
priorityIndex
));
}
for
(
int
i
=
0
;
i
<
activeTeam
.
size
();
i
++)
{
if
(
i
!=
priorityIndex
)
{
checkAndHealAvatar
(
activeTeam
.
get
(
i
));
}
}
}
}
}
public
void
checkAndHealAvatar
(
EntityAvatar
entity
)
{
int
maxHP
=
(
int
)
(
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
)
*
100
);
int
currentHP
=
(
int
)
(
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
)
*
100
);
if
(
currentHP
==
maxHP
)
{
return
;
}
int
targetHP
=
maxHP
*
getAutoRecoveryPercentage
()
/
100
;
if
(
targetHP
>
currentHP
)
{
int
needHP
=
targetHP
-
currentHP
;
int
currentVolume
=
getCurrentVolume
();
if
(
currentVolume
>=
needHP
)
{
// sufficient
setCurrentVolume
(
currentVolume
-
needHP
);
}
else
{
// insufficient balance
needHP
=
currentVolume
;
setCurrentVolume
(
0
);
}
if
(
needHP
>
0
)
{
logger
.
trace
(
"Healing avatar "
+
entity
.
getAvatar
().
getAvatarData
().
getName
()
+
" +"
+
needHP
);
player
.
getTeamManager
().
healAvatar
(
entity
.
getAvatar
(),
0
,
needHP
);
player
.
getSession
().
send
(
new
PacketEntityFightPropChangeReasonNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
,
((
float
)
needHP
/
100
),
List
.
of
(
3
),
PropChangeReason
.
PROP_CHANGE_REASON_STATUE_RECOVER
,
ChangeHpReason
.
CHANGE_HP_REASON_CHANGE_HP_ADD_STATUE
));
player
.
getSession
().
send
(
new
PacketEntityFightPropUpdateNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
}
}
}
public
void
refillSpringVolume
()
{
// Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override until we have statue level.
// TODO: remove
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
setMaxVolume
(
8500000
);
// Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game
// TODO: remove
setAutoRecoveryPercentage
(
100
);
setIsAutoRecoveryEnabled
(
true
);
int
maxVolume
=
getMaxVolume
();
int
currentVolume
=
getCurrentVolume
();
if
(
currentVolume
<
maxVolume
)
{
long
now
=
System
.
currentTimeMillis
()
/
1000
;
int
secondsSinceLastUsed
=
(
int
)
(
now
-
getLastUsed
());
// 15s = 1% max volume
int
volumeRefilled
=
secondsSinceLastUsed
*
maxVolume
/
15
/
100
;
logger
.
trace
(
"Statue has refilled HP volume: "
+
volumeRefilled
);
currentVolume
=
Math
.
min
(
currentVolume
+
volumeRefilled
,
maxVolume
);
logger
.
trace
(
"Statue remaining HP volume: "
+
currentVolume
);
setCurrentVolume
(
currentVolume
);
}
}
}
src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java
deleted
100644 → 0
View file @
304b9cb8
package
emu.grasscutter.game.managers.SotSManager
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.game.avatar.Avatar
;
import
emu.grasscutter.game.entity.EntityAvatar
;
import
emu.grasscutter.game.entity.GameEntity
;
import
emu.grasscutter.game.managers.MovementManager.MovementManager
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.game.world.World
;
import
emu.grasscutter.net.proto.ChangeHpReasonOuterClass
;
import
emu.grasscutter.net.proto.PropChangeReasonOuterClass
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify
;
import
emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify
;
import
emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify
;
import
emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify
;
import
java.util.List
;
import
java.util.Timer
;
import
java.util.TimerTask
;
// Statue of the Seven Manager
public
class
SotSManager
{
// NOTE: Spring volume balance *1 = fight prop HP *100
private
final
Player
player
;
private
Timer
autoRecoverTimer
;
public
SotSManager
(
Player
player
)
{
this
.
player
=
player
;
}
public
boolean
getIsAutoRecoveryEnabled
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_IS_SPRING_AUTO_USE
)
==
1
;
}
public
void
setIsAutoRecoveryEnabled
(
boolean
enabled
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_IS_SPRING_AUTO_USE
,
enabled
?
1
:
0
);
}
public
int
getAutoRecoveryPercentage
()
{
return
player
.
getProperty
(
PlayerProperty
.
PROP_SPRING_AUTO_USE_PERCENT
);
}
public
void
setAutoRecoveryPercentage
(
int
percentage
)
{
player
.
setProperty
(
PlayerProperty
.
PROP_SPRING_AUTO_USE_PERCENT
,
percentage
);
}
// autoRevive automatically revives all team members.
public
void
autoRevive
(
GameSession
session
)
{
player
.
getTeamManager
().
getActiveTeam
().
forEach
(
entity
->
{
boolean
isAlive
=
entity
.
isAlive
();
float
currentHP
=
entity
.
getAvatar
().
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
);
float
maxHP
=
entity
.
getAvatar
().
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
);
// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD"));
float
newHP
=
(
float
)(
maxHP
*
0.3
);
if
(
currentHP
<
newHP
)
{
updateAvatarCurHP
(
session
,
entity
,
newHP
);
}
if
(!
isAlive
)
{
entity
.
getWorld
().
broadcastPacket
(
new
PacketAvatarLifeStateChangeNotify
(
entity
.
getAvatar
()));
}
});
}
public
void
scheduleAutoRecover
(
GameSession
session
)
{
if
(
autoRecoverTimer
==
null
)
{
autoRecoverTimer
=
new
Timer
();
autoRecoverTimer
.
schedule
(
new
AutoRecoverTimerTick
(
session
),
2500
);
}
}
public
void
cancelAutoRecover
()
{
if
(
autoRecoverTimer
!=
null
)
{
autoRecoverTimer
.
cancel
();
autoRecoverTimer
=
null
;
}
}
private
class
AutoRecoverTimerTick
extends
TimerTask
{
private
GameSession
session
;
public
AutoRecoverTimerTick
(
GameSession
session
)
{
this
.
session
=
session
;
}
public
void
run
()
{
autoRecover
(
session
);
cancelAutoRecover
();
}
}
public
void
refillSpringVolume
()
{
// TODO: max spring volume depends on level of the statues in Mondstadt and Liyue.
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
player
.
setProperty
(
PlayerProperty
.
PROP_MAX_SPRING_VOLUME
,
8500000
);
long
now
=
System
.
currentTimeMillis
()
/
1000
;
long
secondsSinceLastUsed
=
now
-
player
.
getSpringLastUsed
();
float
percentageRefilled
=
(
float
)
secondsSinceLastUsed
/
15
/
100
;
// 15s = 1% max volume
int
maxVolume
=
player
.
getProperty
(
PlayerProperty
.
PROP_MAX_SPRING_VOLUME
);
int
currentVolume
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
);
if
(
currentVolume
<
maxVolume
)
{
int
volumeRefilled
=
(
int
)(
percentageRefilled
*
maxVolume
);
int
newVolume
=
currentVolume
+
volumeRefilled
;
if
(
currentVolume
+
volumeRefilled
>
maxVolume
)
{
newVolume
=
maxVolume
;
}
player
.
setProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
,
newVolume
);
}
player
.
setSpringLastUsed
(
now
);
player
.
save
();
}
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level.
public
void
autoRecover
(
GameSession
session
)
{
// TODO: In MP, respect SotS settings from the HOST.
boolean
isAutoRecoveryEnabled
=
getIsAutoRecoveryEnabled
();
int
autoRecoverPercentage
=
getAutoRecoveryPercentage
();
Grasscutter
.
getLogger
().
debug
(
"isAutoRecoveryEnabled: "
+
isAutoRecoveryEnabled
+
"\tautoRecoverPercentage: "
+
autoRecoverPercentage
);
if
(
isAutoRecoveryEnabled
)
{
player
.
getTeamManager
().
getActiveTeam
().
forEach
(
entity
->
{
float
maxHP
=
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
);
float
currentHP
=
entity
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
);
if
(
currentHP
==
maxHP
)
{
return
;
}
float
targetHP
=
maxHP
*
autoRecoverPercentage
/
100
;
if
(
targetHP
>
currentHP
)
{
float
needHP
=
targetHP
-
currentHP
;
float
needSV
=
needHP
*
100
;
// convert HP needed to Spring Volume needed
int
sotsSVBalance
=
player
.
getProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
);
if
(
sotsSVBalance
>=
needSV
)
{
// sufficient
sotsSVBalance
-=
needSV
;
}
else
{
// insufficient balance
needSV
=
sotsSVBalance
;
sotsSVBalance
=
0
;
}
player
.
setProperty
(
PlayerProperty
.
PROP_CUR_SPRING_VOLUME
,
sotsSVBalance
);
player
.
setSpringLastUsed
(
System
.
currentTimeMillis
()
/
1000
);
float
newHP
=
currentHP
+
needSV
/
100
;
// convert SV to HP
updateAvatarCurHP
(
session
,
entity
,
newHP
);
}
});
}
}
private
void
updateAvatarCurHP
(
GameSession
session
,
EntityAvatar
entity
,
float
newHP
)
{
// TODO: Figure out why client shows current HP instead of added HP.
// Say an avatar had 12000 and now has 14000, it should show "2000".
// The client always show "+14000" which is incorrect.
entity
.
setFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
,
newHP
);
session
.
send
(
new
PacketEntityFightPropChangeReasonNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
,
newHP
,
List
.
of
(
3
),
PropChangeReasonOuterClass
.
PropChangeReason
.
PROP_CHANGE_STATUE_RECOVER
,
ChangeHpReasonOuterClass
.
ChangeHpReason
.
ChangeHpAddStatue
));
session
.
send
(
new
PacketEntityFightPropUpdateNotify
(
entity
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
Avatar
avatar
=
entity
.
getAvatar
();
avatar
.
setCurrentHp
(
newHP
);
session
.
send
(
new
PacketAvatarFightPropUpdateNotify
(
avatar
,
FightProperty
.
FIGHT_PROP_CUR_HP
));
player
.
save
();
}
}
src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java
0 → 100644
View file @
a8293102
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
,
boolean
isCharacterStamina
);
}
src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java
0 → 100644
View file @
a8293102
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
,
boolean
isCharacterStamina
);
/**
* 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
,
boolean
isCharacterStamina
);
}
\ No newline at end of file
src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.StaminaManager
;
public
class
Consumption
{
public
ConsumptionType
type
=
ConsumptionType
.
None
;
public
int
amount
=
0
;
public
Consumption
(
ConsumptionType
type
,
int
amount
)
{
this
.
type
=
type
;
this
.
amount
=
amount
;
}
public
Consumption
(
ConsumptionType
type
)
{
this
(
type
,
type
.
amount
);
}
public
Consumption
()
{
}
}
src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.game.managers.StaminaManager
;
public
enum
ConsumptionType
{
None
(
0
),
// consume
CLIMBING
(-
150
),
CLIMB_START
(-
500
),
CLIMB_JUMP
(-
2500
),
DASH
(-
360
),
FIGHT
(
0
),
// See StaminaManager.getFightConsumption()
FLY
(-
60
),
// Slow swimming is handled per movement, not per second.
// Arm movement frequency depends on gender/age/height.
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
SKIFF_DASH
(-
204
),
SPRINT
(-
1800
),
SWIM_DASH_START
(-
2000
),
SWIM_DASH
(-
204
),
// -10.2 per second, 5Hz = -204 each tick
SWIMMING
(-
80
),
TALENT_DASH
(-
300
),
// -1500 per second, 5Hz = -300 each tick
TALENT_DASH_START
(-
1000
),
// restore
POWERED_FLY
(
500
),
POWERED_SKIFF
(
500
),
RUN
(
500
),
SKIFF
(
500
),
STANDBY
(
500
),
WALK
(
500
);
public
final
int
amount
;
ConsumptionType
(
int
amount
)
{
this
.
amount
=
amount
;
}
}
\ No newline at end of file
Prev
1
…
5
6
7
8
9
10
11
12
13
…
21
Next
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