mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-20 02:45:52 +01:00
Run Spotless on src/main
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user