Continue merging quests (pt. 1)

Finished last at: `World.java`, line `player.setAvatarsAbilityForScene(newScene);`
This commit is contained in:
KingRainbow44
2023-04-09 13:25:16 -04:00
parent c64cc7d5e2
commit 97ee71bcf4
26 changed files with 6545 additions and 6479 deletions

View File

@@ -1,24 +1,26 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.ResourceType;
import java.util.List; import emu.grasscutter.data.common.ItemParamData;
import lombok.Getter; import java.util.List;
import lombok.Getter;
@ResourceType(
name = {"CompoundExcelConfigData.json"}, @ResourceType(
loadPriority = ResourceType.LoadPriority.LOW) name = {"CompoundExcelConfigData.json"},
@Getter loadPriority = ResourceType.LoadPriority.LOW)
public class CompoundData extends GameResource { @Getter
@Getter(onMethod_ = @Override) public class CompoundData extends GameResource {
private int id; @Getter(onMethod_ = @Override)
private int id;
private int groupID;
private int rankLevel; @SerializedName("groupID")
private boolean isDefaultUnlocked; private int groupId;
private int costTime; private int rankLevel;
private int queueSize; private boolean isDefaultUnlocked;
private List<ItemParamData> inputVec; private int costTime;
private List<ItemParamData> outputVec; private int queueSize;
} private List<ItemParamData> inputVec;
private List<ItemParamData> outputVec;
}

View File

@@ -1,178 +1,178 @@
package emu.grasscutter.game.dungeons.challenge; package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneTrigger; import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class WorldChallenge { public class WorldChallenge {
private final Scene scene; private final Scene scene;
private final SceneGroup group; private final SceneGroup group;
private final int challengeId; private final int challengeId;
private final int challengeIndex; private final int challengeIndex;
private final List<Integer> paramList; private final List<Integer> paramList;
private final int timeLimit; private final int timeLimit;
private final List<ChallengeTrigger> challengeTriggers; private final List<ChallengeTrigger> challengeTriggers;
private final int goal; private final int goal;
private final AtomicInteger score; private final AtomicInteger score;
private boolean progress; private boolean progress;
private boolean success; private boolean success;
private long startedAt; private long startedAt;
private int finishedTime; private int finishedTime;
public WorldChallenge( public WorldChallenge(
Scene scene, Scene scene,
SceneGroup group, SceneGroup group,
int challengeId, int challengeId,
int challengeIndex, int challengeIndex,
List<Integer> paramList, List<Integer> paramList,
int timeLimit, int timeLimit,
int goal, int goal,
List<ChallengeTrigger> challengeTriggers) { List<ChallengeTrigger> challengeTriggers) {
this.scene = scene; this.scene = scene;
this.group = group; this.group = group;
this.challengeId = challengeId; this.challengeId = challengeId;
this.challengeIndex = challengeIndex; this.challengeIndex = challengeIndex;
this.paramList = paramList; this.paramList = paramList;
this.timeLimit = timeLimit; this.timeLimit = timeLimit;
this.challengeTriggers = challengeTriggers; this.challengeTriggers = challengeTriggers;
this.goal = goal; this.goal = goal;
this.score = new AtomicInteger(0); this.score = new AtomicInteger(0);
} }
public boolean inProgress() { public boolean inProgress() {
return this.progress; return this.progress;
} }
public void onCheckTimeOut() { public void onCheckTimeOut() {
if (!inProgress()) { if (!inProgress()) {
return; return;
} }
if (timeLimit <= 0) { if (timeLimit <= 0) {
return; return;
} }
challengeTriggers.forEach(t -> t.onCheckTimeout(this)); challengeTriggers.forEach(t -> t.onCheckTimeout(this));
} }
public void start() { public void start() {
if (inProgress()) { if (inProgress()) {
Grasscutter.getLogger().info("Could not start a in progress challenge."); Grasscutter.getLogger().info("Could not start a in progress challenge.");
return; return;
} }
this.progress = true; this.progress = true;
this.startedAt = System.currentTimeMillis(); this.startedAt = System.currentTimeMillis();
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
challengeTriggers.forEach(t -> t.onBegin(this)); challengeTriggers.forEach(t -> t.onBegin(this));
} }
public void done() { public void done() {
if (!this.inProgress()) return; if (!this.inProgress()) return;
this.finish(true); this.finish(true);
var scene = this.getScene(); var scene = this.getScene();
var dungeonManager = scene.getDungeonManager(); var dungeonManager = scene.getDungeonManager();
if (dungeonManager != null && dungeonManager.getDungeonData() != null) { if (dungeonManager != null && dungeonManager.getDungeonData() != null) {
scene scene
.getPlayers() .getPlayers()
.forEach( .forEach(
p -> p ->
p.getActivityManager() p.getActivityManager()
.triggerWatcher( .triggerWatcher(
WatcherTriggerType.TRIGGER_FINISH_CHALLENGE, WatcherTriggerType.TRIGGER_FINISH_CHALLENGE,
String.valueOf(dungeonManager.getDungeonData().getId()), String.valueOf(dungeonManager.getDungeonData().getId()),
String.valueOf(this.getGroup().id), String.valueOf(this.getGroup().id),
String.valueOf(this.getChallengeId()))); String.valueOf(this.getChallengeId())));
} }
scene scene
.getScriptManager() .getScriptManager()
.callEvent( .callEvent(
// TODO record the time in PARAM2 and used in action // TODO record the time in PARAM2 and used in action
new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS) new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS)
.setParam2(finishedTime)); .setParam2(finishedTime));
this.getScene() this.getScene()
.triggerDungeonEvent( .triggerDungeonEvent(
DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE, DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE,
getChallengeId(), getChallengeId(),
getChallengeIndex()); getChallengeIndex());
this.challengeTriggers.forEach(t -> t.onFinish(this)); this.challengeTriggers.forEach(t -> t.onFinish(this));
} }
public void fail() { public void fail() {
if (!this.inProgress()) return; if (!this.inProgress()) return;
this.finish(true); this.finish(false);
this.getScene() this.getScene()
.getScriptManager() .getScriptManager()
.callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL)); .callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL));
challengeTriggers.forEach(t -> t.onFinish(this)); challengeTriggers.forEach(t -> t.onFinish(this));
} }
private void finish(boolean success) { private void finish(boolean success) {
this.progress = false; this.progress = false;
this.success = success; this.success = success;
this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L); this.finishedTime = (int) ((this.scene.getSceneTimeSeconds() - this.startedAt));
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
} }
public int increaseScore() { public int increaseScore() {
return score.incrementAndGet(); return score.incrementAndGet();
} }
public void onMonsterDeath(EntityMonster monster) { public void onMonsterDeath(EntityMonster monster) {
if (!inProgress()) { if (!inProgress()) {
return; return;
} }
if (monster.getGroupId() != getGroup().id) { if (monster.getGroupId() != getGroup().id) {
return; return;
} }
this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster)); this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster));
} }
public void onGadgetDeath(EntityGadget gadget) { public void onGadgetDeath(EntityGadget gadget) {
if (!inProgress()) { if (!inProgress()) {
return; return;
} }
if (gadget.getGroupId() != getGroup().id) { if (gadget.getGroupId() != getGroup().id) {
return; return;
} }
this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget)); this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget));
} }
public void onGroupTriggerDeath(SceneTrigger trigger) { public void onGroupTriggerDeath(SceneTrigger trigger) {
if (!this.inProgress()) return; if (!this.inProgress()) return;
var triggerGroup = trigger.getCurrentGroup(); var triggerGroup = trigger.getCurrentGroup();
if (triggerGroup == null || triggerGroup.id != getGroup().id) { if (triggerGroup == null || triggerGroup.id != getGroup().id) {
return; return;
} }
this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger)); this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger));
} }
public void onGadgetDamage(EntityGadget gadget) { public void onGadgetDamage(EntityGadget gadget) {
if (!inProgress()) { if (!inProgress()) {
return; return;
} }
if (gadget.getGroupId() != getGroup().id) { if (gadget.getGroupId() != getGroup().id) {
return; return;
} }
this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget)); this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget));
} }
} }

View File

@@ -1,370 +1,367 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.avatar.AvatarData; import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.EquipType; import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock; import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.event.player.PlayerMoveEvent; import emu.grasscutter.server.event.player.PlayerMoveEvent;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
public class EntityAvatar extends GameEntity { public class EntityAvatar extends GameEntity {
@Getter private final Avatar avatar; @Getter private final Avatar avatar;
@Getter private PlayerDieType killedType; @Getter private PlayerDieType killedType;
@Getter private int killedBy; @Getter private int killedBy;
public EntityAvatar(Avatar avatar) { public EntityAvatar(Avatar avatar) {
this(null, avatar); this(null, avatar);
} }
public EntityAvatar(Scene scene, Avatar avatar) { public EntityAvatar(Scene scene, Avatar avatar) {
super(scene); super(scene);
this.avatar = avatar; this.avatar = avatar;
this.avatar.setCurrentEnergy(); this.avatar.setCurrentEnergy();
if (scene != null) this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
if (getScene() != null) {
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); GameItem weapon = this.getAvatar().getWeapon();
if (weapon != null) {
var weapon = getAvatar().getWeapon(); weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
if (weapon != null) { }
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON)); }
}
} @Override
} public int getEntityTypeId() {
return this.getAvatar().getAvatarId();
@Override }
public int getEntityTypeId() {
return this.getAvatar().getAvatarId(); public Player getPlayer() {
} return this.avatar.getPlayer();
}
public Player getPlayer() {
return this.avatar.getPlayer(); @Override
} public Position getPosition() {
return getPlayer().getPosition();
@Override }
public Position getPosition() {
return getPlayer().getPosition(); @Override
} public Position getRotation() {
return getPlayer().getRotation();
@Override }
public Position getRotation() {
return getPlayer().getRotation(); @Override
} public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
@Override }
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; @Override
} public Int2FloatMap getFightProperties() {
return getAvatar().getFightProperties();
@Override }
public Int2FloatMap getFightProperties() {
return getAvatar().getFightProperties(); public int getWeaponEntityId() {
} if (getAvatar().getWeapon() != null) {
return getAvatar().getWeapon().getWeaponEntityId();
public int getWeaponEntityId() { }
if (getAvatar().getWeapon() != null) { return 0;
return getAvatar().getWeapon().getWeaponEntityId(); }
}
return 0; @Override
} public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
@Override
public void onDeath(int killerId) { this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
super.onDeath(killerId); // Invoke super class's onDeath() method. this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER; }
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); public void onDeath(PlayerDieType dieType, int killerId) {
} super.onDeath(killerId); // Invoke super class's onDeath() method.
public void onDeath(PlayerDieType dieType, int killerId) { this.killedType = dieType;
super.onDeath(killerId); // Invoke super class's onDeath() method. this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
this.killedType = dieType; }
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); @Override
} public float heal(float amount) {
// Do not heal character if they are dead
@Override if (!this.isAlive()) {
public float heal(float amount) { return 0f;
// Do not heal character if they are dead }
if (!this.isAlive()) {
return 0f; float healed = super.heal(amount);
}
if (healed > 0f) {
float healed = super.heal(amount); getScene()
.broadcastPacket(
if (healed > 0f) { new PacketEntityFightPropChangeReasonNotify(
getScene() this,
.broadcastPacket( FightProperty.FIGHT_PROP_CUR_HP,
new PacketEntityFightPropChangeReasonNotify( healed,
this, PropChangeReason.PROP_CHANGE_REASON_ABILITY,
FightProperty.FIGHT_PROP_CUR_HP, ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY));
healed, }
PropChangeReason.PROP_CHANGE_REASON_ABILITY,
ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY)); return healed;
} }
return healed; public void clearEnergy(ChangeEnergyReason reason) {
} // Fight props.
val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
public void clearEnergy(ChangeEnergyReason reason) { float curEnergy = this.getFightProperty(curEnergyProp);
// Fight props.
val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); // Set energy to zero.
float curEnergy = this.getFightProperty(curEnergyProp); this.avatar.setCurrentEnergy(curEnergyProp, 0);
// Set energy to zero. // Send packets.
this.avatar.setCurrentEnergy(curEnergyProp, 0); this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
// Send packets. if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) {
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); this.getScene()
.broadcastPacket(
if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) { new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason));
this.getScene() }
.broadcastPacket( }
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason));
} /**
} * Adds a fixed amount of energy to the current avatar.
*
/** * @param amount The amount of energy to add.
* Adds a fixed amount of energy to the current avatar. * @return True if the energy was added, false if the energy was not added.
* */
* @param amount The amount of energy to add. public boolean addEnergy(float amount) {
* @return True if the energy was added, false if the energy was not added. var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
*/ var curEnergy = this.getFightProperty(curEnergyProp);
public boolean addEnergy(float amount) { if (curEnergy == amount) return false;
var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
var curEnergy = this.getFightProperty(curEnergyProp); this.getAvatar().setCurrentEnergy(curEnergyProp, amount);
if (curEnergy == amount) return false; this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
return true;
this.getAvatar().setCurrentEnergy(curEnergyProp, amount); }
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
return true; public void addEnergy(float amount, PropChangeReason reason) {
} this.addEnergy(amount, reason, false);
}
public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false); public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) {
} // Get current and maximum energy for this avatar.
val elementType = this.getAvatar().getSkillDepot().getElementType();
public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) { val curEnergyProp = elementType.getCurEnergyProp();
// Get current and maximum energy for this avatar. val maxEnergyProp = elementType.getMaxEnergyProp();
val elementType = this.getAvatar().getSkillDepot().getElementType();
val curEnergyProp = elementType.getCurEnergyProp(); float curEnergy = this.getFightProperty(curEnergyProp);
val maxEnergyProp = elementType.getMaxEnergyProp(); float maxEnergy = this.getFightProperty(maxEnergyProp);
float curEnergy = this.getFightProperty(curEnergyProp); // Scale amount by energy recharge, if the amount is not flat.
float maxEnergy = this.getFightProperty(maxEnergyProp); if (!isFlat) {
amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY);
// Scale amount by energy recharge, if the amount is not flat. }
if (!isFlat) {
amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); // Determine the new energy value.
} float newEnergy = Math.min(curEnergy + amount, maxEnergy);
// Determine the new energy value. // Set energy and notify.
float newEnergy = Math.min(curEnergy + amount, maxEnergy); if (newEnergy != curEnergy) {
this.avatar.setCurrentEnergy(curEnergyProp, newEnergy);
// Set energy and notify.
if (newEnergy != curEnergy) { this.getScene()
this.avatar.setCurrentEnergy(curEnergyProp, newEnergy); .broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene()
this.getScene() .broadcastPacket(
.broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp)); new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason));
this.getScene() }
.broadcastPacket( }
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason));
} public SceneAvatarInfo getSceneAvatarInfo() {
} val avatar = this.getAvatar();
val player = this.getPlayer();
public SceneAvatarInfo getSceneAvatarInfo() { SceneAvatarInfo.Builder avatarInfo =
val avatar = this.getAvatar(); SceneAvatarInfo.newBuilder()
val player = this.getPlayer(); .setUid(player.getUid())
SceneAvatarInfo.Builder avatarInfo = .setAvatarId(avatar.getAvatarId())
SceneAvatarInfo.newBuilder() .setGuid(avatar.getGuid())
.setUid(player.getUid()) .setPeerId(player.getPeerId())
.setAvatarId(avatar.getAvatarId()) .addAllTalentIdList(avatar.getTalentIdList())
.setGuid(avatar.getGuid()) .setCoreProudSkillLevel(avatar.getCoreProudSkillLevel())
.setPeerId(player.getPeerId()) .putAllSkillLevelMap(avatar.getSkillLevelMap())
.addAllTalentIdList(avatar.getTalentIdList()) .setSkillDepotId(avatar.getSkillDepotId())
.setCoreProudSkillLevel(avatar.getCoreProudSkillLevel()) .addAllInherentProudSkillList(avatar.getProudSkillList())
.putAllSkillLevelMap(avatar.getSkillLevelMap()) .putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap())
.setSkillDepotId(avatar.getSkillDepotId()) .addAllTeamResonanceList(player.getTeamManager().getTeamResonances())
.addAllInherentProudSkillList(avatar.getProudSkillList()) .setWearingFlycloakId(avatar.getFlyCloak())
.putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap()) .setCostumeId(avatar.getCostume())
.addAllTeamResonanceList(player.getTeamManager().getTeamResonances()) .setBornTime(avatar.getBornTime());
.setWearingFlycloakId(avatar.getFlyCloak())
.setCostumeId(avatar.getCostume()) for (GameItem item : avatar.getEquips().values()) {
.setBornTime(avatar.getBornTime()); if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
avatarInfo.setWeapon(item.createSceneWeaponInfo());
for (GameItem item : avatar.getEquips().values()) { } else {
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { avatarInfo.addReliquaryList(item.createSceneReliquaryInfo());
avatarInfo.setWeapon(item.createSceneWeaponInfo()); }
} else { avatarInfo.addEquipIdList(item.getItemId());
avatarInfo.addReliquaryList(item.createSceneReliquaryInfo()); }
}
avatarInfo.addEquipIdList(item.getItemId()); return avatarInfo.build();
} }
return avatarInfo.build(); @Override
} public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
@Override EntityAuthorityInfo.newBuilder()
public SceneEntityInfo toProto() { .setAbilityInfo(AbilitySyncStateInfo.newBuilder())
EntityAuthorityInfo authority = .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
EntityAuthorityInfo.newBuilder() .setAiInfo(
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) .setBornPos(Vector.newBuilder())
.setAiInfo( .build();
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder()) SceneEntityInfo.Builder entityInfo =
.build(); SceneEntityInfo.newBuilder()
.setEntityId(getId())
SceneEntityInfo.Builder entityInfo = .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR)
SceneEntityInfo.newBuilder() .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityId(getId()) .setEntityClientData(EntityClientData.newBuilder())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR) .setEntityAuthorityInfo(authority)
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
.setEntityClientData(EntityClientData.newBuilder()) .setLastMoveReliableSeq(this.getLastMoveReliableSeq())
.setEntityAuthorityInfo(authority) .setLifeState(this.getLifeState().getValue());
.setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
.setLastMoveReliableSeq(this.getLastMoveReliableSeq()) if (this.getScene() != null) {
.setLifeState(this.getLifeState().getValue()); entityInfo.setMotionInfo(this.getMotionInfo());
}
if (this.getScene() != null) {
entityInfo.setMotionInfo(this.getMotionInfo()); this.addAllFightPropsToEntityInfo(entityInfo);
}
PropPair pair =
this.addAllFightPropsToEntityInfo(entityInfo); PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
PropPair pair = .setPropValue(
PropPair.newBuilder() ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel()))
.setType(PlayerProperty.PROP_LEVEL.getId()) .build();
.setPropValue( entityInfo.addPropList(pair);
ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel()))
.build(); entityInfo.setAvatar(this.getSceneAvatarInfo());
entityInfo.addPropList(pair);
return entityInfo.build();
entityInfo.setAvatar(this.getSceneAvatarInfo()); }
return entityInfo.build(); public AbilityControlBlock getAbilityControlBlock() {
} AvatarData data = this.getAvatar().getAvatarData();
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder();
public AbilityControlBlock getAbilityControlBlock() { int embryoId = 0;
AvatarData data = this.getAvatar().getAvatarData();
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder(); // Add avatar abilities
int embryoId = 0; if (data.getAbilities() != null) {
for (int id : data.getAbilities()) {
// Add avatar abilities AbilityEmbryo emb =
if (data.getAbilities() != null) { AbilityEmbryo.newBuilder()
for (int id : data.getAbilities()) { .setAbilityId(++embryoId)
AbilityEmbryo emb = .setAbilityNameHash(id)
AbilityEmbryo.newBuilder() .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.setAbilityId(++embryoId) .build();
.setAbilityNameHash(id) abilityControlBlock.addAbilityEmbryoList(emb);
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) }
.build(); }
abilityControlBlock.addAbilityEmbryoList(emb); // Add default abilities
} for (int id : GameConstants.DEFAULT_ABILITY_HASHES) {
} AbilityEmbryo emb =
// Add default abilities AbilityEmbryo.newBuilder()
for (int id : GameConstants.DEFAULT_ABILITY_HASHES) { .setAbilityId(++embryoId)
AbilityEmbryo emb = .setAbilityNameHash(id)
AbilityEmbryo.newBuilder() .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.setAbilityId(++embryoId) .build();
.setAbilityNameHash(id) abilityControlBlock.addAbilityEmbryoList(emb);
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) }
.build(); // Add team resonances
abilityControlBlock.addAbilityEmbryoList(emb); for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) {
} AbilityEmbryo emb =
// Add team resonances AbilityEmbryo.newBuilder()
for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) { .setAbilityId(++embryoId)
AbilityEmbryo emb = .setAbilityNameHash(id)
AbilityEmbryo.newBuilder() .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.setAbilityId(++embryoId) .build();
.setAbilityNameHash(id) abilityControlBlock.addAbilityEmbryoList(emb);
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) }
.build(); // Add skill depot abilities
abilityControlBlock.addAbilityEmbryoList(emb); AvatarSkillDepotData skillDepot =
} GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId());
// Add skill depot abilities if (skillDepot != null && skillDepot.getAbilities() != null) {
AvatarSkillDepotData skillDepot = for (int id : skillDepot.getAbilities()) {
GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId()); AbilityEmbryo emb =
if (skillDepot != null && skillDepot.getAbilities() != null) { AbilityEmbryo.newBuilder()
for (int id : skillDepot.getAbilities()) { .setAbilityId(++embryoId)
AbilityEmbryo emb = .setAbilityNameHash(id)
AbilityEmbryo.newBuilder() .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.setAbilityId(++embryoId) .build();
.setAbilityNameHash(id) abilityControlBlock.addAbilityEmbryoList(emb);
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) }
.build(); }
abilityControlBlock.addAbilityEmbryoList(emb); // Add equip abilities
} if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) {
} for (String skill : this.getAvatar().getExtraAbilityEmbryos()) {
// Add equip abilities AbilityEmbryo emb =
if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) { AbilityEmbryo.newBuilder()
for (String skill : this.getAvatar().getExtraAbilityEmbryos()) { .setAbilityId(++embryoId)
AbilityEmbryo emb = .setAbilityNameHash(Utils.abilityHash(skill))
AbilityEmbryo.newBuilder() .setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.setAbilityId(++embryoId) .build();
.setAbilityNameHash(Utils.abilityHash(skill)) abilityControlBlock.addAbilityEmbryoList(emb);
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME) }
.build(); }
abilityControlBlock.addAbilityEmbryoList(emb);
} //
} return abilityControlBlock.build();
}
//
return abilityControlBlock.build(); /**
} * Move this entity to a new position. Additionally invoke player move event.
*
/** * @param newPosition The new position.
* Move this entity to a new position. Additionally invoke player move event. * @param rotation The new rotation.
* */
* @param newPosition The new position. @Override
* @param rotation The new rotation. public void move(Position newPosition, Position rotation) {
*/ // Invoke player move event.
@Override PlayerMoveEvent event =
public void move(Position newPosition, Position rotation) { new PlayerMoveEvent(
// Invoke player move event. this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition);
PlayerMoveEvent event = event.call();
new PlayerMoveEvent(
this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition); // Set position and rotation.
event.call(); super.move(event.getDestination(), rotation);
}
// Set position and rotation. }
super.move(event.getDestination(), rotation);
}
}

View File

@@ -1,63 +1,91 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.config.ConfigEntityGadget; import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.utils.Position; import emu.grasscutter.game.world.Scene;
import lombok.Getter; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
public abstract class EntityBaseGadget extends GameEntity { import emu.grasscutter.utils.Position;
@Getter(onMethod_ = @Override) import lombok.Getter;
protected final Position position;
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_GADGET_HP_CHANGE;
@Getter(onMethod_ = @Override)
protected final Position rotation; public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override)
public EntityBaseGadget(Scene scene) { protected final Position position;
this(scene, null, null);
} @Getter(onMethod_ = @Override)
protected final Position rotation;
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene); public EntityBaseGadget(Scene scene) {
this.position = position != null ? position.clone() : new Position(); this(scene, null, null);
this.rotation = rotation != null ? rotation.clone() : new Position(); }
}
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
public abstract int getGadgetId(); super(scene);
this.position = position != null ? position.clone() : new Position();
@Override this.rotation = rotation != null ? rotation.clone() : new Position();
public int getEntityTypeId() { }
return this.getGadgetId();
} public abstract int getGadgetId();
@Override @Override
public void onDeath(int killerId) { public int getEntityTypeId() {
super.onDeath(killerId); // Invoke super class's onDeath() method. return this.getGadgetId();
} }
protected void fillFightProps(ConfigEntityGadget configGadget) { @Override
if (configGadget == null || configGadget.getCombat() == null) { public void onDeath(int killerId) {
return; super.onDeath(killerId); // Invoke super class's onDeath() method.
}
var combatData = configGadget.getCombat(); getScene()
var combatProperties = combatData.getProperty(); .getPlayers()
.forEach(
var targetHp = combatProperties.getHP(); p ->
setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp); p.getQuestManager()
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp); .queueEvent(QuestContent.QUEST_CONTENT_DESTROY_GADGET, this.getGadgetId()));
if (combatProperties.isInvincible()) { }
targetHp = Float.POSITIVE_INFINITY;
} @Override
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp); public void runLuaCallbacks(EntityDamageEvent event) {
super.runLuaCallbacks(event);
var atk = combatProperties.getAttack(); getScene()
setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk); .getScriptManager()
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk); .callEvent(
new ScriptArgs(
var def = combatProperties.getDefence(); this.getGroupId(),
setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def); EVENT_SPECIFIC_GADGET_HP_CHANGE,
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def); getConfigId(),
getGadgetId())
setLockHP(combatProperties.isLockHP()); .setSourceEntityId(getId())
} .setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
} .setEventSource(Integer.toString(getConfigId())));
}
protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
var targetHp = combatProperties.getHP();
setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp);
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp);
if (combatProperties.isInvincible()) {
targetHp = Float.POSITIVE_INFINITY;
}
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp);
var atk = combatProperties.getAttack();
setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk);
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk);
var def = combatProperties.getDefence();
setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def);
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def);
setLockHP(combatProperties.isLockHP());
}
}

View File

@@ -1,96 +1,96 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass;
import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class EntityRegion extends GameEntity { public class EntityRegion extends GameEntity {
private final Position position; private final Position position;
private final Set<Integer> entities; // Ids of entities inside this region private final Set<Integer> entities; // Ids of entities inside this region
private final SceneRegion metaRegion; private final SceneRegion metaRegion;
private boolean hasNewEntities; private boolean hasNewEntities;
private boolean entityLeave; private boolean entityLeave;
public EntityRegion(Scene scene, SceneRegion region) { public EntityRegion(Scene scene, SceneRegion region) {
super(scene); super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION); this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION);
this.setGroupId(region.group.id); this.setGroupId(region.group.id);
this.setBlockId(region.group.block_id); this.setBlockId(region.group.block_id);
this.setConfigId(region.config_id); this.setConfigId(region.config_id);
this.position = region.pos.clone(); this.position = region.pos.clone();
this.entities = ConcurrentHashMap.newKeySet(); this.entities = ConcurrentHashMap.newKeySet();
this.metaRegion = region; this.metaRegion = region;
} }
@Override @Override
public int getEntityTypeId() { public int getEntityTypeId() {
return this.metaRegion.config_id; return this.metaRegion.config_id;
} }
public void addEntity(GameEntity entity) { public void addEntity(GameEntity entity) {
if (this.getEntities().contains(entity.getId())) { if (this.getEntities().contains(entity.getId())) {
return; return;
} }
this.getEntities().add(entity.getId()); this.getEntities().add(entity.getId());
this.hasNewEntities = true; this.hasNewEntities = true;
} }
public boolean hasNewEntities() { public boolean hasNewEntities() {
return hasNewEntities; return hasNewEntities;
} }
public void resetNewEntities() { public void resetNewEntities() {
hasNewEntities = false; hasNewEntities = false;
} }
public void removeEntity(int entityId) { public void removeEntity(int entityId) {
this.getEntities().remove(entityId); this.getEntities().remove(entityId);
this.entityLeave = true; this.entityLeave = true;
} }
public void removeEntity(GameEntity entity) { public void removeEntity(GameEntity entity) {
this.getEntities().remove(entity.getId()); this.getEntities().remove(entity.getId());
this.entityLeave = true; this.entityLeave = true;
} }
public boolean entityLeave() { public boolean entityLeave() {
return this.entityLeave; return this.entityLeave;
} }
public void resetEntityLeave() { public void resetEntityLeave() {
this.entityLeave = false; this.entityLeave = false;
} }
@Override @Override
public Int2FloatMap getFightProperties() { public Int2FloatMap getFightProperties() {
return null; return null;
} }
@Override @Override
public Position getPosition() { public Position getPosition() {
return position; return position;
} }
@Override @Override
public Position getRotation() { public Position getRotation() {
return null; return null;
} }
@Override @Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
/** The Region Entity would not be sent to client. */ /** The Region Entity would not be sent to client. */
return null; return null;
} }
public int getFirstEntityId() { public int getFirstEntityId() {
return entities.stream().findFirst().orElse(0); return entities.stream().findFirst().orElse(0);
} }
} }

View File

@@ -1,264 +1,271 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.data.controller.EntityController; import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.server.event.entity.EntityDamageEvent; import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.server.event.entity.EntityDeathEvent; import emu.grasscutter.server.event.entity.EntityDeathEvent;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public abstract class GameEntity { public abstract class GameEntity {
@Getter private final Scene scene; @Getter private final Scene scene;
@Getter protected int id; @Getter protected int id;
@Getter @Setter private SpawnDataEntry spawnEntry; @Getter @Setter private SpawnDataEntry spawnEntry;
@Getter @Setter private int blockId; @Getter @Setter private int blockId;
@Getter @Setter private int configId; @Getter @Setter private int configId;
@Getter @Setter private int groupId; @Getter @Setter private int groupId;
@Getter @Setter private MotionState motionState; @Getter @Setter private MotionState motionState;
@Getter @Setter private int lastMoveSceneTimeMs; @Getter @Setter private int lastMoveSceneTimeMs;
@Getter @Setter private int lastMoveReliableSeq; @Getter @Setter private int lastMoveReliableSeq;
@Getter @Setter private boolean lockHP; @Getter @Setter private boolean lockHP;
// Lua controller for specific actions // Lua controller for specific actions
@Getter @Setter private EntityController entityController; @Getter @Setter private EntityController entityController;
@Getter private ElementType lastAttackType = ElementType.None; @Getter private ElementType lastAttackType = ElementType.None;
// Abilities // Abilities
private Object2FloatMap<String> metaOverrideMap; private Object2FloatMap<String> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers; private Int2ObjectMap<String> metaModifiers;
public GameEntity(Scene scene) { public GameEntity(Scene scene) {
this.scene = scene; this.scene = scene;
this.motionState = MotionState.MOTION_STATE_NONE; this.motionState = MotionState.MOTION_STATE_NONE;
} }
public int getEntityType() { public int getEntityType() {
return this.getId() >> 24; return this.getId() >> 24;
} }
public abstract int getEntityTypeId(); public abstract int getEntityTypeId();
public World getWorld() { public World getWorld() {
return this.getScene().getWorld(); return this.getScene().getWorld();
} }
public boolean isAlive() { public boolean isAlive() {
return true; return true;
} }
public LifeState getLifeState() { public LifeState getLifeState() {
return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
} }
public Object2FloatMap<String> getMetaOverrideMap() { public Object2FloatMap<String> getMetaOverrideMap() {
if (this.metaOverrideMap == null) { if (this.metaOverrideMap == null) {
this.metaOverrideMap = new Object2FloatOpenHashMap<>(); this.metaOverrideMap = new Object2FloatOpenHashMap<>();
} }
return this.metaOverrideMap; return this.metaOverrideMap;
} }
public Int2ObjectMap<String> getMetaModifiers() { public Int2ObjectMap<String> getMetaModifiers() {
if (this.metaModifiers == null) { if (this.metaModifiers == null) {
this.metaModifiers = new Int2ObjectOpenHashMap<>(); this.metaModifiers = new Int2ObjectOpenHashMap<>();
} }
return this.metaModifiers; return this.metaModifiers;
} }
public abstract Int2FloatMap getFightProperties(); public abstract Int2FloatMap getFightProperties();
public abstract Position getPosition(); public abstract Position getPosition();
public abstract Position getRotation(); public abstract Position getRotation();
public void setFightProperty(FightProperty prop, float value) { public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value); this.getFightProperties().put(prop.getId(), value);
} }
public void setFightProperty(int id, float value) { public void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value); this.getFightProperties().put(id, value);
} }
public void addFightProperty(FightProperty prop, float value) { public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value); this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value);
} }
public float getFightProperty(FightProperty prop) { public float getFightProperty(FightProperty prop) {
return this.getFightProperties().getOrDefault(prop.getId(), 0f); return this.getFightProperties().getOrDefault(prop.getId(), 0f);
} }
public boolean hasFightProperty(FightProperty prop) { public boolean hasFightProperty(FightProperty prop) {
return this.getFightProperties().containsKey(prop.getId()); return this.getFightProperties().containsKey(prop.getId());
} }
public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) { public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) {
this.getFightProperties() this.getFightProperties()
.forEach( .forEach(
(key, value) -> { (key, value) -> {
if (key == 0) return; if (key == 0) return;
entityInfo.addFightPropList( entityInfo.addFightPropList(
FightPropPair.newBuilder().setPropType(key).setPropValue(value).build()); FightPropPair.newBuilder().setPropType(key).setPropValue(value).build());
}); });
} }
protected MotionInfo getMotionInfo() { protected MotionInfo getMotionInfo() {
return MotionInfo.newBuilder() return MotionInfo.newBuilder()
.setPos(this.getPosition().toProto()) .setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto()) .setRot(this.getRotation().toProto())
.setSpeed(Vector.newBuilder()) .setSpeed(Vector.newBuilder())
.setState(this.getMotionState()) .setState(this.getMotionState())
.build(); .build();
} }
public float heal(float amount) { public float heal(float amount) {
if (this.getFightProperties() == null) { if (this.getFightProperties() == null) {
return 0f; return 0f;
} }
float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (curHp >= maxHp) { if (curHp >= maxHp) {
return 0f; return 0f;
} }
float healed = Math.min(maxHp - curHp, amount); float healed = Math.min(maxHp - curHp, amount);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed);
this.getScene() this.getScene()
.broadcastPacket( .broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
return healed; return healed;
} }
public void damage(float amount) { public void damage(float amount) {
this.damage(amount, 0, ElementType.None); this.damage(amount, 0, ElementType.None);
} }
public void damage(float amount, int killerId, ElementType attackType) { public void damage(float amount, ElementType attackType) {
// Check if the entity has properties. this.damage(amount, 0, attackType);
if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) { }
return;
} public void damage(float amount, int killerId, ElementType attackType) {
// Check if the entity has properties.
// Invoke entity damage event. if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) {
EntityDamageEvent event = return;
new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId)); }
event.call();
if (event.isCanceled()) { // Invoke entity damage event.
return; // If the event is canceled, do not damage the entity. EntityDamageEvent event =
} new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId));
event.call();
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); if (event.isCanceled()) {
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) { return; // If the event is canceled, do not damage the entity.
// Add negative HP to the current HP property. }
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
} float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
// Check if dead // Add negative HP to the current HP property.
boolean isDead = false; this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { }
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true; this.lastAttackType = attackType;
}
// Check if dead
// Packets boolean isDead = false;
this.getScene() if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
.broadcastPacket( this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); isDead = true;
}
// Check if dead. this.runLuaCallbacks(event);
if (isDead) {
this.getScene().killEntity(this, killerId); // Packets
} this.getScene()
} .broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
/**
* Runs the Lua callbacks for {@link EntityDamageEvent}. // Check if dead.
* if (isDead) {
* @param event The damage event. this.getScene().killEntity(this, killerId);
*/ }
public void runLuaCallbacks(EntityDamageEvent event) { }
if (entityController != null) {
entityController.onBeHurt(this, event.getAttackElementType(), true); // todo is host handling /**
} * Runs the Lua callbacks for {@link EntityDamageEvent}.
} *
* @param event The damage event.
/** */
* Move this entity to a new position. public void runLuaCallbacks(EntityDamageEvent event) {
* if (entityController != null) {
* @param position The new position. entityController.onBeHurt(this, event.getAttackElementType(), true); // todo is host handling
* @param rotation The new rotation. }
*/ }
public void move(Position position, Position rotation) {
// Set the position and rotation. /**
this.getPosition().set(position); * Move this entity to a new position.
this.getRotation().set(rotation); *
} * @param position The new position.
* @param rotation The new rotation.
/** */
* Called when a player interacts with this entity public void move(Position position, Position rotation) {
* // Set the position and rotation.
* @param player Player that is interacting with this entity this.getPosition().set(position);
* @param interactReq Interact request protobuf data this.getRotation().set(rotation);
*/ }
public void onInteract(Player player, GadgetInteractReq interactReq) {}
/**
/** Called when this entity is added to the world */ * Called when a player interacts with this entity
public void onCreate() {} *
* @param player Player that is interacting with this entity
public void onRemoved() {} * @param interactReq Interact request protobuf data
*/
public void onTick(int sceneTime) { public void onInteract(Player player, GadgetInteractReq interactReq) {}
if (entityController != null) {
entityController.onTimer(this, sceneTime); /** Called when this entity is added to the world */
} public void onCreate() {}
}
public void onRemoved() {}
public int onClientExecuteRequest(int param1, int param2, int param3) {
if (entityController != null) { public void onTick(int sceneTime) {
return entityController.onClientExecuteRequest(this, param1, param2, param3); if (entityController != null) {
} entityController.onTimer(this, sceneTime);
return 0; }
} }
/** public int onClientExecuteRequest(int param1, int param2, int param3) {
* Called when this entity dies if (entityController != null) {
* return entityController.onClientExecuteRequest(this, param1, param2, param3);
* @param killerId Entity id of the entity that killed this entity }
*/ return 0;
public void onDeath(int killerId) { }
// Invoke entity death event.
EntityDeathEvent event = new EntityDeathEvent(this, killerId); /**
event.call(); * Called when this entity dies
*
// Run Lua callbacks. * @param killerId Entity id of the entity that killed this entity
if (entityController != null) { */
entityController.onDie(this, getLastAttackType()); public void onDeath(int killerId) {
} // Invoke entity death event.
} EntityDeathEvent event = new EntityDeathEvent(this, killerId);
event.call();
public abstract SceneEntityInfo toProto();
} // Run Lua callbacks.
if (entityController != null) {
entityController.onDie(this, getLastAttackType());
}
}
public abstract SceneEntityInfo toProto();
}

View File

@@ -1,86 +1,98 @@
package emu.grasscutter.game.entity.gadget; package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.utils.Utils; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetGatherObject extends GadgetContent { import emu.grasscutter.utils.Utils;
private int itemId;
private boolean isForbidGuest; public final class GadgetGatherObject extends GadgetContent {
private int itemId;
public GadgetGatherObject(EntityGadget gadget) { private boolean isForbidGuest;
super(gadget);
public GadgetGatherObject(EntityGadget gadget) {
if (gadget.getSpawnEntry() != null) { super(gadget);
this.itemId = gadget.getSpawnEntry().getGatherItemId();
} // overwrites the default spawn handling
} if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId();
public int getItemId() { return;
return this.itemId; }
}
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
public boolean isForbidGuest() { if (gatherData != null) {
return isForbidGuest; this.itemId = gatherData.getItemId();
} this.isForbidGuest = gatherData.isForbidGuest();
} else {
public boolean onInteract(Player player, GadgetInteractReq req) { Grasscutter.getLogger().error("invalid gather object: {}", gadget.getConfigId());
// Sanity check }
ItemData itemData = GameData.getItemDataMap().get(getItemId()); }
if (itemData == null) {
return false; public int getItemId() {
} return this.itemId;
}
GameItem item = new GameItem(itemData, 1);
player.getInventory().addItem(item, ActionReason.Gather); public boolean isForbidGuest() {
return isForbidGuest;
getGadget() }
.getScene()
.broadcastPacket( public boolean onInteract(Player player, GadgetInteractReq req) {
new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_GATHER)); // Sanity check
ItemData itemData = GameData.getItemDataMap().get(getItemId());
return true; if (itemData == null) {
} return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo = GameItem item = new GameItem(itemData, 1);
GatherGadgetInfo.newBuilder() player.getInventory().addItem(item, ActionReason.Gather);
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest()) getGadget()
.build(); .getScene()
.broadcastPacket(
gadgetInfo.setGatherGadget(gatherGadgetInfo); new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_GATHER));
}
return true;
public void dropItems(Player player) { }
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2); public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo =
for (int i = 0; i < times; i++) { GatherGadgetInfo.newBuilder()
EntityItem item = .setItemId(this.getItemId())
new EntityItem( .setIsForbidGuest(this.isForbidGuest())
scene, .build();
player,
GameData.getItemDataMap().get(itemId), gadgetInfo.setGatherGadget(gatherGadgetInfo);
getGadget().getPosition().nearby2d(1f).addY(2f), }
1,
true); public void dropItems(Player player) {
Scene scene = getGadget().getScene();
scene.addEntity(item); int times = Utils.randomRange(1, 2);
}
for (int i = 0; i < times; i++) {
scene.killEntity(this.getGadget(), player.getTeamManager().getCurrentAvatarEntity().getId()); EntityItem item =
// Todo: add record new EntityItem(
} scene,
} player,
GameData.getItemDataMap().get(itemId),
getGadget().getPosition().nearby2d(1f).addY(2f),
1,
true);
scene.addEntity(item);
}
scene.killEntity(this.getGadget(), player.getTeamManager().getCurrentAvatarEntity().getId());
// Todo: add record
}
}

View File

@@ -1,83 +1,65 @@
package emu.grasscutter.game.entity.gadget; package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GatherData; import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; /** Spawner for the gather objects */
import emu.grasscutter.utils.Utils; public final class GadgetGatherPoint extends GadgetContent {
private final GatherData gatherData;
public class GadgetGatherPoint extends GadgetContent { private final EntityGadget gatherObjectChild;
private final int itemId;
private boolean isForbidGuest; public GadgetGatherPoint(EntityGadget gadget) {
super(gadget);
public GadgetGatherPoint(EntityGadget gadget) {
super(gadget); this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
if (gadget.getSpawnEntry() != null) { var scene = gadget.getScene();
this.itemId = gadget.getSpawnEntry().getGatherItemId(); gatherObjectChild = new EntityGadget(scene, gatherData.getGadgetId(), gadget.getBornPos());
} else {
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType()); gatherObjectChild.setBlockId(gadget.getBlockId());
this.itemId = gatherData.getItemId(); gatherObjectChild.setConfigId(gadget.getConfigId());
this.isForbidGuest = gatherData.isForbidGuest(); gatherObjectChild.setGroupId(gadget.getGroupId());
} gatherObjectChild.getRotation().set(gadget.getRotation());
} gatherObjectChild.setState(gadget.getState());
gatherObjectChild.setPointType(gadget.getPointType());
public int getItemId() { gatherObjectChild.setMetaGadget(gadget.getMetaGadget());
return this.itemId; gatherObjectChild.buildContent();
}
gadget.getChildren().add(gatherObjectChild);
public boolean isForbidGuest() { scene.addEntity(gatherObjectChild);
return isForbidGuest; }
}
public int getItemId() {
public boolean onInteract(Player player, GadgetInteractReq req) { return this.gatherData.getItemId();
GameItem item = new GameItem(getItemId(), 1); }
player.getInventory().addItem(item, ActionReason.Gather); public boolean isForbidGuest() {
return this.gatherData.isForbidGuest();
return true; }
}
public boolean onInteract(Player player, GadgetInteractReq req) {
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { GameItem item = new GameItem(getItemId(), 1);
GatherGadgetInfo gatherGadgetInfo =
GatherGadgetInfo.newBuilder() player.getInventory().addItem(item, ActionReason.Gather);
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest()) return true;
.build(); }
gadgetInfo.setGatherGadget(gatherGadgetInfo); public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
} // todo does official use this for the spawners?
GatherGadgetInfo gatherGadgetInfo =
public void dropItems(Player player) { GatherGadgetInfo.newBuilder()
Scene scene = getGadget().getScene(); .setItemId(this.getItemId())
int times = Utils.randomRange(1, 2); .setIsForbidGuest(this.isForbidGuest())
.build();
for (int i = 0; i < times; i++) {
EntityItem item = gadgetInfo.setGatherGadget(gatherGadgetInfo);
new EntityItem( }
scene, }
player,
GameData.getItemDataMap().get(itemId),
getGadget()
.getPosition()
.clone()
.addY(2f)
.addX(Utils.randomFloatRange(-1f, 1f))
.addZ(Utils.randomFloatRange(-1f, 1f)),
1,
true);
scene.addEntity(item);
}
scene.killEntity(this.getGadget(), player.getTeamManager().getCurrentAvatarEntity().getId());
// Todo: add record
}
}

View File

@@ -1,65 +1,68 @@
package emu.grasscutter.game.entity.gadget; package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.gadget.worktop.WorktopWorktopOptionHandler; import emu.grasscutter.game.entity.gadget.worktop.WorktopWorktopOptionHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq;
import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo; import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Arrays; import java.util.Arrays;
public class GadgetWorktop extends GadgetContent { public final class GadgetWorktop extends GadgetContent {
private IntSet worktopOptions; private IntSet worktopOptions;
private WorktopWorktopOptionHandler handler; private WorktopWorktopOptionHandler handler;
public GadgetWorktop(EntityGadget gadget) { public GadgetWorktop(EntityGadget gadget) {
super(gadget); super(gadget);
} }
public IntSet getWorktopOptions() { public IntSet getWorktopOptions() {
return worktopOptions; if (this.worktopOptions == null) {
} this.worktopOptions = new IntOpenHashSet();
}
public void addWorktopOptions(int[] options) { return worktopOptions;
if (this.worktopOptions == null) { }
this.worktopOptions = new IntOpenHashSet();
} public void addWorktopOptions(int[] options) {
Arrays.stream(options).forEach(this.worktopOptions::add); if (this.worktopOptions == null) {
} this.worktopOptions = new IntOpenHashSet();
}
public void removeWorktopOption(int option) { Arrays.stream(options).forEach(this.worktopOptions::add);
if (this.worktopOptions == null) { }
return;
} public void removeWorktopOption(int option) {
this.worktopOptions.remove(option); if (this.worktopOptions == null) {
} return;
}
public boolean onInteract(Player player, GadgetInteractReq req) { this.worktopOptions.remove(option);
return false; }
}
public boolean onInteract(Player player, GadgetInteractReq req) {
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { return false;
if (this.worktopOptions == null) { }
return;
} public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (this.worktopOptions == null) {
WorktopInfo worktop = return;
WorktopInfo.newBuilder().addAllOptionList(this.getWorktopOptions()).build(); }
gadgetInfo.setWorktop(worktop); WorktopInfo worktop =
} WorktopInfo.newBuilder().addAllOptionList(this.getWorktopOptions()).build();
public void setOnSelectWorktopOptionEvent(WorktopWorktopOptionHandler handler) { gadgetInfo.setWorktop(worktop);
this.handler = handler; }
}
public void setOnSelectWorktopOptionEvent(WorktopWorktopOptionHandler handler) {
public boolean onSelectWorktopOption(SelectWorktopOptionReq req) { this.handler = handler;
if (this.handler != null) { }
this.handler.onSelectWorktopOption(this, req.getOptionId());
} public boolean onSelectWorktopOption(SelectWorktopOptionReq req) {
return false; if (this.handler != null) {
} this.handler.onSelectWorktopOption(this, req.getOptionId());
} }
return false;
}
}

View File

@@ -1,61 +1,67 @@
package emu.grasscutter.game.entity.gadget.chest; package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.entity.gadget.GadgetChest; import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BossChestInteractHandler implements ChestInteractHandler { public class BossChestInteractHandler implements ChestInteractHandler {
@Override @Override
public boolean isTwoStep() { public boolean isTwoStep() {
return true; return true;
} }
@Override @Override
public boolean onInteract(GadgetChest chest, Player player) { public boolean onInteract(GadgetChest chest, Player player) {
return this.onInteract(chest, player, false); return this.onInteract(chest, player, false);
} }
public boolean onInteract(GadgetChest chest, Player player, boolean useCondensedResin) { public boolean onInteract(GadgetChest chest, Player player, boolean useCondensedResin) {
var blossomRewards = var blossomRewards =
player player
.getScene() .getScene()
.getBlossomManager() .getBlossomManager()
.onReward(player, chest.getGadget(), useCondensedResin); .onReward(player, chest.getGadget(), useCondensedResin);
if (blossomRewards != null) { if (blossomRewards != null) {
player.getInventory().addItems(blossomRewards, ActionReason.OpenWorldBossChest); player.getInventory().addItems(blossomRewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(blossomRewards)); player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(blossomRewards));
return true; return true;
} }
var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem(); var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem();
var monster = var monster =
chest chest
.getGadget() .getGadget()
.getMetaGadget() .getMetaGadget()
.group .group
.monsters .monsters
.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id); .get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id);
var reward = worldDataManager.getRewardByBossId(monster.monster_id); var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if (reward == null) { if (reward == null) {
Grasscutter.getLogger() var dungeonManager = player.getScene().getDungeonManager();
.warn("Could not found the reward of boss monster {}", monster.monster_id);
return false; if (dungeonManager != null) {
} return dungeonManager.getStatueDrops(
List<GameItem> rewards = new ArrayList<>(); player, useCondensedResin, chest.getGadget().getGroupId());
for (ItemParamData param : reward.getPreviewItems()) { }
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); Grasscutter.getLogger()
} .warn("Could not found the reward of boss monster {}", monster.monster_id);
return false;
player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest); }
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.getPreviewItems()) {
return true; rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
} }
}
player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
return true;
}
}

View File

@@ -1,47 +1,45 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap; import lombok.Getter;
import java.util.Map;
import java.util.stream.Stream; import java.util.HashMap;
import java.util.Map;
public enum EquipType { import java.util.stream.Stream;
EQUIP_NONE(0),
EQUIP_BRACER(1), public enum EquipType {
EQUIP_NECKLACE(2), EQUIP_NONE(0),
EQUIP_SHOES(3), EQUIP_BRACER(1),
EQUIP_RING(4), EQUIP_NECKLACE(2),
EQUIP_DRESS(5), EQUIP_SHOES(3),
EQUIP_WEAPON(6); EQUIP_RING(4),
EQUIP_DRESS(5),
private static final Int2ObjectMap<EquipType> map = new Int2ObjectOpenHashMap<>(); EQUIP_WEAPON(6);
private static final Map<String, EquipType> stringMap = new HashMap<>();
private static final Int2ObjectMap<EquipType> map = new Int2ObjectOpenHashMap<>();
static { private static final Map<String, EquipType> stringMap = new HashMap<>();
Stream.of(values())
.forEach( static {
e -> { Stream.of(values())
map.put(e.getValue(), e); .forEach(
stringMap.put(e.name(), e); e -> {
}); map.put(e.getValue(), e);
} stringMap.put(e.name(), e);
});
private final int value; }
EquipType(int value) { @Getter private final int value;
this.value = value;
} EquipType(int value) {
this.value = value;
public static EquipType getTypeByValue(int value) { }
return map.getOrDefault(value, EQUIP_NONE);
} public static EquipType getTypeByValue(int value) {
return map.getOrDefault(value, EQUIP_NONE);
public static EquipType getTypeByName(String name) { }
return stringMap.getOrDefault(name, EQUIP_NONE);
} public static EquipType getTypeByName(String name) {
return stringMap.getOrDefault(name, EQUIP_NONE);
public int getValue() { }
return value; }
}
}

View File

@@ -1,363 +1,371 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import dev.morphia.annotations.*; import dev.morphia.annotations.*;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData; import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData;
import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData; import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.EquipOuterClass.Equip; import emu.grasscutter.net.proto.EquipOuterClass.Equip;
import emu.grasscutter.net.proto.FurnitureOuterClass.Furniture; import emu.grasscutter.net.proto.FurnitureOuterClass.Furniture;
import emu.grasscutter.net.proto.ItemHintOuterClass.ItemHint; import emu.grasscutter.net.proto.ItemHintOuterClass.ItemHint;
import emu.grasscutter.net.proto.ItemOuterClass.Item; import emu.grasscutter.net.proto.ItemOuterClass.Item;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.MaterialOuterClass.Material; import emu.grasscutter.net.proto.MaterialOuterClass.Material;
import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary; import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary;
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo; import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon; import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.WeightedList; import emu.grasscutter.utils.WeightedList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
@Entity(value = "items", useDiscriminator = false) @Entity(value = "items", useDiscriminator = false)
public class GameItem { public class GameItem {
@Id private ObjectId id; @Id private ObjectId id;
@Indexed private int ownerId; @Indexed private int ownerId;
@Getter @Setter private int itemId; @Getter @Setter private int itemId;
@Getter @Setter private int count; @Getter @Setter private int count;
@Transient @Getter private long guid; // Player unique id @Transient @Getter private long guid; // Player unique id
@Transient @Getter @Setter private ItemData itemData; @Transient @Getter @Setter private ItemData itemData;
// Equips // Equips
@Getter @Setter private int level; @Getter @Setter private int level;
@Getter @Setter private int exp; @Getter @Setter private int exp;
@Getter @Setter private int totalExp; @Getter @Setter private int totalExp;
@Getter @Setter private int promoteLevel; @Getter @Setter private int promoteLevel;
@Getter @Setter private boolean locked; @Getter @Setter private boolean locked;
// Weapon // Weapon
@Getter private List<Integer> affixes; @Getter private List<Integer> affixes;
@Getter @Setter private int refinement = 0; @Getter @Setter private int refinement = 0;
// Relic // Relic
@Getter @Setter private int mainPropId; @Getter @Setter private int mainPropId;
@Getter private List<Integer> appendPropIdList; @Getter private List<Integer> appendPropIdList;
@Getter @Setter private int equipCharacter; @Getter @Setter private int equipCharacter;
@Transient @Getter @Setter private int weaponEntityId; @Transient @Getter @Setter private int weaponEntityId;
@Transient @Getter private boolean newItem = false;
public GameItem() {
// Morphia only public GameItem() {
} // Morphia only
}
public GameItem(int itemId) {
this(GameData.getItemDataMap().get(itemId)); public GameItem(int itemId) {
} this(GameData.getItemDataMap().get(itemId));
}
public GameItem(int itemId, int count) {
this(GameData.getItemDataMap().get(itemId), count); public GameItem(int itemId, int count) {
} this(GameData.getItemDataMap().get(itemId), count);
}
public GameItem(ItemParamData itemParamData) {
this(itemParamData.getId(), itemParamData.getCount()); public GameItem(ItemParamData itemParamData) {
} this(itemParamData.getId(), itemParamData.getCount());
}
public GameItem(ItemData data) {
this(data, 1); public GameItem(ItemData data) {
} this(data, 1);
}
public GameItem(ItemData data, int count) {
this.itemId = data.getId(); public GameItem(ItemData data, int count) {
this.itemData = data; this.itemId = data.getId();
this.itemData = data;
switch (data.getItemType()) {
case ITEM_VIRTUAL: switch (data.getItemType()) {
this.count = count; case ITEM_VIRTUAL:
break; this.count = count;
case ITEM_WEAPON: break;
this.count = 1; case ITEM_WEAPON:
this.level = Math.max(this.count, 1); // ?????????????????? this.count = 1;
this.affixes = new ArrayList<>(2); this.level = Math.max(this.count, 1); // ??????????????????
if (data.getSkillAffix() != null) { this.affixes = new ArrayList<>(2);
for (int skillAffix : data.getSkillAffix()) { if (data.getSkillAffix() != null) {
if (skillAffix > 0) { for (int skillAffix : data.getSkillAffix()) {
this.affixes.add(skillAffix); if (skillAffix > 0) {
} this.affixes.add(skillAffix);
} }
} }
break; }
case ITEM_RELIQUARY: break;
this.count = 1; case ITEM_RELIQUARY:
this.level = 1; this.count = 1;
this.appendPropIdList = new ArrayList<>(); this.level = 1;
// Create main property this.appendPropIdList = new ArrayList<>();
ReliquaryMainPropData mainPropData = // Create main property
GameDepot.getRandomRelicMainProp(data.getMainPropDepotId()); ReliquaryMainPropData mainPropData =
if (mainPropData != null) { GameDepot.getRandomRelicMainProp(data.getMainPropDepotId());
this.mainPropId = mainPropData.getId(); if (mainPropData != null) {
} this.mainPropId = mainPropData.getId();
// Create extra stats }
this.addAppendProps(data.getAppendPropNum()); // Create extra stats
break; this.addAppendProps(data.getAppendPropNum());
default: break;
this.count = Math.min(count, data.getStackLimit()); default:
} this.count = Math.min(count, data.getStackLimit());
} }
}
public static int getMinPromoteLevel(int level) {
if (level > 80) { public int getOwnerId() {
return 6; return ownerId;
} else if (level > 70) { }
return 5;
} else if (level > 60) { public void setOwner(Player player) {
return 4; this.ownerId = player.getUid();
} else if (level > 50) { this.guid = player.getNextGameGuid();
return 3; }
} else if (level > 40) {
return 2; public void checkIsNew(Inventory inventory) {
} else if (level > 20) { // display notification when player obtain new item
return 1; if (inventory.getItemByGuid(this.itemId) == null) {
} this.newItem = true;
return 0; }
} }
public int getOwnerId() { public ObjectId getObjectId() {
return ownerId; return id;
} }
public void setOwner(Player player) { public ItemType getItemType() {
this.ownerId = player.getUid(); return this.itemData.getItemType();
this.guid = player.getNextGameGuid(); }
}
public static int getMinPromoteLevel(int level) {
public ObjectId getObjectId() { if (level > 80) {
return id; return 6;
} } else if (level > 70) {
return 5;
public ItemType getItemType() { } else if (level > 60) {
return this.itemData.getItemType(); return 4;
} } else if (level > 50) {
return 3;
public int getEquipSlot() { } else if (level > 40) {
return this.getItemData().getEquipType().getValue(); return 2;
} } else if (level > 20) {
return 1;
public boolean isEquipped() { }
return this.getEquipCharacter() > 0; return 0;
} }
public boolean isDestroyable() { public int getEquipSlot() {
return !this.isLocked() && !this.isEquipped(); return this.getItemData().getEquipType().getValue();
} }
public void addAppendProp() { public boolean isEquipped() {
if (this.appendPropIdList == null) { return this.getEquipCharacter() > 0;
this.appendPropIdList = new ArrayList<>(); }
}
public boolean isDestroyable() {
if (this.appendPropIdList.size() < 4) { return !this.isLocked() && !this.isEquipped();
this.addNewAppendProp(); }
} else {
this.upgradeRandomAppendProp(); public void addAppendProp() {
} if (this.appendPropIdList == null) {
} this.appendPropIdList = new ArrayList<>();
}
public void addAppendProps(int quantity) {
int num = Math.max(quantity, 0); if (this.appendPropIdList.size() < 4) {
for (int i = 0; i < num; i++) { this.addNewAppendProp();
this.addAppendProp(); } else {
} this.upgradeRandomAppendProp();
} }
}
private Set<FightProperty> getAppendFightProperties() {
Set<FightProperty> props = new HashSet<>(); public void addAppendProps(int quantity) {
// Previously this would check no more than the first four affixes, however custom artifacts may int num = Math.max(quantity, 0);
// not respect this order. for (int i = 0; i < num; i++) {
for (int appendPropId : this.appendPropIdList) { this.addAppendProp();
ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId); }
if (affixData != null) { }
props.add(affixData.getFightProp());
} private Set<FightProperty> getAppendFightProperties() {
} Set<FightProperty> props = new HashSet<>();
return props; // Previously this would check no more than the first four affixes, however custom artifacts may
} // not respect this order.
for (int appendPropId : this.appendPropIdList) {
private void addNewAppendProp() { ReliquaryAffixData affixData = GameData.getReliquaryAffixDataMap().get(appendPropId);
List<ReliquaryAffixData> affixList = if (affixData != null) {
GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId()); props.add(affixData.getFightProp());
}
if (affixList == null) { }
return; return props;
} }
// Build blacklist - Dont add same stat as main/sub stat private void addNewAppendProp() {
Set<FightProperty> blacklist = this.getAppendFightProperties(); List<ReliquaryAffixData> affixList =
ReliquaryMainPropData mainPropData = GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
GameData.getReliquaryMainPropDataMap().get(this.mainPropId);
if (mainPropData != null) { if (affixList == null) {
blacklist.add(mainPropData.getFightProp()); return;
} }
// Build random list // Build blacklist - Dont add same stat as main/sub stat
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>(); Set<FightProperty> blacklist = this.getAppendFightProperties();
for (ReliquaryAffixData affix : affixList) { ReliquaryMainPropData mainPropData =
if (!blacklist.contains(affix.getFightProp())) { GameData.getReliquaryMainPropDataMap().get(this.mainPropId);
randomList.add(affix.getWeight(), affix); if (mainPropData != null) {
} blacklist.add(mainPropData.getFightProp());
} }
if (randomList.size() == 0) { // Build random list
return; WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
} for (ReliquaryAffixData affix : affixList) {
if (!blacklist.contains(affix.getFightProp())) {
// Add random stat randomList.add(affix.getWeight(), affix);
ReliquaryAffixData affixData = randomList.next(); }
this.appendPropIdList.add(affixData.getId()); }
}
if (randomList.size() == 0) {
private void upgradeRandomAppendProp() { return;
List<ReliquaryAffixData> affixList = }
GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
// Add random stat
if (affixList == null) { ReliquaryAffixData affixData = randomList.next();
return; this.appendPropIdList.add(affixData.getId());
} }
// Build whitelist private void upgradeRandomAppendProp() {
Set<FightProperty> whitelist = this.getAppendFightProperties(); List<ReliquaryAffixData> affixList =
GameDepot.getRelicAffixList(this.itemData.getAppendPropDepotId());
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>(); if (affixList == null) {
for (ReliquaryAffixData affix : affixList) { return;
if (whitelist.contains(affix.getFightProp())) { }
randomList.add(affix.getUpgradeWeight(), affix);
} // Build whitelist
} Set<FightProperty> whitelist = this.getAppendFightProperties();
// Add random stat // Build random list
ReliquaryAffixData affixData = randomList.next(); WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
this.appendPropIdList.add(affixData.getId()); for (ReliquaryAffixData affix : affixList) {
} if (whitelist.contains(affix.getFightProp())) {
randomList.add(affix.getUpgradeWeight(), affix);
@PostLoad }
public void onLoad() { }
if (this.itemData == null) {
this.itemData = GameData.getItemDataMap().get(getItemId()); // Add random stat
} ReliquaryAffixData affixData = randomList.next();
} this.appendPropIdList.add(affixData.getId());
}
public void save() {
if (this.count > 0 && this.ownerId > 0) { @PostLoad
DatabaseHelper.saveItem(this); public void onLoad() {
} else if (this.getObjectId() != null) { if (this.itemData == null) {
DatabaseHelper.deleteItem(this); this.itemData = GameData.getItemDataMap().get(getItemId());
} }
} }
public SceneWeaponInfo createSceneWeaponInfo() { public void save() {
SceneWeaponInfo.Builder weaponInfo = if (this.count > 0 && this.ownerId > 0) {
SceneWeaponInfo.newBuilder() DatabaseHelper.saveItem(this);
.setEntityId(this.getWeaponEntityId()) } else if (this.getObjectId() != null) {
.setItemId(this.getItemId()) DatabaseHelper.deleteItem(this);
.setGuid(this.getGuid()) }
.setLevel(this.getLevel()) }
.setGadgetId(this.getItemData().getGadgetId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder().setIsInited(getAffixes().size() > 0)); public SceneWeaponInfo createSceneWeaponInfo() {
SceneWeaponInfo.Builder weaponInfo =
if (this.getAffixes() != null && this.getAffixes().size() > 0) { SceneWeaponInfo.newBuilder()
for (int affix : this.getAffixes()) { .setEntityId(this.getWeaponEntityId())
weaponInfo.putAffixMap(affix, this.getRefinement()); .setItemId(this.getItemId())
} .setGuid(this.getGuid())
} .setLevel(this.getLevel())
.setGadgetId(this.getItemData().getGadgetId())
return weaponInfo.build(); .setAbilityInfo(AbilitySyncStateInfo.newBuilder().setIsInited(getAffixes().size() > 0));
}
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
public SceneReliquaryInfo createSceneReliquaryInfo() { for (int affix : this.getAffixes()) {
SceneReliquaryInfo relicInfo = weaponInfo.putAffixMap(affix, this.getRefinement());
SceneReliquaryInfo.newBuilder() }
.setItemId(this.getItemId()) }
.setGuid(this.getGuid())
.setLevel(this.getLevel()) return weaponInfo.build();
.build(); }
return relicInfo; public SceneReliquaryInfo createSceneReliquaryInfo() {
} SceneReliquaryInfo relicInfo =
SceneReliquaryInfo.newBuilder()
public Weapon toWeaponProto() { .setItemId(this.getItemId())
Weapon.Builder weapon = .setGuid(this.getGuid())
Weapon.newBuilder() .setLevel(this.getLevel())
.setLevel(this.getLevel()) .build();
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel()); return relicInfo;
}
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) { public Weapon toWeaponProto() {
weapon.putAffixMap(affix, this.getRefinement()); Weapon.Builder weapon =
} Weapon.newBuilder()
} .setLevel(this.getLevel())
.setExp(this.getExp())
return weapon.build(); .setPromoteLevel(this.getPromoteLevel());
}
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
public Reliquary toReliquaryProto() { for (int affix : this.getAffixes()) {
Reliquary.Builder relic = weapon.putAffixMap(affix, this.getRefinement());
Reliquary.newBuilder() }
.setLevel(this.getLevel()) }
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel()) return weapon.build();
.setMainPropId(this.getMainPropId()) }
.addAllAppendPropIdList(this.getAppendPropIdList());
public Reliquary toReliquaryProto() {
return relic.build(); Reliquary.Builder relic =
} Reliquary.newBuilder()
.setLevel(this.getLevel())
public Item toProto() { .setExp(this.getExp())
Item.Builder proto = Item.newBuilder().setGuid(this.getGuid()).setItemId(this.getItemId()); .setPromoteLevel(this.getPromoteLevel())
.setMainPropId(this.getMainPropId())
switch (getItemType()) { .addAllAppendPropIdList(this.getAppendPropIdList());
case ITEM_WEAPON:
Weapon weapon = this.toWeaponProto(); return relic.build();
proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build()); }
break;
case ITEM_RELIQUARY: public Item toProto() {
Reliquary relic = this.toReliquaryProto(); Item.Builder proto = Item.newBuilder().setGuid(this.getGuid()).setItemId(this.getItemId());
proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
break; switch (getItemType()) {
case ITEM_FURNITURE: case ITEM_WEAPON:
Furniture furniture = Furniture.newBuilder().setCount(getCount()).build(); Weapon weapon = this.toWeaponProto();
proto.setFurniture(furniture); proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build());
break; break;
default: case ITEM_RELIQUARY:
Material material = Material.newBuilder().setCount(getCount()).build(); Reliquary relic = this.toReliquaryProto();
proto.setMaterial(material); proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
break; break;
} case ITEM_FURNITURE:
Furniture furniture = Furniture.newBuilder().setCount(getCount()).build();
return proto.build(); proto.setFurniture(furniture);
} break;
default:
public ItemHint toItemHintProto() { Material material = Material.newBuilder().setCount(getCount()).build();
return ItemHint.newBuilder() proto.setMaterial(material);
.setItemId(getItemId()) break;
.setCount(getCount()) }
.setIsNew(false)
.build(); return proto.build();
} }
public ItemParam toItemParam() { public ItemHint toItemHintProto() {
return ItemParam.newBuilder().setItemId(this.getItemId()).setCount(this.getCount()).build(); return ItemHint.newBuilder()
} .setItemId(getItemId())
} .setCount(getCount())
.setIsNew(this.isNewItem())
.build();
}
public ItemParam toItemParam() {
return ItemParam.newBuilder().setItemId(this.getItemId()).setCount(this.getCount()).build();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,45 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap; import lombok.Getter;
import java.util.Map;
import java.util.stream.Stream; import java.util.HashMap;
import java.util.Map;
public enum ItemQuality { import java.util.stream.Stream;
QUALITY_NONE(0),
QUALITY_WHITE(1), public enum ItemQuality {
QUALITY_GREEN(2), QUALITY_NONE(0),
QUALITY_BLUE(3), QUALITY_WHITE(1),
QUALITY_PURPLE(4), QUALITY_GREEN(2),
QUALITY_ORANGE(5), QUALITY_BLUE(3),
QUALITY_ORANGE_SP(105); QUALITY_PURPLE(4),
QUALITY_ORANGE(5),
private static final Int2ObjectMap<ItemQuality> map = new Int2ObjectOpenHashMap<>(); QUALITY_ORANGE_SP(105);
private static final Map<String, ItemQuality> stringMap = new HashMap<>();
private static final Int2ObjectMap<ItemQuality> map = new Int2ObjectOpenHashMap<>();
static { private static final Map<String, ItemQuality> stringMap = new HashMap<>();
Stream.of(values())
.forEach( static {
e -> { Stream.of(values())
map.put(e.getValue(), e); .forEach(
stringMap.put(e.name(), e); e -> {
}); map.put(e.getValue(), e);
} stringMap.put(e.name(), e);
});
private final int value; }
ItemQuality(int value) { @Getter private final int value;
this.value = value;
} ItemQuality(int value) {
this.value = value;
public static ItemQuality getTypeByValue(int value) { }
return map.getOrDefault(value, QUALITY_NONE);
} public static ItemQuality getTypeByValue(int value) {
return map.getOrDefault(value, QUALITY_NONE);
public static ItemQuality getTypeByName(String name) { }
return stringMap.getOrDefault(name, QUALITY_NONE);
} public static ItemQuality getTypeByName(String name) {
return stringMap.getOrDefault(name, QUALITY_NONE);
public int getValue() { }
return value; }
}
}

View File

@@ -1,47 +1,45 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap; import lombok.Getter;
import java.util.Map;
import java.util.stream.Stream; import java.util.HashMap;
import java.util.Map;
public enum ItemType { import java.util.stream.Stream;
ITEM_NONE(0),
ITEM_VIRTUAL(1), public enum ItemType {
ITEM_MATERIAL(2), ITEM_NONE(0),
ITEM_RELIQUARY(3), ITEM_VIRTUAL(1),
ITEM_WEAPON(4), ITEM_MATERIAL(2),
ITEM_DISPLAY(5), ITEM_RELIQUARY(3),
ITEM_FURNITURE(6); ITEM_WEAPON(4),
ITEM_DISPLAY(5),
private static final Int2ObjectMap<ItemType> map = new Int2ObjectOpenHashMap<>(); ITEM_FURNITURE(6);
private static final Map<String, ItemType> stringMap = new HashMap<>();
private static final Int2ObjectMap<ItemType> map = new Int2ObjectOpenHashMap<>();
static { private static final Map<String, ItemType> stringMap = new HashMap<>();
Stream.of(values())
.forEach( static {
e -> { Stream.of(values())
map.put(e.getValue(), e); .forEach(
stringMap.put(e.name(), e); e -> {
}); map.put(e.getValue(), e);
} stringMap.put(e.name(), e);
});
private final int value; }
ItemType(int value) { @Getter private final int value;
this.value = value;
} ItemType(int value) {
this.value = value;
public static ItemType getTypeByValue(int value) { }
return map.getOrDefault(value, ITEM_NONE);
} public static ItemType getTypeByValue(int value) {
return map.getOrDefault(value, ITEM_NONE);
public static ItemType getTypeByName(String name) { }
return stringMap.getOrDefault(name, ITEM_NONE);
} public static ItemType getTypeByName(String name) {
return stringMap.getOrDefault(name, ITEM_NONE);
public int getValue() { }
return value; }
}
}

View File

@@ -1,81 +1,79 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap; import lombok.Getter;
import java.util.Map;
import java.util.stream.Stream; import java.util.HashMap;
import java.util.Map;
public enum MaterialType { import java.util.stream.Stream;
MATERIAL_NONE(0),
MATERIAL_FOOD(1), public enum MaterialType {
MATERIAL_QUEST(2), MATERIAL_NONE(0),
MATERIAL_EXCHANGE(4), MATERIAL_FOOD(1),
MATERIAL_CONSUME(5), MATERIAL_QUEST(2),
MATERIAL_EXP_FRUIT(6), MATERIAL_EXCHANGE(4),
MATERIAL_AVATAR(7), MATERIAL_CONSUME(5),
MATERIAL_ADSORBATE(8), MATERIAL_EXP_FRUIT(6),
MATERIAL_CRICKET(9), MATERIAL_AVATAR(7),
MATERIAL_ELEM_CRYSTAL(10), MATERIAL_ADSORBATE(8),
MATERIAL_WEAPON_EXP_STONE(11), MATERIAL_CRICKET(9),
MATERIAL_CHEST(12), MATERIAL_ELEM_CRYSTAL(10),
MATERIAL_RELIQUARY_MATERIAL(13), MATERIAL_WEAPON_EXP_STONE(11),
MATERIAL_AVATAR_MATERIAL(14), MATERIAL_CHEST(12),
MATERIAL_NOTICE_ADD_HP(15), MATERIAL_RELIQUARY_MATERIAL(13),
MATERIAL_SEA_LAMP(16), MATERIAL_AVATAR_MATERIAL(14),
MATERIAL_SELECTABLE_CHEST(17), MATERIAL_NOTICE_ADD_HP(15),
MATERIAL_FLYCLOAK(18), MATERIAL_SEA_LAMP(16),
MATERIAL_NAMECARD(19), MATERIAL_SELECTABLE_CHEST(17),
MATERIAL_TALENT(20), MATERIAL_FLYCLOAK(18),
MATERIAL_WIDGET(21), MATERIAL_NAMECARD(19),
MATERIAL_CHEST_BATCH_USE(22), MATERIAL_TALENT(20),
MATERIAL_FAKE_ABSORBATE(23), MATERIAL_WIDGET(21),
MATERIAL_CONSUME_BATCH_USE(24), MATERIAL_CHEST_BATCH_USE(22),
MATERIAL_WOOD(25), MATERIAL_FAKE_ABSORBATE(23),
MATERIAL_FURNITURE_FORMULA(27), MATERIAL_CONSUME_BATCH_USE(24),
MATERIAL_CHANNELLER_SLAB_BUFF(28), MATERIAL_WOOD(25),
MATERIAL_FURNITURE_SUITE_FORMULA(29), MATERIAL_FURNITURE_FORMULA(27),
MATERIAL_COSTUME(30), MATERIAL_CHANNELLER_SLAB_BUFF(28),
MATERIAL_HOME_SEED(31), MATERIAL_FURNITURE_SUITE_FORMULA(29),
MATERIAL_FISH_BAIT(32), MATERIAL_COSTUME(30),
MATERIAL_FISH_ROD(33), MATERIAL_HOME_SEED(31),
MATERIAL_SUMO_BUFF(34), MATERIAL_FISH_BAIT(32),
MATERIAL_FIREWORKS(35), MATERIAL_FISH_ROD(33),
MATERIAL_BGM(36), MATERIAL_SUMO_BUFF(34),
MATERIAL_SPICE_FOOD(37), MATERIAL_FIREWORKS(35),
MATERIAL_ACTIVITY_ROBOT(38), MATERIAL_BGM(36),
MATERIAL_ACTIVITY_GEAR(39), MATERIAL_SPICE_FOOD(37),
MATERIAL_ACTIVITY_JIGSAW(40), MATERIAL_ACTIVITY_ROBOT(38),
MATERIAL_ARANARA(41), MATERIAL_ACTIVITY_GEAR(39),
MATERIAL_DESHRET_MANUAL(46); MATERIAL_ACTIVITY_JIGSAW(40),
MATERIAL_ARANARA(41),
private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>(); MATERIAL_DESHRET_MANUAL(46);
private static final Map<String, MaterialType> stringMap = new HashMap<>();
private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>();
static { private static final Map<String, MaterialType> stringMap = new HashMap<>();
Stream.of(values())
.forEach( static {
e -> { Stream.of(values())
map.put(e.getValue(), e); .forEach(
stringMap.put(e.name(), e); e -> {
}); map.put(e.getValue(), e);
} stringMap.put(e.name(), e);
});
private final int value; }
MaterialType(int value) { @Getter private final int value;
this.value = value;
} MaterialType(int value) {
this.value = value;
public static MaterialType getTypeByValue(int value) { }
return map.getOrDefault(value, MATERIAL_NONE);
} public static MaterialType getTypeByValue(int value) {
return map.getOrDefault(value, MATERIAL_NONE);
public static MaterialType getTypeByName(String name) { }
return stringMap.getOrDefault(name, MATERIAL_NONE);
} public static MaterialType getTypeByName(String name) {
return stringMap.getOrDefault(name, MATERIAL_NONE);
public int getValue() { }
return value; }
}
}

View File

@@ -1,243 +1,244 @@
package emu.grasscutter.game.managers.blossom; package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.RewardPreviewData; import emu.grasscutter.data.excels.RewardPreviewData;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.game.entity.gadget.GadgetWorktop;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.net.proto.BlossomBriefInfoOuterClass; import emu.grasscutter.net.proto.BlossomBriefInfoOuterClass;
import emu.grasscutter.net.proto.VisionTypeOuterClass; import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.packet.send.PacketBlossomBriefInfoNotify; import emu.grasscutter.server.packet.send.PacketBlossomBriefInfoNotify;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BlossomManager { public class BlossomManager {
private final Scene scene; private final Scene scene;
private final List<BlossomActivity> blossomActivities = new ArrayList<>(); private final List<BlossomActivity> blossomActivities = new ArrayList<>();
private final List<BlossomActivity> activeChests = new ArrayList<>(); private final List<BlossomActivity> activeChests = new ArrayList<>();
private final List<EntityGadget> createdEntity = new ArrayList<>(); private final List<EntityGadget> createdEntity = new ArrayList<>();
private final List<SpawnDataEntry> blossomConsumed = new ArrayList<>();
private final List<SpawnDataEntry> blossomConsumed = new ArrayList<>();
public BlossomManager(Scene scene) {
this.scene = scene; public BlossomManager(Scene scene) {
} this.scene = scene;
}
private static Integer getPreviewReward(BlossomType type, int worldLevel) {
// TODO: blossoms should be based on their city public void onTick() {
if (type == null) { synchronized (blossomActivities) {
Grasscutter.getLogger().error("Illegal blossom type {}", type); var it = blossomActivities.iterator();
return null; while (it.hasNext()) {
} var active = it.next();
active.onTick();
int blossomChestId = type.getBlossomChestId(); if (active.getPass()) {
var dataMap = GameData.getBlossomRefreshExcelConfigDataMap(); EntityGadget chest = active.getChest();
for (var data : dataMap.values()) { scene.addEntity(chest);
if (blossomChestId == data.getBlossomChestId()) { scene.setChallenge(null);
var dropVecList = data.getDropVec(); activeChests.add(active);
if (worldLevel > dropVecList.length) { it.remove();
Grasscutter.getLogger().error("Illegal world level {}", worldLevel); }
return null; }
} }
return dropVecList[worldLevel].getPreviewReward(); }
}
} public void recycleGadgetEntity(List<GameEntity> entities) {
Grasscutter.getLogger().error("Cannot find blossom type {}", type); for (var entity : entities) {
return null; if (entity instanceof EntityGadget gadget) {
} createdEntity.remove(gadget);
}
private static RewardPreviewData getRewardList(BlossomType type, int worldLevel) { }
Integer previewReward = getPreviewReward(type, worldLevel); notifyIcon();
if (previewReward == null) return null; }
return GameData.getRewardPreviewDataMap().get((int) previewReward);
} public void initBlossom(EntityGadget gadget) {
if (createdEntity.contains(gadget)) {
public static IntList getRandomMonstersID(int difficulty, int count) { return;
IntList result = new IntArrayList(); }
List<Integer> monsters = if (blossomConsumed.contains(gadget.getSpawnEntry())) {
GameDepot.getBlossomConfig().getMonsterIdsPerDifficulty().get(difficulty); return;
for (int i = 0; i < count; i++) { }
result.add((int) monsters.get(Utils.randomRange(0, monsters.size() - 1))); var id = gadget.getGadgetId();
} if (BlossomType.valueOf(id) == null) {
return result; return;
} }
gadget.buildContent();
public void onTick() { gadget.setState(204);
synchronized (blossomActivities) { int worldLevel = getWorldLevel();
var it = blossomActivities.iterator(); GadgetWorktop gadgetWorktop = ((GadgetWorktop) gadget.getContent());
while (it.hasNext()) { gadgetWorktop.addWorktopOptions(new int[] {187});
var active = it.next(); gadgetWorktop.setOnSelectWorktopOptionEvent(
active.onTick(); (GadgetWorktop context, int option) -> {
if (active.getPass()) { BlossomActivity activity;
EntityGadget chest = active.getChest(); EntityGadget entityGadget = context.getGadget();
scene.addEntity(chest); synchronized (blossomActivities) {
scene.setChallenge(null); for (BlossomActivity i : this.blossomActivities) {
activeChests.add(active); if (i.getGadget() == entityGadget) {
it.remove(); return false;
} }
} }
}
} int volume = 0;
IntList monsters = new IntArrayList();
public void recycleGadgetEntity(List<GameEntity> entities) { while (true) {
for (var entity : entities) { var remain = GameDepot.getBlossomConfig().getMonsterFightingVolume() - volume;
if (entity instanceof EntityGadget gadget) { if (remain <= 0) {
createdEntity.remove(gadget); break;
} }
} var rand = Utils.randomRange(1, 100);
notifyIcon(); if (rand > 85 && remain >= 50) { // 15% ,generate strong monster
} monsters.addAll(getRandomMonstersID(2, 1));
volume += 50;
public void initBlossom(EntityGadget gadget) { } else if (rand > 50 && remain >= 20) { // 35% ,generate normal monster
if (createdEntity.contains(gadget)) { monsters.addAll(getRandomMonstersID(1, 1));
return; volume += 20;
} } else { // 50% ,generate weak monster
if (blossomConsumed.contains(gadget.getSpawnEntry())) { monsters.addAll(getRandomMonstersID(0, 1));
return; volume += 10;
} }
var id = gadget.getGadgetId(); }
if (BlossomType.valueOf(id) == null) {
return; Grasscutter.getLogger().info("Blossom Monsters:" + monsters);
}
gadget.buildContent(); activity = new BlossomActivity(entityGadget, monsters, -1, worldLevel);
gadget.setState(204); blossomActivities.add(activity);
int worldLevel = getWorldLevel(); }
GadgetWorktop gadgetWorktop = ((GadgetWorktop) gadget.getContent()); entityGadget.updateState(201);
gadgetWorktop.addWorktopOptions(new int[] {187}); scene.setChallenge(activity.getChallenge());
gadgetWorktop.setOnSelectWorktopOptionEvent( scene.removeEntity(entityGadget, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
(GadgetWorktop context, int option) -> { activity.start();
BlossomActivity activity; return true;
EntityGadget entityGadget = context.getGadget(); });
synchronized (blossomActivities) { createdEntity.add(gadget);
for (BlossomActivity i : this.blossomActivities) { notifyIcon();
if (i.getGadget() == entityGadget) { }
return false;
} public void notifyIcon() {
} final int wl = getWorldLevel();
final int worldLevel = (wl < 0) ? 0 : ((wl > 8) ? 8 : wl);
int volume = 0; final var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
IntList monsters = new IntArrayList(); final int monsterLevel = (worldLevelData != null) ? worldLevelData.getMonsterLevel() : 1;
while (true) { List<BlossomBriefInfoOuterClass.BlossomBriefInfo> blossoms = new ArrayList<>();
var remain = GameDepot.getBlossomConfig().getMonsterFightingVolume() - volume; GameDepot.getSpawnLists()
if (remain <= 0) { .forEach(
break; (gridBlockId, spawnDataEntryList) -> {
} int sceneId = gridBlockId.getSceneId();
var rand = Utils.randomRange(1, 100); spawnDataEntryList.stream()
if (rand > 85 && remain >= 50) { // 15% ,generate strong monster .map(SpawnDataEntry::getGroup)
monsters.addAll(getRandomMonstersID(2, 1)); .map(SpawnGroupEntry::getSpawns)
volume += 50; .flatMap(List::stream)
} else if (rand > 50 && remain >= 20) { // 35% ,generate normal monster .filter(spawn -> !blossomConsumed.contains(spawn))
monsters.addAll(getRandomMonstersID(1, 1)); .filter(spawn -> BlossomType.valueOf(spawn.getGadgetId()) != null)
volume += 20; .forEach(
} else { // 50% ,generate weak monster spawn -> {
monsters.addAll(getRandomMonstersID(0, 1)); var type = BlossomType.valueOf(spawn.getGadgetId());
volume += 10; int previewReward = getPreviewReward(type, worldLevel);
} blossoms.add(
} BlossomBriefInfoOuterClass.BlossomBriefInfo.newBuilder()
.setSceneId(sceneId)
Grasscutter.getLogger().info("Blossom Monsters:" + monsters); .setPos(spawn.getPos().toProto())
.setResin(20)
activity = new BlossomActivity(entityGadget, monsters, -1, worldLevel); .setMonsterLevel(monsterLevel)
blossomActivities.add(activity); .setRewardId(previewReward)
} .setCircleCampId(type.getCircleCampId())
entityGadget.updateState(201); .setRefreshId(
scene.setChallenge(activity.getChallenge()); type.getBlossomChestId()) // TODO: replace when using actual
scene.removeEntity(entityGadget, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE); // leylines
activity.start(); .build());
return true; });
}); });
createdEntity.add(gadget); scene.broadcastPacket(new PacketBlossomBriefInfoNotify(blossoms));
notifyIcon(); }
}
public int getWorldLevel() {
public void notifyIcon() { return scene.getWorld().getWorldLevel();
final int wl = getWorldLevel(); }
final int worldLevel = (wl < 0) ? 0 : ((wl > 8) ? 8 : wl);
final var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel); private static Integer getPreviewReward(BlossomType type, int worldLevel) {
final int monsterLevel = (worldLevelData != null) ? worldLevelData.getMonsterLevel() : 1; // TODO: blossoms should be based on their city
List<BlossomBriefInfoOuterClass.BlossomBriefInfo> blossoms = new ArrayList<>(); if (type == null) {
GameDepot.getSpawnLists() Grasscutter.getLogger().error("Illegal blossom type {}", type);
.forEach( return null;
(gridBlockId, spawnDataEntryList) -> { }
int sceneId = gridBlockId.getSceneId();
spawnDataEntryList.stream() int blossomChestId = type.getBlossomChestId();
.map(SpawnDataEntry::getGroup) var dataMap = GameData.getBlossomRefreshExcelConfigDataMap();
.map(SpawnGroupEntry::getSpawns) for (var data : dataMap.values()) {
.flatMap(List::stream) if (blossomChestId == data.getBlossomChestId()) {
.filter(spawn -> !blossomConsumed.contains(spawn)) var dropVecList = data.getDropVec();
.filter(spawn -> BlossomType.valueOf(spawn.getGadgetId()) != null) if (worldLevel > dropVecList.length) {
.forEach( Grasscutter.getLogger().error("Illegal world level {}", worldLevel);
spawn -> { return null;
var type = BlossomType.valueOf(spawn.getGadgetId()); }
int previewReward = getPreviewReward(type, worldLevel); return dropVecList[worldLevel].getPreviewReward();
blossoms.add( }
BlossomBriefInfoOuterClass.BlossomBriefInfo.newBuilder() }
.setSceneId(sceneId) Grasscutter.getLogger().error("Cannot find blossom type {}", type);
.setPos(spawn.getPos().toProto()) return null;
.setResin(20) }
.setMonsterLevel(monsterLevel)
.setRewardId(previewReward) private static RewardPreviewData getRewardList(BlossomType type, int worldLevel) {
.setCircleCampId(type.getCircleCampId()) Integer previewReward = getPreviewReward(type, worldLevel);
.setRefreshId( if (previewReward == null) return null;
type.getBlossomChestId()) // TODO: replace when using actual return GameData.getRewardPreviewDataMap().get((int) previewReward);
// leylines }
.build());
}); public List<GameItem> onReward(Player player, EntityGadget chest, boolean useCondensedResin) {
}); var resinManager = player.getResinManager();
scene.broadcastPacket(new PacketBlossomBriefInfoNotify(blossoms)); synchronized (activeChests) {
} var it = activeChests.iterator();
while (it.hasNext()) {
public int getWorldLevel() { var activeChest = it.next();
return scene.getWorld().getWorldLevel(); if (activeChest.getChest() == chest) {
} boolean pay =
useCondensedResin ? resinManager.useCondensedResin(1) : resinManager.useResin(20);
public List<GameItem> onReward(Player player, EntityGadget chest, boolean useCondensedResin) { if (pay) {
var resinManager = player.getResinManager(); int worldLevel = getWorldLevel();
synchronized (activeChests) { List<GameItem> items = new ArrayList<>();
var it = activeChests.iterator(); var gadget = activeChest.getGadget();
while (it.hasNext()) { var type = BlossomType.valueOf(gadget.getGadgetId());
var activeChest = it.next(); RewardPreviewData blossomRewards = getRewardList(type, worldLevel);
if (activeChest.getChest() == chest) { if (blossomRewards == null) {
boolean pay = Grasscutter.getLogger()
useCondensedResin ? resinManager.useCondensedResin(1) : resinManager.useResin(20); .error("Blossom could not support world level : " + worldLevel);
if (pay) { return null;
int worldLevel = getWorldLevel(); }
List<GameItem> items = new ArrayList<>(); var rewards = blossomRewards.getPreviewItems();
var gadget = activeChest.getGadget(); for (ItemParamData blossomReward : rewards) {
var type = BlossomType.valueOf(gadget.getGadgetId()); int rewardCount = blossomReward.getCount();
RewardPreviewData blossomRewards = getRewardList(type, worldLevel); if (useCondensedResin) {
if (blossomRewards == null) { rewardCount += blossomReward.getCount(); // Double!
Grasscutter.getLogger() }
.error("Blossom could not support world level : " + worldLevel); items.add(new GameItem(blossomReward.getItemId(), rewardCount));
return null; }
} it.remove();
var rewards = blossomRewards.getPreviewItems(); recycleGadgetEntity(List.of(gadget));
for (ItemParamData blossomReward : rewards) { blossomConsumed.add(gadget.getSpawnEntry());
int rewardCount = blossomReward.getCount(); return items;
if (useCondensedResin) { }
rewardCount += blossomReward.getCount(); // Double! return null;
} }
items.add(new GameItem(blossomReward.getItemId(), rewardCount)); }
} }
it.remove(); return null;
recycleGadgetEntity(List.of(gadget)); }
blossomConsumed.add(gadget.getSpawnEntry());
return items; public static IntList getRandomMonstersID(int difficulty, int count) {
} IntList result = new IntArrayList();
return null; List<Integer> monsters =
} GameDepot.getBlossomConfig().getMonsterIdsPerDifficulty().get(difficulty);
} for (int i = 0; i < count; i++) {
} result.add((int) monsters.get(Utils.randomRange(0, monsters.size() - 1)));
return null; }
} return result;
} }
}

View File

@@ -1,163 +1,163 @@
package emu.grasscutter.game.managers.cooking; package emu.grasscutter.game.managers.cooking;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CompoundData; import emu.grasscutter.data.excels.CompoundData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager; import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.CompoundQueueDataOuterClass.CompoundQueueData; import emu.grasscutter.net.proto.CompoundQueueDataOuterClass.CompoundQueueData;
import emu.grasscutter.net.proto.GetCompoundDataReqOuterClass.GetCompoundDataReq; import emu.grasscutter.net.proto.GetCompoundDataReqOuterClass.GetCompoundDataReq;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.PlayerCompoundMaterialReqOuterClass.PlayerCompoundMaterialReq; import emu.grasscutter.net.proto.PlayerCompoundMaterialReqOuterClass.PlayerCompoundMaterialReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.TakeCompoundOutputReqOuterClass.TakeCompoundOutputReq; import emu.grasscutter.net.proto.TakeCompoundOutputReqOuterClass.TakeCompoundOutputReq;
import emu.grasscutter.server.packet.send.PackageTakeCompoundOutputRsp; import emu.grasscutter.server.packet.send.PackageTakeCompoundOutputRsp;
import emu.grasscutter.server.packet.send.PacketCompoundDataNotify; import emu.grasscutter.server.packet.send.PacketCompoundDataNotify;
import emu.grasscutter.server.packet.send.PacketGetCompoundDataRsp; import emu.grasscutter.server.packet.send.PacketGetCompoundDataRsp;
import emu.grasscutter.server.packet.send.PacketPlayerCompoundMaterialRsp; import emu.grasscutter.server.packet.send.PacketPlayerCompoundMaterialRsp;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import java.util.*; import java.util.*;
public class CookingCompoundManager extends BasePlayerManager { public class CookingCompoundManager extends BasePlayerManager {
private static Set<Integer> defaultUnlockedCompounds; private static Set<Integer> defaultUnlockedCompounds;
private static Map<Integer, Set<Integer>> compoundGroups; private static Map<Integer, Set<Integer>> compoundGroups;
// TODO:bind it to player // TODO:bind it to player
private static Set<Integer> unlocked; private static Set<Integer> unlocked;
public CookingCompoundManager(Player player) { public CookingCompoundManager(Player player) {
super(player); super(player);
} }
public static void initialize() { public static void initialize() {
defaultUnlockedCompounds = new HashSet<>(); defaultUnlockedCompounds = new HashSet<>();
compoundGroups = new HashMap<>(); compoundGroups = new HashMap<>();
GameData.getCompoundDataMap() GameData.getCompoundDataMap()
.forEach( .forEach(
(id, compound) -> { (id, compound) -> {
if (compound.isDefaultUnlocked()) { if (compound.isDefaultUnlocked()) {
defaultUnlockedCompounds.add(id); defaultUnlockedCompounds.add(id);
} }
compoundGroups.computeIfAbsent(compound.getGroupID(), gid -> new HashSet<>()).add(id); compoundGroups.computeIfAbsent(compound.getGroupId(), gid -> new HashSet<>()).add(id);
}); });
// TODO:Because we haven't implemented fishing feature,unlock all compounds related to // TODO:Because we haven't implemented fishing feature,unlock all compounds related to
// fish.Besides,it should be bound to player rather than manager. // fish.Besides,it should be bound to player rather than manager.
unlocked = new HashSet<>(defaultUnlockedCompounds); unlocked = new HashSet<>(defaultUnlockedCompounds);
if (compoundGroups.containsKey(3)) // Avoid NPE from Resources error if (compoundGroups.containsKey(3)) // Avoid NPE from Resources error
unlocked.addAll(compoundGroups.get(3)); unlocked.addAll(compoundGroups.get(3));
} }
private synchronized List<CompoundQueueData> getCompoundQueueData() { private synchronized List<CompoundQueueData> getCompoundQueueData() {
List<CompoundQueueData> compoundQueueData = List<CompoundQueueData> compoundQueueData =
new ArrayList<>(player.getActiveCookCompounds().size()); new ArrayList<>(player.getActiveCookCompounds().size());
int currentTime = Utils.getCurrentSeconds(); int currentTime = Utils.getCurrentSeconds();
for (var item : player.getActiveCookCompounds().values()) { for (var item : player.getActiveCookCompounds().values()) {
var data = var data =
CompoundQueueData.newBuilder() CompoundQueueData.newBuilder()
.setCompoundId(item.getCompoundId()) .setCompoundId(item.getCompoundId())
.setOutputCount(item.getOutputCount(currentTime)) .setOutputCount(item.getOutputCount(currentTime))
.setOutputTime(item.getOutputTime(currentTime)) .setOutputTime(item.getOutputTime(currentTime))
.setWaitCount(item.getWaitCount(currentTime)) .setWaitCount(item.getWaitCount(currentTime))
.build(); .build();
compoundQueueData.add(data); compoundQueueData.add(data);
} }
return compoundQueueData; return compoundQueueData;
} }
public synchronized void handleGetCompoundDataReq(GetCompoundDataReq req) { public synchronized void handleGetCompoundDataReq(GetCompoundDataReq req) {
player.sendPacket(new PacketGetCompoundDataRsp(unlocked, getCompoundQueueData())); player.sendPacket(new PacketGetCompoundDataRsp(unlocked, getCompoundQueueData()));
} }
public synchronized void handlePlayerCompoundMaterialReq(PlayerCompoundMaterialReq req) { public synchronized void handlePlayerCompoundMaterialReq(PlayerCompoundMaterialReq req) {
int id = req.getCompoundId(), count = req.getCount(); int id = req.getCompoundId(), count = req.getCount();
CompoundData compound = GameData.getCompoundDataMap().get(id); CompoundData compound = GameData.getCompoundDataMap().get(id);
var activeCompounds = player.getActiveCookCompounds(); var activeCompounds = player.getActiveCookCompounds();
// check whether the compound is available // check whether the compound is available
// TODO:add other compounds,see my pr for detail // TODO:add other compounds,see my pr for detail
if (!unlocked.contains(id)) { if (!unlocked.contains(id)) {
player.sendPacket(new PacketPlayerCompoundMaterialRsp(Retcode.RET_FAIL_VALUE)); player.sendPacket(new PacketPlayerCompoundMaterialRsp(Retcode.RET_FAIL_VALUE));
return; return;
} }
// check whether the queue is full // check whether the queue is full
if (activeCompounds.containsKey(id) if (activeCompounds.containsKey(id)
&& activeCompounds.get(id).getTotalCount() + count > compound.getQueueSize()) { && activeCompounds.get(id).getTotalCount() + count > compound.getQueueSize()) {
player.sendPacket(new PacketPlayerCompoundMaterialRsp(Retcode.RET_COMPOUND_QUEUE_FULL_VALUE)); player.sendPacket(new PacketPlayerCompoundMaterialRsp(Retcode.RET_COMPOUND_QUEUE_FULL_VALUE));
return; return;
} }
// try to consume raw materials // try to consume raw materials
if (!player.getInventory().payItems(compound.getInputVec(), count)) { if (!player.getInventory().payItems(compound.getInputVec(), count)) {
// TODO:I'm not sure whether retcode is correct. // TODO:I'm not sure whether retcode is correct.
player.sendPacket( player.sendPacket(
new PacketPlayerCompoundMaterialRsp(Retcode.RET_ITEM_COUNT_NOT_ENOUGH_VALUE)); new PacketPlayerCompoundMaterialRsp(Retcode.RET_ITEM_COUNT_NOT_ENOUGH_VALUE));
return; return;
} }
ActiveCookCompoundData c; ActiveCookCompoundData c;
int currentTime = Utils.getCurrentSeconds(); int currentTime = Utils.getCurrentSeconds();
if (activeCompounds.containsKey(id)) { if (activeCompounds.containsKey(id)) {
c = activeCompounds.get(id); c = activeCompounds.get(id);
c.addCompound(count, currentTime); c.addCompound(count, currentTime);
} else { } else {
c = new ActiveCookCompoundData(id, compound.getCostTime(), count, currentTime); c = new ActiveCookCompoundData(id, compound.getCostTime(), count, currentTime);
activeCompounds.put(id, c); activeCompounds.put(id, c);
} }
var data = var data =
CompoundQueueData.newBuilder() CompoundQueueData.newBuilder()
.setCompoundId(id) .setCompoundId(id)
.setOutputCount(c.getOutputCount(currentTime)) .setOutputCount(c.getOutputCount(currentTime))
.setOutputTime(c.getOutputTime(currentTime)) .setOutputTime(c.getOutputTime(currentTime))
.setWaitCount(c.getWaitCount(currentTime)) .setWaitCount(c.getWaitCount(currentTime))
.build(); .build();
player.sendPacket(new PacketPlayerCompoundMaterialRsp(data)); player.sendPacket(new PacketPlayerCompoundMaterialRsp(data));
} }
public synchronized void handleTakeCompoundOutputReq(TakeCompoundOutputReq req) { public synchronized void handleTakeCompoundOutputReq(TakeCompoundOutputReq req) {
// Client won't set compound_id and will set group_id instead. // Client won't set compound_id and will set group_id instead.
int groupId = req.getCompoundGroupId(); int groupId = req.getCompoundGroupId();
var activeCompounds = player.getActiveCookCompounds(); var activeCompounds = player.getActiveCookCompounds();
int now = Utils.getCurrentSeconds(); int now = Utils.getCurrentSeconds();
// check available queues // check available queues
boolean success = false; boolean success = false;
Map<Integer, GameItem> allRewards = new HashMap<>(); Map<Integer, GameItem> allRewards = new HashMap<>();
for (int id : compoundGroups.get(groupId)) { for (int id : compoundGroups.get(groupId)) {
if (!activeCompounds.containsKey(id)) continue; if (!activeCompounds.containsKey(id)) continue;
int quantity = activeCompounds.get(id).takeCompound(now); int quantity = activeCompounds.get(id).takeCompound(now);
if (activeCompounds.get(id).getTotalCount() == 0) activeCompounds.remove(id); if (activeCompounds.get(id).getTotalCount() == 0) activeCompounds.remove(id);
if (quantity == 0) continue; if (quantity == 0) continue;
List<ItemParamData> rewards = GameData.getCompoundDataMap().get(id).getOutputVec(); List<ItemParamData> rewards = GameData.getCompoundDataMap().get(id).getOutputVec();
for (var i : rewards) { for (var i : rewards) {
if (i.getId() == 0) continue; if (i.getId() == 0) continue;
if (allRewards.containsKey(i.getId())) { if (allRewards.containsKey(i.getId())) {
GameItem item = allRewards.get(i.getId()); GameItem item = allRewards.get(i.getId());
item.setCount(item.getCount() + i.getCount() * quantity); item.setCount(item.getCount() + i.getCount() * quantity);
} else { } else {
allRewards.put(i.getId(), new GameItem(i.getId(), i.getCount() * quantity)); allRewards.put(i.getId(), new GameItem(i.getId(), i.getCount() * quantity));
} }
} }
success = true; success = true;
} }
// give player the rewards // give player the rewards
if (success) { if (success) {
player.getInventory().addItems(allRewards.values(), ActionReason.Compound); player.getInventory().addItems(allRewards.values(), ActionReason.Compound);
player.sendPacket( player.sendPacket(
new PackageTakeCompoundOutputRsp( new PackageTakeCompoundOutputRsp(
allRewards.values().stream() allRewards.values().stream()
.map( .map(
i -> i ->
ItemParam.newBuilder() ItemParam.newBuilder()
.setItemId(i.getItemId()) .setItemId(i.getItemId())
.setCount(i.getCount()) .setCount(i.getCount())
.build()) .build())
.toList(), .toList(),
Retcode.RET_SUCC_VALUE)); Retcode.RET_SUCC_VALUE));
} else { } else {
player.sendPacket( player.sendPacket(
new PackageTakeCompoundOutputRsp(null, Retcode.RET_COMPOUND_NOT_FINISH_VALUE)); new PackageTakeCompoundOutputRsp(null, Retcode.RET_COMPOUND_NOT_FINISH_VALUE));
} }
} }
public void onPlayerLogin() { public void onPlayerLogin() {
player.sendPacket(new PacketCompoundDataNotify(unlocked, getCompoundQueueData())); player.sendPacket(new PacketCompoundDataNotify(unlocked, getCompoundQueueData()));
} }
} }

View File

@@ -1,420 +1,418 @@
package emu.grasscutter.game.managers.energy; package emu.grasscutter.game.managers.energy;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS; import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.monster.MonsterData.HpDrops; import emu.grasscutter.data.excels.monster.MonsterData.HpDrops;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*; import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.BasePlayerManager; import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.MonsterType; import emu.grasscutter.game.props.MonsterType;
import emu.grasscutter.game.props.WeaponType; import emu.grasscutter.game.props.WeaponType;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier; import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason; import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import lombok.Getter; import lombok.Getter;
public class EnergyManager extends BasePlayerManager { public class EnergyManager extends BasePlayerManager {
private static final Int2ObjectMap<List<EnergyDropInfo>> energyDropData = private static final Int2ObjectMap<List<EnergyDropInfo>> energyDropData =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<List<SkillParticleGenerationInfo>> private static final Int2ObjectMap<List<SkillParticleGenerationInfo>>
skillParticleGenerationData = new Int2ObjectOpenHashMap<>(); skillParticleGenerationData = new Int2ObjectOpenHashMap<>();
private final Object2IntMap<EntityAvatar> avatarNormalProbabilities; private final Object2IntMap<EntityAvatar> avatarNormalProbabilities;
@Getter private boolean energyUsage; // Should energy usage be enabled for this player? @Getter private boolean energyUsage; // Should energy usage be enabled for this player?
public EnergyManager(Player player) { public EnergyManager(Player player) {
super(player); super(player);
this.avatarNormalProbabilities = new Object2IntOpenHashMap<>(); this.avatarNormalProbabilities = new Object2IntOpenHashMap<>();
this.energyUsage = GAME_OPTIONS.energyUsage; this.energyUsage = GAME_OPTIONS.energyUsage;
} }
public static void initialize() { public static void initialize() {
// Read the data we need for monster energy drops. // Read the data we need for monster energy drops.
try { try {
DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class) DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class)
.forEach( .forEach(
entry -> { entry -> {
energyDropData.put(entry.getDropId(), entry.getDropList()); energyDropData.put(entry.getDropId(), entry.getDropList());
}); });
Grasscutter.getLogger().debug("Energy drop data successfully loaded."); Grasscutter.getLogger().debug("Energy drop data successfully loaded.");
} catch (Exception ex) { } catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load energy drop data.", ex); Grasscutter.getLogger().error("Unable to load energy drop data.", ex);
} }
// Read the data for particle generation from skills // Read the data for particle generation from skills
try { try {
DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class) DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class)
.forEach( .forEach(
entry -> { entry -> {
skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList()); skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList());
}); });
Grasscutter.getLogger().debug("Skill particle generation data successfully loaded."); Grasscutter.getLogger().debug("Skill particle generation data successfully loaded.");
} catch (Exception ex) { } catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex); Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex);
} }
} }
/** Particle creation for elemental skills. */ /** Particle creation for elemental skills. */
private int getBallCountForAvatar(int avatarId) { private int getBallCountForAvatar(int avatarId) {
// We default to two particles. // We default to two particles.
int count = 2; int count = 2;
// If we don't have any data for this avatar, stop. // If we don't have any data for this avatar, stop.
if (!skillParticleGenerationData.containsKey(avatarId)) { if (!skillParticleGenerationData.containsKey(avatarId)) {
Grasscutter.getLogger().warn("No particle generation data for avatarId {} found.", 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. // If we do have data, roll for how many particles we should generate.
else { else {
int roll = ThreadLocalRandom.current().nextInt(0, 100); int roll = ThreadLocalRandom.current().nextInt(0, 100);
int percentageStack = 0; int percentageStack = 0;
for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) { for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) {
int chance = info.getChance(); int chance = info.getChance();
percentageStack += chance; percentageStack += chance;
if (roll < percentageStack) { if (roll < percentageStack) {
count = info.getValue(); count = info.getValue();
break; break;
} }
} }
} }
// Done. // Done.
return count; return count;
} }
private int getBallIdForElement(ElementType element) { private int getBallIdForElement(ElementType element) {
// If we have no element, we default to an element-less particle. // If we have no element, we default to an element-less particle.
if (element == null) { if (element == null) {
return 2024; return 2024;
} }
// Otherwise, we determine the particle's ID based on the element. // Otherwise, we determine the particle's ID based on the element.
return switch (element) { return switch (element) {
case Fire -> 2017; case Fire -> 2017;
case Water -> 2018; case Water -> 2018;
case Grass -> 2019; case Grass -> 2019;
case Electric -> 2020; case Electric -> 2020;
case Wind -> 2021; case Wind -> 2021;
case Ice -> 2022; case Ice -> 2022;
case Rock -> 2023; case Rock -> 2023;
default -> 2024; default -> 2024;
}; };
} }
public void handleGenerateElemBall(AbilityInvokeEntry invoke) public void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException { throws InvalidProtocolBufferException {
// ToDo: // ToDo:
// This is also called when a weapon like Favonius Warbow etc. creates energy through its // This is also called when a weapon like Favonius Warbow etc. creates energy through its
// passive. // passive.
// We are not handling this correctly at the moment. // We are not handling this correctly at the moment.
// Get action info. // Get action info.
AbilityActionGenerateElemBall action = AbilityActionGenerateElemBall action =
AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData()); AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData());
if (action == null) { if (action == null) {
return; return;
} }
// Default to an elementless particle. // Default to an elementless particle.
int itemId = 2024; int itemId = 2024;
// Generate 2 particles by default. // Generate 2 particles by default.
int amount = 2; int amount = 2;
// Try to get the casting avatar from the player's party. // Try to get the casting avatar from the player's party.
Optional<EntityAvatar> avatarEntity = Optional<EntityAvatar> avatarEntity =
this.getCastingAvatarEntityForEnergy(invoke.getEntityId()); this.getCastingAvatarEntityForEnergy(invoke.getEntityId());
// Bug: invokes twice sometimes, Ayato, Keqing // Bug: invokes twice sometimes, Ayato, Keqing
// ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin) // ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin)
if (avatarEntity.isPresent()) { if (avatarEntity.isPresent()) {
Avatar avatar = avatarEntity.get().getAvatar(); Avatar avatar = avatarEntity.get().getAvatar();
if (avatar != null) { if (avatar != null) {
int avatarId = avatar.getAvatarId(); int avatarId = avatar.getAvatarId();
AvatarSkillDepotData skillDepotData = avatar.getSkillDepot(); AvatarSkillDepotData skillDepotData = avatar.getSkillDepot();
// Determine how many particles we need to create for this avatar. // Determine how many particles we need to create for this avatar.
amount = this.getBallCountForAvatar(avatarId); amount = this.getBallCountForAvatar(avatarId);
// Determine the avatar's element, and based on that the ID of the // Determine the avatar's element, and based on that the ID of the
// particles we have to generate. // particles we have to generate.
if (skillDepotData != null) { if (skillDepotData != null) {
ElementType element = skillDepotData.getElementType(); ElementType element = skillDepotData.getElementType();
itemId = this.getBallIdForElement(element); itemId = this.getBallIdForElement(element);
} }
} }
} }
// Generate the particles. // Generate the particles.
var pos = new Position(action.getPos()); var pos = new Position(action.getPos());
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
this.generateElemBall(itemId, pos, 1); this.generateElemBall(itemId, pos, 1);
} }
} }
/** /**
* Energy generation for NAs/CAs. * Energy generation for NAs/CAs.
* *
* @param avatar The avatar. * @param avatar The avatar.
*/ */
private void generateEnergyForNormalAndCharged(EntityAvatar avatar) { private void generateEnergyForNormalAndCharged(EntityAvatar avatar) {
// This logic is based on the descriptions given in // This logic is based on the descriptions given in
// https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks // https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks
// https://library.keqingmains.com/combat-mechanics/energy#auto-attacking // https://library.keqingmains.com/combat-mechanics/energy#auto-attacking
// Those descriptions are lacking in some information, so this implementation most likely // 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 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 after some time?
// - Does the probability for a character reset when switching them out? // - Does the probability for a character reset when switching them out?
// - Does this really count every individual hit separately? // - Does this really count every individual hit separately?
// Get the avatar's weapon type. // Get the avatar's weapon type.
WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType(); WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType();
// Check if we already have probability data for this avatar. If not, insert it. // Check if we already have probability data for this avatar. If not, insert it.
if (!this.avatarNormalProbabilities.containsKey(avatar)) { if (!this.avatarNormalProbabilities.containsKey(avatar)) {
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
} }
// Roll for energy. // Roll for energy.
int currentProbability = this.avatarNormalProbabilities.getInt(avatar); int currentProbability = this.avatarNormalProbabilities.getInt(avatar);
int roll = ThreadLocalRandom.current().nextInt(0, 100); int roll = ThreadLocalRandom.current().nextInt(0, 100);
// If the player wins the roll, we increase the avatar's energy and reset the probability. // If the player wins the roll, we increase the avatar's energy and reset the probability.
if (roll < currentProbability) { if (roll < currentProbability) {
avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true); avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true);
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability()); this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
} }
// Otherwise, we increase the probability for the next hit. // Otherwise, we increase the probability for the next hit.
else { else {
this.avatarNormalProbabilities.put( this.avatarNormalProbabilities.put(
avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability()); avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability());
} }
} }
public void handleAttackHit(EvtBeingHitInfo hitInfo) { public void handleAttackHit(EvtBeingHitInfo hitInfo) {
// Get the attack result. // Get the attack result.
AttackResult attackRes = hitInfo.getAttackResult(); AttackResult attackRes = hitInfo.getAttackResult();
// Make sure the attack was performed by the currently active avatar. If not, we ignore the hit. // Make sure the attack was performed by the currently active avatar. If not, we ignore the hit.
Optional<EntityAvatar> attackerEntity = Optional<EntityAvatar> attackerEntity =
this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId()); this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId());
if (attackerEntity.isEmpty() if (attackerEntity.isEmpty()
|| this.player.getTeamManager().getCurrentAvatarEntity().getId() || this.player.getTeamManager().getCurrentAvatarEntity().getId()
!= attackerEntity.get().getId()) { != attackerEntity.get().getId()) {
return; return;
} }
// Make sure the target is an actual enemy. // Make sure the target is an actual enemy.
GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId()); GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId());
if (!(targetEntity instanceof EntityMonster targetMonster)) { if (!(targetEntity instanceof EntityMonster targetMonster)) {
return; return;
} }
MonsterType targetType = targetMonster.getMonsterData().getType(); MonsterType targetType = targetMonster.getMonsterData().getType();
if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) { if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) {
return; return;
} }
// Get the ability that caused this hit. // Get the ability that caused this hit.
AbilityIdentifier ability = attackRes.getAbilityIdentifier(); AbilityIdentifier ability = attackRes.getAbilityIdentifier();
// Make sure there is no actual "ability" associated with the hit. For now, this is how we // 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: // 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, // - Many character's charged attacks have an ability associated with them. This means that,
// for now, we don't identify charged attacks reliably. // for now, we don't identify charged attacks reliably.
// - There might also be some cases where we incorrectly identify something as a normal or // - There might also be some cases where we incorrectly identify something as a normal or
// charged attack that is not (Diluc's E?). // charged attack that is not (Diluc's E?).
// - Catalyst normal attacks have an ability, so we don't handle those for now. // - Catalyst normal attacks have an ability, so we don't handle those for now.
// ToDo: Fix all of that. // ToDo: Fix all of that.
if (ability != AbilityIdentifier.getDefaultInstance()) { if (ability != AbilityIdentifier.getDefaultInstance()) {
return; return;
} }
// Handle the energy generation. // Handle the energy generation.
this.generateEnergyForNormalAndCharged(attackerEntity.get()); this.generateEnergyForNormalAndCharged(attackerEntity.get());
} }
/* /*
* Energy logic related to using skills. * Energy logic related to using skills.
*/ */
private void handleBurstCast(Avatar avatar, int skillId) { private void handleBurstCast(Avatar avatar, int skillId) {
// Don't do anything if energy usage is disabled. // Don't do anything if energy usage is disabled.
if (!GAME_OPTIONS.energyUsage || !this.energyUsage) { if (!GAME_OPTIONS.energyUsage || !this.energyUsage) {
return; return;
} }
// If the cast skill was a burst, consume energy. // If the cast skill was a burst, consume energy.
if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) { if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START); avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
} }
} }
public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { 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. // Determine the entity that has cast the skill. Cancel if we can't find that avatar.
Optional<EntityAvatar> caster = Optional<EntityAvatar> caster =
this.player.getTeamManager().getActiveTeam().stream() this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == casterId) .filter(character -> character.getId() == casterId)
.findFirst(); .findFirst();
if (caster.isEmpty()) { if (caster.isEmpty()) {
return; return;
} }
Avatar avatar = caster.get().getAvatar(); Avatar avatar = caster.get().getAvatar();
// Handle elemental burst. // Handle elemental burst.
this.handleBurstCast(avatar, skillId); this.handleBurstCast(avatar, skillId);
} }
/* /*
* Monster energy drops. * Monster energy drops.
*/ */
private void generateElemBallDrops(EntityMonster monster, int dropId) { private void generateElemBallDrops(EntityMonster monster, int dropId) {
// Generate all drops specified for the given drop id. // Generate all drops specified for the given drop id.
if (!energyDropData.containsKey(dropId)) { if (!energyDropData.containsKey(dropId)) {
Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId); Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId);
return; return;
} }
for (EnergyDropInfo info : energyDropData.get(dropId)) { for (EnergyDropInfo info : energyDropData.get(dropId)) {
this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount()); this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount());
} }
} }
public void handleMonsterEnergyDrop( public void handleMonsterEnergyDrop(
EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) { EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) {
// Make sure this is actually a monster. // Make sure this is actually a monster.
// Note that some wildlife also has that type, like boars or birds. // Note that some wildlife also has that type, like boars or birds.
MonsterType type = monster.getMonsterData().getType(); MonsterType type = monster.getMonsterData().getType();
if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) { if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) {
return; return;
} }
// Calculate the HP thresholds for before and after the damage was taken. // Calculate the HP thresholds for before and after the damage was taken.
float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float thresholdBefore = hpBeforeDamage / maxHp; float thresholdBefore = hpBeforeDamage / maxHp;
float thresholdAfter = hpAfterDamage / maxHp; float thresholdAfter = hpAfterDamage / maxHp;
// Determine the thresholds the monster has passed, and generate drops based on that. // Determine the thresholds the monster has passed, and generate drops based on that.
for (HpDrops drop : monster.getMonsterData().getHpDrops()) { for (HpDrops drop : monster.getMonsterData().getHpDrops()) {
if (drop.getDropId() == 0) { if (drop.getDropId() == 0) {
continue; continue;
} }
float threshold = drop.getHpPercent() / 100.0f; float threshold = drop.getHpPercent() / 100.0f;
if (threshold < thresholdBefore && threshold >= thresholdAfter) { if (threshold < thresholdBefore && threshold >= thresholdAfter) {
this.generateElemBallDrops(monster, drop.getDropId()); this.generateElemBallDrops(monster, drop.getDropId());
} }
} }
// Handle kill drops. // Handle kill drops.
if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) { if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) {
this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId()); this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId());
} }
} }
/* /*
* Utilities. * Utilities.
*/ */
private void generateElemBall(int ballId, Position position, int count) { private void generateElemBall(int ballId, Position position, int count) {
// Generate a particle/orb with the specified parameters. // Generate a particle/orb with the specified parameters.
ItemData itemData = GameData.getItemDataMap().get(ballId); ItemData itemData = GameData.getItemDataMap().get(ballId);
if (itemData == null) { if (itemData == null) {
return; return;
} }
EntityItem energyBall = EntityItem energyBall =
new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count); new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count);
this.getPlayer().getScene().addEntity(energyBall); this.getPlayer().getScene().addEntity(energyBall);
} }
private Optional<EntityAvatar> getCastingAvatarEntityForEnergy(int invokeEntityId) { private Optional<EntityAvatar> getCastingAvatarEntityForEnergy(int invokeEntityId) {
// To determine the avatar that has cast the skill that caused the energy particle to be // To determine the avatar that has cast the skill that caused the energy particle to be
// generated, // generated,
// we have to look at the entity that has invoked the ability. This can either be that avatar // we have to look at the entity that has invoked the ability. This can either be that avatar
// directly, // directly,
// or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar // or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar
// that cast the skill. // that cast the skill.
// Try to get the invoking entity from the scene. // Try to get the invoking entity from the scene.
GameEntity entity = this.player.getScene().getEntityById(invokeEntityId); GameEntity entity = this.player.getScene().getEntityById(invokeEntityId);
// Determine the ID of the entity that originally cast this skill. If the scene entity is null, // 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 // 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 // (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 // particle being generated). If the scene entity is an `EntityClientGadget`, we need to find
// the // the
// ID of the original owner of that gadget. // ID of the original owner of that gadget.
int avatarEntityId = int avatarEntityId =
(!(entity instanceof EntityClientGadget)) (!(entity instanceof EntityClientGadget))
? invokeEntityId ? invokeEntityId
: ((EntityClientGadget) entity).getOriginalOwnerEntityId(); : ((EntityClientGadget) entity).getOriginalOwnerEntityId();
// Finally, find the avatar entity in the player's team. // Finally, find the avatar entity in the player's team.
return this.player.getTeamManager().getActiveTeam().stream() return this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == avatarEntityId) .filter(character -> character.getId() == avatarEntityId)
.findFirst(); .findFirst();
} }
/** /**
* Refills the energy of the active avatar. * Refills the energy of the active avatar.
* *
* @return True if the energy was refilled, false otherwise. * @return True if the energy was refilled, false otherwise.
*/ */
public boolean refillActiveEnergy() { public boolean refillActiveEnergy() {
var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity(); var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity();
return activeEntity.addEnergy( return activeEntity.addEnergy(
activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal()); activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal());
} }
/** /**
* Refills the energy of the entire team. * Refills the energy of the entire team.
* *
* @param changeReason The reason for the energy change. * @param changeReason The reason for the energy change.
* @param isFlat Whether the energy should be added as a flat value. * @param isFlat Whether the energy should be added as a flat value.
*/ */
public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) { public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) {
for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) { for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) {
// giving the exact amount read off the AvatarSkillData.json // giving the exact amount read off the AvatarSkillData.json
entityAvatar.addEnergy( entityAvatar.addEnergy(
entityAvatar.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal(), entityAvatar.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal(),
changeReason, changeReason,
isFlat); isFlat);
} }
} }
public void setEnergyUsage(boolean energyUsage) { public void setEnergyUsage(boolean energyUsage) {
this.energyUsage = energyUsage; this.energyUsage = energyUsage;
if (!energyUsage) { // Refill team energy if usage is disabled if (!energyUsage) { // Refill team energy if usage is disabled
for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) { this.refillTeamEnergy(PropChangeReason.PROP_CHANGE_REASON_GM, true);
entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true); }
} }
} }
}
}

View File

@@ -141,6 +141,7 @@ public class StaminaManager extends BasePlayerManager {
put(242301, 0.8f); put(242301, 0.8f);
put(542301, 0.8f); put(542301, 0.8f);
}}; }};
private final Logger logger = Grasscutter.getLogger(); private final Logger logger = Grasscutter.getLogger();
private final HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>(); private final HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>();
private final HashMap<String, AfterUpdateStaminaListener> afterUpdateStaminaListeners = new HashMap<>(); private final HashMap<String, AfterUpdateStaminaListener> afterUpdateStaminaListeners = new HashMap<>();
@@ -414,13 +415,7 @@ public class StaminaManager extends BasePlayerManager {
// Internal handler // Internal handler
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
if (currentState == motionState) { if (currentState == motionState) return;
if (motionState.equals(MotionState.MOTION_STATE_CLIMB_JUMP)) {
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true);
}
return;
}
switch (motionState) { switch (motionState) {
case MOTION_STATE_CLIMB -> case MOTION_STATE_CLIMB ->
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true);
@@ -440,6 +435,73 @@ public class StaminaManager extends BasePlayerManager {
updateStaminaRelative(session, consumption, true); updateStaminaRelative(session, consumption, true);
} }
private class SustainedStaminaHandler extends TimerTask {
public void run() {
boolean moving = isPlayerMoving();
int currentCharacterStamina = getCurrentCharacterStamina();
int maxCharacterStamina = getMaxCharacterStamina();
int currentVehicleStamina = getCurrentVehicleStamina();
int maxVehicleStamina = getMaxVehicleStamina();
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
logger.trace("Player moving: " + moving + ", stamina full: " +
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
boolean isCharacterStamina = true;
Consumption consumption;
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbConsumption();
} else if (MotionStatesCategorized.get("DASH").contains(currentState)) {
consumption = getDashConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlyConsumption();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = new Consumption(ConsumptionType.RUN);
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
consumption = getSkiffConsumption();
isCharacterStamina = false;
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = new Consumption(ConsumptionType.STANDBY);
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
consumption = getSwimConsumptions();
} else if (MotionStatesCategorized.get("WALK").contains(currentState)) {
consumption = new Consumption(ConsumptionType.WALK);
} else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) {
consumption = new Consumption();
} else if (MotionStatesCategorized.get("OTHER").contains(currentState)) {
consumption = getOtherConsumptions();
} else { // ignore
return;
}
if (consumption.amount < 0 && isCharacterStamina) {
// Do not apply reduction factor when recovering stamina
if (player.getTeamManager().getTeamResonances().contains(10301)) {
consumption.amount *= 0.85f;
}
}
// Delay 1 seconds before starts recovering stamina
if (consumption.amount != 0 && cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0
&& consumption.type != ConsumptionType.POWERED_FLY
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
if (staminaRecoverDelay < 5) {
// For others recover after 1 seconds (5 ticks) - as official server does.
staminaRecoverDelay++;
consumption.amount = 0;
logger.trace("Delaying recovery: " + staminaRecoverDelay);
}
}
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
}
}
previousState = currentState;
previousCoordinates = currentCoordinates.clone();
}
}
private void handleDrowning() { private void handleDrowning() {
// TODO: fix drowning waverider entity // TODO: fix drowning waverider entity
int stamina = getCurrentCharacterStamina(); int stamina = getCurrentCharacterStamina();
@@ -452,6 +514,10 @@ public class StaminaManager extends BasePlayerManager {
} }
} }
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private Consumption getFightConsumption(int skillCasting) { private Consumption getFightConsumption(int skillCasting) {
// Talent moving // Talent moving
if (TalentMovements.contains(skillCasting)) { if (TalentMovements.contains(skillCasting)) {
@@ -471,10 +537,6 @@ public class StaminaManager extends BasePlayerManager {
}; };
} }
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private Consumption getClimbConsumption() { private Consumption getClimbConsumption() {
Consumption consumption = new Consumption(); Consumption consumption = new Consumption();
if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) { if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) {
@@ -552,6 +614,8 @@ public class StaminaManager extends BasePlayerManager {
return new Consumption(); return new Consumption();
} }
// Reduction getter
private float getTalentCostReductionFactor(HashMap<Integer, Float> talentReductionMap) { private float getTalentCostReductionFactor(HashMap<Integer, Float> talentReductionMap) {
// All known talents reductions are not stackable // All known talents reductions are not stackable
float reduction = 1; float reduction = 1;
@@ -568,8 +632,6 @@ public class StaminaManager extends BasePlayerManager {
return reduction; return reduction;
} }
// Reduction getter
private float getFoodCostReductionFactor(HashMap<Integer, Float> foodReductionMap) { private float getFoodCostReductionFactor(HashMap<Integer, Float> foodReductionMap) {
// All known food reductions are not stackable // All known food reductions are not stackable
// TODO: Check consumed food (buff?) and return proper factor // TODO: Check consumed food (buff?) and return proper factor
@@ -633,76 +695,11 @@ public class StaminaManager extends BasePlayerManager {
private Consumption getSwordCost(int skillId) { private Consumption getSwordCost(int skillId) {
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000);
// Character specific handling // Character specific handling
if (skillId == 10421) { switch (skillId) {
consumption.amount = -2500; case 10421:
consumption.amount = -2500;
break;
} }
return consumption; return consumption;
} }
private class SustainedStaminaHandler extends TimerTask {
public void run() {
boolean moving = isPlayerMoving();
int currentCharacterStamina = getCurrentCharacterStamina();
int maxCharacterStamina = getMaxCharacterStamina();
int currentVehicleStamina = getCurrentVehicleStamina();
int maxVehicleStamina = getMaxVehicleStamina();
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
logger.trace("Player moving: " + moving + ", stamina full: " +
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
boolean isCharacterStamina = true;
Consumption consumption;
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbConsumption();
} else if (MotionStatesCategorized.get("DASH").contains(currentState)) {
consumption = getDashConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlyConsumption();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = new Consumption(ConsumptionType.RUN);
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
consumption = getSkiffConsumption();
isCharacterStamina = false;
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = new Consumption(ConsumptionType.STANDBY);
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
consumption = getSwimConsumptions();
} else if (MotionStatesCategorized.get("WALK").contains(currentState)) {
consumption = new Consumption(ConsumptionType.WALK);
} else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) {
consumption = new Consumption();
} else if (MotionStatesCategorized.get("OTHER").contains(currentState)) {
consumption = getOtherConsumptions();
} else { // ignore
return;
}
if (consumption.amount < 0 && isCharacterStamina) {
// Do not apply reduction factor when recovering stamina
if (player.getTeamManager().getTeamResonances().contains(10301)) {
consumption.amount *= 0.85f;
}
}
// Delay 1 seconds before starts recovering stamina
if (consumption.amount != 0 && cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0
&& consumption.type != ConsumptionType.POWERED_FLY
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
if (staminaRecoverDelay < 5) {
// For others recover after 1 seconds (5 ticks) - as official server does.
staminaRecoverDelay++;
consumption.amount = 0;
logger.trace("Delaying recovery: " + staminaRecoverDelay);
}
}
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
}
}
previousState = currentState;
previousCoordinates = currentCoordinates.clone();
}
}
} }

View File

@@ -1,276 +1,303 @@
package emu.grasscutter.game.player; package emu.grasscutter.game.player;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry; import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.OpenStateData; import emu.grasscutter.data.excels.OpenStateData;
import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType; import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestContent; import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import java.util.Set; import emu.grasscutter.scripts.data.ScriptArgs;
import java.util.stream.Collectors; import emu.grasscutter.server.packet.send.*;
import java.util.Set;
// @Entity import java.util.stream.Collectors;
public final class PlayerProgressManager extends BasePlayerDataManager {
/****************************************************************************************************************** import static emu.grasscutter.scripts.constants.EventType.EVENT_UNLOCK_TRANS_POINT;
******************************************************************************************************************
* OPEN STATES // @Entity
****************************************************************************************************************** public final class PlayerProgressManager extends BasePlayerDataManager {
*****************************************************************************************************************/ /******************************************************************************************************************
******************************************************************************************************************
// Set of open states that are never unlocked, whether they fulfill the conditions or not. * OPEN STATES
public static final Set<Integer> BLACKLIST_OPEN_STATES = ******************************************************************************************************************
Set.of( *****************************************************************************************************************/
48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as
// soon as quest unlocks are fully implemented. // Set of open states that are never unlocked, whether they fulfill the conditions or not.
); public static final Set<Integer> BLACKLIST_OPEN_STATES =
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in Set.of(
// `map`. 48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as
public static final Set<Integer> DEFAULT_OPEN_STATES = // soon as quest unlocks are fully implemented.
GameData.getOpenStateList().stream() );
.filter( // Set of open states that are set per default for all accounts. Can be overwritten by an entry in
s -> // `map`.
s.isDefaultState() // Actual default-opened states. public static final Set<Integer> DEFAULT_OPEN_STATES =
// All states whose unlock we don't handle correctly yet. GameData.getOpenStateList().stream()
|| (s.getCond().stream() .filter(
.filter( s ->
c -> s.isDefaultState() && !s.isAllowClientOpen() // Actual default-opened states.
c.getCondType() || ((s.getCond().size() == 1)
== OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) && (s.getCond().get(0).getCondType()
.count() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL)
== 0) && (s.getCond().get(0).getParam() == 1))
// Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a // All states whose unlock we don't handle correctly yet.
// working chat. || (s.getCond().stream()
|| s.getId() == 1) .anyMatch(
.filter( c ->
s -> c.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL
!BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist. || c.getCondType()
.map(s -> s.getId()) == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL))
.collect(Collectors.toSet()); // Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a
// working chat.
public PlayerProgressManager(Player player) { || s.getId() == 1)
super(player); .filter(
} s ->
!BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist.
/********** .map(OpenStateData::getId)
* Handler for player login. .collect(Collectors.toSet());
**********/
public void onPlayerLogin() { public PlayerProgressManager(Player player) {
// Try unlocking open states on player login. This handles accounts where unlock conditions were super(player);
// already met before certain open state unlocks were implemented. }
this.tryUnlockOpenStates(false);
/**********
// Send notify to the client. * Handler for player login.
player.getSession().send(new PacketOpenStateUpdateNotify(this.player)); **********/
public void onPlayerLogin() {
// Add statue quests if necessary. // Try unlocking open states on player login. This handles accounts where unlock conditions were
this.addStatueQuestsOnLogin(); // already met before certain open state unlocks were implemented.
this.tryUnlockOpenStates(false);
// Auto-unlock the first statue and map area, until we figure out how to make
// that particular statue interactable. // Send notify to the client.
this.player.getUnlockedScenePoints(3).add(7); player.getSession().send(new PacketOpenStateUpdateNotify(this.player));
this.player.getUnlockedSceneAreas(3).add(1);
} // Add statue quests if necessary.
this.addStatueQuestsOnLogin();
/**********
* Direct getters and setters for open states. // Auto-unlock the first statue and map area, until we figure out how to make
**********/ // that particular statue interactable.
public int getOpenState(int openState) { this.player.getUnlockedScenePoints(3).add(7);
return this.player.getOpenStates().getOrDefault(openState, 0); this.player.getUnlockedSceneAreas(3).add(1);
} }
private void setOpenState(int openState, int value, boolean sendNotify) { /**********
int previousValue = this.player.getOpenStates().getOrDefault(openState, 0); * Direct getters and setters for open states.
**********/
if (value != previousValue) { public int getOpenState(int openState) {
this.player.getOpenStates().put(openState, value); return this.player.getOpenStates().getOrDefault(openState, 0);
}
if (sendNotify) {
player.getSession().send(new PacketOpenStateChangeNotify(openState, value)); private void setOpenState(int openState, int value, boolean sendNotify) {
} int previousValue = this.player.getOpenStates().getOrDefault(openState, 0);
}
} if (value != previousValue) {
this.player.getOpenStates().put(openState, value);
private void setOpenState(int openState, int value) {
this.setOpenState(openState, value, true); this.player
} .getQuestManager()
.queueEvent(QuestCond.QUEST_COND_OPEN_STATE_EQUAL, openState, value);
/**********
* Condition checking for setting open states. if (sendNotify) {
**********/ player.getSession().send(new PacketOpenStateChangeNotify(openState, value));
private boolean areConditionsMet(OpenStateData openState) { }
// Check all conditions and test if at least one of them is violated. }
for (var condition : openState.getCond()) { }
// For level conditions, check if the player has reached the necessary level.
if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) { private void setOpenState(int openState, int value) {
if (this.player.getLevel() < condition.getParam()) { this.setOpenState(openState, value, true);
return false; }
}
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) { /**********
// ToDo: Implement. * Condition checking for setting open states.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) { **********/
// ToDo: Implement. private boolean areConditionsMet(OpenStateData openState) {
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) { // Check all conditions and test if at least one of them is violated.
// ToDo: Implement. for (var condition : openState.getCond()) {
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) { switch (condition.getCondType()) {
// ToDo: Implement. // For level conditions, check if the player has reached the necessary level.
} case OPEN_STATE_COND_PLAYER_LEVEL -> {
} if (this.player.getLevel() < condition.getParam()) {
return false;
// Done. If we didn't find any violations, all conditions are met. }
return true; }
} case OPEN_STATE_COND_QUEST -> {
// check sub quest id for quest finished met requirements
/********** var quest = this.player.getQuestManager().getQuestById(condition.getParam());
* Setting open states from the client (via `SetOpenStateReq`). if (quest == null || quest.getState() != QuestState.QUEST_STATE_FINISHED) {
**********/ return false;
public void setOpenStateFromClient(int openState, int value) { }
// Get the data for this open state. }
OpenStateData data = GameData.getOpenStateDataMap().get(openState); case OPEN_STATE_COND_PARENT_QUEST -> {
if (data == null) { // check main quest id for quest finished met requirements
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); // TODO not sure if its having or finished quest
return; var mainQuest = this.player.getQuestManager().getMainQuestById(condition.getParam());
} if (mainQuest == null
|| mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED) {
// Make sure that this is an open state that the client is allowed to set, return false;
// and that it doesn't have any further conditions attached. }
if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) { }
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL)); // ToDo: Implement.
return; case OPEN_STATE_OFFERING_LEVEL, OPEN_STATE_CITY_REPUTATION_LEVEL -> {}
} }
}
// Set.
this.setOpenState(openState, value); // Done. If we didn't find any violations, all conditions are met.
this.player.sendPacket(new PacketSetOpenStateRsp(openState, value)); return true;
} }
/** This force sets an open state, ignoring all conditions and permissions */ /**********
public void forceSetOpenState(int openState, int value) { * Setting open states from the client (via `SetOpenStateReq`).
this.setOpenState(openState, value); **********/
} public void setOpenStateFromClient(int openState, int value) {
// Get the data for this open state.
/********** OpenStateData data = GameData.getOpenStateDataMap().get(openState);
* Triggered unlocking of open states (unlock states whose conditions have been met.) if (data == null) {
**********/ this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
public void tryUnlockOpenStates(boolean sendNotify) { return;
// Get list of open states that are not yet unlocked. }
var lockedStates =
GameData.getOpenStateList().stream() // Make sure that this is an open state that the client is allowed to set,
.filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0) // and that it doesn't have any further conditions attached.
.toList(); if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) {
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
// Try unlocking all of them. return;
for (var state : lockedStates) { }
// To auto-unlock a state, it has to meet three conditions:
// * it can not be a state that is unlocked by the client, // Set.
// * it has to meet all its unlock conditions, and this.setOpenState(openState, value);
// * it can not be in the blacklist. this.player.sendPacket(new PacketSetOpenStateRsp(openState, value));
if (!state.isAllowClientOpen() }
&& this.areConditionsMet(state)
&& !BLACKLIST_OPEN_STATES.contains(state.getId())) { /** This force sets an open state, ignoring all conditions and permissions */
this.setOpenState(state.getId(), 1, sendNotify); public void forceSetOpenState(int openState, int value) {
} this.setOpenState(openState, value);
} }
}
/**********
public void tryUnlockOpenStates() { * Triggered unlocking of open states (unlock states whose conditions have been met.)
this.tryUnlockOpenStates(true); **********/
} public void tryUnlockOpenStates(boolean sendNotify) {
// Get list of open states that are not yet unlocked.
/****************************************************************************************************************** var lockedStates =
****************************************************************************************************************** GameData.getOpenStateList().stream()
* MAP AREAS AND POINTS .filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0)
****************************************************************************************************************** .toList();
*****************************************************************************************************************/
private void addStatueQuestsOnLogin() { // Try unlocking all of them.
// Get all currently existing subquests for the "unlock all statues" main quest. for (var state : lockedStates) {
var statueMainQuest = GameData.getMainQuestDataMap().get(303); // To auto-unlock a state, it has to meet three conditions:
var statueSubQuests = statueMainQuest.getSubQuests(); // * it can not be a state that is unlocked by the client,
// * it has to meet all its unlock conditions, and
// Add the main statue quest if it isn't active yet. // * it can not be in the blacklist.
var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); if (!state.isAllowClientOpen()
if (statueGameMainQuest == null) { && this.areConditionsMet(state)
this.player.getQuestManager().addQuest(30302); && !BLACKLIST_OPEN_STATES.contains(state.getId())) {
statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303); this.setOpenState(state.getId(), 1, sendNotify);
} }
}
// Set all subquests to active if they aren't already finished. }
for (var subData : statueSubQuests) {
var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId()); public void tryUnlockOpenStates() {
if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) { this.tryUnlockOpenStates(true);
this.player.getQuestManager().addQuest(subData.getSubId()); }
}
} /******************************************************************************************************************
} ******************************************************************************************************************
* MAP AREAS AND POINTS
public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) { ******************************************************************************************************************
// Check whether the unlocked point exists and whether it is still locked. *****************************************************************************************************************/
ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId); private void addStatueQuestsOnLogin() {
// Get all currently existing subquests for the "unlock all statues" main quest.
if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) { var statueMainQuest = GameData.getMainQuestDataMap().get(303);
return false; var statueSubQuests = statueMainQuest.getSubQuests();
}
// Add the main statue quest if it isn't active yet.
// Add the point to the list of unlocked points for its scene. var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
this.player.getUnlockedScenePoints(sceneId).add(pointId); if (statueGameMainQuest == null) {
this.player.getQuestManager().addQuest(30302);
// Give primogems and Adventure EXP for unlocking. statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward); }
this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward);
// Set all subquests to active if they aren't already finished.
// this.player.sendPacket(new for (var subData : statueSubQuests) {
// PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP), var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId());
// PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP)); if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) {
this.player.getQuestManager().addQuest(subData.getSubId());
// Fire quest trigger for trans point unlock. }
this.player }
.getQuestManager() }
.queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) {
// Send packet. // Check whether the unlocked point exists and whether it is still locked.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId)); ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId);
return true;
} if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) {
return false;
public void unlockSceneArea(int sceneId, int areaId) { }
// Add the area to the list of unlocked areas in its scene.
this.player.getUnlockedSceneAreas(sceneId).add(areaId); // Add the point to the list of unlocked points for its scene.
this.player.getUnlockedScenePoints(sceneId).add(pointId);
// Send packet.
this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId)); // Give primogems and Adventure EXP for unlocking.
} this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward);
this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward);
/** Give replace costume to player (Amber, Jean, Mona, Rosaria) */
public void addReplaceCostumes() { // this.player.sendPacket(new
var currentPlayerCostumes = player.getCostumeList(); // PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP),
GameData.getAvatarReplaceCostumeDataMap() // PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP));
.keySet()
.forEach( // Fire quest trigger for trans point unlock.
costumeId -> { this.player
if (GameData.getAvatarCostumeDataMap().get(costumeId) == null .getQuestManager()
|| currentPlayerCostumes.contains(costumeId)) { .queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
return; this.player
} .getScene()
this.player.addCostume(costumeId); .getScriptManager()
}); .callEvent(new ScriptArgs(0, EVENT_UNLOCK_TRANS_POINT, sceneId, pointId));
}
// Send packet.
/** Quest progress */ this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));
public void addQuestProgress(int id, int count) { return true;
var newCount = player.getPlayerProgress().addToCurrentProgress(id, count); }
player.save();
player public void unlockSceneArea(int sceneId, int areaId) {
.getQuestManager() // Add the area to the list of unlocked areas in its scene.
.queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount); this.player.getUnlockedSceneAreas(sceneId).add(areaId);
}
// Send packet.
/** Item history */ this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId));
public void addItemObtainedHistory(int id, int count) { }
var newCount = player.getPlayerProgress().addToItemHistory(id, count);
player.save(); /** Give replace costume to player (Amber, Jean, Mona, Rosaria) */
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount); public void addReplaceCostumes() {
} var currentPlayerCostumes = player.getCostumeList();
} GameData.getAvatarReplaceCostumeDataMap()
.keySet()
.forEach(
costumeId -> {
if (GameData.getAvatarCostumeDataMap().get(costumeId) == null
|| currentPlayerCostumes.contains(costumeId)) {
return;
}
this.player.addCostume(costumeId);
});
}
/** Quest progress */
public void addQuestProgress(int id, int count) {
var newCount = player.getPlayerProgress().addToCurrentProgress(id, count);
player.save();
player
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount);
}
/** Item history */
public void addItemObtainedHistory(int id, int count) {
var newCount = player.getPlayerProgress().addToItemHistory(id, count);
player.save();
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,19 @@
package emu.grasscutter.game.props.ItemUseAction; package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp; import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseUnlockHomeModule extends ItemUseInt { public class ItemUseUnlockHomeModule extends ItemUseInt {
public ItemUseUnlockHomeModule(String[] useParam) { public ItemUseUnlockHomeModule(String[] useParam) {
super(useParam); super(useParam);
} }
@Override @Override
public ItemUseOp getItemUseOp() { public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_UNLOCK_HOME_MODULE; return ItemUseOp.ITEM_USE_UNLOCK_HOME_MODULE;
} }
@Override @Override
public boolean useItem(UseItemParams params) { public boolean useItem(UseItemParams params) {
return true; return false;
} }
}
@Override
public boolean postUseItem(UseItemParams params) {
params.player.addRealmList(this.i);
return true;
}
}

View File

@@ -1,95 +1,95 @@
package emu.grasscutter.game.shop; package emu.grasscutter.game.shop;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ShopGoodsData; import emu.grasscutter.data.excels.ShopGoodsData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class ShopInfo { public class ShopInfo {
@Getter @Setter private int goodsId = 0; @Getter @Setter private int goodsId = 0;
@Getter @Setter private ItemParamData goodsItem; @Getter @Setter private ItemParamData goodsItem;
@Getter @Setter private int scoin = 0; @Getter @Setter private int scoin = 0;
@Getter @Setter private List<ItemParamData> costItemList; @Getter @Setter private List<ItemParamData> costItemList;
@Getter @Setter private int boughtNum = 0; @Getter @Setter private int boughtNum = 0;
@Getter @Setter private int buyLimit = 0; @Getter @Setter private int buyLimit = 0;
@Getter @Setter private int beginTime = 0; @Getter @Setter private int beginTime = 0;
@Getter @Setter private int endTime = 1924992000; @Getter @Setter private int endTime = 1924992000;
@Getter @Setter private int minLevel = 0; @Getter @Setter private int minLevel = 0;
@Getter @Setter private int maxLevel = 61; @Getter @Setter private int maxLevel = 61;
@Getter @Setter private List<Integer> preGoodsIdList = new ArrayList<>(); @Getter @Setter private List<Integer> preGoodsIdList = new ArrayList<>();
@Getter @Setter private int mcoin = 0; @Getter @Setter private int mcoin = 0;
@Getter @Setter private int hcoin = 0; @Getter @Setter private int hcoin = 0;
@Getter @Setter private int disableType = 0; @Getter @Setter private int disableType = 0;
@Getter @Setter private int secondarySheetId = 0; @Getter @Setter private int secondarySheetId = 0;
private String refreshType; private String refreshType;
@Setter private transient ShopRefreshType shopRefreshType; @Setter private transient ShopRefreshType shopRefreshType;
@Getter @Setter private int shopRefreshParam; @Getter @Setter private int shopRefreshParam;
public ShopInfo(ShopGoodsData sgd) { public ShopInfo(ShopGoodsData sgd) {
this.goodsId = sgd.getGoodsId(); this.goodsId = sgd.getGoodsId();
this.goodsItem = new ItemParamData(sgd.getItemId(), sgd.getItemCount()); this.goodsItem = new ItemParamData(sgd.getItemId(), sgd.getItemCount());
this.scoin = sgd.getCostScoin(); this.scoin = sgd.getCostScoin();
this.mcoin = sgd.getCostMcoin(); this.mcoin = sgd.getCostMcoin();
this.hcoin = sgd.getCostHcoin(); this.hcoin = sgd.getCostHcoin();
this.buyLimit = sgd.getBuyLimit(); this.buyLimit = sgd.getBuyLimit();
this.minLevel = sgd.getMinPlayerLevel(); this.minLevel = sgd.getMinPlayerLevel();
this.maxLevel = sgd.getMaxPlayerLevel(); this.maxLevel = sgd.getMaxPlayerLevel();
this.costItemList = this.costItemList =
sgd.getCostItems().stream() sgd.getCostItems().stream()
.filter(x -> x.getId() != 0) .filter(x -> x.getId() != 0)
.map(x -> new ItemParamData(x.getId(), x.getCount())) .map(x -> new ItemParamData(x.getId(), x.getCount()))
.toList(); .toList();
this.secondarySheetId = sgd.getSubTabId(); this.secondarySheetId = sgd.getSubTabId();
this.shopRefreshType = sgd.getRefreshType(); this.shopRefreshType = sgd.getRefreshType();
this.shopRefreshParam = sgd.getRefreshParam(); this.shopRefreshParam = sgd.getRefreshParam();
} }
public ShopRefreshType getShopRefreshType() { public ShopRefreshType getShopRefreshType() {
if (refreshType == null) return ShopRefreshType.NONE; if (refreshType == null) return ShopRefreshType.NONE;
return switch (refreshType) { return switch (refreshType) {
case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY; case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY;
case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY; case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY;
case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY; case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY;
default -> ShopInfo.ShopRefreshType.NONE; default -> ShopInfo.ShopRefreshType.NONE;
}; };
} }
private boolean evaluateVirtualCost(ItemParamData item) { private boolean evaluateVirtualCost(ItemParamData item) {
return switch (item.getId()) { return switch (item.getId()) {
case 201 -> { case 201 -> {
this.hcoin += item.getCount(); this.hcoin += item.getCount();
yield true; yield true;
} }
case 203 -> { case 203 -> {
this.mcoin += item.getCount(); this.mcoin += item.getCount();
yield true; yield true;
} }
default -> false; default -> false;
}; };
} }
public void removeVirtualCosts() { public void removeVirtualCosts() {
if (this.costItemList != null) this.costItemList.removeIf(item -> evaluateVirtualCost(item)); if (this.costItemList != null) this.costItemList.removeIf(item -> evaluateVirtualCost(item));
} }
public enum ShopRefreshType { public enum ShopRefreshType {
NONE(0), NONE(0),
SHOP_REFRESH_DAILY(1), SHOP_REFRESH_DAILY(1),
SHOP_REFRESH_WEEKLY(2), SHOP_REFRESH_WEEKLY(2),
SHOP_REFRESH_MONTHLY(3); SHOP_REFRESH_MONTHLY(3);
private final int value; private final int value;
ShopRefreshType(int value) { ShopRefreshType(int value) {
this.value = value; this.value = value;
} }
public int value() { public int value() {
return value; return value;
} }
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff