Run Spotless on src/main

This commit is contained in:
KingRainbow44
2023-03-31 22:30:45 -04:00
parent 99822b0e22
commit fc05602128
1003 changed files with 60650 additions and 58050 deletions

View File

@@ -1,242 +1,244 @@
package emu.grasscutter.game;
import dev.morphia.annotations.*;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import org.bson.Document;
import java.util.*;
import java.util.stream.Stream;
import static emu.grasscutter.config.Configuration.ACCOUNT;
import static emu.grasscutter.config.Configuration.LANGUAGE;
@Entity(value = "accounts", useDiscriminator = false)
public class Account {
@Id
private String id;
@Indexed(options = @IndexOptions(unique = true))
@Collation(locale = "simple", caseLevel = true)
private String username;
private String password; // Unused for now
private int reservedPlayerId;
private String email;
private String token;
private String sessionKey; // Session token for dispatch server
private final List<String> permissions;
private Locale locale;
private String banReason;
private int banEndTime;
private int banStartTime;
private boolean isBanned;
@Deprecated
public Account() {
this.permissions = new ArrayList<>();
this.locale = LANGUAGE;
}
public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) {
String[] wildcardParts = wildcard.split("\\.");
if (permissionParts.length < wildcardParts.length) { // A longer wildcard can never match a shorter permission
return false;
}
for (int i = 0; i < wildcardParts.length; i++) {
switch (wildcardParts[i]) {
case "**": // Recursing match
return true;
case "*": // Match only one layer
if (i >= (permissionParts.length - 1)) {
return true;
}
break;
default: // This layer isn't a wildcard, it needs to match exactly
if (!wildcardParts[i].equals(permissionParts[i])) {
return false;
}
}
}
// At this point the wildcard will have matched every layer, but if it is shorter then the permission then this is not a match at this point (no **).
return (wildcardParts.length == permissionParts.length);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getReservedPlayerUid() {
return this.reservedPlayerId;
}
public void setReservedPlayerUid(int playerId) {
this.reservedPlayerId = playerId;
}
public String getEmail() {
if (email != null && !email.isEmpty()) {
return email;
} else {
return "";
}
}
public void setEmail(String email) {
this.email = email;
}
public String getSessionKey() {
return this.sessionKey;
}
public String generateSessionKey() {
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.sessionKey;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public String getBanReason() {
return banReason;
}
public void setBanReason(String banReason) {
this.banReason = banReason;
}
public int getBanEndTime() {
return banEndTime;
}
public void setBanEndTime(int banEndTime) {
this.banEndTime = banEndTime;
}
public int getBanStartTime() {
return banStartTime;
}
public void setBanStartTime(int banStartTime) {
this.banStartTime = banStartTime;
}
public boolean isBanned() {
if (banEndTime > 0 && banEndTime < System.currentTimeMillis() / 1000) {
this.isBanned = false;
this.banEndTime = 0;
this.banStartTime = 0;
this.banReason = null;
save();
}
return isBanned;
}
public void setBanned(boolean isBanned) {
this.isBanned = isBanned;
}
/**
* The collection of a player's permissions.
*/
public List<String> getPermissions() {
return this.permissions;
}
public boolean addPermission(String permission) {
if (this.permissions.contains(permission)) return false;
this.permissions.add(permission);
return true;
}
public boolean hasPermission(String permission) {
if (permission.isEmpty()) return true;
if (this.permissions.contains("*") && this.permissions.size() == 1) return true;
// Add default permissions if it doesn't exist
List<String> permissions = Stream.of(this.permissions, Arrays.asList(ACCOUNT.defaultPermissions))
.flatMap(Collection::stream)
.distinct().toList();
if (permissions.contains(permission)) return true;
String[] permissionParts = permission.split("\\.");
for (String p : permissions) {
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false;
if (permissionMatchesWildcard(p, permissionParts)) return true;
}
return permissions.contains("*");
}
public boolean removePermission(String permission) {
return this.permissions.remove(permission);
}
public void clearPermission() {
this.permissions.clear();
}
// TODO make unique
public String generateLoginToken() {
this.token = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.token;
}
public void save() {
DatabaseHelper.saveAccount(this);
}
@PreLoad
public void onLoad(Document document) {
// Grant the superuser permissions to accounts created before the permissions update
if (!document.containsKey("permissions")) {
this.addPermission("*");
}
// Set account default language as server default language
if (!document.containsKey("locale")) {
this.locale = LANGUAGE;
}
}
}
package emu.grasscutter.game;
import static emu.grasscutter.config.Configuration.ACCOUNT;
import static emu.grasscutter.config.Configuration.LANGUAGE;
import dev.morphia.annotations.*;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import java.util.*;
import java.util.stream.Stream;
import org.bson.Document;
@Entity(value = "accounts", useDiscriminator = false)
public class Account {
@Id private String id;
@Indexed(options = @IndexOptions(unique = true))
@Collation(locale = "simple", caseLevel = true)
private String username;
private String password; // Unused for now
private int reservedPlayerId;
private String email;
private String token;
private String sessionKey; // Session token for dispatch server
private final List<String> permissions;
private Locale locale;
private String banReason;
private int banEndTime;
private int banStartTime;
private boolean isBanned;
@Deprecated
public Account() {
this.permissions = new ArrayList<>();
this.locale = LANGUAGE;
}
public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) {
String[] wildcardParts = wildcard.split("\\.");
if (permissionParts.length
< wildcardParts.length) { // A longer wildcard can never match a shorter permission
return false;
}
for (int i = 0; i < wildcardParts.length; i++) {
switch (wildcardParts[i]) {
case "**": // Recursing match
return true;
case "*": // Match only one layer
if (i >= (permissionParts.length - 1)) {
return true;
}
break;
default: // This layer isn't a wildcard, it needs to match exactly
if (!wildcardParts[i].equals(permissionParts[i])) {
return false;
}
}
}
// At this point the wildcard will have matched every layer, but if it is shorter then the
// permission then this is not a match at this point (no **).
return (wildcardParts.length == permissionParts.length);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getReservedPlayerUid() {
return this.reservedPlayerId;
}
public void setReservedPlayerUid(int playerId) {
this.reservedPlayerId = playerId;
}
public String getEmail() {
if (email != null && !email.isEmpty()) {
return email;
} else {
return "";
}
}
public void setEmail(String email) {
this.email = email;
}
public String getSessionKey() {
return this.sessionKey;
}
public String generateSessionKey() {
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.sessionKey;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public String getBanReason() {
return banReason;
}
public void setBanReason(String banReason) {
this.banReason = banReason;
}
public int getBanEndTime() {
return banEndTime;
}
public void setBanEndTime(int banEndTime) {
this.banEndTime = banEndTime;
}
public int getBanStartTime() {
return banStartTime;
}
public void setBanStartTime(int banStartTime) {
this.banStartTime = banStartTime;
}
public boolean isBanned() {
if (banEndTime > 0 && banEndTime < System.currentTimeMillis() / 1000) {
this.isBanned = false;
this.banEndTime = 0;
this.banStartTime = 0;
this.banReason = null;
save();
}
return isBanned;
}
public void setBanned(boolean isBanned) {
this.isBanned = isBanned;
}
/** The collection of a player's permissions. */
public List<String> getPermissions() {
return this.permissions;
}
public boolean addPermission(String permission) {
if (this.permissions.contains(permission)) return false;
this.permissions.add(permission);
return true;
}
public boolean hasPermission(String permission) {
if (permission.isEmpty()) return true;
if (this.permissions.contains("*") && this.permissions.size() == 1) return true;
// Add default permissions if it doesn't exist
List<String> permissions =
Stream.of(this.permissions, Arrays.asList(ACCOUNT.defaultPermissions))
.flatMap(Collection::stream)
.distinct()
.toList();
if (permissions.contains(permission)) return true;
String[] permissionParts = permission.split("\\.");
for (String p : permissions) {
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts))
return false;
if (permissionMatchesWildcard(p, permissionParts)) return true;
}
return permissions.contains("*");
}
public boolean removePermission(String permission) {
return this.permissions.remove(permission);
}
public void clearPermission() {
this.permissions.clear();
}
// TODO make unique
public String generateLoginToken() {
this.token = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.token;
}
public void save() {
DatabaseHelper.saveAccount(this);
}
@PreLoad
public void onLoad(Document document) {
// Grant the superuser permissions to accounts created before the permissions update
if (!document.containsKey("permissions")) {
this.addPermission("*");
}
// Set account default language as server default language
if (!document.containsKey("locale")) {
this.locale = LANGUAGE;
}
}
}

View File

@@ -1,31 +1,31 @@
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}

View File

@@ -1,222 +1,228 @@
package emu.grasscutter.game.ability;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifierEntry;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetGatherObject;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap;
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import lombok.Getter;
public final class AbilityManager extends BasePlayerManager {
HealAbilityManager healAbilityManager;
@Getter
private boolean abilityInvulnerable = false;
public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
this.healAbilityManager.healHandler(invoke);
//Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke);
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke);
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE -> this.handleModifierChange(invoke);
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke);
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke);
default -> {
}
}
}
/**
* Invoked when a player starts a skill.
*
* @param player The player who started the skill.
* @param skillId The skill ID.
* @param casterId The caster ID.
*/
public void onSkillStart(Player player, int skillId, int casterId) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the caster matches the player.
if (player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) {
return;
}
var skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}
// Check if the skill is an elemental burst.
if (skillData.getCostElemVal() <= 0) {
return;
}
// Set the player as invulnerable.
this.abilityInvulnerable = true;
}
/**
* Invoked when a player ends a skill.
*
* @param player The player who started the skill.
*/
public void onSkillEnd(Player player) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the player is invulnerable.
if (!this.abilityInvulnerable) {
return;
}
// Set the player as not invulnerable.
this.abilityInvulnerable = false;
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget && targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
}
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work properly anyways
if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED && data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
this.player.getEnergyManager().handleGenerateElemBall(invoke);
}
private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {
case HealHP -> {
}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = action.amount.get();
// if (action.amount.isDynamic && action.amount.dynamicKey != null) {
// damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
// }
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
default -> {
}
}
}
}
package emu.grasscutter.game.ability;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifierEntry;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetGatherObject;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap;
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import lombok.Getter;
public final class AbilityManager extends BasePlayerManager {
HealAbilityManager healAbilityManager;
@Getter private boolean abilityInvulnerable = false;
public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
this.healAbilityManager.healHandler(invoke);
// Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue()
// + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke);
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke);
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE -> this.handleModifierChange(invoke);
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke);
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke);
default -> {}
}
}
/**
* Invoked when a player starts a skill.
*
* @param player The player who started the skill.
* @param skillId The skill ID.
* @param casterId The caster ID.
*/
public void onSkillStart(Player player, int skillId, int casterId) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the caster matches the player.
if (player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) {
return;
}
var skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}
// Check if the skill is an elemental burst.
if (skillData.getCostElemVal() <= 0) {
return;
}
// Set the player as invulnerable.
this.abilityInvulnerable = true;
}
/**
* Invoked when a player ends a skill.
*
* @param player The player who started the skill.
*/
public void onSkillEnd(Player player) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the player is invulnerable.
if (!this.abilityInvulnerable) {
return;
}
// Set the player as not invulnerable.
this.abilityInvulnerable = false;
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map =
AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget
&& targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
}
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work
// properly anyways
if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED
&& data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina =
AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
this.player.getEnergyManager().handleGenerateElemBall(invoke);
}
private void invokeAction(
AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {
case HealHP -> {}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = action.amount.get();
// if (action.amount.isDynamic && action.amount.dynamicKey != null) {
// damageAmount =
// sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
// }
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
default -> {}
}
}
}

View File

@@ -1,185 +1,214 @@
package emu.grasscutter.game.ability;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HealAbilityManager {
ArrayList<HealDataAvatar> healDataAvatarList;
private final Player player;
public HealAbilityManager(Player player) {
this.player = player;
healDataAvatarList = new ArrayList();
healDataAvatarList.add(new HealDataAvatar(10000054, "Kokomi", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Base_Percentage", "ElementalArt_Heal_Base_Amount", false).addHealData("Q", "Avatar_Kokomi_ElementalBurst_Heal", 0.0172f, 212f, false));
healDataAvatarList.add(new HealDataAvatar(10000003, "Qin", 1).addHealData("Q", "Heal", "BurstHealConst", true));
healDataAvatarList.add(new HealDataAvatar(10000034, "Noel", 2).addHealData("E", "OnAttack_HealthRate", 0.452f, 282f, true));
healDataAvatarList.add(new HealDataAvatar(10000032, "Bennett", 0).addHealData("Q", "HealMaxHpRatio", "HealConst", false));
healDataAvatarList.add(new HealDataAvatar(10000039, "Diona", 0).addHealData("Q", "HealHPRatio", "HealHP_Const", false));
healDataAvatarList.add(new HealDataAvatar(10000053, "Sayu", 1).addHealData("Q", "Constellation_6_Damage", "Heal_BaseAmount", true).addHealData("Q", "Heal_AttackRatio", "Constellation_6_Heal", true));
healDataAvatarList.add(new HealDataAvatar(10000014, "Barbara", 0).addHealData("E", "HealHPOnAdded", "HealHPOnAdded_Const", true).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true).addHealData("Q", "Avatar_Barbara_IdolHeal", 0.374f, 4660f, true));
healDataAvatarList.add(new HealDataAvatar(10000065, "Shinobu", 0).addHealData("E", "ElementalArt_Heal_MaxHP_Percentage", 0.064f, 795f, false));
healDataAvatarList.add(new HealDataAvatar(10000035, "Qiqi", 1).addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true).addHealData("E", "ElementalArt_HealHp_Ratio", "ElementalArt_HealHp_Const", true).addHealData("Q", "Avatar_Qiqi_ElementalBurst_ApplyModifier", 0.0191f, 1588f, false));
healDataAvatarList.add(new HealDataAvatar(10000046, "Hutao", 0).addHealData("Q", "Avatar_Hutao_VermilionBite_BakeMesh", 0.1166f, 0f, false));
}
public Player getPlayer() {
return this.player;
}
public void healHandler(AbilityInvokeEntry invoke) throws Exception {
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
String modifierString = "";
if (data.getParentAbilityName() != null)
modifierString = data.getParentAbilityName().getStr();
if (sourceEntity != null)
checkAndHeal(sourceEntity, modifierString);
}
public void checkAndHeal(GameEntity sourceEntity, String modifierString) {
int fightPropertyType = 0;
float healAmount = 0;
float ratio = 0, base = 0;
float maxHP, curHP, curAttack, curDefense;
Map<String, Float> map = sourceEntity.getMetaOverrideMap();
for (int i = 0; i < healDataAvatarList.size(); i++) {
HealDataAvatar healDataAvatar = healDataAvatarList.get(i);
if (modifierString.contains(healDataAvatar.avatarName)) {
fightPropertyType = healDataAvatar.fightPropertyType;
ArrayList<HealData> healDataList = healDataAvatar.healDataList;
for (int j = 0; j < healDataList.size(); j++) {
HealData healData = healDataList.get(j);
if (map.containsKey(healData.sRatio) || modifierString.equals(healData.sRatio)) {
if (healData.isString) {
ratio = map.get(healData.sRatio);
base = map.get(healData.sBase);
} else {
ratio = healData.fRatio;
base = healData.fBase;
}
}
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
List<EntityAvatar> needHealAvatars = new ArrayList();
int currentIndex = player.getTeamManager().getCurrentCharacterIndex();
EntityAvatar currentAvatar = activeTeam.get(currentIndex);
if (healData.healAll) {
needHealAvatars = activeTeam;
} else {
needHealAvatars.add(currentAvatar);
}
EntityAvatar healActionAvatar = null;
for (int k = 0; k < activeTeam.size(); k++) {
EntityAvatar avatar = activeTeam.get(k);
int avatarId = avatar.getAvatar().getAvatarId();
if (avatarId == healDataAvatar.avatarId) {
healActionAvatar = avatar;
break;
}
}
if (healActionAvatar != null) {
maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
curHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK);
curDefense = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE);
//Special case for Hu Tao:
if (healDataAvatar.avatarName.equals("Hutao") && curHP <= maxHP * 0.5 && ratio != 0) {
ratio = 0.1555f;
}
switch (fightPropertyType) {
case 0:
healAmount = ratio * maxHP + base;
break;
case 1:
healAmount = ratio * curAttack + base;
break;
case 2:
healAmount = ratio * curDefense + base;
break;
}
}
for (int k = 0; k < needHealAvatars.size(); k++) {
EntityAvatar avatar = needHealAvatars.get(k);
avatar.heal(healAmount);
}
}
break;
}
}
}
private class HealData {
public boolean isString = true;
public String abilityType = ""; //"E" or "Q"
public String sRatio = "";
public String sBase = "";
public float fRatio = 0;
public float fBase = 0;
public boolean healAll = false;
public HealData(String _abilityType, String _sRatio, String _sBase, boolean _healAll) {
abilityType = _abilityType;
isString = true;
sRatio = _sRatio;
sBase = _sBase;
healAll = _healAll;
}
public HealData(String _abilityType, String _sRatio, float _fRatio, float _fBase, boolean _healAll) {
abilityType = _abilityType;
isString = false;
sRatio = _sRatio;
fRatio = _fRatio;
fBase = _fBase;
healAll = _healAll;
}
}
private class HealDataAvatar {
public int avatarId = 0;
public String avatarName = "";
public int fightPropertyType = 0; //0: maxHP, 1: curAttack, 2: curDefense
public ArrayList<HealData> healDataList;
public HealDataAvatar(int _avatarId, String _avatarName, int _fightPropertyType) {
avatarId = _avatarId;
avatarName = _avatarName;
fightPropertyType = _fightPropertyType;
healDataList = new ArrayList();
}
public HealDataAvatar addHealData(String abilityType, String sRatio, String sBase, boolean healAll) {
HealData healData = new HealData(abilityType, sRatio, sBase, healAll);
healDataList.add(healData);
return this;
}
public HealDataAvatar addHealData(String abilityType, String sRatio, float fRatio, float fBase, boolean healAll) {
HealData healData = new HealData(abilityType, sRatio, fRatio, fBase, healAll);
healDataList.add(healData);
return this;
}
}
}
package emu.grasscutter.game.ability;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HealAbilityManager {
ArrayList<HealDataAvatar> healDataAvatarList;
private final Player player;
public HealAbilityManager(Player player) {
this.player = player;
healDataAvatarList = new ArrayList();
healDataAvatarList.add(
new HealDataAvatar(10000054, "Kokomi", 0)
.addHealData(
"E",
"ElementalArt_Heal_MaxHP_Base_Percentage",
"ElementalArt_Heal_Base_Amount",
false)
.addHealData("Q", "Avatar_Kokomi_ElementalBurst_Heal", 0.0172f, 212f, false));
healDataAvatarList.add(
new HealDataAvatar(10000003, "Qin", 1).addHealData("Q", "Heal", "BurstHealConst", true));
healDataAvatarList.add(
new HealDataAvatar(10000034, "Noel", 2)
.addHealData("E", "OnAttack_HealthRate", 0.452f, 282f, true));
healDataAvatarList.add(
new HealDataAvatar(10000032, "Bennett", 0)
.addHealData("Q", "HealMaxHpRatio", "HealConst", false));
healDataAvatarList.add(
new HealDataAvatar(10000039, "Diona", 0)
.addHealData("Q", "HealHPRatio", "HealHP_Const", false));
healDataAvatarList.add(
new HealDataAvatar(10000053, "Sayu", 1)
.addHealData("Q", "Constellation_6_Damage", "Heal_BaseAmount", true)
.addHealData("Q", "Heal_AttackRatio", "Constellation_6_Heal", true));
healDataAvatarList.add(
new HealDataAvatar(10000014, "Barbara", 0)
.addHealData("E", "HealHPOnAdded", "HealHPOnAdded_Const", true)
.addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true)
.addHealData("Q", "Avatar_Barbara_IdolHeal", 0.374f, 4660f, true));
healDataAvatarList.add(
new HealDataAvatar(10000065, "Shinobu", 0)
.addHealData("E", "ElementalArt_Heal_MaxHP_Percentage", 0.064f, 795f, false));
healDataAvatarList.add(
new HealDataAvatar(10000035, "Qiqi", 1)
.addHealData("E", "HealHP_OnHittingOthers", "HealHP_Const_OnHittingOthers", true)
.addHealData("E", "ElementalArt_HealHp_Ratio", "ElementalArt_HealHp_Const", true)
.addHealData("Q", "Avatar_Qiqi_ElementalBurst_ApplyModifier", 0.0191f, 1588f, false));
healDataAvatarList.add(
new HealDataAvatar(10000046, "Hutao", 0)
.addHealData("Q", "Avatar_Hutao_VermilionBite_BakeMesh", 0.1166f, 0f, false));
}
public Player getPlayer() {
return this.player;
}
public void healHandler(AbilityInvokeEntry invoke) throws Exception {
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
String modifierString = "";
if (data.getParentAbilityName() != null) modifierString = data.getParentAbilityName().getStr();
if (sourceEntity != null) checkAndHeal(sourceEntity, modifierString);
}
public void checkAndHeal(GameEntity sourceEntity, String modifierString) {
int fightPropertyType = 0;
float healAmount = 0;
float ratio = 0, base = 0;
float maxHP, curHP, curAttack, curDefense;
Map<String, Float> map = sourceEntity.getMetaOverrideMap();
for (int i = 0; i < healDataAvatarList.size(); i++) {
HealDataAvatar healDataAvatar = healDataAvatarList.get(i);
if (modifierString.contains(healDataAvatar.avatarName)) {
fightPropertyType = healDataAvatar.fightPropertyType;
ArrayList<HealData> healDataList = healDataAvatar.healDataList;
for (int j = 0; j < healDataList.size(); j++) {
HealData healData = healDataList.get(j);
if (map.containsKey(healData.sRatio) || modifierString.equals(healData.sRatio)) {
if (healData.isString) {
ratio = map.get(healData.sRatio);
base = map.get(healData.sBase);
} else {
ratio = healData.fRatio;
base = healData.fBase;
}
}
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
List<EntityAvatar> needHealAvatars = new ArrayList();
int currentIndex = player.getTeamManager().getCurrentCharacterIndex();
EntityAvatar currentAvatar = activeTeam.get(currentIndex);
if (healData.healAll) {
needHealAvatars = activeTeam;
} else {
needHealAvatars.add(currentAvatar);
}
EntityAvatar healActionAvatar = null;
for (int k = 0; k < activeTeam.size(); k++) {
EntityAvatar avatar = activeTeam.get(k);
int avatarId = avatar.getAvatar().getAvatarId();
if (avatarId == healDataAvatar.avatarId) {
healActionAvatar = avatar;
break;
}
}
if (healActionAvatar != null) {
maxHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
curHP = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
curAttack = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK);
curDefense = healActionAvatar.getFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE);
// Special case for Hu Tao:
if (healDataAvatar.avatarName.equals("Hutao") && curHP <= maxHP * 0.5 && ratio != 0) {
ratio = 0.1555f;
}
switch (fightPropertyType) {
case 0:
healAmount = ratio * maxHP + base;
break;
case 1:
healAmount = ratio * curAttack + base;
break;
case 2:
healAmount = ratio * curDefense + base;
break;
}
}
for (int k = 0; k < needHealAvatars.size(); k++) {
EntityAvatar avatar = needHealAvatars.get(k);
avatar.heal(healAmount);
}
}
break;
}
}
}
private class HealData {
public boolean isString = true;
public String abilityType = ""; // "E" or "Q"
public String sRatio = "";
public String sBase = "";
public float fRatio = 0;
public float fBase = 0;
public boolean healAll = false;
public HealData(String _abilityType, String _sRatio, String _sBase, boolean _healAll) {
abilityType = _abilityType;
isString = true;
sRatio = _sRatio;
sBase = _sBase;
healAll = _healAll;
}
public HealData(
String _abilityType, String _sRatio, float _fRatio, float _fBase, boolean _healAll) {
abilityType = _abilityType;
isString = false;
sRatio = _sRatio;
fRatio = _fRatio;
fBase = _fBase;
healAll = _healAll;
}
}
private class HealDataAvatar {
public int avatarId = 0;
public String avatarName = "";
public int fightPropertyType = 0; // 0: maxHP, 1: curAttack, 2: curDefense
public ArrayList<HealData> healDataList;
public HealDataAvatar(int _avatarId, String _avatarName, int _fightPropertyType) {
avatarId = _avatarId;
avatarName = _avatarName;
fightPropertyType = _fightPropertyType;
healDataList = new ArrayList();
}
public HealDataAvatar addHealData(
String abilityType, String sRatio, String sBase, boolean healAll) {
HealData healData = new HealData(abilityType, sRatio, sBase, healAll);
healDataList.add(healData);
return this;
}
public HealDataAvatar addHealData(
String abilityType, String sRatio, float fRatio, float fBase, boolean healAll) {
HealData healData = new HealData(abilityType, sRatio, fRatio, fBase, healAll);
healDataList.add(healData);
return this;
}
}
}

View File

@@ -1,38 +1,40 @@
package emu.grasscutter.game.achievement;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AchievementOuterClass;
import emu.grasscutter.net.proto.StatusOuterClass;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
public class Achievement {
@Setter
private StatusOuterClass.Status status;
private final int id;
private final int totalProgress;
@Setter
private int curProgress;
@Setter
private int finishTimestampSec;
public Achievement(StatusOuterClass.Status status, int id, int totalProgress, int curProgress, int finishTimestampSec) {
this.status = status;
this.id = id;
this.totalProgress = totalProgress;
this.curProgress = curProgress;
this.finishTimestampSec = finishTimestampSec;
}
public AchievementOuterClass.Achievement toProto() {
return AchievementOuterClass.Achievement.newBuilder()
.setStatus(this.getStatus())
.setId(this.getId())
.setTotalProgress(this.getTotalProgress())
.setCurProgress(this.getCurProgress())
.setFinishTimestamp(this.getFinishTimestampSec())
.build();
}
}
package emu.grasscutter.game.achievement;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AchievementOuterClass;
import emu.grasscutter.net.proto.StatusOuterClass;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
public class Achievement {
@Setter private StatusOuterClass.Status status;
private final int id;
private final int totalProgress;
@Setter private int curProgress;
@Setter private int finishTimestampSec;
public Achievement(
StatusOuterClass.Status status,
int id,
int totalProgress,
int curProgress,
int finishTimestampSec) {
this.status = status;
this.id = id;
this.totalProgress = totalProgress;
this.curProgress = curProgress;
this.finishTimestampSec = finishTimestampSec;
}
public AchievementOuterClass.Achievement toProto() {
return AchievementOuterClass.Achievement.newBuilder()
.setStatus(this.getStatus())
.setId(this.getId())
.setTotalProgress(this.getTotalProgress())
.setCurProgress(this.getCurProgress())
.setFinishTimestamp(this.getFinishTimestampSec())
.build();
}
}

View File

@@ -1,48 +1,47 @@
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter
private final String key;
Return(String key) {
this.key = key;
}
}
}
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter private final String key;
Return(String key) {
this.key = key;
}
}
}

View File

@@ -1,293 +1,323 @@
package emu.grasscutter.game.achievement;
import com.github.davidmoten.guavamini.Lists;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AchievementData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.StatusOuterClass;
import emu.grasscutter.server.packet.send.PacketAchievementAllDataNotify;
import emu.grasscutter.server.packet.send.PacketAchievementUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeAchievementGoalRewardRsp;
import emu.grasscutter.server.packet.send.PacketTakeAchievementRewardRsp;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import org.bson.types.ObjectId;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
@Entity("achievements")
@Data
@Builder(builderMethodName = "of")
public class Achievements {
private static final IntSupplier currentTimeSecs = () -> (int) (System.currentTimeMillis() / 1000L);
private static final Achievement INVALID = new Achievement(StatusOuterClass.Status.STATUS_INVALID, -1, 0, 0, 0);
@Id
private ObjectId id;
private int uid;
@Transient
private Player player;
private Map<Integer, Achievement> achievementList;
@Getter
private int finishedAchievementNum;
private List<Integer> takenGoalRewardIdList;
public static Achievements getByPlayer(Player player) {
var achievements = player.getAchievements() == null ? DatabaseHelper.getAchievementData(player.getUid()) : player.getAchievements();
if (achievements == null) {
achievements = create(player.getUid());
}
return achievements;
}
public static Achievements create(int uid) {
var newAchievement = Achievements.of()
.uid(uid)
.achievementList(init())
.finishedAchievementNum(0)
.takenGoalRewardIdList(Lists.newArrayList())
.build();
newAchievement.save();
return newAchievement;
}
private static Map<Integer, Achievement> init() {
Map<Integer, Achievement> map = new HashMap<>();
GameData.getAchievementDataMap().values().stream()
.filter(AchievementData::isUsed)
.forEach(a -> {
map.put(a.getId(), new Achievement(StatusOuterClass.Status.STATUS_UNFINISHED, a.getId(), a.getProgress(), 0, 0));
});
return map;
}
public AchievementControlReturns grant(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || this.isFinished(achievementId)) {
return a == null ? AchievementControlReturns.achievementNotFound() : AchievementControlReturns.alreadyAchieved();
}
return this.progress(achievementId, a.getTotalProgress());
}
public AchievementControlReturns revoke(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || !this.isFinished(achievementId)) {
return a == null ? AchievementControlReturns.achievementNotFound() : AchievementControlReturns.notYetAchieved();
}
return this.progress(achievementId, 0);
}
public AchievementControlReturns progress(int achievementId, int progress) {
var a = this.getAchievement(achievementId);
if (a == null) {
return AchievementControlReturns.achievementNotFound();
}
a.setCurProgress(progress);
return AchievementControlReturns.success(this.notifyOtherAchievements(a));
}
private int notifyOtherAchievements(Achievement a) {
var changedNum = new AtomicInteger();
changedNum.addAndGet(this.update(a) ? 1 : 0);
GameData.getAchievementDataMap().get(a.getId())
.getExcludedGroupAchievementIdList().stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.forEach(other -> {
other.setCurProgress(a.getCurProgress());
changedNum.addAndGet(this.update(other) ? 1 : 0);
});
this.computeFinishedAchievementNum();
this.save();
this.sendUpdatePacket(a);
return changedNum.intValue();
}
private boolean update(Achievement a) {
if (a.getStatus() == StatusOuterClass.Status.STATUS_UNFINISHED && a.getCurProgress() >= a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_FINISHED);
a.setFinishTimestampSec(currentTimeSecs.getAsInt());
return true;
} else if (this.isFinished(a.getId()) && a.getCurProgress() < a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_UNFINISHED);
a.setFinishTimestampSec(0);
return true;
}
return false;
}
private void computeFinishedAchievementNum() {
this.finishedAchievementNum = GameData.getAchievementDataMap().values().stream()
.filter(a -> this.isFinished(a.getId()))
.mapToInt(value -> 1)
.sum();
}
private void sendUpdatePacket(Achievement achievement) {
List<Achievement> achievements = Lists.newArrayList(achievement);
achievements.addAll(GameData.getAchievementDataMap().get(achievement.getId())
.getExcludedGroupAchievementIdList()
.stream().map(this::getAchievement)
.filter(Objects::nonNull)
.toList()
);
this.sendUpdatePacket(achievements);
}
private void sendUpdatePacket(List<Achievement> achievement) {
if (this.isPacketSendable()) {
this.player.sendPacket(new PacketAchievementUpdateNotify(achievement));
}
}
@Nullable
public Achievement getAchievement(int achievementId) {
if (this.isInvalid(achievementId)) {
return null;
}
return this.getAchievementList().computeIfAbsent(achievementId, id -> {
return new Achievement(StatusOuterClass.Status.STATUS_UNFINISHED, id, GameData.getAchievementDataMap().get(id.intValue()).getProgress(), 0, 0);
});
}
public boolean isInvalid(int achievementId) {
var data = GameData.getAchievementDataMap().get(achievementId);
return data == null || data.isDisuse();
}
public StatusOuterClass.Status getStatus(int achievementId) {
return this.getAchievementList().getOrDefault(achievementId, INVALID).getStatus();
}
public boolean isFinished(int achievementId) {
var status = this.getStatus(achievementId);
return status == StatusOuterClass.Status.STATUS_FINISHED || status == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public void takeReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
var target = GameData.getAchievementDataMap().get(i);
if (target == null) {
Grasscutter.getLogger().warn("null returned while taking reward!");
return;
}
if (this.isRewardTaken(i)) {
this.player.sendPacket(new PacketTakeAchievementRewardRsp());
return;
}
var data = GameData.getRewardDataMap().get(target.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList().forEach(itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
var a = this.getAchievement(i);
a.setStatus(StatusOuterClass.Status.STATUS_REWARD_TAKEN);
this.save();
this.sendUpdatePacket(a);
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementReward);
this.player.sendPacket(new PacketTakeAchievementRewardRsp(ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public void takeGoalReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
if (this.takenGoalRewardIdList.contains(i)) {
this.player.sendPacket(new PacketTakeAchievementGoalRewardRsp());
}
var goalData = GameData.getAchievementGoalDataMap().get(i);
if (goalData == null) {
Grasscutter.getLogger().warn("null returned while getting goal reward data!");
continue;
}
var data = GameData.getRewardDataMap().get(goalData.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList().forEach(itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
this.takenGoalRewardIdList.add(i);
this.save();
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementGoalReward);
this.player.sendPacket(new PacketTakeAchievementGoalRewardRsp(ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public boolean isRewardTaken(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public boolean isRewardLeft(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_FINISHED;
}
private boolean isPacketSendable() {
return this.player != null;
}
public void save() {
DatabaseHelper.saveAchievementData(this);
}
public void onLogin(Player player) {
if (this.player == null) {
this.player = player;
}
this.player.sendPacket(new PacketAchievementAllDataNotify(this.player));
}
}
package emu.grasscutter.game.achievement;
import com.github.davidmoten.guavamini.Lists;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AchievementData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.StatusOuterClass;
import emu.grasscutter.server.packet.send.PacketAchievementAllDataNotify;
import emu.grasscutter.server.packet.send.PacketAchievementUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeAchievementGoalRewardRsp;
import emu.grasscutter.server.packet.send.PacketTakeAchievementRewardRsp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity("achievements")
@Data
@Builder(builderMethodName = "of")
public class Achievements {
private static final IntSupplier currentTimeSecs =
() -> (int) (System.currentTimeMillis() / 1000L);
private static final Achievement INVALID =
new Achievement(StatusOuterClass.Status.STATUS_INVALID, -1, 0, 0, 0);
@Id private ObjectId id;
private int uid;
@Transient private Player player;
private Map<Integer, Achievement> achievementList;
@Getter private int finishedAchievementNum;
private List<Integer> takenGoalRewardIdList;
public static Achievements getByPlayer(Player player) {
var achievements =
player.getAchievements() == null
? DatabaseHelper.getAchievementData(player.getUid())
: player.getAchievements();
if (achievements == null) {
achievements = create(player.getUid());
}
return achievements;
}
public static Achievements create(int uid) {
var newAchievement =
Achievements.of()
.uid(uid)
.achievementList(init())
.finishedAchievementNum(0)
.takenGoalRewardIdList(Lists.newArrayList())
.build();
newAchievement.save();
return newAchievement;
}
private static Map<Integer, Achievement> init() {
Map<Integer, Achievement> map = new HashMap<>();
GameData.getAchievementDataMap().values().stream()
.filter(AchievementData::isUsed)
.forEach(
a -> {
map.put(
a.getId(),
new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED, a.getId(), a.getProgress(), 0, 0));
});
return map;
}
public AchievementControlReturns grant(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.alreadyAchieved();
}
return this.progress(achievementId, a.getTotalProgress());
}
public AchievementControlReturns revoke(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || !this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.notYetAchieved();
}
return this.progress(achievementId, 0);
}
public AchievementControlReturns progress(int achievementId, int progress) {
var a = this.getAchievement(achievementId);
if (a == null) {
return AchievementControlReturns.achievementNotFound();
}
a.setCurProgress(progress);
return AchievementControlReturns.success(this.notifyOtherAchievements(a));
}
private int notifyOtherAchievements(Achievement a) {
var changedNum = new AtomicInteger();
changedNum.addAndGet(this.update(a) ? 1 : 0);
GameData.getAchievementDataMap().get(a.getId()).getExcludedGroupAchievementIdList().stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.forEach(
other -> {
other.setCurProgress(a.getCurProgress());
changedNum.addAndGet(this.update(other) ? 1 : 0);
});
this.computeFinishedAchievementNum();
this.save();
this.sendUpdatePacket(a);
return changedNum.intValue();
}
private boolean update(Achievement a) {
if (a.getStatus() == StatusOuterClass.Status.STATUS_UNFINISHED
&& a.getCurProgress() >= a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_FINISHED);
a.setFinishTimestampSec(currentTimeSecs.getAsInt());
return true;
} else if (this.isFinished(a.getId()) && a.getCurProgress() < a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_UNFINISHED);
a.setFinishTimestampSec(0);
return true;
}
return false;
}
private void computeFinishedAchievementNum() {
this.finishedAchievementNum =
GameData.getAchievementDataMap().values().stream()
.filter(a -> this.isFinished(a.getId()))
.mapToInt(value -> 1)
.sum();
}
private void sendUpdatePacket(Achievement achievement) {
List<Achievement> achievements = Lists.newArrayList(achievement);
achievements.addAll(
GameData.getAchievementDataMap()
.get(achievement.getId())
.getExcludedGroupAchievementIdList()
.stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.toList());
this.sendUpdatePacket(achievements);
}
private void sendUpdatePacket(List<Achievement> achievement) {
if (this.isPacketSendable()) {
this.player.sendPacket(new PacketAchievementUpdateNotify(achievement));
}
}
@Nullable public Achievement getAchievement(int achievementId) {
if (this.isInvalid(achievementId)) {
return null;
}
return this.getAchievementList()
.computeIfAbsent(
achievementId,
id -> {
return new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED,
id,
GameData.getAchievementDataMap().get(id.intValue()).getProgress(),
0,
0);
});
}
public boolean isInvalid(int achievementId) {
var data = GameData.getAchievementDataMap().get(achievementId);
return data == null || data.isDisuse();
}
public StatusOuterClass.Status getStatus(int achievementId) {
return this.getAchievementList().getOrDefault(achievementId, INVALID).getStatus();
}
public boolean isFinished(int achievementId) {
var status = this.getStatus(achievementId);
return status == StatusOuterClass.Status.STATUS_FINISHED
|| status == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public void takeReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
var target = GameData.getAchievementDataMap().get(i);
if (target == null) {
Grasscutter.getLogger().warn("null returned while taking reward!");
return;
}
if (this.isRewardTaken(i)) {
this.player.sendPacket(new PacketTakeAchievementRewardRsp());
return;
}
var data = GameData.getRewardDataMap().get(target.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
var a = this.getAchievement(i);
a.setStatus(StatusOuterClass.Status.STATUS_REWARD_TAKEN);
this.save();
this.sendUpdatePacket(a);
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementReward);
this.player.sendPacket(
new PacketTakeAchievementRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public void takeGoalReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
if (this.takenGoalRewardIdList.contains(i)) {
this.player.sendPacket(new PacketTakeAchievementGoalRewardRsp());
}
var goalData = GameData.getAchievementGoalDataMap().get(i);
if (goalData == null) {
Grasscutter.getLogger().warn("null returned while getting goal reward data!");
continue;
}
var data = GameData.getRewardDataMap().get(goalData.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
this.takenGoalRewardIdList.add(i);
this.save();
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementGoalReward);
this.player.sendPacket(
new PacketTakeAchievementGoalRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public boolean isRewardTaken(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public boolean isRewardLeft(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_FINISHED;
}
private boolean isPacketSendable() {
return this.player != null;
}
public void save() {
DatabaseHelper.saveAchievementData(this);
}
public void onLogin(Player player) {
if (this.player == null) {
this.player = player;
}
this.player.sendPacket(new PacketAchievementAllDataNotify(this.player));
}
}

View File

@@ -1,21 +1,20 @@
package emu.grasscutter.game.activity;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.Date;
import java.util.List;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ActivityConfigItem {
int activityId;
int activityType;
int scheduleId;
List<Integer> meetCondList;
Date beginTime;
Date endTime;
transient ActivityHandler activityHandler;
}
package emu.grasscutter.game.activity;
import java.util.Date;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ActivityConfigItem {
int activityId;
int activityType;
int scheduleId;
List<Integer> meetCondList;
Date beginTime;
Date endTime;
transient ActivityHandler activityHandler;
}

View File

@@ -1,91 +1,98 @@
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ActivityData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.utils.DateHelper;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
import java.util.*;
import java.util.stream.Collectors;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityHandler {
/**
* Must set before initWatchers
*/
ActivityConfigItem activityConfigItem;
ActivityData activityData;
Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
abstract public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo);
abstract public void onInitPlayerActivityData(PlayerActivityData playerActivityData);
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
activityData = GameData.getActivityDataMap().get(activityConfigItem.getActivityId());
// add watcher to map by id
activityData.getWatcherDataList().forEach(watcherData -> {
var watcherType = activityWatcherTypeMap.get(watcherData.getTriggerConfig().getWatcherTriggerType());
ActivityWatcher watcher;
if (watcherType != null) {
watcher = (ActivityWatcher) watcherType.newInstance();
} else {
watcher = new DefaultWatcher();
}
watcher.setWatcherId(watcherData.getId());
watcher.setActivityHandler(this);
watcher.setActivityWatcherData(watcherData);
watchersMap.computeIfAbsent(watcherData.getTriggerConfig().getWatcherTriggerType(), k -> new ArrayList<>());
watchersMap.get(watcherData.getTriggerConfig().getWatcherTriggerType()).add(watcher);
});
}
private Map<Integer, PlayerActivityData.WatcherInfo> initWatchersDataForPlayer() {
return watchersMap.values().stream()
.flatMap(Collection::stream)
.map(PlayerActivityData.WatcherInfo::init)
.collect(Collectors.toMap(PlayerActivityData.WatcherInfo::getWatcherId, y -> y));
}
public PlayerActivityData initPlayerActivityData(Player player) {
PlayerActivityData playerActivityData = PlayerActivityData.of()
.activityId(activityConfigItem.getActivityId())
.uid(player.getUid())
.watcherInfoMap(initWatchersDataForPlayer())
.build();
onInitPlayerActivityData(playerActivityData);
return playerActivityData;
}
public ActivityInfoOuterClass.ActivityInfo toProto(PlayerActivityData playerActivityData) {
var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
proto.setActivityId(activityConfigItem.getActivityId())
.setActivityType(activityConfigItem.getActivityType())
.setScheduleId(activityConfigItem.getScheduleId())
.setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setFirstDayStartTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setEndTime(DateHelper.getUnixTime(activityConfigItem.getEndTime()))
.addAllMeetCondList(activityConfigItem.getMeetCondList());
if (playerActivityData != null) {
proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
}
onProtoBuild(playerActivityData, proto);
return proto.build();
}
}
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ActivityData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.utils.DateHelper;
import java.util.*;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityHandler {
/** Must set before initWatchers */
ActivityConfigItem activityConfigItem;
ActivityData activityData;
Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
public abstract void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo);
public abstract void onInitPlayerActivityData(PlayerActivityData playerActivityData);
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
activityData = GameData.getActivityDataMap().get(activityConfigItem.getActivityId());
// add watcher to map by id
activityData
.getWatcherDataList()
.forEach(
watcherData -> {
var watcherType =
activityWatcherTypeMap.get(
watcherData.getTriggerConfig().getWatcherTriggerType());
ActivityWatcher watcher;
if (watcherType != null) {
watcher = (ActivityWatcher) watcherType.newInstance();
} else {
watcher = new DefaultWatcher();
}
watcher.setWatcherId(watcherData.getId());
watcher.setActivityHandler(this);
watcher.setActivityWatcherData(watcherData);
watchersMap.computeIfAbsent(
watcherData.getTriggerConfig().getWatcherTriggerType(), k -> new ArrayList<>());
watchersMap.get(watcherData.getTriggerConfig().getWatcherTriggerType()).add(watcher);
});
}
private Map<Integer, PlayerActivityData.WatcherInfo> initWatchersDataForPlayer() {
return watchersMap.values().stream()
.flatMap(Collection::stream)
.map(PlayerActivityData.WatcherInfo::init)
.collect(Collectors.toMap(PlayerActivityData.WatcherInfo::getWatcherId, y -> y));
}
public PlayerActivityData initPlayerActivityData(Player player) {
PlayerActivityData playerActivityData =
PlayerActivityData.of()
.activityId(activityConfigItem.getActivityId())
.uid(player.getUid())
.watcherInfoMap(initWatchersDataForPlayer())
.build();
onInitPlayerActivityData(playerActivityData);
return playerActivityData;
}
public ActivityInfoOuterClass.ActivityInfo toProto(PlayerActivityData playerActivityData) {
var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
proto
.setActivityId(activityConfigItem.getActivityId())
.setActivityType(activityConfigItem.getActivityType())
.setScheduleId(activityConfigItem.getScheduleId())
.setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setFirstDayStartTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setEndTime(DateHelper.getUnixTime(activityConfigItem.getEndTime()))
.addAllMeetCondList(activityConfigItem.getMeetCondList());
if (playerActivityData != null) {
proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
}
onProtoBuild(playerActivityData, proto);
return proto.build();
}
}

View File

@@ -1,150 +1,161 @@
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
import lombok.Getter;
import org.reflections.Reflections;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Getter
public class ActivityManager extends BasePlayerManager {
private static final Map<Integer, ActivityConfigItem> activityConfigItemMap;
@Getter
private static final Map<Integer, ActivityConfigItem> scheduleActivityConfigMap;
static {
activityConfigItemMap = new HashMap<>();
scheduleActivityConfigMap = new HashMap<>();
loadActivityConfigData();
}
private final Map<Integer, PlayerActivityData> playerActivityDataMap;
public ActivityManager(Player player) {
super(player);
playerActivityDataMap = new ConcurrentHashMap<>();
// load data for player
activityConfigItemMap.values().forEach(item -> {
var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
if (data == null) {
data = item.getActivityHandler().initPlayerActivityData(player);
data.save();
}
data.setPlayer(player);
data.setActivityHandler(item.getActivityHandler());
playerActivityDataMap.put(item.getActivityId(), data);
});
player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
}
private static void loadActivityConfigData() {
// scan activity type handler & watcher type
var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
var activityWatcherTypeMap = new HashMap<WatcherTriggerType, ConstructorAccess<?>>();
var reflections = new Reflections(ActivityManager.class.getPackage().getName());
reflections.getSubTypesOf(ActivityHandler.class).forEach(item -> {
var typeName = item.getAnnotation(GameActivity.class);
activityHandlerTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
reflections.getSubTypesOf(ActivityWatcher.class).forEach(item -> {
var typeName = item.getAnnotation(ActivityWatcherType.class);
activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
try {
DataLoader.loadList("ActivityConfig.json", ActivityConfigItem.class).forEach(item -> {
var activityData = GameData.getActivityDataMap().get(item.getActivityId());
if (activityData == null) {
Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId());
return;
}
var activityHandlerType = activityHandlerTypeMap.get(ActivityType.getTypeByName(activityData.getActivityType()));
ActivityHandler activityHandler;
if (activityHandlerType != null) {
activityHandler = (ActivityHandler) activityHandlerType.newInstance();
} else {
activityHandler = new DefaultActivityHandler();
}
activityHandler.setActivityConfigItem(item);
activityHandler.initWatchers(activityWatcherTypeMap);
item.setActivityHandler(activityHandler);
activityConfigItemMap.putIfAbsent(item.getActivityId(), item);
scheduleActivityConfigMap.putIfAbsent(item.getScheduleId(), item);
});
Grasscutter.getLogger().info("Enable {} activities.", activityConfigItemMap.size());
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load activities config.", e);
}
}
/**
* trigger activity watcher
*/
public void triggerWatcher(WatcherTriggerType watcherTriggerType, String... params) {
var watchers = activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(Objects::nonNull)
.map(ActivityHandler::getWatchersMap)
.map(map -> map.get(watcherTriggerType))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
watchers.forEach(watcher -> watcher.trigger(
playerActivityDataMap.get(watcher.getActivityHandler().getActivityConfigItem().getActivityId()),
params));
}
public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId) {
var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
var activityData = playerActivityDataMap.get(activityId);
return activityHandler.toProto(activityData);
}
public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
return activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
.findFirst();
}
@SuppressWarnings("unchecked")
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T) x);
}
public Optional<Integer> getActivityIdByActivityType(ActivityType type) {
return getActivityHandler(type)
.map(ActivityHandler::getActivityConfigItem)
.map(ActivityConfigItem::getActivityId);
}
public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type) {
return getActivityIdByActivityType(type)
.map(playerActivityDataMap::get);
}
public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(ActivityType type) {
return getActivityIdByActivityType(type)
.map(this::getInfoProtoByActivityId);
}
}
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import org.reflections.Reflections;
@Getter
public class ActivityManager extends BasePlayerManager {
private static final Map<Integer, ActivityConfigItem> activityConfigItemMap;
@Getter private static final Map<Integer, ActivityConfigItem> scheduleActivityConfigMap;
static {
activityConfigItemMap = new HashMap<>();
scheduleActivityConfigMap = new HashMap<>();
loadActivityConfigData();
}
private final Map<Integer, PlayerActivityData> playerActivityDataMap;
public ActivityManager(Player player) {
super(player);
playerActivityDataMap = new ConcurrentHashMap<>();
// load data for player
activityConfigItemMap
.values()
.forEach(
item -> {
var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
if (data == null) {
data = item.getActivityHandler().initPlayerActivityData(player);
data.save();
}
data.setPlayer(player);
data.setActivityHandler(item.getActivityHandler());
playerActivityDataMap.put(item.getActivityId(), data);
});
player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
}
private static void loadActivityConfigData() {
// scan activity type handler & watcher type
var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
var activityWatcherTypeMap = new HashMap<WatcherTriggerType, ConstructorAccess<?>>();
var reflections = new Reflections(ActivityManager.class.getPackage().getName());
reflections
.getSubTypesOf(ActivityHandler.class)
.forEach(
item -> {
var typeName = item.getAnnotation(GameActivity.class);
activityHandlerTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
reflections
.getSubTypesOf(ActivityWatcher.class)
.forEach(
item -> {
var typeName = item.getAnnotation(ActivityWatcherType.class);
activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
try {
DataLoader.loadList("ActivityConfig.json", ActivityConfigItem.class)
.forEach(
item -> {
var activityData = GameData.getActivityDataMap().get(item.getActivityId());
if (activityData == null) {
Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId());
return;
}
var activityHandlerType =
activityHandlerTypeMap.get(
ActivityType.getTypeByName(activityData.getActivityType()));
ActivityHandler activityHandler;
if (activityHandlerType != null) {
activityHandler = (ActivityHandler) activityHandlerType.newInstance();
} else {
activityHandler = new DefaultActivityHandler();
}
activityHandler.setActivityConfigItem(item);
activityHandler.initWatchers(activityWatcherTypeMap);
item.setActivityHandler(activityHandler);
activityConfigItemMap.putIfAbsent(item.getActivityId(), item);
scheduleActivityConfigMap.putIfAbsent(item.getScheduleId(), item);
});
Grasscutter.getLogger().info("Enable {} activities.", activityConfigItemMap.size());
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load activities config.", e);
}
}
/** trigger activity watcher */
public void triggerWatcher(WatcherTriggerType watcherTriggerType, String... params) {
var watchers =
activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(Objects::nonNull)
.map(ActivityHandler::getWatchersMap)
.map(map -> map.get(watcherTriggerType))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
watchers.forEach(
watcher ->
watcher.trigger(
playerActivityDataMap.get(
watcher.getActivityHandler().getActivityConfigItem().getActivityId()),
params));
}
public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId) {
var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
var activityData = playerActivityDataMap.get(activityId);
return activityHandler.toProto(activityData);
}
public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
return activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
.findFirst();
}
@SuppressWarnings("unchecked")
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(
ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T) x);
}
public Optional<Integer> getActivityIdByActivityType(ActivityType type) {
return getActivityHandler(type)
.map(ActivityHandler::getActivityConfigItem)
.map(ActivityConfigItem::getActivityId);
}
public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type) {
return getActivityIdByActivityType(type).map(playerActivityDataMap::get);
}
public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(
ActivityType type) {
return getActivityIdByActivityType(type).map(this::getInfoProtoByActivityId);
}
}

View File

@@ -1,26 +1,25 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.data.excels.ActivityWatcherData;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityWatcher {
int watcherId;
ActivityWatcherData activityWatcherData;
ActivityHandler activityHandler;
protected abstract boolean isMeet(String... param);
public void trigger(PlayerActivityData playerActivityData, String... param) {
if (isMeet(param)) {
playerActivityData.addWatcherProgress(watcherId);
playerActivityData.save();
}
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.data.excels.ActivityWatcherData;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityWatcher {
int watcherId;
ActivityWatcherData activityWatcherData;
ActivityHandler activityHandler;
protected abstract boolean isMeet(String... param);
public void trigger(PlayerActivityData playerActivityData, String... param) {
if (isMeet(param)) {
playerActivityData.addWatcherProgress(watcherId);
playerActivityData.save();
}
}
}

View File

@@ -1,14 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}

View File

@@ -1,17 +1,15 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {}
}

View File

@@ -1,11 +1,11 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}

View File

@@ -1,14 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}

View File

@@ -1,133 +1,127 @@
package emu.grasscutter.game.activity;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
@Id
String id;
int uid;
int activityId;
Map<Integer, WatcherInfo> watcherInfoMap;
/**
* the detail data of each type of activity (Json format)
*/
String detail;
@Transient
Player player;
@Transient
ActivityHandler activityHandler;
public static PlayerActivityData getByPlayer(Player player, int activityId) {
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
}
public void save() {
DatabaseHelper.savePlayerActivityData(this);
}
public synchronized void addWatcherProgress(int watcherId) {
var watcherInfo = watcherInfoMap.get(watcherId);
if (watcherInfo == null) {
return;
}
if (watcherInfo.curProgress >= watcherInfo.totalProgress) {
return;
}
watcherInfo.curProgress++;
getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo));
}
public List<ActivityWatcherInfoOuterClass.ActivityWatcherInfo> getAllWatcherInfoList() {
return watcherInfoMap.values().stream()
.map(WatcherInfo::toProto)
.toList();
}
public void setDetail(Object detail) {
this.detail = JsonUtils.encode(detail);
}
public void takeWatcherReward(int watcherId) {
var watcher = watcherInfoMap.get(watcherId);
if (watcher == null || watcher.isTakenReward()) {
return;
}
var reward = Optional.of(watcher)
.map(WatcherInfo::getMetadata)
.map(ActivityWatcherData::getRewardID)
.map(id -> GameData.getRewardDataMap().get(id.intValue()));
if (reward.isEmpty()) {
return;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.get().getRewardItemList()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
watcher.setTakenReward(true);
save();
}
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class WatcherInfo {
int watcherId;
int totalProgress;
int curProgress;
boolean isTakenReward;
public static WatcherInfo init(ActivityWatcher watcher) {
return WatcherInfo.of()
.watcherId(watcher.getWatcherId())
.totalProgress(watcher.getActivityWatcherData().getProgress())
.isTakenReward(false)
.build();
}
public ActivityWatcherData getMetadata() {
return GameData.getActivityWatcherDataMap().get(watcherId);
}
public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() {
return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder()
.setWatcherId(watcherId)
.setCurProgress(curProgress)
.setTotalProgress(totalProgress)
.setIsTakenReward(isTakenReward)
.build();
}
}
}
package emu.grasscutter.game.activity;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
@Id String id;
int uid;
int activityId;
Map<Integer, WatcherInfo> watcherInfoMap;
/** the detail data of each type of activity (Json format) */
String detail;
@Transient Player player;
@Transient ActivityHandler activityHandler;
public static PlayerActivityData getByPlayer(Player player, int activityId) {
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
}
public void save() {
DatabaseHelper.savePlayerActivityData(this);
}
public synchronized void addWatcherProgress(int watcherId) {
var watcherInfo = watcherInfoMap.get(watcherId);
if (watcherInfo == null) {
return;
}
if (watcherInfo.curProgress >= watcherInfo.totalProgress) {
return;
}
watcherInfo.curProgress++;
getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo));
}
public List<ActivityWatcherInfoOuterClass.ActivityWatcherInfo> getAllWatcherInfoList() {
return watcherInfoMap.values().stream().map(WatcherInfo::toProto).toList();
}
public void setDetail(Object detail) {
this.detail = JsonUtils.encode(detail);
}
public void takeWatcherReward(int watcherId) {
var watcher = watcherInfoMap.get(watcherId);
if (watcher == null || watcher.isTakenReward()) {
return;
}
var reward =
Optional.of(watcher)
.map(WatcherInfo::getMetadata)
.map(ActivityWatcherData::getRewardID)
.map(id -> GameData.getRewardDataMap().get(id.intValue()));
if (reward.isEmpty()) {
return;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.get().getRewardItemList()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
watcher.setTakenReward(true);
save();
}
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class WatcherInfo {
int watcherId;
int totalProgress;
int curProgress;
boolean isTakenReward;
public static WatcherInfo init(ActivityWatcher watcher) {
return WatcherInfo.of()
.watcherId(watcher.getWatcherId())
.totalProgress(watcher.getActivityWatcherData().getProgress())
.isTakenReward(false)
.build();
}
public ActivityWatcherData getMetadata() {
return GameData.getActivityWatcherDataMap().get(watcherId);
}
public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() {
return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder()
.setWatcherId(watcherId)
.setCurProgress(curProgress)
.setTotalProgress(totalProgress)
.setIsTakenReward(isTakenReward)
.build();
}
}
}

View File

@@ -1,91 +1,105 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(Collectors.toMap(MusicGamePlayerData.MusicGameRecord::getMusicId, MusicGamePlayerData.MusicGameRecord::toProto)))
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().put(musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(
MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(
Collectors.toMap(
MusicGamePlayerData.MusicGameRecord::getMusicId,
MusicGamePlayerData.MusicGameRecord::toProto)))
//
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData
.getPersonalCustomBeatmapRecord()
.put(
musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}

View File

@@ -1,106 +1,97 @@
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
import java.util.Random;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id
long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream()
.map(BeatmapNote::parse)
.toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream()
.map(this::musicBeatmapListToProto)
.toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream()
.map(BeatmapNote::toProto)
.toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of()
.startTime(note.getStartTime())
.endTime(note.getEndTime())
.build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import java.util.List;
import java.util.Random;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(
List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream().map(BeatmapNote::parse).toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream().map(this::musicBeatmapListToProto).toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(
List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream().map(BeatmapNote::toProto).toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of().startTime(note.getStartTime()).endTime(note.getEndTime()).build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}

View File

@@ -1,87 +1,86 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(GameData.getMusicGameBasicDataMap().values().stream()
.collect(Collectors.toMap(MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of()
.musicId(musicGameBasicData.getId())
.build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(
GameData.getMusicGameBasicDataMap().values().stream()
.collect(
Collectors.toMap(
MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of().musicId(musicGameBasicData.getId()).build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}

View File

@@ -1,23 +1,23 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,173 +1,173 @@
package emu.grasscutter.game.avatar;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.Iterator;
import java.util.List;
public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Int2ObjectMap<Avatar> avatars;
private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
super(player);
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public Int2ObjectMap<Avatar> getAvatars() {
return avatars;
}
public int getAvatarCount() {
return this.avatars.size();
}
public Avatar getAvatarById(int id) {
return getAvatars().get(id);
}
public Avatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<Avatar> iterator() {
return getAvatars().values().iterator();
}
}
package emu.grasscutter.game.avatar;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.Iterator;
import java.util.List;
public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Int2ObjectMap<Avatar> avatars;
private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
super(player);
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public Int2ObjectMap<Avatar> getAvatars() {
return avatars;
}
public int getAvatarCount() {
return this.avatars.size();
}
public Avatar getAvatarById(int id) {
return getAvatars().get(id);
}
public Avatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot =
GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<Avatar> iterator() {
return getAvatars().values().iterator();
}
}

View File

@@ -1,392 +1,415 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import lombok.Getter;
import org.bson.types.ObjectId;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id
@Getter
private ObjectId id;
@Indexed
private int ownerUid;
@Getter
private int point;
@Getter
private int cyclePoints; // Weekly maximum cap
@Getter
private int level;
@Getter
private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {
}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem = new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem = new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0 || getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData = GameData.getBattlePassRewardDataMap().get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid() && rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward = new BattlePassReward(tag.getLevel(), tag.getRewardId(), tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null || mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate = (currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime = LocalDateTime.of(nextSundayDate.getYear(), nextSundayDate.getMonthValue(), nextSundayDate.getDayOfMonth(), 23, 59, 59);
BattlePassSchedule.Builder schedule = BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3)
);
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id;
@Indexed private int ownerUid;
@Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL
&& this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer()
.getServer()
.getBattlePassSystem()
.triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(
ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's
// data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp()
== ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0
|| getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData =
GameData.getBattlePassRewardDataMap()
.get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid()
&& rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward =
new BattlePassReward(
tag.getLevel(),
tag.getRewardId(),
tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null
|| mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate =
(currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime =
LocalDateTime.of(
nextSundayDate.getYear(),
nextSundayDate.getMonthValue(),
nextSundayDate.getDayOfMonth(),
23,
59,
59);
BattlePassSchedule.Builder schedule =
BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(
BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}

View File

@@ -1,75 +1,75 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient
private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {
}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder = emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder =
emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(
getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}

View File

@@ -1,50 +1,51 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient
private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {
}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}

View File

@@ -1,78 +1,80 @@
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList = getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each
// player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList =
getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(
Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}

View File

@@ -1,204 +1,215 @@
package emu.grasscutter.game.chat;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static emu.grasscutter.config.Configuration.GAME_INFO;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches())
return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory = this.history.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history.computeIfAbsent(player.getUid(), x -> new HashMap<>()).containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength = this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages = this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}
package emu.grasscutter.game.chat;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches()) return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history
.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory =
this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last
// chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength =
this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages =
this.history
.get(player.getUid())
.get(GameConstants.SERVER_CONSOLE_UID)
.subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(
player.getUid(),
joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}

View File

@@ -1,26 +1,26 @@
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}

View File

@@ -1,132 +1,143 @@
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private final static Int2ObjectMap<List<Integer>> reliquaryDecomposeData = new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class).forEach(entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger().debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player.getInventory().addItem(combineData.getResultItemId(),
combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(List.of(new ItemParamData(combineData.getResultItemId(),
combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private static final Int2ObjectMap<List<Integer>> reliquaryDecomposeData =
new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class)
.forEach(
entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger()
.debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(
new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player
.getInventory()
.addItem(combineData.getResultItemId(), combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(
List.of(
new ItemParamData(
combineData.getResultItemId(), combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(
Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}

View File

@@ -1,45 +1,43 @@
package emu.grasscutter.game.combine;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
public class CombineResult {
private List<ItemParamData> material;
private List<ItemParamData> result;
private List<ItemParamData> extra;
private List<ItemParamData> back;
public List<ItemParamData> getMaterial() {
return material;
}
public void setMaterial(List<ItemParamData> material) {
this.material = material;
}
public List<ItemParamData> getResult() {
return result;
}
public void setResult(List<ItemParamData> result) {
this.result = result;
}
public List<ItemParamData> getExtra() {
return extra;
}
public void setExtra(List<ItemParamData> extra) {
this.extra = extra;
}
public List<ItemParamData> getBack() {
return back;
}
public void setBack(List<ItemParamData> back) {
this.back = back;
}
}
package emu.grasscutter.game.combine;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
public class CombineResult {
private List<ItemParamData> material;
private List<ItemParamData> result;
private List<ItemParamData> extra;
private List<ItemParamData> back;
public List<ItemParamData> getMaterial() {
return material;
}
public void setMaterial(List<ItemParamData> material) {
this.material = material;
}
public List<ItemParamData> getResult() {
return result;
}
public void setResult(List<ItemParamData> result) {
this.result = result;
}
public List<ItemParamData> getExtra() {
return extra;
}
public void setExtra(List<ItemParamData> extra) {
this.extra = extra;
}
public List<ItemParamData> getBack() {
return back;
}
public void setBack(List<ItemParamData> back) {
this.back = back;
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.combine;
import java.util.List;
public class ReliquaryDecomposeEntry {
private int configId;
private List<Integer> items;
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
}
package emu.grasscutter.game.combine;
import java.util.List;
public class ReliquaryDecomposeEntry {
private int configId;
private List<Integer> items;
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
}

View File

@@ -1,55 +1,51 @@
package emu.grasscutter.game.drop;
public class DropData {
private int minWeight;
private int maxWeight;
private int itemId;
private int minCount;
private int maxCount;
private boolean share = false;
private boolean give = false;
public boolean isGive() {
return give;
}
public void setGive(boolean give) {
this.give = give;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getMinCount() {
return minCount;
}
public int getMaxCount() {
return maxCount;
}
public int getMinWeight() {
return minWeight;
}
public int getMaxWeight() {
return maxWeight;
}
public boolean isShare() {
return share;
}
public void setIsShare(boolean share) {
this.share = share;
}
}
package emu.grasscutter.game.drop;
public class DropData {
private int minWeight;
private int maxWeight;
private int itemId;
private int minCount;
private int maxCount;
private boolean share = false;
private boolean give = false;
public boolean isGive() {
return give;
}
public void setGive(boolean give) {
this.give = give;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getMinCount() {
return minCount;
}
public int getMaxCount() {
return maxCount;
}
public int getMinWeight() {
return minWeight;
}
public int getMaxWeight() {
return maxWeight;
}
public boolean isShare() {
return share;
}
public void setIsShare(boolean share) {
this.share = share;
}
}

View File

@@ -1,16 +1,16 @@
package emu.grasscutter.game.drop;
import java.util.List;
public class DropInfo {
private int monsterId;
private List<DropData> dropDataList;
public int getMonsterId() {
return monsterId;
}
public List<DropData> getDropDataList() {
return dropDataList;
}
}
package emu.grasscutter.game.drop;
import java.util.List;
public class DropInfo {
private int monsterId;
private List<DropData> dropDataList;
public int getMonsterId() {
return monsterId;
}
public List<DropData> getDropDataList() {
return dropDataList;
}
}

View File

@@ -1,107 +1,111 @@
package emu.grasscutter.game.drop;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class DropSystem extends BaseGameSystem {
private final Int2ObjectMap<List<DropData>> dropData;
public DropSystem(GameServer server) {
super(server);
this.dropData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<DropData>> getDropData() {
return dropData;
}
public synchronized void load() {
getDropData().clear();
try {
List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class);
if (banners.size() > 0) {
for (DropInfo di : banners) {
getDropData().put(di.getMonsterId(), di.getDropDataList());
}
Grasscutter.getLogger().debug("Drop data successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load drop data.", e);
}
}
private void addDropEntity(DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
if (!dd.isGive() && (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
if (!dd.isShare())
dropScene.addEntityToSingleClient(target, entity);
else
dropScene.addEntity(entity);
} else {
if (target != null) {
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
} else {
// target is null if items will be added are shared. no one could pick it up because of the combination(give + shared)
// so it will be sent to all players' inventories directly.
dropScene.getPlayers().forEach(x -> x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
}
}
}
private void processDrop(DropData dd, EntityMonster em, Player gp) {
int target = Utils.randomRange(1, 10000);
if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) {
ItemData itemData = GameData.getItemDataMap().get(dd.getItemId());
int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount());
if (itemData == null) {
return;
}
if (itemData.isEquip()) {
for (int i = 0; i < num; i++) {
float range = (2.5f + (.05f * num));
Position pos = em.getPosition().nearby2d(range).addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
}
} else {
Position pos = em.getPosition().clone().addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
}
}
}
public void callDrop(EntityMonster em) {
int id = em.getMonsterData().getId();
if (getDropData().containsKey(id)) {
for (DropData dd : getDropData().get(id)) {
if (dd.isShare())
processDrop(dd, em, null);
else {
for (Player gp : em.getScene().getPlayers()) {
processDrop(dd, em, gp);
}
}
}
}
}
}
package emu.grasscutter.game.drop;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class DropSystem extends BaseGameSystem {
private final Int2ObjectMap<List<DropData>> dropData;
public DropSystem(GameServer server) {
super(server);
this.dropData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<DropData>> getDropData() {
return dropData;
}
public synchronized void load() {
getDropData().clear();
try {
List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class);
if (banners.size() > 0) {
for (DropInfo di : banners) {
getDropData().put(di.getMonsterId(), di.getDropDataList());
}
Grasscutter.getLogger().debug("Drop data successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load drop data.", e);
}
}
private void addDropEntity(
DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
if (!dd.isGive()
&& (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity);
else dropScene.addEntity(entity);
} else {
if (target != null) {
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
} else {
// target is null if items will be added are shared. no one could pick it up because of the
// combination(give + shared)
// so it will be sent to all players' inventories directly.
dropScene
.getPlayers()
.forEach(
x ->
x.getInventory()
.addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
}
}
}
private void processDrop(DropData dd, EntityMonster em, Player gp) {
int target = Utils.randomRange(1, 10000);
if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) {
ItemData itemData = GameData.getItemDataMap().get(dd.getItemId());
int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount());
if (itemData == null) {
return;
}
if (itemData.isEquip()) {
for (int i = 0; i < num; i++) {
float range = (2.5f + (.05f * num));
Position pos = em.getPosition().nearby2d(range).addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
}
} else {
Position pos = em.getPosition().clone().addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
}
}
}
public void callDrop(EntityMonster em) {
int id = em.getMonsterData().getId();
if (getDropData().containsKey(id)) {
for (DropData dd : getDropData().get(id)) {
if (dd.isShare()) processDrop(dd, em, null);
else {
for (Player gp : em.getScene().getPlayers()) {
processDrop(dd, em, gp);
}
}
}
}
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDrop {
private int dungeonId;
private List<DungeonDropEntry> drops;
public int getDungeonId() {
return dungeonId;
}
public void setDungeonId(int dungeonId) {
this.dungeonId = dungeonId;
}
public List<DungeonDropEntry> getDrops() {
return drops;
}
public void setDrops(List<DungeonDropEntry> drops) {
this.drops = drops;
}
}
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDrop {
private int dungeonId;
private List<DungeonDropEntry> drops;
public int getDungeonId() {
return dungeonId;
}
public void setDungeonId(int dungeonId) {
this.dungeonId = dungeonId;
}
public List<DungeonDropEntry> getDrops() {
return drops;
}
public void setDrops(List<DungeonDropEntry> drops) {
this.drops = drops;
}
}

View File

@@ -1,51 +1,51 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDropEntry {
private List<Integer> counts;
private List<Integer> items;
private List<Integer> probabilities;
private List<Integer> itemProbabilities;
private boolean mpDouble;
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
public List<Integer> getProbabilities() {
return probabilities;
}
public void setProbabilities(List<Integer> probabilities) {
this.probabilities = probabilities;
}
public List<Integer> getItemProbabilities() {
return itemProbabilities;
}
public void setItemProbabilities(List<Integer> itemProbabilities) {
this.itemProbabilities = itemProbabilities;
}
public boolean isMpDouble() {
return mpDouble;
}
public void setMpDouble(boolean mpDouble) {
this.mpDouble = mpDouble;
}
}
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDropEntry {
private List<Integer> counts;
private List<Integer> items;
private List<Integer> probabilities;
private List<Integer> itemProbabilities;
private boolean mpDouble;
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
public List<Integer> getProbabilities() {
return probabilities;
}
public void setProbabilities(List<Integer> probabilities) {
this.probabilities = probabilities;
}
public List<Integer> getItemProbabilities() {
return itemProbabilities;
}
public void setItemProbabilities(List<Integer> itemProbabilities) {
this.itemProbabilities = itemProbabilities;
}
public boolean isMpDouble() {
return mpDouble;
}
public void setMpDouble(boolean mpDouble) {
this.mpDouble = mpDouble;
}
}

View File

@@ -1,112 +1,122 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
import java.util.List;
public class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener();
public DungeonSystem(GameServer server) {
super(server);
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter dungeon {}", player.getNickname(), player.getUid(), dungeonId);
int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
return true;
}
/**
* used in tower dungeons handoff
*/
public boolean handoffDungeon(Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter tower dungeon {}", player.getNickname(), player.getUid(), dungeonId);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
GameData.getScenePointEntries().forEach((id, entry) -> entry.getPointData().updateDailyDungeon());
}
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
import java.util.List;
public class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
new BasicDungeonSettleListener();
public DungeonSystem(GameServer server) {
super(server);
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
return true;
}
/** used in tower dungeons handoff */
public boolean handoffDungeon(
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter tower dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
GameData.getScenePointEntries()
.forEach((id, entry) -> entry.getPointData().updateDailyDungeon());
}
}

View File

@@ -1,34 +1,31 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
import emu.grasscutter.utils.Utils;
public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(Scene scene) {
if (scene.getScriptManager().getVariables().containsKey("stage")
&& scene.getScriptManager().getVariables().get("stage") == 1) {
return;
}
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(),
3,
towerManager.canEnterScheduleFloor()
));
scene.broadcastPacket(new PacketDungeonSettleNotify(
scene.getChallenge(),
towerManager.hasNextFloor(),
towerManager.hasNextLevel(),
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()
));
}
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
import emu.grasscutter.utils.Utils;
public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(Scene scene) {
if (scene.getScriptManager().getVariables().containsKey("stage")
&& scene.getScriptManager().getVariables().get("stage") == 1) {
return;
}
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(
new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor()));
scene.broadcastPacket(
new PacketDungeonSettleNotify(
scene.getChallenge(),
towerManager.hasNextFloor(),
towerManager.hasNextLevel(),
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()));
}
}

View File

@@ -1,194 +1,213 @@
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonDrop;
import emu.grasscutter.game.dungeons.DungeonDropEntry;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DungeonChallenge extends WorldChallenge {
private final static Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData = new Int2ObjectOpenHashMap<>();
/**
* has more challenge
*/
private boolean stage;
private IntSet rewardedPlayers;
public DungeonChallenge(Scene scene, SceneGroup group,
int challengeId, int challengeIndex,
List<Integer> paramList,
int timeLimit, int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet());
}
public static void initialize() {
// Read the data we need for dungeon rewards drops.
try {
DataLoader.loadList("DungeonDrop.json", DungeonDrop.class).forEach(entry -> {
dungeonDropData.put(entry.getDungeonId(), entry.getDrops());
});
Grasscutter.getLogger().debug("Loaded {} dungeon drop data entries.", dungeonDropData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex);
}
}
public boolean isStage() {
return stage;
}
public void setStage(boolean stage) {
this.stage = stage;
}
public IntSet getRewardedPlayers() {
return rewardedPlayers;
}
public void setRewardedPlayers(IntSet rewardedPlayers) {
this.rewardedPlayers = rewardedPlayers;
}
@Override
public void done() {
super.done();
if (this.isSuccess()) {
// Settle
settle();
}
}
private void settle() {
if (!stage) {
var scene = this.getScene();
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(getScene()));
scene.getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
new ScriptArgs(this.isSuccess() ? 1 : 0));
// Battle pass trigger
scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON));
}
}
private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.getScene().getDungeonData().getId();
// If we have specific drop data for this dungeon, we use it.
if (dungeonDropData.containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = dungeonDropData.get(dungeonId);
// Roll for each drop group.
for (var entry : dropEntries) {
// Determine the number of drops we get for this entry.
int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1);
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
if (useCondensed) {
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
}
// Double rewards in multiplay mode, if specified.
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
amount *= 2;
}
// Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly.
// For now, we use the following logic: If the possible drop item are a list of multiple items,
// we roll them separately. If not, we stack them. This should work out in practice, at least
// for the currently existing set of dungeons.
if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount));
} else {
for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex);
int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
rewards.add(new GameItem(itemId, 1));
}
}
}
}
// Otherwise, we fall back to the preview data.
else {
Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
}
return rewards;
}
public void getStatueDrops(Player player, GadgetInteractReq request) {
DungeonData dungeonData = getScene().getDungeonData();
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return;
}
// Already rewarded
if (getRewardedPlayers().contains(player.getUid())) {
return;
}
// Get rewards.
List<GameItem> rewards = new ArrayList<>();
if (request.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE) {
// Check if condensed resin is usable here.
// For this, we use the following logic for now:
// The normal resin cost of the dungeon has to be 20.
if (resinCost != 20) {
return;
}
// Spend the condensed resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useCondensedResin(1)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(true));
} else {
// Spend the resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useResin(resinCost)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(false));
}
// Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
getRewardedPlayers().add(player.getUid());
}
}
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonDrop;
import emu.grasscutter.game.dungeons.DungeonDropEntry;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DungeonChallenge extends WorldChallenge {
private static final Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData =
new Int2ObjectOpenHashMap<>();
/** has more challenge */
private boolean stage;
private IntSet rewardedPlayers;
public DungeonChallenge(
Scene scene,
SceneGroup group,
int challengeId,
int challengeIndex,
List<Integer> paramList,
int timeLimit,
int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet());
}
public static void initialize() {
// Read the data we need for dungeon rewards drops.
try {
DataLoader.loadList("DungeonDrop.json", DungeonDrop.class)
.forEach(
entry -> {
dungeonDropData.put(entry.getDungeonId(), entry.getDrops());
});
Grasscutter.getLogger().debug("Loaded {} dungeon drop data entries.", dungeonDropData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex);
}
}
public boolean isStage() {
return stage;
}
public void setStage(boolean stage) {
this.stage = stage;
}
public IntSet getRewardedPlayers() {
return rewardedPlayers;
}
public void setRewardedPlayers(IntSet rewardedPlayers) {
this.rewardedPlayers = rewardedPlayers;
}
@Override
public void done() {
super.done();
if (this.isSuccess()) {
// Settle
settle();
}
}
private void settle() {
if (!stage) {
var scene = this.getScene();
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(getScene()));
scene
.getScriptManager()
.callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0));
// Battle pass trigger
scene
.getPlayers()
.forEach(
p ->
p.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON));
}
}
private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.getScene().getDungeonData().getId();
// If we have specific drop data for this dungeon, we use it.
if (dungeonDropData.containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = dungeonDropData.get(dungeonId);
// Roll for each drop group.
for (var entry : dropEntries) {
// Determine the number of drops we get for this entry.
int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1);
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
if (useCondensed) {
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
}
// Double rewards in multiplay mode, if specified.
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
amount *= 2;
}
// Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly.
// For now, we use the following logic: If the possible drop item are a list of multiple
// items,
// we roll them separately. If not, we stack them. This should work out in practice, at
// least
// for the currently existing set of dungeons.
if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount));
} else {
for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex);
int itemId =
Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
rewards.add(new GameItem(itemId, 1));
}
}
}
}
// Otherwise, we fall back to the preview data.
else {
Grasscutter.getLogger()
.info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
}
return rewards;
}
public void getStatueDrops(Player player, GadgetInteractReq request) {
DungeonData dungeonData = getScene().getDungeonData();
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (!isSuccess()
|| dungeonData == null
|| dungeonData.getRewardPreview() == null
|| dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return;
}
// Already rewarded
if (getRewardedPlayers().contains(player.getUid())) {
return;
}
// Get rewards.
List<GameItem> rewards = new ArrayList<>();
if (request.getResinCostType()
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE) {
// Check if condensed resin is usable here.
// For this, we use the following logic for now:
// The normal resin cost of the dungeon has to be 20.
if (resinCost != 20) {
return;
}
// Spend the condensed resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useCondensedResin(1)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(true));
} else {
// Spend the resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useResin(resinCost)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(false));
}
// Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
getRewardedPlayers().add(player.getUid());
}
}

View File

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

View File

@@ -1,30 +1,37 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.ArrayList;
import java.util.List;
public class ChallengeFactory {
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
static {
challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler());
challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler());
}
public static WorldChallenge getChallenge(int param1, int param2, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
for (var handler : challengeFactoryHandlers) {
if (!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)) {
continue;
}
return handler.build(param1, param2, param3, param4, param5, param6, scene, group);
}
return null;
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.ArrayList;
import java.util.List;
public class ChallengeFactory {
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
static {
challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler());
challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler());
}
public static WorldChallenge getChallenge(
int param1,
int param2,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
for (var handler : challengeFactoryHandlers) {
if (!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)) {
continue;
}
return handler.build(param1, param2, param3, param4, param5, param6, scene, group);
}
return null;
}
}

View File

@@ -1,11 +1,27 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
public interface ChallengeFactoryHandler {
boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group);
WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group);
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
public interface ChallengeFactoryHandler {
boolean isThisType(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
}

View File

@@ -1,33 +1,48 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class DungeonChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
// ActiveChallenge with 1,1000,300,233101003,15,0
return scene.getSceneType() == SceneType.SCENE_DUNGEON
&& param4 == group.id;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param4);
return new DungeonChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param5, param3),
param3, // Limit
param5, // Goal
List.of(new InTimeTrigger(), new KillMonsterTrigger()));
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class DungeonChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
// ActiveChallenge with 1,1000,300,233101003,15,0
return scene.getSceneType() == SceneType.SCENE_DUNGEON && param4 == group.id;
}
@Override
public WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param4);
return new DungeonChallenge(
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param5, param3),
param3, // Limit
param5, // Goal
List.of(new InTimeTrigger(), new KillMonsterTrigger()));
}
}

View File

@@ -1,32 +1,47 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class DungeonGuardChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
// ActiveChallenge with 1,188,234101003,12,3030,0
return scene.getSceneType() == SceneType.SCENE_DUNGEON
&& param3 == group.id;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param3);
return new DungeonChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param4, 0),
0, // Limit
param4, // Goal
List.of(new GuardTrigger()));
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class DungeonGuardChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
// ActiveChallenge with 1,188,234101003,12,3030,0
return scene.getSceneType() == SceneType.SCENE_DUNGEON && param3 == group.id;
}
@Override
public WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param3);
return new DungeonChallenge(
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param4, 0),
0, // Limit
param4, // Goal
List.of(new GuardTrigger()));
}
}

View File

@@ -1,33 +1,48 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillGadgetTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class KillGadgetChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
// kill gadgets(explosive barrel) in time
// ActiveChallenge with 56,201,20,2,201,4
// open chest in time
// ActiveChallenge with 666,202,30,7,202,1
return challengeId == 201 || challengeId == 202;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
return new WorldChallenge(
scene, group,
challengeId, // Id
challengeIndex, // Index
List.of(param3, param6, 0),
param3, // Limit
param6, // Goal
List.of(new InTimeTrigger(), new KillGadgetTrigger())
);
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillGadgetTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class KillGadgetChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
// kill gadgets(explosive barrel) in time
// ActiveChallenge with 56,201,20,2,201,4
// open chest in time
// ActiveChallenge with 666,202,30,7,202,1
return challengeId == 201 || challengeId == 202;
}
@Override
public WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
return new WorldChallenge(
scene,
group,
challengeId, // Id
challengeIndex, // Index
List.of(param3, param6, 0),
param3, // Limit
param6, // Goal
List.of(new InTimeTrigger(), new KillGadgetTrigger()));
}
}

View File

@@ -1,31 +1,46 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class KillMonsterChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
// ActiveChallenge with 180,180,45,133108061,1,0
return challengeId == 180;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param4);
return new WorldChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param5, param3),
param3, // Limit
param5, // Goal
List.of(new KillMonsterTrigger(), new InTimeTrigger())
);
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public class KillMonsterChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
// ActiveChallenge with 180,180,45,133108061,1,0
return challengeId == 180;
}
@Override
public WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
var realGroup = scene.getScriptManager().getGroupById(param4);
return new WorldChallenge(
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(param5, param3),
param3, // Limit
param5, // Goal
List.of(new KillMonsterTrigger(), new InTimeTrigger()));
}
}

View File

@@ -1,25 +1,19 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {
}
public void onFinish(WorldChallenge challenge) {
}
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
}
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {
}
public void onCheckTimeout(WorldChallenge challenge) {
}
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {}
public void onFinish(WorldChallenge challenge) {}
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {}
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {}
public void onCheckTimeout(WorldChallenge challenge) {}
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {}
}

View File

@@ -1,26 +1,26 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class GuardTrigger extends KillMonsterTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
super.onBegin(challenge);
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
}
@Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
int percent = (int) (curHp / maxHp);
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
if (percent <= 0) {
challenge.fail();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class GuardTrigger extends KillMonsterTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
super.onBegin(challenge);
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
}
@Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
int percent = (int) (curHp / maxHp);
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
if (percent <= 0) {
challenge.fail();
}
}
}

View File

@@ -1,13 +1,13 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
public class InTimeTrigger extends ChallengeTrigger {
@Override
public void onCheckTimeout(WorldChallenge challenge) {
var current = System.currentTimeMillis();
if (current - challenge.getStartedAt() > challenge.getTimeLimit() * 1000L) {
challenge.fail();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
public class InTimeTrigger extends ChallengeTrigger {
@Override
public void onCheckTimeout(WorldChallenge challenge) {
var current = System.currentTimeMillis();
if (current - challenge.getStartedAt() > challenge.getTimeLimit() * 1000L) {
challenge.fail();
}
}
}

View File

@@ -1,23 +1,24 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillGadgetTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get()));
}
@Override
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillGadgetTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get()));
}
@Override
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}

View File

@@ -1,23 +1,24 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillMonsterTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillMonsterTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}

View File

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

View File

@@ -1,57 +1,58 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
import lombok.Getter;
public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override)
protected final Position position;
@Getter(onMethod_ = @Override)
protected final Position rotation;
public EntityBaseGadget(Scene scene) {
this(scene, null, null);
}
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene);
this.position = position != null ? position.clone() : new Position();
this.rotation = rotation != null ? rotation.clone() : new Position();
}
public abstract int getGadgetId();
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
protected void fillFightProps(ConfigGadget 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());
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
import lombok.Getter;
public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override)
protected final Position position;
@Getter(onMethod_ = @Override)
protected final Position rotation;
public EntityBaseGadget(Scene scene) {
this(scene, null, null);
}
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene);
this.position = position != null ? position.clone() : new Position();
this.rotation = rotation != null ? rotation.clone() : new Position();
}
public abstract int getGadgetId();
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
protected void fillFightProps(ConfigGadget 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,119 +1,122 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityClientGadget extends EntityBaseGadget {
@Getter
private final Player owner;
@Getter(onMethod_ = @Override)
private final int gadgetId;
@Getter
private final int campId;
@Getter
private final int campType;
@Getter
private final int ownerEntityId;
@Getter
private final int targetEntityId;
@Getter
private final boolean asyncLoad;
@Getter
private final int originalOwnerEntityId;
public EntityClientGadget(Scene scene, Player player, EvtCreateGadgetNotify notify) {
super(scene, new Position(notify.getInitPos()), new Position(notify.getInitEulerAngles()));
this.owner = player;
this.id = notify.getEntityId();
this.gadgetId = notify.getConfigId();
this.campId = notify.getCampId();
this.campType = notify.getCampType();
this.ownerEntityId = notify.getPropOwnerEntityId();
this.targetEntityId = notify.getTargetEntityId();
this.asyncLoad = notify.getIsAsyncLoad();
GameEntity owner = scene.getEntityById(this.ownerEntityId);
if (owner instanceof EntityClientGadget ownerGadget) {
this.originalOwnerEntityId = ownerGadget.getOriginalOwnerEntityId();
} else {
this.originalOwnerEntityId = this.ownerEntityId;
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
ClientGadgetInfoOuterClass.ClientGadgetInfo clientGadget = ClientGadgetInfoOuterClass.ClientGadgetInfo.newBuilder()
.setCampId(this.getCampId())
.setCampType(this.getCampType())
.setOwnerEntityId(this.getOwnerEntityId())
.setTargetEntityId(this.getTargetEntityId())
.setAsyncLoad(this.isAsyncLoad())
.build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setOwnerEntityId(this.getOwnerEntityId())
.setIsEnableInteract(true)
.setClientGadget(clientGadget)
.setPropOwnerEntityId(this.getOwnerEntityId())
.setAuthorityPeerId(this.getOwner().getPeerId());
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityClientGadget extends EntityBaseGadget {
@Getter private final Player owner;
@Getter(onMethod_ = @Override)
private final int gadgetId;
@Getter private final int campId;
@Getter private final int campType;
@Getter private final int ownerEntityId;
@Getter private final int targetEntityId;
@Getter private final boolean asyncLoad;
@Getter private final int originalOwnerEntityId;
public EntityClientGadget(Scene scene, Player player, EvtCreateGadgetNotify notify) {
super(scene, new Position(notify.getInitPos()), new Position(notify.getInitEulerAngles()));
this.owner = player;
this.id = notify.getEntityId();
this.gadgetId = notify.getConfigId();
this.campId = notify.getCampId();
this.campType = notify.getCampType();
this.ownerEntityId = notify.getPropOwnerEntityId();
this.targetEntityId = notify.getTargetEntityId();
this.asyncLoad = notify.getIsAsyncLoad();
GameEntity owner = scene.getEntityById(this.ownerEntityId);
if (owner instanceof EntityClientGadget ownerGadget) {
this.originalOwnerEntityId = ownerGadget.getOriginalOwnerEntityId();
} else {
this.originalOwnerEntityId = this.ownerEntityId;
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
ClientGadgetInfoOuterClass.ClientGadgetInfo clientGadget =
ClientGadgetInfoOuterClass.ClientGadgetInfo.newBuilder()
.setCampId(this.getCampId())
.setCampType(this.getCampType())
.setOwnerEntityId(this.getOwnerEntityId())
.setTargetEntityId(this.getTargetEntityId())
.setAsyncLoad(this.isAsyncLoad())
.build();
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setOwnerEntityId(this.getOwnerEntityId())
.setIsEnableInteract(true)
.setClientGadget(clientGadget)
.setPropOwnerEntityId(this.getOwnerEntityId())
.setAuthorityPeerId(this.getOwner().getPeerId());
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@@ -1,190 +1,206 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.annotation.Nullable;
import java.util.Optional;
@ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget {
@Getter
private final GadgetData gadgetData;
@Getter(onMethod_ = @Override, lazy = true)
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
@Getter(onMethod_ = @Override)
@Setter
private int gadgetId;
@Getter
@Setter
private int state;
@Getter
@Setter
private int pointType;
@Getter
private GadgetContent content;
@Getter
@Setter
private SceneGadget metaGadget;
@Nullable
@Getter
private final ConfigGadget configGadget;
public EntityGadget(Scene scene, int gadgetId, Position pos) {
this(scene, gadgetId, pos, null, null);
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
this(scene, gadgetId, pos, rot, null);
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
super(scene, pos, rot);
this.gadgetData = GameData.getGadgetDataMap().get(gadgetId);
this.configGadget = Optional.ofNullable(this.gadgetData).map(GadgetData::getJsonName).map(GameData.getGadgetConfigData()::get).orElse(null);
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId;
this.content = content;
fillFightProps(configGadget);
}
public void updateState(int state) {
this.setState(state);
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId()));
}
@Deprecated(forRemoval = true) // Dont use!
public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content;
}
// TODO refactor
public void buildContent() {
if (this.getContent() != null || this.getGadgetData() == null || this.getGadgetData().getType() == null) {
return;
}
this.content = switch (this.getGadgetData().getType()) {
case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this);
case Worktop -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this);
case Gadget -> new GadgetObject(this);
default -> null;
};
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
if (this.getContent() == null) {
return;
}
boolean shouldDelete = this.getContent().onInteract(player, interactReq);
if (shouldDelete) {
this.getScene().killEntity(this);
}
}
@Override
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId()));
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
// We do not use the getter to null check because the getter will create a fight prop map if it is null
if (this.fightProperties != null) {
addAllFightPropsToEntityInfo(entityInfo);
}
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.setGadgetState(this.getState())
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.draft_id);
}
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.Optional;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget {
@Getter private final GadgetData gadgetData;
@Getter(onMethod_ = @Override, lazy = true)
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
@Getter(onMethod_ = @Override)
@Setter
private int gadgetId;
@Getter @Setter private int state;
@Getter @Setter private int pointType;
@Getter private GadgetContent content;
@Getter @Setter private SceneGadget metaGadget;
@Nullable @Getter private final ConfigGadget configGadget;
public EntityGadget(Scene scene, int gadgetId, Position pos) {
this(scene, gadgetId, pos, null, null);
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
this(scene, gadgetId, pos, rot, null);
}
public EntityGadget(
Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
super(scene, pos, rot);
this.gadgetData = GameData.getGadgetDataMap().get(gadgetId);
this.configGadget =
Optional.ofNullable(this.gadgetData)
.map(GadgetData::getJsonName)
.map(GameData.getGadgetConfigData()::get)
.orElse(null);
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId;
this.content = content;
fillFightProps(configGadget);
}
public void updateState(int state) {
this.setState(state);
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene()
.getScriptManager()
.callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId()));
}
@Deprecated(forRemoval = true) // Dont use!
public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content;
}
// TODO refactor
public void buildContent() {
if (this.getContent() != null
|| this.getGadgetData() == null
|| this.getGadgetData().getType() == null) {
return;
}
this.content =
switch (this.getGadgetData().getType()) {
case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this);
case Worktop -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this);
case Gadget -> new GadgetObject(this);
default -> null;
};
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
if (this.getContent() == null) {
return;
}
boolean shouldDelete = this.getContent().onInteract(player, interactReq);
if (shouldDelete) {
this.getScene().killEntity(this);
}
}
@Override
public void onCreate() {
// Lua event
getScene()
.getScriptManager()
.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId()));
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene()
.getScriptManager()
.callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
// We do not use the getter to null check because the getter will create a fight prop map if it
// is null
if (this.fightProperties != null) {
addAllFightPropsToEntityInfo(entityInfo);
}
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.setGadgetState(this.getState())
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.draft_id);
}
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@@ -1,131 +1,143 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityItem extends EntityBaseGadget {
@Getter
private final GameItem item;
@Getter
private final long guid;
@Getter
private final boolean share;
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) {
this(scene, player, itemData, pos, count, true);
}
// In official game, some drop items are shared to all players, and some other items are independent to all players
// For example, if you killed a monster in MP mode, all players could get drops but rarity and number of them are different
// but if you broke regional mine, when someone picked up the drop then it disappeared
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) {
super(scene, pos, null);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = share;
}
public ItemData getItemData() {
return this.getItem().getItemData();
}
public int getCount() {
return this.getItem().getCount();
}
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
// check drop owner to avoid someone picked up item in others' world
if (!this.isShare()) {
int dropOwner = (int) (this.getGuid() >> 32);
if (dropOwner != player.getUid()) {
return;
}
}
this.getScene().removeEntity(this);
GameItem item = new GameItem(this.getItemData(), this.getCount());
// Add to inventory
boolean success = player.getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) {
if (!this.isShare()) { // not shared drop
player.sendPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
} else {
this.getScene().broadcastPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
}
}
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityItem extends EntityBaseGadget {
@Getter private final GameItem item;
@Getter private final long guid;
@Getter private final boolean share;
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) {
this(scene, player, itemData, pos, count, true);
}
// In official game, some drop items are shared to all players, and some other items are
// independent to all players
// For example, if you killed a monster in MP mode, all players could get drops but rarity and
// number of them are different
// but if you broke regional mine, when someone picked up the drop then it disappeared
public EntityItem(
Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) {
super(scene, pos, null);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.guid =
player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = share;
}
public ItemData getItemData() {
return this.getItem().getItemData();
}
public int getCount() {
return this.getItem().getCount();
}
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
// check drop owner to avoid someone picked up item in others' world
if (!this.isShare()) {
int dropOwner = (int) (this.getGuid() >> 32);
if (dropOwner != player.getUid()) {
return;
}
}
this.getScene().removeEntity(this);
GameItem item = new GameItem(this.getItemData(), this.getCount());
// Add to inventory
boolean success = player.getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) {
if (!this.isShare()) { // not shared drop
player.sendPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
} else {
this.getScene()
.broadcastPacket(
new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
}
}
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@@ -1,233 +1,260 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import java.util.Optional;
public class EntityMonster extends GameEntity {
@Getter
private final MonsterData monsterData;
@Getter(onMethod_ = @Override)
private final Int2FloatOpenHashMap fightProperties;
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
@Getter
private final Position bornPos;
@Getter
private final int level;
private int weaponEntityId;
@Getter
@Setter
private int poseId;
@Getter
@Setter
private int aiId = -1;
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
super(scene);
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProperties = new Int2FloatOpenHashMap();
this.position = new Position(pos);
this.rotation = new Position();
this.bornPos = getPosition().clone();
this.level = level;
// Monster weapon
if (getMonsterWeaponId() > 0) {
this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
}
this.recalcStats();
}
public int getMonsterWeaponId() {
return this.getMonsterData().getWeaponId();
}
private int getMonsterId() {
return this.getMonsterData().getId();
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
EnvAnimalGatherConfigData gatherData = GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
if (gatherData == null) {
return;
}
player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop);
this.getScene().killEntity(this);
}
@Override
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
}
@Override
public void damage(float amount, int killerId) {
// Get HP before damage.
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Apply damage.
super.damage(amount, killerId);
// Get HP after damage.
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Invoke energy drop logic.
for (Player player : this.getScene().getPlayers()) {
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
var scene = this.getScene();
var challenge = Optional.ofNullable(scene.getChallenge());
var scriptManager = scene.getScriptManager();
Optional.ofNullable(this.getSpawnEntry()).ifPresent(scene.getDeadSpawnedEntities()::add);
// first set the challenge data
challenge.ifPresent(c -> c.onMonsterDeath(this));
if (scriptManager.isInit() && this.getGroupId() > 0) {
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService()).ifPresent(s -> s.onMonsterDead(this));
// prevent spawn monster after success
if (challenge.map(c -> c.inProgress()).orElse(true))
scriptManager.callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}
// Battle Pass trigger
scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
MonsterData.definedFightProperties.forEach(prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
// Level curve
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
FightProperty.forEachCompoundProperty(c -> this.setFightProperty(c.getResult(),
this.getFightProperty(c.getFlat()) + (this.getFightProperty(c.getBase()) * (1f + this.getFightProperty(c.getPercent())))));
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override
public SceneEntityInfo toProto() {
var authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
var entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build());
var monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(this.getPoseId())
.setBlockId(3001)
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.addWeaponList(weaponInfo);
}
if (this.aiId != -1) {
monsterInfo.setAiConfigId(aiId);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.Optional;
import lombok.Getter;
import lombok.Setter;
public class EntityMonster extends GameEntity {
@Getter private final MonsterData monsterData;
@Getter(onMethod_ = @Override)
private final Int2FloatOpenHashMap fightProperties;
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
@Getter private final Position bornPos;
@Getter private final int level;
private int weaponEntityId;
@Getter @Setter private int poseId;
@Getter @Setter private int aiId = -1;
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
super(scene);
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProperties = new Int2FloatOpenHashMap();
this.position = new Position(pos);
this.rotation = new Position();
this.bornPos = getPosition().clone();
this.level = level;
// Monster weapon
if (getMonsterWeaponId() > 0) {
this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
}
this.recalcStats();
}
public int getMonsterWeaponId() {
return this.getMonsterData().getWeaponId();
}
private int getMonsterId() {
return this.getMonsterData().getId();
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
EnvAnimalGatherConfigData gatherData =
GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
if (gatherData == null) {
return;
}
player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop);
this.getScene().killEntity(this);
}
@Override
public void onCreate() {
// Lua event
getScene()
.getScriptManager()
.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
}
@Override
public void damage(float amount, int killerId) {
// Get HP before damage.
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Apply damage.
super.damage(amount, killerId);
// Get HP after damage.
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Invoke energy drop logic.
for (Player player : this.getScene().getPlayers()) {
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
var scene = this.getScene();
var challenge = Optional.ofNullable(scene.getChallenge());
var scriptManager = scene.getScriptManager();
Optional.ofNullable(this.getSpawnEntry()).ifPresent(scene.getDeadSpawnedEntities()::add);
// first set the challenge data
challenge.ifPresent(c -> c.onMonsterDeath(this));
if (scriptManager.isInit() && this.getGroupId() > 0) {
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService())
.ifPresent(s -> s.onMonsterDead(this));
// prevent spawn monster after success
if (challenge.map(c -> c.inProgress()).orElse(true))
scriptManager.callEvent(
EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}
// Battle Pass trigger
scene
.getPlayers()
.forEach(
p ->
p.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none
float hpPercent =
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0
? 1f
: this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP)
/ this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
MonsterData.definedFightProperties.forEach(
prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
// Level curve
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(
prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
FightProperty.forEachCompoundProperty(
c ->
this.setFightProperty(
c.getResult(),
this.getFightProperty(c.getFlat())
+ (this.getFightProperty(c.getBase())
* (1f + this.getFightProperty(c.getPercent())))));
// Set current hp
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override
public SceneEntityInfo toProto() {
var authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
var entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build());
var monsterInfo =
SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(this.getPoseId())
.setBlockId(3001)
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo =
SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.addWeaponList(weaponInfo);
}
if (this.aiId != -1) {
monsterInfo.setAiConfigId(aiId);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build();
}
}

View File

@@ -1,70 +1,77 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.scripts.data.SceneNPC;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityNPC extends GameEntity {
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
private final SceneNPC metaNpc;
@Getter
private final int suiteId;
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.config_id);
setGroupId(metaNPC.group.id);
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.pos.clone();
this.rotation = metaNPC.rot.clone();
this.metaNpc = metaNPC;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC)
.setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(VectorOuterClass.Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
entityInfo.setNpc(SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
.setNpcId(metaNpc.npc_id)
.setBlockId(getBlockId())
.build());
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.scripts.data.SceneNPC;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityNPC extends GameEntity {
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
private final SceneNPC metaNpc;
@Getter private final int suiteId;
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.config_id);
setGroupId(metaNPC.group.id);
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.pos.clone();
this.rotation = metaNPC.rot.clone();
this.metaNpc = metaNPC;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority =
EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(
EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo =
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC)
.setMotionInfo(
MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(VectorOuterClass.Vector.newBuilder()))
.addAnimatorParaList(
AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair
.newBuilder())
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
entityInfo.setNpc(
SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
.setNpcId(metaNpc.npc_id)
.setBlockId(getBlockId())
.build());
return entityInfo.build();
}
}

View File

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

View File

@@ -1,32 +1,34 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.entity.platform.EntityPlatform;
import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass;
import lombok.Getter;
public class EntitySolarIsotomaClientGadget extends EntityClientGadget {
public static final int GADGET_ID = 41038001;
public static final int ELEVATOR_GADGET_ID = 41038002;
@Getter
private EntityPlatform platformGadget;
public EntitySolarIsotomaClientGadget(Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
super(scene, player, notify);
}
@Override
public void onCreate() {
//Create solar isotoma elevator and send to all.
this.platformGadget = new EntitySolarIsotomaElevatorPlatform(this, getScene(), getOwner(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
getScene().addEntity(this.platformGadget);
}
@Override
public void onRemoved() {
//Remove solar isotoma elevator entity.
getScene().removeEntity(this.platformGadget);
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.entity.platform.EntityPlatform;
import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass;
import lombok.Getter;
public class EntitySolarIsotomaClientGadget extends EntityClientGadget {
public static final int GADGET_ID = 41038001;
public static final int ELEVATOR_GADGET_ID = 41038002;
@Getter private EntityPlatform platformGadget;
public EntitySolarIsotomaClientGadget(
Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
super(scene, player, notify);
}
@Override
public void onCreate() {
// Create solar isotoma elevator and send to all.
this.platformGadget =
new EntitySolarIsotomaElevatorPlatform(
this, getScene(), getOwner(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
getScene().addEntity(this.platformGadget);
}
@Override
public void onRemoved() {
// Remove solar isotoma elevator entity.
getScene().removeEntity(this.platformGadget);
}
}

View File

@@ -1,120 +1,125 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo;
import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EntityVehicle extends EntityBaseGadget {
@Getter
private final Player owner;
@Getter(onMethod_ = @Override)
private final Int2FloatMap fightProperties;
@Getter
private final int pointId;
@Getter
private final int gadgetId;
@Getter
@Setter
private float curStamina;
@Getter
private final List<VehicleMember> vehicleMembers;
@Nullable
@Getter
private ConfigGadget configGadget;
public EntityVehicle(Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) {
super(scene, pos, rot);
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.gadgetId = gadgetId;
this.pointId = pointId;
this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP
this.vehicleMembers = new ArrayList<>();
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
protected void fillFightProps(ConfigGadget configGadget) {
super.fillFightProps(configGadget);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0);
this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0);
}
@Override
public SceneEntityInfo toProto() {
VehicleInfo vehicle = VehicleInfo.newBuilder()
.setOwnerUid(this.owner.getUid())
.setCurStamina(getCurStamina())
.build();
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setAuthorityPeerId(this.getOwner().getPeerId())
.setIsEnableInteract(true)
.setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.build();
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(pair);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo;
import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public class EntityVehicle extends EntityBaseGadget {
@Getter private final Player owner;
@Getter(onMethod_ = @Override)
private final Int2FloatMap fightProperties;
@Getter private final int pointId;
@Getter private final int gadgetId;
@Getter @Setter private float curStamina;
@Getter private final List<VehicleMember> vehicleMembers;
@Nullable @Getter private ConfigGadget configGadget;
public EntityVehicle(
Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) {
super(scene, pos, rot);
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.gadgetId = gadgetId;
this.pointId = pointId;
this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP
this.vehicleMembers = new ArrayList<>();
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
protected void fillFightProps(ConfigGadget configGadget) {
super.fillFightProps(configGadget);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0);
this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0);
}
@Override
public SceneEntityInfo toProto() {
VehicleInfo vehicle =
VehicleInfo.newBuilder()
.setOwnerUid(this.owner.getUid())
.setCurStamina(getCurStamina())
.build();
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setAuthorityPeerId(this.getOwner().getPeerId())
.setIsEnableInteract(true)
.setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.build();
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(pair);
return entityInfo.build();
}
}

View File

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

View File

@@ -1,71 +1,88 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
import emu.grasscutter.net.proto.InteractTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataSystem().getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if (handler == null) {
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName());
return false;
}
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
return false;
} else {
boolean success;
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
success = bossChestInteractHandler.onInteract(this, player,
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
} else {
success = handler.onInteract(this, player);
}
if (!success) {
return false;
}
getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
return true;
}
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (getGadget().getMetaGadget() == null) {
return;
}
var bossChest = getGadget().getMetaGadget().boss_chest;
if (bossChest != null) {
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest(BossChestInfo.newBuilder()
.setMonsterConfigId(bossChest.monster_config_id)
.setResin(bossChest.resin)
.addAllQualifyUidList(players)
.addAllRemainUidList(players)
.build());
}
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
import emu.grasscutter.net.proto.InteractTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
var chestInteractHandlerMap =
getGadget()
.getScene()
.getWorld()
.getServer()
.getWorldDataSystem()
.getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if (handler == null) {
Grasscutter.getLogger()
.warn(
"Could not found the handler of this type of Chests {}",
getGadget().getGadgetData().getJsonName());
return false;
}
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket(
new PacketGadgetInteractRsp(
getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
return false;
} else {
boolean success;
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
success =
bossChestInteractHandler.onInteract(
this,
player,
req.getResinCostType()
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
} else {
success = handler.onInteract(this, player);
}
if (!success) {
return false;
}
getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(
new PacketGadgetInteractRsp(
this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
return true;
}
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (getGadget().getMetaGadget() == null) {
return;
}
var bossChest = getGadget().getMetaGadget().boss_chest;
if (bossChest != null) {
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest(
BossChestInfo.newBuilder()
.setMonsterConfigId(bossChest.monster_config_id)
.setResin(bossChest.resin)
.addAllQualifyUidList(players)
.addAllRemainUidList(players)
.build());
}
}
}

View File

@@ -1,22 +1,22 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
private final EntityGadget gadget;
public GadgetContent(EntityGadget gadget) {
this.gadget = gadget;
}
public EntityGadget getGadget() {
return gadget;
}
public abstract boolean onInteract(Player player, GadgetInteractReq req);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
private final EntityGadget gadget;
public GadgetContent(EntityGadget gadget) {
this.gadget = gadget;
}
public EntityGadget getGadget() {
return gadget;
}
public abstract boolean onInteract(Player player, GadgetInteractReq req);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}

View File

@@ -1,81 +1,86 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Utils;
public class GadgetGatherObject extends GadgetContent {
private int itemId;
private boolean isForbidGuest;
public GadgetGatherObject(EntityGadget gadget) {
super(gadget);
if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId();
}
}
public int getItemId() {
return this.itemId;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean onInteract(Player player, GadgetInteractReq req) {
// Sanity check
ItemData itemData = GameData.getItemDataMap().get(getItemId());
if (itemData == null) {
return false;
}
GameItem item = new GameItem(itemData, 1);
player.getInventory().addItem(item, ActionReason.Gather);
getGadget().getScene().broadcastPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_GATHER));
return true;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest())
.build();
gadgetInfo.setGatherGadget(gatherGadgetInfo);
}
public void dropItems(Player player) {
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2);
for (int i = 0; i < times; i++) {
EntityItem item = 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
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Utils;
public class GadgetGatherObject extends GadgetContent {
private int itemId;
private boolean isForbidGuest;
public GadgetGatherObject(EntityGadget gadget) {
super(gadget);
if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId();
}
}
public int getItemId() {
return this.itemId;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean onInteract(Player player, GadgetInteractReq req) {
// Sanity check
ItemData itemData = GameData.getItemDataMap().get(getItemId());
if (itemData == null) {
return false;
}
GameItem item = new GameItem(itemData, 1);
player.getInventory().addItem(item, ActionReason.Gather);
getGadget()
.getScene()
.broadcastPacket(
new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_GATHER));
return true;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo =
GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest())
.build();
gadgetInfo.setGatherGadget(gatherGadgetInfo);
}
public void dropItems(Player player) {
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2);
for (int i = 0; i < times; i++) {
EntityItem item =
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,79 +1,83 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.utils.Utils;
public class GadgetGatherPoint extends GadgetContent {
private final int itemId;
private boolean isForbidGuest;
public GadgetGatherPoint(EntityGadget gadget) {
super(gadget);
if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId();
} else {
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
this.itemId = gatherData.getItemId();
this.isForbidGuest = gatherData.isForbidGuest();
}
}
public int getItemId() {
return this.itemId;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean onInteract(Player player, GadgetInteractReq req) {
GameItem item = new GameItem(getItemId(), 1);
player.getInventory().addItem(item, ActionReason.Gather);
return true;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest())
.build();
gadgetInfo.setGatherGadget(gatherGadgetInfo);
}
public void dropItems(Player player) {
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2);
for (int i = 0; i < times; i++) {
EntityItem item = 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
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.utils.Utils;
public class GadgetGatherPoint extends GadgetContent {
private final int itemId;
private boolean isForbidGuest;
public GadgetGatherPoint(EntityGadget gadget) {
super(gadget);
if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId();
} else {
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
this.itemId = gatherData.getItemId();
this.isForbidGuest = gatherData.isForbidGuest();
}
}
public int getItemId() {
return this.itemId;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean onInteract(Player player, GadgetInteractReq req) {
GameItem item = new GameItem(getItemId(), 1);
player.getInventory().addItem(item, ActionReason.Gather);
return true;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo =
GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId())
.setIsForbidGuest(this.isForbidGuest())
.build();
gadgetInfo.setGatherGadget(gatherGadgetInfo);
}
public void dropItems(Player player) {
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2);
for (int i = 0; i < times; i++) {
EntityItem item =
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,22 +1,20 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass;
public class GadgetObject extends GadgetContent {
public GadgetObject(EntityGadget gadget) {
super(gadget);
}
@Override
public boolean onInteract(Player player, GadgetInteractReqOuterClass.GadgetInteractReq req) {
return false;
}
@Override
public void onBuildProto(SceneGadgetInfoOuterClass.SceneGadgetInfo.Builder gadgetInfo) {
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass;
public class GadgetObject extends GadgetContent {
public GadgetObject(EntityGadget gadget) {
super(gadget);
}
@Override
public boolean onInteract(Player player, GadgetInteractReqOuterClass.GadgetInteractReq req) {
return false;
}
@Override
public void onBuildProto(SceneGadgetInfoOuterClass.SceneGadgetInfo.Builder gadgetInfo) {}
}

View File

@@ -1,30 +1,30 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
dungeonChallenge.getStatueDrops(player, req);
}
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
if (player.getScene().getChallenge() != null
&& player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
dungeonChallenge.getStatueDrops(player, req);
}
player.sendPacket(
new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {}
}

View File

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

View File

@@ -1,51 +1,61 @@
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import java.util.ArrayList;
import java.util.List;
public class BossChestInteractHandler implements ChestInteractHandler {
@Override
public boolean isTwoStep() {
return true;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
return this.onInteract(chest, player, false);
}
public boolean onInteract(GadgetChest chest, Player player, boolean useCondensedResin) {
var blossomRewards = player.getScene().getBlossomManager().onReward(player, chest.getGadget(), useCondensedResin);
if (blossomRewards != null) {
player.getInventory().addItems(blossomRewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(blossomRewards));
return true;
}
var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem();
var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id);
var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if (reward == null) {
Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id);
return false;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
return true;
}
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import java.util.ArrayList;
import java.util.List;
public class BossChestInteractHandler implements ChestInteractHandler {
@Override
public boolean isTwoStep() {
return true;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
return this.onInteract(chest, player, false);
}
public boolean onInteract(GadgetChest chest, Player player, boolean useCondensedResin) {
var blossomRewards =
player
.getScene()
.getBlossomManager()
.onReward(player, chest.getGadget(), useCondensedResin);
if (blossomRewards != null) {
player.getInventory().addItems(blossomRewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(blossomRewards));
return true;
}
var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem();
var monster =
chest
.getGadget()
.getMetaGadget()
.group
.monsters
.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id);
var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if (reward == null) {
Grasscutter.getLogger()
.warn("Could not found the reward of boss monster {}", monster.monster_id);
return false;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.getPreviewItems()) {
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,42 +1,50 @@
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.ChestReward;
import java.util.Random;
public class NormalChestInteractHandler implements ChestInteractHandler {
private final ChestReward chestReward;
public NormalChestInteractHandler(ChestReward rewardData) {
this.chestReward = rewardData;
}
@Override
public boolean isTwoStep() {
return false;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
player.earnExp(chestReward.getAdvExp());
player.getInventory().addItem(201, chestReward.getResin());
var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5);
player.getInventory().addItem(202, (int) mora);
for (int i = 0; i < chestReward.getContent().size(); i++) {
chest.getGadget().getScene().addItemEntity(chestReward.getContent().get(i).getItemId(), chestReward.getContent().get(i).getCount(), chest.getGadget());
}
var random = new Random(System.currentTimeMillis());
for (int i = 0; i < chestReward.getRandomCount(); i++) {
var index = random.nextInt(chestReward.getRandomContent().size());
var item = chestReward.getRandomContent().get(index);
chest.getGadget().getScene().addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
}
return true;
}
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.ChestReward;
import java.util.Random;
public class NormalChestInteractHandler implements ChestInteractHandler {
private final ChestReward chestReward;
public NormalChestInteractHandler(ChestReward rewardData) {
this.chestReward = rewardData;
}
@Override
public boolean isTwoStep() {
return false;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
player.earnExp(chestReward.getAdvExp());
player.getInventory().addItem(201, chestReward.getResin());
var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5);
player.getInventory().addItem(202, (int) mora);
for (int i = 0; i < chestReward.getContent().size(); i++) {
chest
.getGadget()
.getScene()
.addItemEntity(
chestReward.getContent().get(i).getItemId(),
chestReward.getContent().get(i).getCount(),
chest.getGadget());
}
var random = new Random(System.currentTimeMillis());
for (int i = 0; i < chestReward.getRandomCount(); i++) {
var index = random.nextInt(chestReward.getRandomContent().size());
var item = chestReward.getRandomContent().get(index);
chest
.getGadget()
.getScene()
.addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
}
return true;
}
}

View File

@@ -1,7 +1,7 @@
package emu.grasscutter.game.entity.gadget.worktop;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
public interface WorktopWorktopOptionHandler {
boolean onSelectWorktopOption(GadgetWorktop gadgetWorktop, int option);
}
package emu.grasscutter.game.entity.gadget.worktop;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
public interface WorktopWorktopOptionHandler {
boolean onSelectWorktopOption(GadgetWorktop gadgetWorktop, int option);
}

View File

@@ -1,97 +1,101 @@
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.EntityBaseGadget;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
public class EntityPlatform extends EntityBaseGadget {
@Getter
private final Player owner;
@Getter(onMethod_ = @Override)
private final int gadgetId;
@Getter
private final EntityClientGadget gadget;
@Getter(onMethod_ = @Override)
private final Int2FloatMap fightProperties;
@Getter
private final MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType;
@Nullable
@Getter
private ConfigGadget configGadget;
@Getter
@Setter
private boolean isStarted;
@Getter
@Setter
private boolean isActive;
public EntityPlatform(EntityClientGadget gadget, Scene scene, Player player, int gadgetId, Position pos, Position rot, MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType) {
super(scene, pos, rot);
this.gadget = gadget;
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.movingPlatformType = movingPlatformType;
this.gadgetId = gadgetId;
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
var platform = PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setMovingPlatformType(movingPlatformType)
.build();
var gadgetInfo = SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
.setGadgetId(getGadgetId())
.setAuthorityPeerId(getOwner().getPeerId())
.setPlatform(platform);
var entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setGadget(gadgetInfo)
.setLifeState(1);
this.addAllFightPropsToEntityInfo(entityInfo);
return entityInfo.build();
}
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(getScene().getSceneTime())
.setIsStarted(true)
.setPosOffset(getPosition().toProto())
.setMovingPlatformType(getMovingPlatformType())
.setIsActive(true)
.build();
}
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
var sceneTime = getScene().getSceneTime();
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(sceneTime)
.setStopSceneTime(sceneTime)
.setPosOffset(getPosition().toProto())
.setMovingPlatformType(getMovingPlatformType())
.build();
}
}
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.EntityBaseGadget;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public class EntityPlatform extends EntityBaseGadget {
@Getter private final Player owner;
@Getter(onMethod_ = @Override)
private final int gadgetId;
@Getter private final EntityClientGadget gadget;
@Getter(onMethod_ = @Override)
private final Int2FloatMap fightProperties;
@Getter private final MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType;
@Nullable @Getter private ConfigGadget configGadget;
@Getter @Setter private boolean isStarted;
@Getter @Setter private boolean isActive;
public EntityPlatform(
EntityClientGadget gadget,
Scene scene,
Player player,
int gadgetId,
Position pos,
Position rot,
MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType) {
super(scene, pos, rot);
this.gadget = gadget;
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.movingPlatformType = movingPlatformType;
this.gadgetId = gadgetId;
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
var platform =
PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setMovingPlatformType(movingPlatformType)
.build();
var gadgetInfo =
SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
.setGadgetId(getGadgetId())
.setAuthorityPeerId(getOwner().getPeerId())
.setPlatform(platform);
var entityInfo =
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setGadget(gadgetInfo)
.setLifeState(1);
this.addAllFightPropsToEntityInfo(entityInfo);
return entityInfo.build();
}
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(getScene().getSceneTime())
.setIsStarted(true)
.setPosOffset(getPosition().toProto())
.setMovingPlatformType(getMovingPlatformType())
.setIsActive(true)
.build();
}
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
var sceneTime = getScene().getSceneTime();
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(sceneTime)
.setStopSceneTime(sceneTime)
.setPosOffset(getPosition().toProto())
.setMovingPlatformType(getMovingPlatformType())
.build();
}
}

View File

@@ -1,136 +1,153 @@
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntitySolarIsotomaClientGadget;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform {
public EntitySolarIsotomaElevatorPlatform(EntitySolarIsotomaClientGadget isotoma, Scene scene, Player player, int gadgetId, Position pos, Position rot) {
super(isotoma, scene, player, gadgetId, pos, rot, MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY);
}
@Override
protected void fillFightProps(ConfigGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
if (combatProperties.isUseCreatorProperty()) {
//If useCreatorProperty == true, use owner's property;
GameEntity ownerAvatar = getScene().getEntityById(getGadget().getOwnerEntityId());
if (ownerAvatar != null) {
getFightProperties().putAll(ownerAvatar.getFightProperties());
return;
} else {
Grasscutter.getLogger().warn("Why gadget owner is null?");
}
}
super.fillFightProps(configGadget);
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
var gadget = SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
.setGadgetId(getGadgetId())
.setOwnerEntityId(getGadget().getId())
.setAuthorityPeerId(getOwner().getPeerId())
.setIsEnableInteract(true)
.setAbilityGadget(AbilityGadgetInfoOuterClass.AbilityGadgetInfo.newBuilder()
.setCampId(getGadget().getCampId())
.setCampTargetType(getGadget().getCampType())
.setTargetEntityId(getGadget().getId())
.build())
.setPlatform(PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartRot(MathQuaternionOuterClass.MathQuaternion.newBuilder()
.setW(1.0F)
.build())
.setPosOffset(getGadget().getPosition().toProto())
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder()
.setW(1.0F)
.build())
.setMovingPlatformType(MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY)
.build())
.build();
var authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getGadget().getPosition().toProto()))
.setBornPos(getGadget().getPosition().toProto())
.build();
var info = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setEntityId(getId())
.setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getGadget().getPosition().toProto())
.setRot(getGadget().getRotation().toProto())
.build());
GameEntity entity = getScene().getEntityById(getGadget().getOwnerEntityId());
if (entity instanceof EntityAvatar avatar) {
info.addPropList(PropPairOuterClass.PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, avatar.getAvatar().getLevel()))
.build());
} else {
Grasscutter.getLogger().warn("Why gadget owner doesn't exist?");
}
this.addAllFightPropsToEntityInfo(info);
info.setLifeState(1)
.setGadget(gadget)
.setEntityAuthorityInfo(authority);
return info.build();
}
@Override
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
setStarted(true);
setActive(true);
var sceneTime = getScene().getSceneTime();
getOwner().sendPacket(new PacketSceneTimeNotify(getOwner()));
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(sceneTime + 300)
.setIsStarted(true)
.setPosOffset(getPosition().toProto())
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder()
.setW(1.0F)
.build())
.setMovingPlatformType(getMovingPlatformType())
.setIsActive(true)
.build();
}
@Override
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
setStarted(false);
setActive(false);
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(getScene().getSceneTime())
.setStopSceneTime(getScene().getSceneTime())
.setPosOffset(getPosition().toProto())
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder()
.setW(1.0F)
.build())
.setMovingPlatformType(getMovingPlatformType())
.build();
}
}
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.ConfigGadget;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntitySolarIsotomaClientGadget;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform {
public EntitySolarIsotomaElevatorPlatform(
EntitySolarIsotomaClientGadget isotoma,
Scene scene,
Player player,
int gadgetId,
Position pos,
Position rot) {
super(
isotoma,
scene,
player,
gadgetId,
pos,
rot,
MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY);
}
@Override
protected void fillFightProps(ConfigGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
if (combatProperties.isUseCreatorProperty()) {
// If useCreatorProperty == true, use owner's property;
GameEntity ownerAvatar = getScene().getEntityById(getGadget().getOwnerEntityId());
if (ownerAvatar != null) {
getFightProperties().putAll(ownerAvatar.getFightProperties());
return;
} else {
Grasscutter.getLogger().warn("Why gadget owner is null?");
}
}
super.fillFightProps(configGadget);
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
var gadget =
SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
.setGadgetId(getGadgetId())
.setOwnerEntityId(getGadget().getId())
.setAuthorityPeerId(getOwner().getPeerId())
.setIsEnableInteract(true)
.setAbilityGadget(
AbilityGadgetInfoOuterClass.AbilityGadgetInfo.newBuilder()
.setCampId(getGadget().getCampId())
.setCampTargetType(getGadget().getCampType())
.setTargetEntityId(getGadget().getId())
.build())
.setPlatform(
PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartRot(
MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
.setPosOffset(getGadget().getPosition().toProto())
.setRotOffset(
MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
.setMovingPlatformType(
MovingPlatformTypeOuterClass.MovingPlatformType
.MOVING_PLATFORM_TYPE_ABILITY)
.build())
.build();
var authority =
EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAiInfo(
SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getGadget().getPosition().toProto()))
.setBornPos(getGadget().getPosition().toProto())
.build();
var info =
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setEntityId(getId())
.setMotionInfo(
MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getGadget().getPosition().toProto())
.setRot(getGadget().getRotation().toProto())
.build());
GameEntity entity = getScene().getEntityById(getGadget().getOwnerEntityId());
if (entity instanceof EntityAvatar avatar) {
info.addPropList(
PropPairOuterClass.PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(
ProtoHelper.newPropValue(
PlayerProperty.PROP_LEVEL, avatar.getAvatar().getLevel()))
.build());
} else {
Grasscutter.getLogger().warn("Why gadget owner doesn't exist?");
}
this.addAllFightPropsToEntityInfo(info);
info.setLifeState(1).setGadget(gadget).setEntityAuthorityInfo(authority);
return info.build();
}
@Override
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
setStarted(true);
setActive(true);
var sceneTime = getScene().getSceneTime();
getOwner().sendPacket(new PacketSceneTimeNotify(getOwner()));
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(sceneTime + 300)
.setIsStarted(true)
.setPosOffset(getPosition().toProto())
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
.setMovingPlatformType(getMovingPlatformType())
.setIsActive(true)
.build();
}
@Override
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
setStarted(false);
setActive(false);
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
.setStartSceneTime(getScene().getSceneTime())
.setStopSceneTime(getScene().getSceneTime())
.setPosOffset(getPosition().toProto())
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
.setMovingPlatformType(getMovingPlatformType())
.build();
}
}

View File

@@ -1,25 +1,25 @@
package emu.grasscutter.game.expedition;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class ExpeditionInfo {
private int state;
private int expId;
private int hourTime;
private int startTime;
public AvatarExpeditionInfo toProto() {
return AvatarExpeditionInfo.newBuilder()
.setStateValue(this.getState())
.setExpId(this.getExpId())
.setHourTime(this.getHourTime())
.setStartTime(this.getStartTime())
.build();
}
}
package emu.grasscutter.game.expedition;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class ExpeditionInfo {
private int state;
private int expId;
private int hourTime;
private int startTime;
public AvatarExpeditionInfo toProto() {
return AvatarExpeditionInfo.newBuilder()
.setStateValue(this.getState())
.setExpId(this.getExpId())
.setHourTime(this.getHourTime())
.setStartTime(this.getStartTime())
.build();
}
}

View File

@@ -1,18 +1,15 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class ExpeditionRewardData {
@Getter
private int itemId;
@Getter
private int minCount;
@Getter
private int maxCount;
public GameItem getReward() {
return new GameItem(itemId, Utils.randomRange(minCount, maxCount));
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class ExpeditionRewardData {
@Getter private int itemId;
@Getter private int minCount;
@Getter private int maxCount;
public GameItem getReward() {
return new GameItem(itemId, Utils.randomRange(minCount, maxCount));
}
}

View File

@@ -1,22 +1,19 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
public class ExpeditionRewardDataList {
@Getter
private int hourTime;
@Getter
private List<ExpeditionRewardData> expeditionRewardData;
public List<GameItem> getRewards() {
List<GameItem> rewards = new ArrayList<>();
if (expeditionRewardData != null) {
expeditionRewardData.forEach(data -> rewards.add(data.getReward()));
}
return rewards;
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardDataList {
@Getter private int hourTime;
@Getter private List<ExpeditionRewardData> expeditionRewardData;
public List<GameItem> getRewards() {
List<GameItem> rewards = new ArrayList<>();
if (expeditionRewardData != null) {
expeditionRewardData.forEach(data -> rewards.add(data.getReward()));
}
return rewards;
}
}

View File

@@ -1,12 +1,9 @@
package emu.grasscutter.game.expedition;
import lombok.Getter;
import java.util.List;
public class ExpeditionRewardInfo {
@Getter
private int expId;
@Getter
private List<ExpeditionRewardDataList> expeditionRewardDataList;
}
package emu.grasscutter.game.expedition;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardInfo {
@Getter private int expId;
@Getter private List<ExpeditionRewardDataList> expeditionRewardDataList;
}

View File

@@ -1,41 +1,42 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class ExpeditionSystem extends BaseGameSystem {
private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData;
public ExpeditionSystem(GameServer server) {
super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {
return expeditionRewardData;
}
public synchronized void load() {
getExpeditionRewardDataList().clear();
try {
List<ExpeditionRewardInfo> banners = DataLoader.loadList("ExpeditionReward.json", ExpeditionRewardInfo.class);
if (banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
}
Grasscutter.getLogger().debug("Expedition reward successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load expedition reward. Expedition reward size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load expedition reward.", e);
}
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class ExpeditionSystem extends BaseGameSystem {
private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData;
public ExpeditionSystem(GameServer server) {
super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {
return expeditionRewardData;
}
public synchronized void load() {
getExpeditionRewardDataList().clear();
try {
List<ExpeditionRewardInfo> banners =
DataLoader.loadList("ExpeditionReward.json", ExpeditionRewardInfo.class);
if (banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
}
Grasscutter.getLogger().debug("Expedition reward successfully loaded.");
} else {
Grasscutter.getLogger()
.error("Unable to load expedition reward. Expedition reward size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load expedition reward.", e);
}
}
}

View File

@@ -1,254 +1,254 @@
package emu.grasscutter.game.friends;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class FriendsList extends BasePlayerManager {
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(Player player) {
super(player);
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
return;
}
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid) || this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/**
* Gets total amount of potential friends
*/
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
Player friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship = friend.getFriendsList().getFriendshipById(getPlayer().getUid());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}
package emu.grasscutter.game.friends;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class FriendsList extends BasePlayerManager {
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(Player player) {
super(player);
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
return;
}
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid)
|| this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
Player friend =
getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship =
friend.getFriendsList().getFriendshipById(getPlayer().getUid());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}

View File

@@ -1,116 +1,116 @@
package emu.grasscutter.game.friends;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.PlatformTypeOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import org.bson.types.ObjectId;
@Entity(value = "friendships", useDiscriminator = false)
public class Friendship {
@Id
private ObjectId id;
@Transient
private Player owner;
@Indexed
private int ownerId;
@Indexed
private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() {
}
public Friendship(Player owner, Player friend, Player asker) {
this.setOwner(owner);
this.ownerId = owner.getUid();
this.friendId = friend.getUid();
this.profile = friend.getProfile();
this.askerId = asker.getUid();
}
public Player getOwner() {
return owner;
}
public void setOwner(Player owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(Player character) {
if (character == null || this.friendId != character.getUid()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto = FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(getFriendProfile().isOnline() ? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE : FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build();
return proto;
}
}
package emu.grasscutter.game.friends;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.PlatformTypeOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import org.bson.types.ObjectId;
@Entity(value = "friendships", useDiscriminator = false)
public class Friendship {
@Id private ObjectId id;
@Transient private Player owner;
@Indexed private int ownerId;
@Indexed private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() {}
public Friendship(Player owner, Player friend, Player asker) {
this.setOwner(owner);
this.ownerId = owner.getUid();
this.friendId = friend.getUid();
this.profile = friend.getProfile();
this.askerId = asker.getUid();
}
public Player getOwner() {
return owner;
}
public void setOwner(Player owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(Player character) {
if (character == null || this.friendId != character.getUid()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto =
FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(
ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(
getFriendProfile().isOnline()
? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE
: FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build();
return proto;
}
}

View File

@@ -1,106 +1,105 @@
package emu.grasscutter.game.friends;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
@Entity
public class PlayerProfile {
@Transient
private Player player;
@AlsoLoad("id")
private int uid;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() {
}
public PlayerProfile(Player player) {
this.uid = player.getUid();
this.syncWithCharacter(player);
}
public int getUid() {
return uid;
}
public Player getPlayer() {
return player;
}
public synchronized void setPlayer(Player player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(Player player) {
if (player == null) {
return;
}
this.uid = player.getUid();
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
//this.achievements = 0;
this.updateLastActiveTime();
}
}
package emu.grasscutter.game.friends;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
@Entity
public class PlayerProfile {
@Transient private Player player;
@AlsoLoad("id")
private int uid;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() {}
public PlayerProfile(Player player) {
this.uid = player.getUid();
this.syncWithCharacter(player);
}
public int getUid() {
return uid;
}
public Player getPlayer() {
return player;
}
public synchronized void setPlayer(Player player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(Player player) {
if (player == null) {
return;
}
this.uid = player.getUid();
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
// this.achievements = 0;
this.updateLastActiveTime();
}
}

View File

@@ -1,292 +1,352 @@
package emu.grasscutter.game.gacha;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
import static emu.grasscutter.config.Configuration.*;
public class GachaBanner {
// Constants used by the BannerType enum
static final int[][] DEFAULT_WEIGHTS_4 = {{1, 510}, {8, 510}, {10, 10000}};
static final int[][] DEFAULT_WEIGHTS_4_WEAPON = {{1, 600}, {7, 600}, {8, 6600}, {10, 12600}};
static final int[][] DEFAULT_WEIGHTS_5 = {{1, 75}, {73, 150}, {90, 10000}};
static final int[][] DEFAULT_WEIGHTS_5_CHARACTER = {{1, 80}, {73, 80}, {90, 10000}};
static final int[][] DEFAULT_WEIGHTS_5_WEAPON = {{1, 100}, {62, 100}, {73, 7800}, {80, 10000}};
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; // Default weapons
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_1 = {1003, 1016, 1042, 1035, 1041, 1069}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; // Default weapons
static final int[] EMPTY_POOL = {}; // Used to remove a type of fallback
@Getter
int scheduleId = -1;
@Getter
int sortId = -1;
@Getter
private int gachaType = -1;
@Getter
private String prefabPath;
@Getter
private String previewPrefabPath;
@Getter
private String titlePath;
private int costItemId = 0;
private final int costItemAmount = 1;
private int costItemId10 = 0;
private final int costItemAmount10 = 10;
@Getter
private final int beginTime = 0;
@Getter
private final int endTime = 1924992000;
@Getter
private final int gachaTimesLimit = Integer.MAX_VALUE;
@Getter
private final int[] rateUpItems4 = {};
@Getter
private final int[] rateUpItems5 = {};
// This now handles default values for the fields below
@Getter
private final BannerType bannerType = BannerType.STANDARD;
// These don't change between banner types (apart from Standard having three extra 4star avatars)
@Getter
private final int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
@Getter
private final int[] fallbackItems4Pool1 = DEFAULT_FALLBACK_ITEMS_4_POOL_1;
@Getter
private final int[] fallbackItems4Pool2 = DEFAULT_FALLBACK_ITEMS_4_POOL_2;
// Different banner types have different defaults, see above for default values and the enum for which are used where.
@Getter
private int[] fallbackItems5Pool1;
@Getter
private int[] fallbackItems5Pool2;
private int[][] weights4;
private int[][] weights5;
private int eventChance4 = -1; // Chance to win a featured event item
private int eventChance5 = -1; // Chance to win a featured event item
//
@Getter
private final boolean removeC6FromPool = false;
@Getter
private final boolean autoStripRateUpFromFallback = true; // Ensures that featured items won't "double dip" into the losing pool
private final int[][] poolBalanceWeights4 = {{1, 255}, {17, 255}, {21, 10455}}; // Used to ensure that players won't go too many rolls without getting something from pool 1 (avatar) or pool 2 (weapon)
private final int[][] poolBalanceWeights5 = {{1, 30}, {147, 150}, {181, 10230}};
@Getter
private final int wishMaxProgress = 2;
// Deprecated fields that were tolerated in early May 2022 but have apparently still being circulating in new custom configs
// For now, throw up big scary errors on load telling people that they will be banned outright in a future version
@Deprecated
private final int[] rateUpItems1 = {};
@Deprecated
private final int[] rateUpItems2 = {};
@Deprecated
private final int eventChance = -1;
@Deprecated
private final int costItem = 0;
@Deprecated
private final int softPity = -1;
@Deprecated
private final int hardPity = -1;
@Deprecated
private final int minItemType = -1;
@Deprecated
private final int maxItemType = -1;
@Getter
private boolean deprecated = false;
@Getter
private final boolean disabled = false;
private void warnDeprecated(String name, String replacement) {
Grasscutter.getLogger().error("Deprecated field found in Banners config: " + name + " was replaced back in early May 2022, use " + replacement + " instead. You MUST remove this field from your config.");
this.deprecated = true;
}
public void onLoad() {
// Handle deprecated configs
if (eventChance != -1)
warnDeprecated("eventChance", "eventChance4 & eventChance5");
if (costItem != 0)
warnDeprecated("costItem", "costItemId");
if (softPity != -1)
warnDeprecated("softPity", "weights5");
if (hardPity != -1)
warnDeprecated("hardPity", "weights5");
if (minItemType != -1)
warnDeprecated("minItemType", "fallbackItems[4,5]Pool[1,2]");
if (maxItemType != -1)
warnDeprecated("maxItemType", "fallbackItems[4,5]Pool[1,2]");
if (rateUpItems1.length > 0)
warnDeprecated("rateUpItems1", "rateUpItems5");
if (rateUpItems2.length > 0)
warnDeprecated("rateUpItems2", "rateUpItems4");
// Handle default values
if (this.previewPrefabPath != null && this.previewPrefabPath.equals("UI_Tab_" + this.prefabPath))
Grasscutter.getLogger().error("Redundant field found in Banners config: previewPrefabPath does not need to be specified if it is identical to prefabPath prefixed with \"UI_Tab_\".");
if (this.previewPrefabPath == null || this.previewPrefabPath.isEmpty())
this.previewPrefabPath = "UI_Tab_" + this.prefabPath;
if (this.gachaType < 0)
this.gachaType = this.bannerType.gachaType;
if (this.costItemId == 0)
this.costItemId = this.bannerType.costItemId;
if (this.costItemId10 == 0)
this.costItemId10 = this.costItemId;
if (this.weights4 == null)
this.weights4 = this.bannerType.weights4;
if (this.weights5 == null)
this.weights5 = this.bannerType.weights5;
if (this.eventChance4 < 0)
this.eventChance4 = this.bannerType.eventChance4;
if (this.eventChance5 < 0)
this.eventChance5 = this.bannerType.eventChance5;
if (this.fallbackItems5Pool1 == null)
this.fallbackItems5Pool1 = this.bannerType.fallbackItems5Pool1;
if (this.fallbackItems5Pool2 == null)
this.fallbackItems5Pool2 = this.bannerType.fallbackItems5Pool2;
}
public ItemParamData getCost(int numRolls) {
return switch (numRolls) {
case 10 -> new ItemParamData(costItemId10, costItemAmount10);
default -> new ItemParamData(costItemId, costItemAmount * numRolls);
};
}
@Deprecated
public int getCostItem() {
return costItemId;
}
public boolean hasEpitomized() {
return bannerType.equals(BannerType.WEAPON);
}
public int getWeight(int rarity, int pity) {
return switch (rarity) {
case 4 -> Utils.lerp(pity, weights4);
default -> Utils.lerp(pity, weights5);
};
}
public int getPoolBalanceWeight(int rarity, int pity) {
return switch (rarity) {
case 4 -> Utils.lerp(pity, poolBalanceWeights4);
default -> Utils.lerp(pity, poolBalanceWeights5);
};
}
public int getEventChance(int rarity) {
return switch (rarity) {
case 4 -> eventChance4;
default -> eventChance5;
};
}
public GachaInfo toProto(Player player) {
// TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
String sessionKey = player.getAccount().getSessionKey();
String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;
// Grasscutter.getLogger().info("record = " + record);
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
int leftGachaTimes = switch (gachaTimesLimit) {
case Integer.MAX_VALUE -> Integer.MAX_VALUE;
default -> Math.max(gachaTimesLimit - gachaInfo.getTotalPulls(), 0);
};
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(this.costItemId)
.setCostItemNum(this.costItemAmount)
.setTenCostItemId(this.costItemId10)
.setTenCostItemNum(this.costItemAmount10)
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(details)
.setGachaProbUrlOversea(details)
.setGachaRecordUrl(record)
.setGachaRecordUrlOversea(record)
.setLeftGachaTimes(leftGachaTimes)
.setGachaTimesLimit(gachaTimesLimit)
.setGachaSortId(this.getSortId());
if (hasEpitomized()) {
info.setWishItemId(gachaInfo.getWishItemId())
.setWishProgress(gachaInfo.getFailedChosenItemPulls())
.setWishMaxProgress(this.getWishMaxProgress());
}
if (this.getTitlePath() != null) {
info.setTitleTextmap(this.getTitlePath());
}
if (this.getRateUpItems5().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems5()) {
upInfo.addItemIdList(id);
info.addDisplayUp5ItemList(id);
}
info.addGachaUpInfoList(upInfo);
}
if (this.getRateUpItems4().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems4()) {
upInfo.addItemIdList(id);
if (info.getDisplayUp4ItemListCount() == 0) {
info.addDisplayUp4ItemList(id);
}
}
info.addGachaUpInfoList(upInfo);
}
return info.build();
}
public enum BannerType {
STANDARD(200, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
BEGINNER(100, 224, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2),
EVENT(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, DEFAULT_FALLBACK_ITEMS_5_POOL_2), // Legacy value for CHARACTER
CHARACTER(301, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
CHARACTER2(400, 223, DEFAULT_WEIGHTS_4, DEFAULT_WEIGHTS_5_CHARACTER, 50, 50, DEFAULT_FALLBACK_ITEMS_5_POOL_1, EMPTY_POOL),
WEAPON(302, 223, DEFAULT_WEIGHTS_4_WEAPON, DEFAULT_WEIGHTS_5_WEAPON, 75, 75, EMPTY_POOL, DEFAULT_FALLBACK_ITEMS_5_POOL_2);
public final int gachaType;
public final int costItemId;
public final int[][] weights4;
public final int[][] weights5;
public final int eventChance4;
public final int eventChance5;
public final int[] fallbackItems5Pool1;
public final int[] fallbackItems5Pool2;
BannerType(int gachaType, int costItemId, int[][] weights4, int[][] weights5, int eventChance4, int eventChance5, int[] fallbackItems5Pool1, int[] fallbackItems5Pool2) {
this.gachaType = gachaType;
this.costItemId = costItemId;
this.weights4 = weights4;
this.weights5 = weights5;
this.eventChance4 = eventChance4;
this.eventChance5 = eventChance5;
this.fallbackItems5Pool1 = fallbackItems5Pool1;
this.fallbackItems5Pool2 = fallbackItems5Pool2;
}
}
}
package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class GachaBanner {
// Constants used by the BannerType enum
static final int[][] DEFAULT_WEIGHTS_4 = {{1, 510}, {8, 510}, {10, 10000}};
static final int[][] DEFAULT_WEIGHTS_4_WEAPON = {{1, 600}, {7, 600}, {8, 6600}, {10, 12600}};
static final int[][] DEFAULT_WEIGHTS_5 = {{1, 75}, {73, 150}, {90, 10000}};
static final int[][] DEFAULT_WEIGHTS_5_CHARACTER = {{1, 80}, {73, 80}, {90, 10000}};
static final int[][] DEFAULT_WEIGHTS_5_WEAPON = {{1, 100}, {62, 100}, {73, 7800}, {80, 10000}};
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_1 = {
1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053,
1055, 1056, 1059, 1064, 1065, 1067, 1068, 1072
}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_4_POOL_2 = {
11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403,
14409, 15401, 15402, 15403, 15405
}; // Default weapons
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_1 = {
1003, 1016, 1042, 1035, 1041, 1069
}; // Default avatars
static final int[] DEFAULT_FALLBACK_ITEMS_5_POOL_2 = {
11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502
}; // Default weapons
static final int[] EMPTY_POOL = {}; // Used to remove a type of fallback
@Getter int scheduleId = -1;
@Getter int sortId = -1;
@Getter private int gachaType = -1;
@Getter private String prefabPath;
@Getter private String previewPrefabPath;
@Getter private String titlePath;
private int costItemId = 0;
private final int costItemAmount = 1;
private int costItemId10 = 0;
private final int costItemAmount10 = 10;
@Getter private final int beginTime = 0;
@Getter private final int endTime = 1924992000;
@Getter private final int gachaTimesLimit = Integer.MAX_VALUE;
@Getter private final int[] rateUpItems4 = {};
@Getter private final int[] rateUpItems5 = {};
// This now handles default values for the fields below
@Getter private final BannerType bannerType = BannerType.STANDARD;
// These don't change between banner types (apart from Standard having three extra 4star avatars)
@Getter
private final int[] fallbackItems3 = {
11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304
};
@Getter private final int[] fallbackItems4Pool1 = DEFAULT_FALLBACK_ITEMS_4_POOL_1;
@Getter private final int[] fallbackItems4Pool2 = DEFAULT_FALLBACK_ITEMS_4_POOL_2;
// Different banner types have different defaults, see above for default values and the enum for
// which are used where.
@Getter private int[] fallbackItems5Pool1;
@Getter private int[] fallbackItems5Pool2;
private int[][] weights4;
private int[][] weights5;
private int eventChance4 = -1; // Chance to win a featured event item
private int eventChance5 = -1; // Chance to win a featured event item
//
@Getter private final boolean removeC6FromPool = false;
@Getter
private final boolean autoStripRateUpFromFallback =
true; // Ensures that featured items won't "double dip" into the losing pool
private final int[][] poolBalanceWeights4 = {
{1, 255}, {17, 255}, {21, 10455}
}; // Used to ensure that players won't go too many rolls without getting something from pool 1
// (avatar) or pool 2 (weapon)
private final int[][] poolBalanceWeights5 = {{1, 30}, {147, 150}, {181, 10230}};
@Getter private final int wishMaxProgress = 2;
// Deprecated fields that were tolerated in early May 2022 but have apparently still being
// circulating in new custom configs
// For now, throw up big scary errors on load telling people that they will be banned outright in
// a future version
@Deprecated private final int[] rateUpItems1 = {};
@Deprecated private final int[] rateUpItems2 = {};
@Deprecated private final int eventChance = -1;
@Deprecated private final int costItem = 0;
@Deprecated private final int softPity = -1;
@Deprecated private final int hardPity = -1;
@Deprecated private final int minItemType = -1;
@Deprecated private final int maxItemType = -1;
@Getter private boolean deprecated = false;
@Getter private final boolean disabled = false;
private void warnDeprecated(String name, String replacement) {
Grasscutter.getLogger()
.error(
"Deprecated field found in Banners config: "
+ name
+ " was replaced back in early May 2022, use "
+ replacement
+ " instead. You MUST remove this field from your config.");
this.deprecated = true;
}
public void onLoad() {
// Handle deprecated configs
if (eventChance != -1) warnDeprecated("eventChance", "eventChance4 & eventChance5");
if (costItem != 0) warnDeprecated("costItem", "costItemId");
if (softPity != -1) warnDeprecated("softPity", "weights5");
if (hardPity != -1) warnDeprecated("hardPity", "weights5");
if (minItemType != -1) warnDeprecated("minItemType", "fallbackItems[4,5]Pool[1,2]");
if (maxItemType != -1) warnDeprecated("maxItemType", "fallbackItems[4,5]Pool[1,2]");
if (rateUpItems1.length > 0) warnDeprecated("rateUpItems1", "rateUpItems5");
if (rateUpItems2.length > 0) warnDeprecated("rateUpItems2", "rateUpItems4");
// Handle default values
if (this.previewPrefabPath != null
&& this.previewPrefabPath.equals("UI_Tab_" + this.prefabPath))
Grasscutter.getLogger()
.error(
"Redundant field found in Banners config: previewPrefabPath does not need to be specified if it is identical to prefabPath prefixed with \"UI_Tab_\".");
if (this.previewPrefabPath == null || this.previewPrefabPath.isEmpty())
this.previewPrefabPath = "UI_Tab_" + this.prefabPath;
if (this.gachaType < 0) this.gachaType = this.bannerType.gachaType;
if (this.costItemId == 0) this.costItemId = this.bannerType.costItemId;
if (this.costItemId10 == 0) this.costItemId10 = this.costItemId;
if (this.weights4 == null) this.weights4 = this.bannerType.weights4;
if (this.weights5 == null) this.weights5 = this.bannerType.weights5;
if (this.eventChance4 < 0) this.eventChance4 = this.bannerType.eventChance4;
if (this.eventChance5 < 0) this.eventChance5 = this.bannerType.eventChance5;
if (this.fallbackItems5Pool1 == null)
this.fallbackItems5Pool1 = this.bannerType.fallbackItems5Pool1;
if (this.fallbackItems5Pool2 == null)
this.fallbackItems5Pool2 = this.bannerType.fallbackItems5Pool2;
}
public ItemParamData getCost(int numRolls) {
return switch (numRolls) {
case 10 -> new ItemParamData(costItemId10, costItemAmount10);
default -> new ItemParamData(costItemId, costItemAmount * numRolls);
};
}
@Deprecated
public int getCostItem() {
return costItemId;
}
public boolean hasEpitomized() {
return bannerType.equals(BannerType.WEAPON);
}
public int getWeight(int rarity, int pity) {
return switch (rarity) {
case 4 -> Utils.lerp(pity, weights4);
default -> Utils.lerp(pity, weights5);
};
}
public int getPoolBalanceWeight(int rarity, int pity) {
return switch (rarity) {
case 4 -> Utils.lerp(pity, poolBalanceWeights4);
default -> Utils.lerp(pity, poolBalanceWeights5);
};
}
public int getEventChance(int rarity) {
return switch (rarity) {
case 4 -> eventChance4;
default -> eventChance5;
};
}
public GachaInfo toProto(Player player) {
// TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
String sessionKey = player.getAccount().getSessionKey();
String record =
"http"
+ (HTTP_ENCRYPTION.useInRouting ? "s" : "")
+ "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress)
+ ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha?s="
+ sessionKey
+ "&gachaType="
+ gachaType;
String details =
"http"
+ (HTTP_ENCRYPTION.useInRouting ? "s" : "")
+ "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress)
+ ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha/details?s="
+ sessionKey
+ "&scheduleId="
+ scheduleId;
// Grasscutter.getLogger().info("record = " + record);
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
int leftGachaTimes =
switch (gachaTimesLimit) {
case Integer.MAX_VALUE -> Integer.MAX_VALUE;
default -> Math.max(gachaTimesLimit - gachaInfo.getTotalPulls(), 0);
};
GachaInfo.Builder info =
GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(this.costItemId)
.setCostItemNum(this.costItemAmount)
.setTenCostItemId(this.costItemId10)
.setTenCostItemNum(this.costItemAmount10)
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(details)
.setGachaProbUrlOversea(details)
.setGachaRecordUrl(record)
.setGachaRecordUrlOversea(record)
.setLeftGachaTimes(leftGachaTimes)
.setGachaTimesLimit(gachaTimesLimit)
.setGachaSortId(this.getSortId());
if (hasEpitomized()) {
info.setWishItemId(gachaInfo.getWishItemId())
.setWishProgress(gachaInfo.getFailedChosenItemPulls())
.setWishMaxProgress(this.getWishMaxProgress());
}
if (this.getTitlePath() != null) {
info.setTitleTextmap(this.getTitlePath());
}
if (this.getRateUpItems5().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems5()) {
upInfo.addItemIdList(id);
info.addDisplayUp5ItemList(id);
}
info.addGachaUpInfoList(upInfo);
}
if (this.getRateUpItems4().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems4()) {
upInfo.addItemIdList(id);
if (info.getDisplayUp4ItemListCount() == 0) {
info.addDisplayUp4ItemList(id);
}
}
info.addGachaUpInfoList(upInfo);
}
return info.build();
}
public enum BannerType {
STANDARD(
200,
224,
DEFAULT_WEIGHTS_4,
DEFAULT_WEIGHTS_5,
50,
50,
DEFAULT_FALLBACK_ITEMS_5_POOL_1,
DEFAULT_FALLBACK_ITEMS_5_POOL_2),
BEGINNER(
100,
224,
DEFAULT_WEIGHTS_4,
DEFAULT_WEIGHTS_5,
50,
50,
DEFAULT_FALLBACK_ITEMS_5_POOL_1,
DEFAULT_FALLBACK_ITEMS_5_POOL_2),
EVENT(
301,
223,
DEFAULT_WEIGHTS_4,
DEFAULT_WEIGHTS_5_CHARACTER,
50,
50,
DEFAULT_FALLBACK_ITEMS_5_POOL_1,
DEFAULT_FALLBACK_ITEMS_5_POOL_2), // Legacy value for CHARACTER
CHARACTER(
301,
223,
DEFAULT_WEIGHTS_4,
DEFAULT_WEIGHTS_5_CHARACTER,
50,
50,
DEFAULT_FALLBACK_ITEMS_5_POOL_1,
EMPTY_POOL),
CHARACTER2(
400,
223,
DEFAULT_WEIGHTS_4,
DEFAULT_WEIGHTS_5_CHARACTER,
50,
50,
DEFAULT_FALLBACK_ITEMS_5_POOL_1,
EMPTY_POOL),
WEAPON(
302,
223,
DEFAULT_WEIGHTS_4_WEAPON,
DEFAULT_WEIGHTS_5_WEAPON,
75,
75,
EMPTY_POOL,
DEFAULT_FALLBACK_ITEMS_5_POOL_2);
public final int gachaType;
public final int costItemId;
public final int[][] weights4;
public final int[][] weights5;
public final int eventChance4;
public final int eventChance5;
public final int[] fallbackItems5Pool1;
public final int[] fallbackItems5Pool2;
BannerType(
int gachaType,
int costItemId,
int[][] weights4,
int[][] weights5,
int eventChance4,
int eventChance5,
int[] fallbackItems5Pool1,
int[] fallbackItems5Pool2) {
this.gachaType = gachaType;
this.costItemId = costItemId;
this.weights4 = weights4;
this.weights5 = weights5;
this.eventChance4 = eventChance4;
this.eventChance5 = eventChance5;
this.fallbackItems5Pool1 = fallbackItems5Pool1;
this.fallbackItems5Pool2 = fallbackItems5Pool2;
}
}
}

View File

@@ -1,81 +1,75 @@
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import org.bson.types.ObjectId;
import java.util.Date;
@Entity(value = "gachas", useDiscriminator = false)
public class GachaRecord {
@Id
private ObjectId id;
@Indexed
private int ownerId;
private Date transactionDate;
private int itemID;
@Indexed
private int gachaType;
public GachaRecord() {
}
public GachaRecord(int itemId, int ownerId, int gachaType) {
this.transactionDate = new Date();
this.itemID = itemId;
this.ownerId = ownerId;
this.gachaType = gachaType;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public int getGachaType() {
return gachaType;
}
public void setGachaType(int type) {
this.gachaType = type;
}
public Date getTransactionDate() {
return transactionDate;
}
public void setTransactionDate(Date transactionDate) {
this.transactionDate = transactionDate;
}
public int getItemID() {
return itemID;
}
public void setItemID(int itemID) {
this.itemID = itemID;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String toString() {
return toJsonString();
}
public String toJsonString() {
return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}";
}
}
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import java.util.Date;
import org.bson.types.ObjectId;
@Entity(value = "gachas", useDiscriminator = false)
public class GachaRecord {
@Id private ObjectId id;
@Indexed private int ownerId;
private Date transactionDate;
private int itemID;
@Indexed private int gachaType;
public GachaRecord() {}
public GachaRecord(int itemId, int ownerId, int gachaType) {
this.transactionDate = new Date();
this.itemID = itemId;
this.ownerId = ownerId;
this.gachaType = gachaType;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public int getGachaType() {
return gachaType;
}
public void setGachaType(int type) {
this.gachaType = type;
}
public Date getTransactionDate() {
return transactionDate;
}
public void setTransactionDate(Date transactionDate) {
this.transactionDate = transactionDate;
}
public int getItemID() {
return itemID;
}
public void setItemID(int itemID) {
this.itemID = itemID;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String toString() {
return toJsonString();
}
public String toJsonString() {
return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}";
}
}

View File

@@ -1,429 +1,491 @@
package emu.grasscutter.game.gacha;
import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.greenrobot.eventbus.Subscribe;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
public GachaSystem(GameServer server) {
super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
getGachaBanners().clear();
int autoScheduleId = 1000;
int autoSortId = 9000;
try {
List<GachaBanner> banners = DataLoader.loadTableToList("Banners", GachaBanner.class);
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
banner.onLoad();
if (banner.isDeprecated()) {
Grasscutter.getLogger().error("A Banner has not been loaded because it contains one or more deprecated fields. Remove the fields mentioned above and reload.");
} else if (banner.isDisabled()) {
Grasscutter.getLogger().debug("A Banner has not been loaded because it is disabled.");
} else {
if (banner.scheduleId < 0)
banner.scheduleId = autoScheduleId++;
if (banner.sortId < 0)
banner.sortId = autoSortId--;
getGachaBanners().put(banner.scheduleId, banner);
}
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff) ? total : cutoff);
int subTotal = 0;
for (int i = 0; i < weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom((rarity == 5) ? GachaBanner.DEFAULT_FALLBACK_ITEMS_5_POOL_2 : GachaBanner.DEFAULT_FALLBACK_ITEMS_4_POOL_2);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool = switch ((pityPool1 >= pityPool2) ? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[]{pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[]{pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized = (banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized = (gachaInfo.getFailedChosenItemPulls() >= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured = (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(rarity, 1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if (itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE && (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel() == 5) ? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[]{itemId});
}
addStarglitter = (itemData.getRankLevel() == 5) ? 10 : 2;
int constItemId = itemId + 100; // This may not hold true for future characters. Examples of strictly correct constellation item lookup are elsewhere for now.
boolean haveConstItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId) == null;
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(haveConstItem));
//inventory.addItem(constItemId, 1); // This is now managed by the avatar card item itself
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
FileUtils.getDataUserPath("").register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if (GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger().info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger().error("Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD)) {
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
}
package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.greenrobot.eventbus.Subscribe;
public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
public GachaSystem(GameServer server) {
super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
getGachaBanners().clear();
int autoScheduleId = 1000;
int autoSortId = 9000;
try {
List<GachaBanner> banners = DataLoader.loadTableToList("Banners", GachaBanner.class);
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
banner.onLoad();
if (banner.isDeprecated()) {
Grasscutter.getLogger()
.error(
"A Banner has not been loaded because it contains one or more deprecated fields. Remove the fields mentioned above and reload.");
} else if (banner.isDisabled()) {
Grasscutter.getLogger().debug("A Banner has not been loaded because it is disabled.");
} else {
if (banner.scheduleId < 0) banner.scheduleId = autoScheduleId++;
if (banner.sortId < 0) banner.sortId = autoSortId--;
getGachaBanners().put(banner.scheduleId, banner);
}
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff) ? total : cutoff);
int subTotal = 0;
for (int i = 0; i < weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom(
(rarity == 5)
? GachaBanner.DEFAULT_FALLBACK_ITEMS_5_POOL_2
: GachaBanner.DEFAULT_FALLBACK_ITEMS_4_POOL_2);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool =
switch ((pityPool1 >= pityPool2)
? 1
: 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(
int[] featured,
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized =
(banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized =
(gachaInfo.getFailedChosenItemPulls()
>= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured =
(gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured =
(this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(
rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(
rarity,
1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if (itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(
GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {
banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000
};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(
pools.rateUpItems5,
pools.fallbackItems5Pool1,
pools.fallbackItems5Pool2,
5,
banner,
gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(
pools.rateUpItems4,
pools.fallbackItems4Pool1,
pools.fallbackItems4Pool2,
4,
banner,
gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times
> inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE
&& (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel() == 5) ? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool()
&& constellation
== 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel() == 5) ? 10 : 2;
int constItemId =
itemId + 100; // This may not hold true for future characters. Examples of strictly
// correct constellation item lookup are elsewhere for now.
boolean haveConstItem =
inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId) == null;
gachaItem.addTransferItems(
GachaTransferItem.newBuilder()
.setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1))
.setIsTransferItemNew(haveConstItem));
// inventory.addItem(constItemId, 1); // This is now managed by the avatar card item
// itself
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(
ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam =
ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
FileUtils.getDataUserPath("")
.register(
watchService,
new WatchEvent.Kind[] {StandardWatchEventKinds.ENTRY_MODIFY},
SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger()
.error(
"Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if (GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger()
.info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger()
.error(
"Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime)
|| (banner.getBannerType() == BannerType.STANDARD)) {
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
}

View File

@@ -1,132 +1,122 @@
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import lombok.Getter;
import lombok.Setter;
@Entity
public class PlayerGachaBannerInfo {
@Getter
@Setter
private int totalPulls = 0;
@Getter
@Setter
private int pity5 = 0;
@Getter
@Setter
private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
private int failedFeatured4ItemPulls = 0;
private int pity5Pool1 = 0;
private int pity5Pool2 = 0;
private int pity4Pool1 = 0;
private int pity4Pool2 = 0;
@Getter
@Setter
private int failedChosenItemPulls = 0;
@Getter
@Setter
private int wishItemId = 0;
public void addTotalPulls(int amount) {
this.totalPulls += amount;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public void addFailedChosenItemPulls(int amount) {
failedChosenItemPulls += amount;
}
public int getFailedFeaturedItemPulls(int rarity) {
return switch (rarity) {
case 4 -> failedFeatured4ItemPulls;
default -> failedFeaturedItemPulls; // 5
};
}
public void setFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls = amount;
} else {
failedFeaturedItemPulls = amount; // 5
}
}
public void addFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls += amount;
} else {
failedFeaturedItemPulls += amount; // 5
}
}
public int getPityPool(int rarity, int pool) {
return switch (rarity) {
case 4 -> switch (pool) {
case 1 -> pity4Pool1;
default -> pity4Pool2;
};
default -> switch (pool) {
case 1 -> pity5Pool1;
default -> pity5Pool2;
};
};
}
public void setPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 = amount;
} else {
pity4Pool2 = amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 = amount;
} else {
pity5Pool2 = amount;
}
break;
}
}
public void addPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 += amount;
} else {
pity4Pool2 += amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 += amount;
} else {
pity5Pool2 += amount;
}
break;
}
}
public void incPityAll() {
pity4++;
pity5++;
pity4Pool1++;
pity4Pool2++;
pity5Pool1++;
pity5Pool2++;
}
}
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import lombok.Getter;
import lombok.Setter;
@Entity
public class PlayerGachaBannerInfo {
@Getter @Setter private int totalPulls = 0;
@Getter @Setter private int pity5 = 0;
@Getter @Setter private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
private int failedFeatured4ItemPulls = 0;
private int pity5Pool1 = 0;
private int pity5Pool2 = 0;
private int pity4Pool1 = 0;
private int pity4Pool2 = 0;
@Getter @Setter private int failedChosenItemPulls = 0;
@Getter @Setter private int wishItemId = 0;
public void addTotalPulls(int amount) {
this.totalPulls += amount;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public void addFailedChosenItemPulls(int amount) {
failedChosenItemPulls += amount;
}
public int getFailedFeaturedItemPulls(int rarity) {
return switch (rarity) {
case 4 -> failedFeatured4ItemPulls;
default -> failedFeaturedItemPulls; // 5
};
}
public void setFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls = amount;
} else {
failedFeaturedItemPulls = amount; // 5
}
}
public void addFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls += amount;
} else {
failedFeaturedItemPulls += amount; // 5
}
}
public int getPityPool(int rarity, int pool) {
return switch (rarity) {
case 4 -> switch (pool) {
case 1 -> pity4Pool1;
default -> pity4Pool2;
};
default -> switch (pool) {
case 1 -> pity5Pool1;
default -> pity5Pool2;
};
};
}
public void setPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 = amount;
} else {
pity4Pool2 = amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 = amount;
} else {
pity5Pool2 = amount;
}
break;
}
}
public void addPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 += amount;
} else {
pity4Pool2 += amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 += amount;
} else {
pity5Pool2 += amount;
}
break;
}
}
public void incPityAll() {
pity4++;
pity5++;
pity4Pool1++;
pity4Pool2++;
pity5Pool1++;
pity5Pool2++;
}
}

View File

@@ -1,32 +1,31 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.net.proto.FurnitureMakeDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class FurnitureMakeSlotItem {
@Id
int index;
int makeId;
int avatarId;
int beginTime;
int durTime;
public FurnitureMakeDataOuterClass.FurnitureMakeData toProto() {
return FurnitureMakeDataOuterClass.FurnitureMakeData.newBuilder()
.setIndex(index)
.setAvatarId(avatarId)
.setMakeId(makeId)
.setBeginTime(beginTime)
.setDurTime(durTime)
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.net.proto.FurnitureMakeDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class FurnitureMakeSlotItem {
@Id int index;
int makeId;
int avatarId;
int beginTime;
int durTime;
public FurnitureMakeDataOuterClass.FurnitureMakeData toProto() {
return FurnitureMakeDataOuterClass.FurnitureMakeData.newBuilder()
.setIndex(index)
.setAvatarId(avatarId)
.setMakeId(makeId)
.setBeginTime(beginTime)
.setDurTime(durTime)
.build();
}
}

View File

@@ -1,341 +1,358 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Entity(value = "homes", useDiscriminator = false)
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id
String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient
Player player;
int level;
int exp;
int lastUpdatedTime;
int nextUpdateTime;
int storedCoin;
int storedFetterExp;
List<FurnitureMakeSlotItem> furnitureMakeSlotItemList;
ConcurrentHashMap<Integer, HomeSceneItem> sceneMap;
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
if (home == null) {
home = GameHome.create(uid);
}
return home;
}
public static GameHome create(Integer uid) {
return GameHome.of()
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
}
public void save() {
DatabaseHelper.saveHome(this);
}
public HomeSceneItem getHomeSceneItem(int sceneId) {
return sceneMap.computeIfAbsent(sceneId, e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger().info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
}
public void onOwnerLogin(Player player) {
if (this.player == null)
this.player = player;
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
player.getSession().send(new PacketHomeComfortInfoNotify(player));
player.getSession().send(new PacketFurnitureCurModuleArrangeCountNotify());
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
// Tell the client the reward is claimed or realm unlocked
public void onClaimReward(Player player) {
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
}
public Player getPlayer() {
if (this.player == null)
this.player = Grasscutter.getGameServer().getPlayerByUid((int) this.ownerUid, true);
return this.player;
}
public HomeWorldLevelData getLevelData() {
return GameData.getHomeWorldLevelDataMap().get(level);
}
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
var player = this.getPlayer();
player.sendPacket(new PacketHomeNewUnlockedBgmIdListNotify(homeBgmId));
player.sendPacket(new PacketHomeAllUnlockedBgmIdListNotify(player));
save();
return true;
}
public Set<Integer> getUnlockedHomeBgmList() {
if (this.unlockedHomeBgmList == null) {
this.unlockedHomeBgmList = new HashSet<>();
}
if (this.unlockedHomeBgmList.addAll(getDefaultUnlockedHomeBgmIds())) {
save();
}
return this.unlockedHomeBgmList;
}
private Set<Integer> getDefaultUnlockedHomeBgmIds() {
return GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
}
// Same as Player.java addExpDirectly
public void addExp(Player player, int count) {
exp += count;
int reqExp = getExpRequired(level);
while (exp >= reqExp && reqExp > 0) {
exp -= reqExp;
level += 1;
reqExp = getExpRequired(level);
// Update client level and exp
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
}
// Update client home
onOwnerLogin(player);
}
private void checkAccumulatedResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
int owedRewards = 0;
// Don't owe if previous update hasn't passed
if (nextUpdateTime > clientTime) {
return;
}
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Calculate number of owed rewards
owedRewards = 1 + ((clientTime - nextUpdateTime) / 3600);
// Ensure next update is at top of the hour
nextUpdateTime = (int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
// Get resources
var hourlyResources = getComfortResources(player);
var owedCoin = hourlyResources.get(0) * owedRewards;
var owedFetter = hourlyResources.get(1) * owedRewards;
// Update stored amounts
storeResources(player, owedCoin, owedFetter);
}
public void takeHomeCoin(Player player) {
player.getInventory().addItem(204, storedCoin);
storedCoin = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void takeHomeFetter(Player player) {
List<Integer> invitedAvatars = new ArrayList<>();
// Outdoors avatars
sceneMap.get(player.getCurrentRealmId() + 2000).getBlockItems().forEach((i, e) -> {
e.getDeployNPCList().forEach(id -> {
invitedAvatars.add(id.getAvatarId());
});
});
// Check as realm 5 inside is not in defaults and will be null
if (Objects.nonNull(sceneMap.get(player.getCurrentRealmId() + 2200))) {
// Indoors avatars
sceneMap.get(player.getCurrentRealmId() + 2200).getBlockItems().forEach((i, e) -> {
e.getDeployNPCList().forEach(id -> {
invitedAvatars.add(id.getAvatarId());
});
});
}
// Add exp to all avatars
invitedAvatars.forEach(id -> {
var avatar = player.getAvatars().getAvatarById(id);
player.getServer().getInventorySystem().upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
storedFetterExp = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void updateHourlyResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
// Check if resources can update
if (nextUpdateTime > clientTime) {
return;
}
// If no update has occurred before
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Update stored resources
storeResources(player, 0, 0);
lastUpdatedTime = clientTime;
nextUpdateTime = (int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
save();
// Send packet
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void storeResources(Player player, int owedCoin, int owedFetter) {
// Get max values
var maxCoin = getMaxCoin(level);
var maxFetter = getMaxFetter(level);
int newCoin = 0;
int newFetter = 0;
// Check if resources are already max
if (storedCoin >= maxCoin && storedFetterExp >= maxFetter) {
return;
}
// Get resources
var hourlyResources = getComfortResources(player);
// Update home coin
if (storedCoin < maxCoin) {
// Check if owed or hourly
if (owedCoin == 0) {
newCoin = storedCoin + hourlyResources.get(0);
} else {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
}
// Update fetter exp
if (storedFetterExp < maxFetter) {
// Check if owed or hourly
if (owedFetter == 0) {
newFetter = storedFetterExp + hourlyResources.get(1);
} else {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
}
save();
}
public List<Integer> getComfortResources(Player player) {
List<Integer> allHomesComfort = new ArrayList<>();
int highestComfort = 0;
// Use HomeComfortInfoNotify data since comfort value isn't stored
if (player.getRealmList() == null) {
return List.of(0, 0);
}
// Calculate comfort value for each home
for (int moduleId : player.getRealmList()) {
var homeScene = player.getHome().getHomeSceneItem(moduleId + 2000);
allHomesComfort.add(homeScene.calComfort());
}
// Get highest comfort value
highestComfort = Collections.max(allHomesComfort);
// Determine hourly resources
if (highestComfort >= 20000) {
return List.of(30, 5);
} else if (highestComfort >= 15000) {
return List.of(28, 5);
} else if (highestComfort >= 12000) {
return List.of(26, 5);
} else if (highestComfort >= 10000) {
return List.of(24, 4);
} else if (highestComfort >= 8000) {
return List.of(22, 4);
} else if (highestComfort >= 6000) {
return List.of(20, 4);
} else if (highestComfort >= 4500) {
return List.of(16, 3);
} else if (highestComfort >= 3000) {
return List.of(12, 3);
} else if (highestComfort >= 2000) {
return List.of(8, 2);
} else
return List.of(4, 2);
}
private int getExpRequired(int level) {
HomeWorldLevelData levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
}
public int getMaxCoin(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeCoinStoreLimit() : 0;
}
public int getMaxFetter(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeFetterExpStoreLimit() : 0;
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity(value = "homes", useDiscriminator = false)
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient Player player;
int level;
int exp;
int lastUpdatedTime;
int nextUpdateTime;
int storedCoin;
int storedFetterExp;
List<FurnitureMakeSlotItem> furnitureMakeSlotItemList;
ConcurrentHashMap<Integer, HomeSceneItem> sceneMap;
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
if (home == null) {
home = GameHome.create(uid);
}
return home;
}
public static GameHome create(Integer uid) {
return GameHome.of()
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
}
public void save() {
DatabaseHelper.saveHome(this);
}
public HomeSceneItem getHomeSceneItem(int sceneId) {
return sceneMap.computeIfAbsent(
sceneId,
e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
}
public void onOwnerLogin(Player player) {
if (this.player == null) this.player = player;
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
player.getSession().send(new PacketHomeComfortInfoNotify(player));
player.getSession().send(new PacketFurnitureCurModuleArrangeCountNotify());
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
// Tell the client the reward is claimed or realm unlocked
public void onClaimReward(Player player) {
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
}
public Player getPlayer() {
if (this.player == null)
this.player = Grasscutter.getGameServer().getPlayerByUid((int) this.ownerUid, true);
return this.player;
}
public HomeWorldLevelData getLevelData() {
return GameData.getHomeWorldLevelDataMap().get(level);
}
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
var player = this.getPlayer();
player.sendPacket(new PacketHomeNewUnlockedBgmIdListNotify(homeBgmId));
player.sendPacket(new PacketHomeAllUnlockedBgmIdListNotify(player));
save();
return true;
}
public Set<Integer> getUnlockedHomeBgmList() {
if (this.unlockedHomeBgmList == null) {
this.unlockedHomeBgmList = new HashSet<>();
}
if (this.unlockedHomeBgmList.addAll(getDefaultUnlockedHomeBgmIds())) {
save();
}
return this.unlockedHomeBgmList;
}
private Set<Integer> getDefaultUnlockedHomeBgmIds() {
return GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
}
// Same as Player.java addExpDirectly
public void addExp(Player player, int count) {
exp += count;
int reqExp = getExpRequired(level);
while (exp >= reqExp && reqExp > 0) {
exp -= reqExp;
level += 1;
reqExp = getExpRequired(level);
// Update client level and exp
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
}
// Update client home
onOwnerLogin(player);
}
private void checkAccumulatedResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
int owedRewards = 0;
// Don't owe if previous update hasn't passed
if (nextUpdateTime > clientTime) {
return;
}
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Calculate number of owed rewards
owedRewards = 1 + ((clientTime - nextUpdateTime) / 3600);
// Ensure next update is at top of the hour
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
// Get resources
var hourlyResources = getComfortResources(player);
var owedCoin = hourlyResources.get(0) * owedRewards;
var owedFetter = hourlyResources.get(1) * owedRewards;
// Update stored amounts
storeResources(player, owedCoin, owedFetter);
}
public void takeHomeCoin(Player player) {
player.getInventory().addItem(204, storedCoin);
storedCoin = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void takeHomeFetter(Player player) {
List<Integer> invitedAvatars = new ArrayList<>();
// Outdoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2000)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
// Check as realm 5 inside is not in defaults and will be null
if (Objects.nonNull(sceneMap.get(player.getCurrentRealmId() + 2200))) {
// Indoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2200)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
}
// Add exp to all avatars
invitedAvatars.forEach(
id -> {
var avatar = player.getAvatars().getAvatarById(id);
player
.getServer()
.getInventorySystem()
.upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
storedFetterExp = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void updateHourlyResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
// Check if resources can update
if (nextUpdateTime > clientTime) {
return;
}
// If no update has occurred before
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Update stored resources
storeResources(player, 0, 0);
lastUpdatedTime = clientTime;
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
save();
// Send packet
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void storeResources(Player player, int owedCoin, int owedFetter) {
// Get max values
var maxCoin = getMaxCoin(level);
var maxFetter = getMaxFetter(level);
int newCoin = 0;
int newFetter = 0;
// Check if resources are already max
if (storedCoin >= maxCoin && storedFetterExp >= maxFetter) {
return;
}
// Get resources
var hourlyResources = getComfortResources(player);
// Update home coin
if (storedCoin < maxCoin) {
// Check if owed or hourly
if (owedCoin == 0) {
newCoin = storedCoin + hourlyResources.get(0);
} else {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
}
// Update fetter exp
if (storedFetterExp < maxFetter) {
// Check if owed or hourly
if (owedFetter == 0) {
newFetter = storedFetterExp + hourlyResources.get(1);
} else {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
}
save();
}
public List<Integer> getComfortResources(Player player) {
List<Integer> allHomesComfort = new ArrayList<>();
int highestComfort = 0;
// Use HomeComfortInfoNotify data since comfort value isn't stored
if (player.getRealmList() == null) {
return List.of(0, 0);
}
// Calculate comfort value for each home
for (int moduleId : player.getRealmList()) {
var homeScene = player.getHome().getHomeSceneItem(moduleId + 2000);
allHomesComfort.add(homeScene.calComfort());
}
// Get highest comfort value
highestComfort = Collections.max(allHomesComfort);
// Determine hourly resources
if (highestComfort >= 20000) {
return List.of(30, 5);
} else if (highestComfort >= 15000) {
return List.of(28, 5);
} else if (highestComfort >= 12000) {
return List.of(26, 5);
} else if (highestComfort >= 10000) {
return List.of(24, 4);
} else if (highestComfort >= 8000) {
return List.of(22, 4);
} else if (highestComfort >= 6000) {
return List.of(20, 4);
} else if (highestComfort >= 4500) {
return List.of(16, 3);
} else if (highestComfort >= 3000) {
return List.of(12, 3);
} else if (highestComfort >= 2000) {
return List.of(8, 2);
} else return List.of(4, 2);
}
private int getExpRequired(int level) {
HomeWorldLevelData levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
}
public int getMaxCoin(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeCoinStoreLimit() : 0;
}
public int getMaxFetter(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeFetterExpStoreLimit() : 0;
}
}

View File

@@ -1,36 +1,35 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeAnimalDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeAnimalItem {
int furnitureId;
Position spawnPos;
Position spawnRot;
public static HomeAnimalItem parseFrom(HomeAnimalDataOuterClass.HomeAnimalData homeAnimalData) {
return HomeAnimalItem.of()
.furnitureId(homeAnimalData.getFurnitureId())
.spawnPos(new Position(homeAnimalData.getSpawnPos()))
.spawnRot(new Position(homeAnimalData.getSpawnRot()))
.build();
}
public HomeAnimalDataOuterClass.HomeAnimalData toProto() {
return HomeAnimalDataOuterClass.HomeAnimalData.newBuilder()
.setFurnitureId(furnitureId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeAnimalDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeAnimalItem {
int furnitureId;
Position spawnPos;
Position spawnRot;
public static HomeAnimalItem parseFrom(HomeAnimalDataOuterClass.HomeAnimalData homeAnimalData) {
return HomeAnimalItem.of()
.furnitureId(homeAnimalData.getFurnitureId())
.spawnPos(new Position(homeAnimalData.getSpawnPos()))
.spawnRot(new Position(homeAnimalData.getSpawnRot()))
.build();
}
public HomeAnimalDataOuterClass.HomeAnimalData toProto() {
return HomeAnimalDataOuterClass.HomeAnimalData.newBuilder()
.setFurnitureId(furnitureId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.build();
}
}

View File

@@ -1,86 +1,87 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeBlockItem {
@Id
int blockId;
boolean unlocked;
List<HomeFurnitureItem> deployFurnitureList;
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
return HomeBlockItem.of()
.blockId(homeBlock.getBlockId())
.unlocked(homeBlock.getFurnitures() != null)
.deployFurnitureList(
homeBlock.getFurnitures() == null ? List.of() :
homeBlock.getFurnitures().stream()
.map(HomeFurnitureItem::parseFrom)
.toList())
.persistentFurnitureList(
homeBlock.getPersistentFurnitures() == null ? List.of() :
homeBlock.getPersistentFurnitures().stream()
.map(HomeFurnitureItem::parseFrom)
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList = homeBlockArrangementInfo.getDeployFurniureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.persistentFurnitureList = homeBlockArrangementInfo.getPersistentFurnitureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.deployAnimalList = homeBlockArrangementInfo.getDeployAnimalListList().stream()
.map(HomeAnimalItem::parseFrom)
.toList();
this.deployNPCList = homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.toList();
}
public int calComfort() {
return this.deployFurnitureList.stream()
.mapToInt(HomeFurnitureItem::getComfort)
.sum();
}
public HomeBlockArrangementInfo toProto() {
var proto = HomeBlockArrangementInfo.newBuilder()
.setBlockId(blockId)
.setIsUnlocked(unlocked)
.setComfortValue(calComfort());
this.deployFurnitureList.forEach(f -> proto.addDeployFurniureList(f.toProto()));
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
return proto.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeBlockItem {
@Id int blockId;
boolean unlocked;
List<HomeFurnitureItem> deployFurnitureList;
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
return HomeBlockItem.of()
.blockId(homeBlock.getBlockId())
.unlocked(homeBlock.getFurnitures() != null)
.deployFurnitureList(
homeBlock.getFurnitures() == null
? List.of()
: homeBlock.getFurnitures().stream().map(HomeFurnitureItem::parseFrom).toList())
.persistentFurnitureList(
homeBlock.getPersistentFurnitures() == null
? List.of()
: homeBlock.getPersistentFurnitures().stream()
.map(HomeFurnitureItem::parseFrom)
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList =
homeBlockArrangementInfo.getDeployFurniureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.persistentFurnitureList =
homeBlockArrangementInfo.getPersistentFurnitureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.deployAnimalList =
homeBlockArrangementInfo.getDeployAnimalListList().stream()
.map(HomeAnimalItem::parseFrom)
.toList();
this.deployNPCList =
homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.toList();
}
public int calComfort() {
return this.deployFurnitureList.stream().mapToInt(HomeFurnitureItem::getComfort).sum();
}
public HomeBlockArrangementInfo toProto() {
var proto =
HomeBlockArrangementInfo.newBuilder()
.setBlockId(blockId)
.setIsUnlocked(unlocked)
.setComfortValue(calComfort());
this.deployFurnitureList.forEach(f -> proto.addDeployFurniureList(f.toProto()));
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
return proto.build();
}
}

View File

@@ -1,80 +1,82 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.net.proto.HomeFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeFurnitureItem {
int furnitureId;
int guid;
int parentFurnitureIndex;
Position spawnPos;
Position spawnRot;
int version;
public static HomeFurnitureItem parseFrom(HomeFurnitureDataOuterClass.HomeFurnitureData homeFurnitureData) {
return HomeFurnitureItem.of()
.furnitureId(homeFurnitureData.getFurnitureId())
.guid(homeFurnitureData.getGuid())
.parentFurnitureIndex(homeFurnitureData.getParentFurnitureIndex())
.spawnPos(new Position(homeFurnitureData.getSpawnPos()))
.spawnRot(new Position(homeFurnitureData.getSpawnRot()))
.version(homeFurnitureData.getVersion())
.build();
}
public static HomeFurnitureItem parseFrom(HomeworldDefaultSaveData.HomeFurniture homeFurniture) {
return HomeFurnitureItem.of()
.furnitureId(homeFurniture.getId())
.parentFurnitureIndex(1)
.spawnPos(homeFurniture.getPos() == null ? new Position() : homeFurniture.getPos())
.spawnRot(homeFurniture.getRot() == null ? new Position() : homeFurniture.getRot())
.build();
}
public HomeFurnitureDataOuterClass.HomeFurnitureData toProto() {
return HomeFurnitureDataOuterClass.HomeFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setParentFurnitureIndex(parentFurnitureIndex)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setVersion(version)
.build();
}
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto(int type) {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setFurnitureType(type)
.setPos(spawnPos.toProto())
// TODO NPC and farm
.build();
}
public ItemData getAsItem() {
return GameData.getItemDataMap().get(this.furnitureId);
}
public int getComfort() {
var item = getAsItem();
if (item == null) {
return 0;
}
return item.getComfort();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.net.proto.HomeFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeFurnitureItem {
int furnitureId;
int guid;
int parentFurnitureIndex;
Position spawnPos;
Position spawnRot;
int version;
public static HomeFurnitureItem parseFrom(
HomeFurnitureDataOuterClass.HomeFurnitureData homeFurnitureData) {
return HomeFurnitureItem.of()
.furnitureId(homeFurnitureData.getFurnitureId())
.guid(homeFurnitureData.getGuid())
.parentFurnitureIndex(homeFurnitureData.getParentFurnitureIndex())
.spawnPos(new Position(homeFurnitureData.getSpawnPos()))
.spawnRot(new Position(homeFurnitureData.getSpawnRot()))
.version(homeFurnitureData.getVersion())
.build();
}
public static HomeFurnitureItem parseFrom(HomeworldDefaultSaveData.HomeFurniture homeFurniture) {
return HomeFurnitureItem.of()
.furnitureId(homeFurniture.getId())
.parentFurnitureIndex(1)
.spawnPos(homeFurniture.getPos() == null ? new Position() : homeFurniture.getPos())
.spawnRot(homeFurniture.getRot() == null ? new Position() : homeFurniture.getRot())
.build();
}
public HomeFurnitureDataOuterClass.HomeFurnitureData toProto() {
return HomeFurnitureDataOuterClass.HomeFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setParentFurnitureIndex(parentFurnitureIndex)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setVersion(version)
.build();
}
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto(
int type) {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setFurnitureType(type)
.setPos(spawnPos.toProto())
// TODO NPC and farm
.build();
}
public ItemData getAsItem() {
return GameData.getItemDataMap().get(this.furnitureId);
}
public int getComfort() {
var item = getAsItem();
if (item == null) {
return 0;
}
return item.getComfort();
}
}

View File

@@ -1,38 +1,38 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeNpcDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeNPCItem {
int avatarId;
Position spawnPos;
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
}
public HomeNpcDataOuterClass.HomeNpcData toProto() {
return HomeNpcDataOuterClass.HomeNpcData.newBuilder()
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeNpcDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeNPCItem {
int avatarId;
Position spawnPos;
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
}
public HomeNpcDataOuterClass.HomeNpcData toProto() {
return HomeNpcDataOuterClass.HomeNpcData.newBuilder()
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
}
}

View File

@@ -1,97 +1,96 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.Map;
import java.util.stream.Collectors;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeSceneItem {
@Id
int sceneId;
Map<Integer, HomeBlockItem> blockItems;
Position bornPos;
Position bornRot;
Position djinnPos;
int homeBgmId;
HomeFurnitureItem mainHouse;
int tmpVersion;
public static HomeSceneItem parseFrom(HomeworldDefaultSaveData defaultItem, int sceneId) {
return HomeSceneItem.of()
.sceneId(sceneId)
.blockItems(defaultItem.getHomeBlockLists().stream()
.map(HomeBlockItem::parseFrom)
.collect(Collectors.toMap(HomeBlockItem::getBlockId, y -> y)))
.bornPos(defaultItem.getBornPos())
.bornRot(defaultItem.getBornRot() == null ? new Position() : defaultItem.getBornRot())
.djinnPos(defaultItem.getDjinPos() == null ? new Position() : defaultItem.getDjinPos())
.mainHouse(defaultItem.getMainhouse() == null ? null :
HomeFurnitureItem.parseFrom(defaultItem.getMainhouse()))
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
this.blockItems.put(blockItem.getBlockId(), block);
}
this.bornPos = new Position(arrangementInfo.getBornPos());
this.bornRot = new Position(arrangementInfo.getBornRot());
this.djinnPos = new Position(arrangementInfo.getDjinnPos());
this.homeBgmId = arrangementInfo.getBgmId();
this.mainHouse = HomeFurnitureItem.parseFrom(arrangementInfo.getMainHouse());
this.tmpVersion = arrangementInfo.getTmpVersion();
}
public int getRoomSceneId() {
if (mainHouse == null || mainHouse.getAsItem() == null) {
return 0;
}
return mainHouse.getAsItem().getRoomSceneId();
}
public int calComfort() {
return this.blockItems.values().stream()
.mapToInt(HomeBlockItem::calComfort)
.sum();
}
public HomeSceneArrangementInfo toProto() {
var proto = HomeSceneArrangementInfo.newBuilder();
blockItems.values().forEach(b -> proto.addBlockArrangementInfoList(b.toProto()));
proto.setComfortValue(calComfort())
.setBornPos(bornPos.toProto())
.setBornRot(bornRot.toProto())
.setDjinnPos(djinnPos.toProto())
.setIsSetBornPos(true)
.setSceneId(sceneId)
.setBgmId(homeBgmId)
.setTmpVersion(tmpVersion);
if (mainHouse != null) {
proto.setMainHouse(mainHouse.toProto());
}
return proto.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import emu.grasscutter.utils.Position;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeSceneItem {
@Id int sceneId;
Map<Integer, HomeBlockItem> blockItems;
Position bornPos;
Position bornRot;
Position djinnPos;
int homeBgmId;
HomeFurnitureItem mainHouse;
int tmpVersion;
public static HomeSceneItem parseFrom(HomeworldDefaultSaveData defaultItem, int sceneId) {
return HomeSceneItem.of()
.sceneId(sceneId)
.blockItems(
defaultItem.getHomeBlockLists().stream()
.map(HomeBlockItem::parseFrom)
.collect(Collectors.toMap(HomeBlockItem::getBlockId, y -> y)))
.bornPos(defaultItem.getBornPos())
.bornRot(defaultItem.getBornRot() == null ? new Position() : defaultItem.getBornRot())
.djinnPos(defaultItem.getDjinPos() == null ? new Position() : defaultItem.getDjinPos())
.mainHouse(
defaultItem.getMainhouse() == null
? null
: HomeFurnitureItem.parseFrom(defaultItem.getMainhouse()))
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
this.blockItems.put(blockItem.getBlockId(), block);
}
this.bornPos = new Position(arrangementInfo.getBornPos());
this.bornRot = new Position(arrangementInfo.getBornRot());
this.djinnPos = new Position(arrangementInfo.getDjinnPos());
this.homeBgmId = arrangementInfo.getBgmId();
this.mainHouse = HomeFurnitureItem.parseFrom(arrangementInfo.getMainHouse());
this.tmpVersion = arrangementInfo.getTmpVersion();
}
public int getRoomSceneId() {
if (mainHouse == null || mainHouse.getAsItem() == null) {
return 0;
}
return mainHouse.getAsItem().getRoomSceneId();
}
public int calComfort() {
return this.blockItems.values().stream().mapToInt(HomeBlockItem::calComfort).sum();
}
public HomeSceneArrangementInfo toProto() {
var proto = HomeSceneArrangementInfo.newBuilder();
blockItems.values().forEach(b -> proto.addBlockArrangementInfoList(b.toProto()));
proto
.setComfortValue(calComfort())
.setBornPos(bornPos.toProto())
.setBornRot(bornRot.toProto())
.setDjinnPos(djinnPos.toProto())
.setIsSetBornPos(true)
.setSceneId(sceneId)
.setBgmId(homeBgmId)
.setTmpVersion(tmpVersion);
if (mainHouse != null) {
proto.setMainHouse(mainHouse.toProto());
}
return proto.build();
}
}

View File

@@ -1,39 +1,39 @@
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GameItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GameItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GameItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GameItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GameItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GameItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}

View File

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

View File

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

View File

@@ -1,27 +1,27 @@
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More