Continue updating/refactoring classes

Most code is matched from `Grasscutter-Quests`.
This commit is contained in:
KingRainbow44 2023-04-01 22:17:10 -04:00
parent 772532515e
commit 9fbb7fb3be
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
97 changed files with 14397 additions and 12921 deletions

View File

@ -1,139 +1,139 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import static emu.grasscutter.command.CommandHelpers.*; import static emu.grasscutter.command.CommandHelpers.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.*; import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.event.entity.EntityDamageEvent; import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import lombok.Setter; import lombok.Setter;
@Command( @Command(
label = "entity", label = "entity",
usage = { usage = {
"<configId gadget> [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>]", "<configId gadget> [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>]",
"<configId monster> [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>]" "<configId monster> [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>]"
}, },
permission = "server.entity") permission = "server.entity")
public final class EntityCommand implements CommandHandler { public final class EntityCommand implements CommandHandler {
private static final Map<Pattern, BiConsumer<EntityParameters, Integer>> intCommandHandlers = private static final Map<Pattern, BiConsumer<EntityParameters, Integer>> intCommandHandlers =
Map.ofEntries( Map.ofEntries(
Map.entry(stateRegex, EntityParameters::setState), Map.entry(stateRegex, EntityParameters::setState),
Map.entry(maxHPRegex, EntityParameters::setMaxHP), Map.entry(maxHPRegex, EntityParameters::setMaxHP),
Map.entry(hpRegex, EntityParameters::setHp), Map.entry(hpRegex, EntityParameters::setHp),
Map.entry(defRegex, EntityParameters::setDef), Map.entry(defRegex, EntityParameters::setDef),
Map.entry(atkRegex, EntityParameters::setAtk), Map.entry(atkRegex, EntityParameters::setAtk),
Map.entry(aiRegex, EntityParameters::setAi)); Map.entry(aiRegex, EntityParameters::setAi));
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
EntityParameters param = new EntityParameters(); EntityParameters param = new EntityParameters();
parseIntParameters(args, param, intCommandHandlers); parseIntParameters(args, param, intCommandHandlers);
// At this point, first remaining argument MUST be the id and the rest the pos // At this point, first remaining argument MUST be the id and the rest the pos
if (args.size() != 1) { if (args.size() != 1) {
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
try { try {
param.configId = Integer.parseInt(args.get(0)); param.configId = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.cfgId")); CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.cfgId"));
} }
param.scene = targetPlayer.getScene(); param.scene = targetPlayer.getScene();
var entity = param.scene.getEntityByConfigId(param.configId); var entity = param.scene.getEntityByConfigId(param.configId);
if (entity == null) { if (entity == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error")); CommandHandler.sendMessage(sender, translate(sender, "commands.entity.not_found_error"));
return; return;
} }
applyFightProps(entity, param); applyFightProps(entity, param);
applyGadgetParams(entity, param); applyGadgetParams(entity, param);
applyMonsterParams(entity, param); applyMonsterParams(entity, param);
CommandHandler.sendMessage(sender, translate(sender, "commands.status.success")); CommandHandler.sendMessage(sender, translate(sender, "commands.status.success"));
} }
private void applyGadgetParams(GameEntity entity, EntityParameters param) { private void applyGadgetParams(GameEntity entity, EntityParameters param) {
if (!(entity instanceof EntityGadget)) { if (!(entity instanceof EntityGadget)) {
return; return;
} }
if (param.state != -1) { if (param.state != -1) {
((EntityGadget) entity).updateState(param.state); ((EntityGadget) entity).updateState(param.state);
} }
} }
private void applyMonsterParams(GameEntity entity, EntityParameters param) { private void applyMonsterParams(GameEntity entity, EntityParameters param) {
if (!(entity instanceof EntityMonster)) { if (!(entity instanceof EntityMonster)) {
return; return;
} }
if (param.ai != -1) { if (param.ai != -1) {
((EntityMonster) entity).setAiId(param.ai); ((EntityMonster) entity).setAiId(param.ai);
// TODO notify // TODO notify
} }
} }
private void applyFightProps(GameEntity entity, EntityParameters param) { private void applyFightProps(GameEntity entity, EntityParameters param) {
var changedFields = new ArrayList<FightProperty>(); var changedFields = new ArrayList<FightProperty>();
if (param.maxHP != -1) { if (param.maxHP != -1) {
setFightProperty(entity, FightProperty.FIGHT_PROP_MAX_HP, param.maxHP, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_MAX_HP, param.maxHP, changedFields);
} }
if (param.hp != -1) { if (param.hp != -1) {
float targetHp = param.hp == 0 ? Float.MAX_VALUE : param.hp; float targetHp = param.hp == 0 ? Float.MAX_VALUE : param.hp;
float oldHp = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float oldHp = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_HP, targetHp, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_HP, targetHp, changedFields);
EntityDamageEvent event = EntityDamageEvent event =
new EntityDamageEvent(entity, oldHp - targetHp, ElementType.None, null); new EntityDamageEvent(entity, oldHp - targetHp, ElementType.None, null);
callHPEvents(entity, event); callHPEvents(entity, event);
} }
if (param.atk != -1) { if (param.atk != -1) {
setFightProperty(entity, FightProperty.FIGHT_PROP_ATTACK, param.atk, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_ATTACK, param.atk, changedFields);
setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk, changedFields);
} }
if (param.def != -1) { if (param.def != -1) {
setFightProperty(entity, FightProperty.FIGHT_PROP_DEFENSE, param.def, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_DEFENSE, param.def, changedFields);
setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def, changedFields); setFightProperty(entity, FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def, changedFields);
} }
if (!changedFields.isEmpty()) { if (!changedFields.isEmpty()) {
entity entity
.getScene() .getScene()
.broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, changedFields)); .broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, changedFields));
} }
} }
private void callHPEvents(GameEntity entity, EntityDamageEvent event) { private void callHPEvents(GameEntity entity, EntityDamageEvent event) {
entity.callLuaHPEvent(event); entity.runLuaCallbacks(event);
} }
private void setFightProperty( private void setFightProperty(
GameEntity entity, FightProperty property, float value, List<FightProperty> modifiedProps) { GameEntity entity, FightProperty property, float value, List<FightProperty> modifiedProps) {
entity.setFightProperty(property, value); entity.setFightProperty(property, value);
modifiedProps.add(property); modifiedProps.add(property);
} }
private static class EntityParameters { private static class EntityParameters {
@Setter public int configId = -1; @Setter public int configId = -1;
@Setter public int state = -1; @Setter public int state = -1;
@Setter public int hp = -1; @Setter public int hp = -1;
@Setter public int maxHP = -1; @Setter public int maxHP = -1;
@Setter public int atk = -1; @Setter public int atk = -1;
@Setter public int def = -1; @Setter public int def = -1;
@Setter public int ai = -1; @Setter public int ai = -1;
public Scene scene = null; public Scene scene = null;
} }
} }

View File

@ -1,279 +1,279 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.tower.TowerLevelRecord; import emu.grasscutter.game.tower.TowerLevelRecord;
import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify;
import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Command( @Command(
label = "setProp", label = "setProp",
aliases = {"prop"}, aliases = {"prop"},
usage = {"<prop> <value>"}, usage = {"<prop> <value>"},
permission = "player.setprop", permission = "player.setprop",
permissionTargeted = "player.setprop.others") permissionTargeted = "player.setprop.others")
public final class SetPropCommand implements CommandHandler { public final class SetPropCommand implements CommandHandler {
// List of map areas. Unfortunately, there is no readily available source for them in excels or // List of map areas. Unfortunately, there is no readily available source for them in excels or
// bins. // bins.
private static final List<Integer> sceneAreas = private static final List<Integer> sceneAreas =
List.of( List.of(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403); 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403);
Map<String, Prop> props; Map<String, Prop> props;
public SetPropCommand() { public SetPropCommand() {
this.props = new HashMap<>(); this.props = new HashMap<>();
// Full PlayerProperty enum that won't be advertised but can be used by devs // Full PlayerProperty enum that won't be advertised but can be used by devs
for (PlayerProperty prop : PlayerProperty.values()) { for (PlayerProperty prop : PlayerProperty.values()) {
String name = prop.toString().substring(5); // PROP_EXP -> EXP String name = prop.toString().substring(5); // PROP_EXP -> EXP
String key = name.toLowerCase(); // EXP -> exp String key = name.toLowerCase(); // EXP -> exp
this.props.put(key, new Prop(name, prop)); this.props.put(key, new Prop(name, prop));
} }
// Add special props // Add special props
Prop worldlevel = Prop worldlevel =
new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL);
this.props.put("worldlevel", worldlevel); this.props.put("worldlevel", worldlevel);
this.props.put("wl", worldlevel); this.props.put("wl", worldlevel);
Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL);
this.props.put("abyss", abyss); this.props.put("abyss", abyss);
this.props.put("abyssfloor", abyss); this.props.put("abyssfloor", abyss);
this.props.put("ut", abyss); this.props.put("ut", abyss);
this.props.put("tower", abyss); this.props.put("tower", abyss);
this.props.put("towerlevel", abyss); this.props.put("towerlevel", abyss);
this.props.put("unlocktower", abyss); this.props.put("unlocktower", abyss);
Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL);
this.props.put("bplevel", bplevel); this.props.put("bplevel", bplevel);
this.props.put("bp", bplevel); this.props.put("bp", bplevel);
this.props.put("battlepass", bplevel); this.props.put("battlepass", bplevel);
Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE);
this.props.put("godmode", godmode); this.props.put("godmode", godmode);
this.props.put("god", godmode); this.props.put("god", godmode);
Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA);
this.props.put("unlimitedstamina", nostamina); this.props.put("unlimitedstamina", nostamina);
this.props.put("us", nostamina); this.props.put("us", nostamina);
this.props.put("nostamina", nostamina); this.props.put("nostamina", nostamina);
this.props.put("nostam", nostamina); this.props.put("nostam", nostamina);
this.props.put("ns", nostamina); this.props.put("ns", nostamina);
Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY);
this.props.put("unlimitedenergy", unlimitedenergy); this.props.put("unlimitedenergy", unlimitedenergy);
this.props.put("ue", unlimitedenergy); this.props.put("ue", unlimitedenergy);
Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE);
this.props.put("setopenstate", setopenstate); this.props.put("setopenstate", setopenstate);
this.props.put("so", setopenstate); this.props.put("so", setopenstate);
Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE); Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE);
this.props.put("unsetopenstate", unsetopenstate); this.props.put("unsetopenstate", unsetopenstate);
this.props.put("uo", unsetopenstate); this.props.put("uo", unsetopenstate);
Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP); Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP);
this.props.put("unlockmap", unlockmap); this.props.put("unlockmap", unlockmap);
this.props.put("um", unlockmap); this.props.put("um", unlockmap);
} }
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) { if (args.size() != 2) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
String propStr = args.get(0).toLowerCase(); String propStr = args.get(0).toLowerCase();
String valueStr = args.get(1).toLowerCase(); String valueStr = args.get(1).toLowerCase();
int value; int value;
if (!props.containsKey(propStr)) { if (!props.containsKey(propStr)) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
try { try {
value = value =
switch (valueStr.toLowerCase()) { switch (valueStr.toLowerCase()) {
case "on", "true" -> 1; case "on", "true" -> 1;
case "off", "false" -> 0; case "off", "false" -> 0;
case "toggle" -> -1; case "toggle" -> -1;
default -> Integer.parseInt(valueStr); default -> Integer.parseInt(valueStr);
}; };
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return; return;
} }
boolean success = false; boolean success = false;
Prop prop = props.get(propStr); Prop prop = props.get(propStr);
success = success =
switch (prop.pseudoProp) { switch (prop.pseudoProp) {
case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); case WORLD_LEVEL -> targetPlayer.setWorldLevel(value);
case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value);
case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value);
case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool( case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool(
sender, targetPlayer, prop.pseudoProp, value); sender, targetPlayer, prop.pseudoProp, value);
case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1); case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1);
case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0);
case UNLOCK_MAP -> unlockMap(targetPlayer); case UNLOCK_MAP -> unlockMap(targetPlayer);
default -> targetPlayer.setProperty(prop.prop, value); default -> targetPlayer.setProperty(prop.prop, value);
}; };
if (success) { if (success) {
if (targetPlayer == sender) { if (targetPlayer == sender) {
CommandHandler.sendTranslatedMessage( CommandHandler.sendTranslatedMessage(
sender, "commands.generic.set_to", prop.name, valueStr); sender, "commands.generic.set_to", prop.name, valueStr);
} else { } else {
String uidStr = targetPlayer.getAccount().getId(); String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendTranslatedMessage( CommandHandler.sendTranslatedMessage(
sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr);
} }
} else { } else {
if (prop.prop if (prop.prop
!= PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages
int min = targetPlayer.getPropertyMin(prop.prop); int min = targetPlayer.getPropertyMin(prop.prop);
int max = targetPlayer.getPropertyMax(prop.prop); int max = targetPlayer.getPropertyMax(prop.prop);
CommandHandler.sendTranslatedMessage( CommandHandler.sendTranslatedMessage(
sender, "commands.generic.invalid.value_between", prop.name, min, max); sender, "commands.generic.invalid.value_between", prop.name, min, max);
} }
} }
} }
private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) {
List<Integer> floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); List<Integer> floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors();
if (topFloor < 0 || topFloor > floorIds.size()) { if (topFloor < 0 || topFloor > floorIds.size()) {
CommandHandler.sendTranslatedMessage( CommandHandler.sendTranslatedMessage(
sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size());
return false; return false;
} }
Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap(); Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap();
// Add records for each unlocked floor // Add records for each unlocked floor
for (int floor : floorIds.subList(0, topFloor)) { for (int floor : floorIds.subList(0, topFloor)) {
if (!recordMap.containsKey(floor)) { if (!recordMap.containsKey(floor)) {
recordMap.put(floor, new TowerLevelRecord(floor)); recordMap.put(floor, new TowerLevelRecord(floor));
} }
} }
// Remove records for each floor past our target // Remove records for each floor past our target
for (int floor : floorIds.subList(topFloor, floorIds.size())) { for (int floor : floorIds.subList(topFloor, floorIds.size())) {
recordMap.remove(floor); recordMap.remove(floor);
} }
// Six stars required on Floor 8 to unlock Floor 9+ // Six stars required on Floor 8 to unlock Floor 9+
if (topFloor > 8) { if (topFloor > 8) {
recordMap recordMap
.get(floorIds.get(7)) .get(floorIds.get(7))
.setLevelStars( .setLevelStars(
0, 0,
6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at
// all // all
} }
return true; return true;
} }
private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) {
boolean enabled = boolean enabled =
switch (pseudoProp) { switch (pseudoProp) {
case GOD_MODE -> targetPlayer.inGodmode(); case GOD_MODE -> targetPlayer.isInGodMode();
case UNLIMITED_STAMINA -> targetPlayer.getUnlimitedStamina(); case UNLIMITED_STAMINA -> targetPlayer.isUnlimitedStamina();
case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage(); case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().isEnergyUsage();
default -> false; default -> false;
}; };
enabled = enabled =
switch (value) { switch (value) {
case -1 -> !enabled; case -1 -> !enabled;
case 0 -> false; case 0 -> false;
default -> true; default -> true;
}; };
switch (pseudoProp) { switch (pseudoProp) {
case GOD_MODE: case GOD_MODE:
targetPlayer.setGodmode(enabled); targetPlayer.setInGodMode(enabled);
break; break;
case UNLIMITED_STAMINA: case UNLIMITED_STAMINA:
targetPlayer.setUnlimitedStamina(enabled); targetPlayer.setUnlimitedStamina(enabled);
break; break;
case UNLIMITED_ENERGY: case UNLIMITED_ENERGY:
targetPlayer.getEnergyManager().setEnergyUsage(!enabled); targetPlayer.getEnergyManager().setEnergyUsage(!enabled);
break; break;
default: default:
return false; return false;
} }
return true; return true;
} }
private boolean setOpenState(Player targetPlayer, int state, int value) { private boolean setOpenState(Player targetPlayer, int state, int value) {
targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value));
return true; return true;
} }
private boolean unlockMap(Player targetPlayer) { private boolean unlockMap(Player targetPlayer) {
// Unlock. // Unlock.
GameData.getScenePointsPerScene() GameData.getScenePointsPerScene()
.forEach( .forEach(
(sceneId, scenePoints) -> { (sceneId, scenePoints) -> {
// Unlock trans points. // Unlock trans points.
targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints);
// Unlock map areas. // Unlock map areas.
targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas);
}); });
// Send notify. // Send notify.
int playerScene = targetPlayer.getSceneId(); int playerScene = targetPlayer.getSceneId();
targetPlayer.sendPacket( targetPlayer.sendPacket(
new PacketScenePointUnlockNotify( new PacketScenePointUnlockNotify(
playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); playerScene, targetPlayer.getUnlockedScenePoints(playerScene)));
targetPlayer.sendPacket( targetPlayer.sendPacket(
new PacketSceneAreaUnlockNotify( new PacketSceneAreaUnlockNotify(
playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); playerScene, targetPlayer.getUnlockedSceneAreas(playerScene)));
return true; return true;
} }
enum PseudoProp { enum PseudoProp {
NONE, NONE,
WORLD_LEVEL, WORLD_LEVEL,
TOWER_LEVEL, TOWER_LEVEL,
BP_LEVEL, BP_LEVEL,
GOD_MODE, GOD_MODE,
UNLIMITED_STAMINA, UNLIMITED_STAMINA,
UNLIMITED_ENERGY, UNLIMITED_ENERGY,
SET_OPENSTATE, SET_OPENSTATE,
UNSET_OPENSTATE, UNSET_OPENSTATE,
UNLOCK_MAP UNLOCK_MAP
} }
static class Prop { static class Prop {
String name; String name;
PlayerProperty prop; PlayerProperty prop;
PseudoProp pseudoProp; PseudoProp pseudoProp;
public Prop(PlayerProperty prop) { public Prop(PlayerProperty prop) {
this(prop.toString(), prop, PseudoProp.NONE); this(prop.toString(), prop, PseudoProp.NONE);
} }
public Prop(String name) { public Prop(String name) {
this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE);
} }
public Prop(String name, PseudoProp pseudoProp) { public Prop(String name, PseudoProp pseudoProp) {
this(name, PlayerProperty.PROP_NONE, pseudoProp); this(name, PlayerProperty.PROP_NONE, pseudoProp);
} }
public Prop(String name, PlayerProperty prop) { public Prop(String name, PlayerProperty prop) {
this(name, prop, PseudoProp.NONE); this(name, prop, PseudoProp.NONE);
} }
public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) {
this.name = name; this.name = name;
this.prop = prop; this.prop = prop;
this.pseudoProp = pseudoProp; this.pseudoProp = pseudoProp;
} }
} }
} }

View File

@ -6,11 +6,12 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import lombok.NoArgsConstructor;
import java.util.Set;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import static emu.grasscutter.Grasscutter.config; import static emu.grasscutter.Grasscutter.config;
@ -18,14 +19,6 @@ import static emu.grasscutter.Grasscutter.config;
* *when your JVM fails* * *when your JVM fails*
*/ */
public class ConfigContainer { public class ConfigContainer {
public Structure folderStructure = new Structure();
public Database databaseInfo = new Database();
public Language language = new Language();
public Account account = new Account();
public Server server = new Server();
// DO NOT. TOUCH. THE VERSION NUMBER.
public int version = version();
private static int version() { private static int version() {
return 4; return 4;
} }
@ -40,8 +33,7 @@ public class ConfigContainer {
Grasscutter.getLogger().info("Updating legacy .."); Grasscutter.getLogger().info("Updating legacy ..");
Grasscutter.saveConfig(null); Grasscutter.saveConfig(null);
} }
} catch (Exception ignored) { } catch (Exception ignored) { }
}
var existing = config.version; var existing = config.version;
var latest = version(); var latest = version();
@ -59,8 +51,7 @@ public class ConfigContainer {
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().error("Failed to update a configuration field.", exception); Grasscutter.getLogger().error("Failed to update a configuration field.", exception);
} }
}); }); updated.version = version();
updated.version = version();
try { // Save configuration & reload. try { // Save configuration & reload.
Grasscutter.saveConfig(updated); Grasscutter.saveConfig(updated);
@ -70,6 +61,15 @@ public class ConfigContainer {
} }
} }
public Structure folderStructure = new Structure();
public Database databaseInfo = new Database();
public Language language = new Language();
public Account account = new Account();
public Server server = new Server();
// DO NOT. TOUCH. THE VERSION NUMBER.
public int version = version();
/* Option containers. */ /* Option containers. */
public static class Database { public static class Database {
@ -145,7 +145,7 @@ public class ConfigContainer {
public int accessPort = 0; public int accessPort = 0;
/* Entities within a certain range will be loaded for the player */ /* Entities within a certain range will be loaded for the player */
public int loadEntitiesForPlayerRange = 100; public int loadEntitiesForPlayerRange = 300;
public boolean enableScriptInBigWorld = false; public boolean enableScriptInBigWorld = false;
public boolean enableConsole = true; public boolean enableConsole = true;
@ -154,13 +154,24 @@ public class ConfigContainer {
/* Controls whether packets should be logged in console or not */ /* Controls whether packets should be logged in console or not */
public ServerDebugMode logPackets = ServerDebugMode.NONE; public ServerDebugMode logPackets = ServerDebugMode.NONE;
/* Show packet payload in console or no (in any case the payload is shown in encrypted view) */ /* Show packet payload in console or no (in any case the payload is shown in encrypted view) */
public Boolean isShowPacketPayload = false; public boolean isShowPacketPayload = false;
/* Show annoying loop packets or no */ /* Show annoying loop packets or no */
public Boolean isShowLoopPackets = false; public boolean isShowLoopPackets = false;
public boolean cacheSceneEntitiesEveryRun = false;
public GameOptions gameOptions = new GameOptions(); public GameOptions gameOptions = new GameOptions();
public JoinOptions joinOptions = new JoinOptions(); public JoinOptions joinOptions = new JoinOptions();
public ConsoleAccount serverAccount = new ConsoleAccount(); public ConsoleAccount serverAccount = new ConsoleAccount();
public VisionOptions[] visionOptions = new VisionOptions[] {
new VisionOptions("VISION_LEVEL_NORMAL" , 80 , 20),
new VisionOptions("VISION_LEVEL_LITTLE_REMOTE" , 16 , 40),
new VisionOptions("VISION_LEVEL_REMOTE" , 1000 , 250),
new VisionOptions("VISION_LEVEL_SUPER" , 4000 , 1000),
new VisionOptions("VISION_LEVEL_NEARBY" , 40 , 20),
new VisionOptions("VISION_LEVEL_SUPER_NEARBY" , 20 , 20)
};
} }
/* Data containers. */ /* Data containers. */
@ -188,10 +199,10 @@ public class ConfigContainer {
public ServerDebugMode logPackets = ServerDebugMode.ALL; public ServerDebugMode logPackets = ServerDebugMode.ALL;
/* Show packet payload in console or no (in any case the payload is shown in encrypted view) */ /* Show packet payload in console or no (in any case the payload is shown in encrypted view) */
public Boolean isShowPacketPayload = false; public boolean isShowPacketPayload = false;
/* Show annoying loop packets or no */ /* Show annoying loop packets or no */
public Boolean isShowLoopPackets = false; public boolean isShowLoopPackets = false;
/* Controls whether http requests should be logged in console or not */ /* Controls whether http requests should be logged in console or not */
public ServerDebugMode logRequests = ServerDebugMode.ALL; public ServerDebugMode logRequests = ServerDebugMode.ALL;
@ -224,6 +235,7 @@ public class ConfigContainer {
public boolean staminaUsage = true; public boolean staminaUsage = true;
public boolean energyUsage = true; public boolean energyUsage = true;
public boolean fishhookTeleport = true; public boolean fishhookTeleport = true;
public boolean questing = false;
public ResinOptions resinOptions = new ResinOptions(); public ResinOptions resinOptions = new ResinOptions();
public Rates rates = new Rates(); public Rates rates = new Rates();
@ -253,6 +265,18 @@ public class ConfigContainer {
} }
} }
public static class VisionOptions {
public String name;
public int visionRange;
public int gridWidth;
public VisionOptions(String name, int visionRange, int gridWidth) {
this.name = name;
this.visionRange = visionRange;
this.gridWidth = gridWidth;
}
}
public static class JoinOptions { public static class JoinOptions {
public int[] welcomeEmotes = {2007, 1002, 4010}; public int[] welcomeEmotes = {2007, 1002, 4010};
public String welcomeMessage = "Welcome to a Grasscutter server."; public String welcomeMessage = "Welcome to a Grasscutter server.";
@ -261,12 +285,12 @@ public class ConfigContainer {
public static class Mail { public static class Mail {
public String title = "Welcome to Grasscutter!"; public String title = "Welcome to Grasscutter!";
public String content = """ public String content = """
Hi there!\r Hi there!\r
First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r
\r \r
Check out our:\r Check out our:\r
<type="browser" text="Discord" href="https://discord.gg/T5vZU6UyeG"/> <type="browser" text="Discord" href="https://discord.gg/T5vZU6UyeG"/>
"""; """;
public String sender = "Lawnmower"; public String sender = "Lawnmower";
public emu.grasscutter.game.mail.Mail.MailItem[] items = { public emu.grasscutter.game.mail.Mail.MailItem[] items = {
new emu.grasscutter.game.mail.Mail.MailItem(13509, 1, 1), new emu.grasscutter.game.mail.Mail.MailItem(13509, 1, 1),
@ -292,13 +316,13 @@ public class ConfigContainer {
/* Objects. */ /* Objects. */
@NoArgsConstructor
public static class Region { public static class Region {
public String Name = "os_usa"; public String Name = "os_usa";
public String Title = "Grasscutter"; public String Title = "Grasscutter";
public String Ip = "127.0.0.1"; public String Ip = "127.0.0.1";
public int Port = 22102; public int Port = 22102;
public Region() {
}
public Region( public Region(
String name, String title, String name, String title,
String address, int port String address, int port
@ -306,7 +330,7 @@ public class ConfigContainer {
this.Name = name; this.Name = name;
this.Title = title; this.Title = title;
this.Ip = address; this.Ip = address;
this.Port = port; this.Port = port;
} }
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +1,58 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.dungeon.DailyDungeonData; import emu.grasscutter.data.GameData;
import emu.grasscutter.utils.Position; import emu.grasscutter.data.excels.dungeon.DailyDungeonData;
import it.unimi.dsi.fastutil.ints.IntArrayList; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import lombok.Getter; import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Setter; import lombok.Getter;
import lombok.Setter;
public class PointData {
@Getter @Setter private int id; public final class PointData {
private String $type; @Getter @Setter private int id;
@Getter private Position tranPos; private String $type;
@Getter private Position tranPos;
@SerializedName( @Getter private Position pos;
value = "dungeonIds", @Getter private Position rot;
alternate = {"JHHFPGJNMIN"}) @Getter private Position size;
@Getter
private int[] dungeonIds; @SerializedName(value="dungeonIds", alternate={"JHHFPGJNMIN"})
@Getter private int[] dungeonIds;
@SerializedName(
value = "dungeonRandomList", @SerializedName(value="dungeonRandomList", alternate={"OIBKFJNBLHO"})
alternate = {"OIBKFJNBLHO"}) @Getter private int[] dungeonRandomList;
@Getter
private int[] dungeonRandomList; @SerializedName(value="groupIDs", alternate={"HFOBOOHKBGF"})
@Getter private int[] groupIDs;
@SerializedName(
value = "tranSceneId", @SerializedName(value="tranSceneId", alternate={"JHBICGBAPIH"})
alternate = {"JHBICGBAPIH"}) @Getter @Setter private int tranSceneId;
@Getter
@Setter public String getType() {
private int tranSceneId; return $type;
}
public String getType() {
return $type; public void updateDailyDungeon() {
} if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) {
return;
public void updateDailyDungeon() { }
if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) {
return; IntList newDungeons = new IntArrayList();
} int day = Grasscutter.getCurrentDayOfWeek();
IntList newDungeons = new IntArrayList(); for (int randomId : this.dungeonRandomList) {
int day = Grasscutter.getCurrentDayOfWeek(); DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId);
for (int randomId : this.dungeonRandomList) { if (data != null) {
DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId); for (int d : data.getDungeonsByDay(day)) {
newDungeons.add(d);
if (data != null) { }
for (int d : data.getDungeonsByDay(day)) { }
newDungeons.add(d); }
}
} this.dungeonIds = newDungeons.toIntArray();
} }
}
this.dungeonIds = newDungeons.toIntArray();
}
}

View File

@ -7,7 +7,7 @@ import lombok.Getter;
@ResourceType(name = "GadgetExcelConfigData.json") @ResourceType(name = "GadgetExcelConfigData.json")
@Getter @Getter
public class GadgetData extends GameResource { public final class GadgetData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
@ -17,5 +17,6 @@ public class GadgetData extends GameResource {
private String[] tags; private String[] tags;
private String itemJsonName; private String itemJsonName;
private long nameTextMapHash; private long nameTextMapHash;
private int campID; private int campId;
private String visionLevel;
} }

View File

@ -41,7 +41,7 @@ public class AvatarData extends GameResource {
@Getter @Getter
private int staminaRecoverSpeed; private int staminaRecoverSpeed;
@Getter @Getter
private List<String> candSkillDepotIds; private List<Integer> candSkillDepotIds;
@Getter @Getter
private String avatarIdentityType; private String avatarIdentityType;
@Getter @Getter

View File

@ -1,117 +1,120 @@
package emu.grasscutter.data.excels.monster; package emu.grasscutter.data.excels.monster;
import com.google.gson.annotations.SerializedName; import java.util.List;
import emu.grasscutter.data.GameData; import java.util.Map.Entry;
import emu.grasscutter.data.GameResource; import java.util.Set;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.data.GameResource;
import emu.grasscutter.game.props.MonsterType; import emu.grasscutter.data.ResourceType;
import java.util.List; import emu.grasscutter.data.ResourceType.LoadPriority;
import java.util.Set; import emu.grasscutter.data.common.PropGrowCurve;
import lombok.Getter; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.props.FightProperty;
@ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW) import emu.grasscutter.game.props.MonsterType;
@Getter import lombok.Getter;
public class MonsterData extends GameResource {
public static Set<FightProperty> definedFightProperties = @ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW)
Set.of( @Getter
FightProperty.FIGHT_PROP_BASE_HP, public class MonsterData extends GameResource {
FightProperty.FIGHT_PROP_BASE_ATTACK, static public Set<FightProperty> definedFightProperties = Set.of(FightProperty.FIGHT_PROP_BASE_HP, FightProperty.FIGHT_PROP_BASE_ATTACK, FightProperty.FIGHT_PROP_BASE_DEFENSE, FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FightProperty.FIGHT_PROP_ELEC_SUB_HURT, FightProperty.FIGHT_PROP_WATER_SUB_HURT, FightProperty.FIGHT_PROP_GRASS_SUB_HURT, FightProperty.FIGHT_PROP_WIND_SUB_HURT, FightProperty.FIGHT_PROP_ROCK_SUB_HURT, FightProperty.FIGHT_PROP_ICE_SUB_HURT);
FightProperty.FIGHT_PROP_BASE_DEFENSE,
FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, @Getter(onMethod_ = @Override)
FightProperty.FIGHT_PROP_FIRE_SUB_HURT, private int id;
FightProperty.FIGHT_PROP_ELEC_SUB_HURT,
FightProperty.FIGHT_PROP_WATER_SUB_HURT, private String monsterName;
FightProperty.FIGHT_PROP_GRASS_SUB_HURT, private MonsterType type;
FightProperty.FIGHT_PROP_WIND_SUB_HURT, private String serverScript;
FightProperty.FIGHT_PROP_ROCK_SUB_HURT, private List<Integer> affix;
FightProperty.FIGHT_PROP_ICE_SUB_HURT); private String ai;
private int[] equips;
@Getter(onMethod_ = @Override) private List<HpDrops> hpDrops;
private int id; private int killDropId;
private String excludeWeathers;
private String monsterName; private int featureTagGroupID;
private MonsterType type; private int mpPropID;
private String serverScript; private String skin;
private List<Integer> affix; private int describeId;
private String ai; private int combatBGMLevel;
private int[] equips; private int entityBudgetLevel;
private List<HpDrops> hpDrops;
private int killDropId; @SerializedName("hpBase")
private String excludeWeathers; private float baseHp;
private int featureTagGroupID; @SerializedName("attackBase")
private int mpPropID; private float baseAttack;
private String skin; @SerializedName("defenseBase")
private int describeId; private float baseDefense;
private int combatBGMLevel;
private int entityBudgetLevel; private float fireSubHurt;
private float elecSubHurt;
@SerializedName("hpBase") private float grassSubHurt;
private float baseHp; private float waterSubHurt;
private float windSubHurt;
@SerializedName("attackBase") private float rockSubHurt;
private float baseAttack; private float iceSubHurt;
private float physicalSubHurt;
@SerializedName("defenseBase") private List<PropGrowCurve> propGrowCurves;
private float baseDefense; private long nameTextMapHash;
private int campID;
private float fireSubHurt;
private float elecSubHurt; // Transient
private float grassSubHurt; private int weaponId;
private float waterSubHurt; private MonsterDescribeData describeData;
private float windSubHurt;
private float rockSubHurt; private int specialNameId; // will only be set if describe data is available
private float iceSubHurt;
private float physicalSubHurt; @Override
private List<PropGrowCurve> propGrowCurves; public void onLoad() {
private long nameTextMapHash; for (int id : this.equips) {
private int campID; if (id == 0) {
continue;
// Transient }
private int weaponId;
private MonsterDescribeData describeData; GadgetData gadget = GameData.getGadgetDataMap().get(id);
if (gadget == null) {
public float getFightProperty(FightProperty prop) { continue;
return switch (prop) { }
case FIGHT_PROP_BASE_HP -> this.baseHp;
case FIGHT_PROP_BASE_ATTACK -> this.baseAttack; if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) {
case FIGHT_PROP_BASE_DEFENSE -> this.baseDefense; this.weaponId = id;
case FIGHT_PROP_PHYSICAL_SUB_HURT -> this.physicalSubHurt; }
case FIGHT_PROP_FIRE_SUB_HURT -> this.fireSubHurt; }
case FIGHT_PROP_ELEC_SUB_HURT -> this.elecSubHurt;
case FIGHT_PROP_WATER_SUB_HURT -> this.waterSubHurt; this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId());
case FIGHT_PROP_GRASS_SUB_HURT -> this.grassSubHurt;
case FIGHT_PROP_WIND_SUB_HURT -> this.windSubHurt; if (this.describeData == null){
case FIGHT_PROP_ROCK_SUB_HURT -> this.rockSubHurt; return;
case FIGHT_PROP_ICE_SUB_HURT -> this.iceSubHurt; }
default -> 0f; for(Entry<Integer, MonsterSpecialNameData> entry: GameData.getMonsterSpecialNameDataMap().entrySet()) {
}; if (entry.getValue().getSpecialNameLabId() == this.getDescribeData().getSpecialNameLabId()){
} this.specialNameId = entry.getKey();
break;
@Override }
public void onLoad() { }
this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId()); }
for (int id : this.equips) { public float getFightProperty(FightProperty prop) {
if (id == 0) { return switch (prop) {
continue; case FIGHT_PROP_BASE_HP -> this.baseHp;
} case FIGHT_PROP_BASE_ATTACK -> this.baseAttack;
GadgetData gadget = GameData.getGadgetDataMap().get(id); case FIGHT_PROP_BASE_DEFENSE -> this.baseDefense;
if (gadget == null) { case FIGHT_PROP_PHYSICAL_SUB_HURT -> this.physicalSubHurt;
continue; case FIGHT_PROP_FIRE_SUB_HURT -> this.fireSubHurt;
} case FIGHT_PROP_ELEC_SUB_HURT -> this.elecSubHurt;
if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) { case FIGHT_PROP_WATER_SUB_HURT -> this.waterSubHurt;
this.weaponId = id; case FIGHT_PROP_GRASS_SUB_HURT -> this.grassSubHurt;
} case FIGHT_PROP_WIND_SUB_HURT -> this.windSubHurt;
} case FIGHT_PROP_ROCK_SUB_HURT -> this.rockSubHurt;
} case FIGHT_PROP_ICE_SUB_HURT -> this.iceSubHurt;
default -> 0f;
@Getter };
public class HpDrops { }
private int DropId;
private int HpPercent; @Getter
} public class HpDrops {
} private int DropId;
private int HpPercent;
}
}

View File

@ -1,17 +1,21 @@
package emu.grasscutter.data.excels.monster; package emu.grasscutter.data.excels.monster;
import emu.grasscutter.data.GameResource; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType;
import lombok.Getter; import emu.grasscutter.data.ResourceType.LoadPriority;
import lombok.Getter;
@ResourceType(name = "MonsterDescribeExcelConfigData.json", loadPriority = LoadPriority.HIGH)
@Getter @ResourceType(name = "MonsterDescribeExcelConfigData.json", loadPriority = LoadPriority.HIGH)
public class MonsterDescribeData extends GameResource { @Getter
@Getter(onMethod_ = @Override) public class MonsterDescribeData extends GameResource {
private int id; @Getter(onMethod_ = @Override)
private int id;
private long nameTextMapHash;
private int titleID; private long nameTextMapHash;
private int specialNameLabID; @SerializedName(value = "titleId", alternate={"titleID"})
} private int titleId;
@SerializedName(value = "specialNameLabId", alternate={"specialNameLabID"})
private int specialNameLabId;
private MonsterSpecialNameData specialNameData;
}

View File

@ -1,452 +1,464 @@
package emu.grasscutter.database; package emu.grasscutter.database;
import static com.mongodb.client.model.Filters.eq; import static com.mongodb.client.model.Filters.eq;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import dev.morphia.query.FindOptions; import dev.morphia.query.FindOptions;
import dev.morphia.query.Sort; import dev.morphia.query.Sort;
import dev.morphia.query.experimental.filters.Filters; import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.achievement.Achievements; import emu.grasscutter.game.achievement.Achievements;
import emu.grasscutter.game.activity.PlayerActivityData; import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap; import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.battlepass.BattlePassManager; import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.friends.Friendship;
import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.gacha.GachaRecord;
import emu.grasscutter.game.home.GameHome; import emu.grasscutter.game.home.GameHome;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameMainQuest;
import java.util.List; import emu.grasscutter.game.world.SceneGroupInstance;
import java.util.stream.Stream;
import java.util.List;
public final class DatabaseHelper { import java.util.stream.Stream;
public static Account createAccount(String username) {
return createAccountWithUid(username, 0); public final class DatabaseHelper {
} public static Account createAccount(String username) {
return createAccountWithUid(username, 0);
public static Account createAccountWithUid(String username, int reservedUid) { }
// Unique names only
if (DatabaseHelper.checkIfAccountExists(username)) { public static Account createAccountWithUid(String username, int reservedUid) {
return null; // Unique names only
} if (DatabaseHelper.checkIfAccountExists(username)) {
return null;
// Make sure there are no id collisions }
if (reservedUid > 0) {
// Cannot make account with the same uid as the server console // Make sure there are no id collisions
if (reservedUid == GameConstants.SERVER_CONSOLE_UID) { if (reservedUid > 0) {
return null; // Cannot make account with the same uid as the server console
} if (reservedUid == GameConstants.SERVER_CONSOLE_UID) {
return null;
if (DatabaseHelper.checkIfAccountExists(reservedUid)) { }
return null;
} if (DatabaseHelper.checkIfAccountExists(reservedUid)) {
return null;
// Make sure no existing player already has this id. }
if (DatabaseHelper.checkIfPlayerExists(reservedUid)) {
return null; // Make sure no existing player already has this id.
} if (DatabaseHelper.checkIfPlayerExists(reservedUid)) {
} return null;
}
// Account }
Account account = new Account();
account.setUsername(username); // Account
account.setId(Integer.toString(DatabaseManager.getNextId(account))); Account account = new Account();
account.setUsername(username);
if (reservedUid > 0) { account.setId(Integer.toString(DatabaseManager.getNextId(account)));
account.setReservedPlayerUid(reservedUid);
} if (reservedUid > 0) {
account.setReservedPlayerUid(reservedUid);
DatabaseHelper.saveAccount(account); }
return account;
} DatabaseHelper.saveAccount(account);
return account;
@Deprecated }
public static Account createAccountWithPassword(String username, String password) {
// Unique names only @Deprecated
Account exists = DatabaseHelper.getAccountByName(username); public static Account createAccountWithPassword(String username, String password) {
if (exists != null) { // Unique names only
return null; Account exists = DatabaseHelper.getAccountByName(username);
} if (exists != null) {
return null;
// Account }
Account account = new Account();
account.setId(Integer.toString(DatabaseManager.getNextId(account))); // Account
account.setUsername(username); Account account = new Account();
account.setPassword(password); account.setId(Integer.toString(DatabaseManager.getNextId(account)));
DatabaseHelper.saveAccount(account); account.setUsername(username);
return account; account.setPassword(password);
} DatabaseHelper.saveAccount(account);
return account;
public static void saveAccount(Account account) { }
DatabaseManager.getAccountDatastore().save(account);
} public static void saveAccount(Account account) {
DatabaseManager.getAccountDatastore().save(account);
public static Account getAccountByName(String username) { }
return DatabaseManager.getAccountDatastore()
.find(Account.class) public static Account getAccountByName(String username) {
.filter(Filters.eq("username", username)) return DatabaseManager.getAccountDatastore()
.first(); .find(Account.class)
} .filter(Filters.eq("username", username))
.first();
public static Account getAccountByToken(String token) { }
if (token == null) return null;
return DatabaseManager.getAccountDatastore() public static Account getAccountByToken(String token) {
.find(Account.class) if (token == null) return null;
.filter(Filters.eq("token", token)) return DatabaseManager.getAccountDatastore()
.first(); .find(Account.class)
} .filter(Filters.eq("token", token))
.first();
public static Account getAccountBySessionKey(String sessionKey) { }
if (sessionKey == null) return null;
return DatabaseManager.getAccountDatastore() public static Account getAccountBySessionKey(String sessionKey) {
.find(Account.class) if (sessionKey == null) return null;
.filter(Filters.eq("sessionKey", sessionKey)) return DatabaseManager.getAccountDatastore()
.first(); .find(Account.class)
} .filter(Filters.eq("sessionKey", sessionKey))
.first();
public static Account getAccountById(String uid) { }
return DatabaseManager.getAccountDatastore()
.find(Account.class) public static Account getAccountById(String uid) {
.filter(Filters.eq("_id", uid)) return DatabaseManager.getAccountDatastore()
.first(); .find(Account.class)
} .filter(Filters.eq("_id", uid))
.first();
public static Account getAccountByPlayerId(int playerId) { }
return DatabaseManager.getAccountDatastore()
.find(Account.class) public static Account getAccountByPlayerId(int playerId) {
.filter(Filters.eq("reservedPlayerId", playerId)) return DatabaseManager.getAccountDatastore()
.first(); .find(Account.class)
} .filter(Filters.eq("reservedPlayerId", playerId))
.first();
public static boolean checkIfAccountExists(String name) { }
return DatabaseManager.getAccountDatastore()
.find(Account.class) public static boolean checkIfAccountExists(String name) {
.filter(Filters.eq("username", name)) return DatabaseManager.getAccountDatastore()
.count() .find(Account.class)
> 0; .filter(Filters.eq("username", name))
} .count()
> 0;
public static boolean checkIfAccountExists(int reservedUid) { }
return DatabaseManager.getAccountDatastore()
.find(Account.class) public static boolean checkIfAccountExists(int reservedUid) {
.filter(Filters.eq("reservedPlayerId", reservedUid)) return DatabaseManager.getAccountDatastore()
.count() .find(Account.class)
> 0; .filter(Filters.eq("reservedPlayerId", reservedUid))
} .count()
> 0;
public static synchronized void deleteAccount(Account target) { }
// To delete an account, we need to also delete all the other documents in the database that
// reference the account. public static synchronized void deleteAccount(Account target) {
// This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way // To delete an account, we need to also delete all the other documents in the database that
// does not leave the // reference the account.
// database in an inconsistent state, but unfortunately Mongo only supports that when we have a // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way
// replica set ... // does not leave the
// database in an inconsistent state, but unfortunately Mongo only supports that when we have a
Player player = Grasscutter.getGameServer().getPlayerByAccountId(target.getId()); // replica set ...
// Close session first Player player = Grasscutter.getGameServer().getPlayerByAccountId(target.getId());
if (player != null) {
player.getSession().close(); // Close session first
} else { if (player != null) {
player = getPlayerByAccount(target); player.getSession().close();
if (player == null) return; } else {
} player = getPlayerByAccount(target);
int uid = player.getUid(); if (player == null) return;
// Delete data from collections }
DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid)); int uid = player.getUid();
DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid)); // Delete data from collections
DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid)); DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid)); DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid)); DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid)); DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid)); DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid)); DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid)); DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid));
// Delete friendships. DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid));
// Here, we need to make sure to not only delete the deleted account's friendships,
// but also all friendship entries for that account's friends. // Delete friendships.
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid)); // Here, we need to make sure to not only delete the deleted account's friendships,
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid)); // but also all friendship entries for that account's friends.
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid));
// Delete the player last. DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid));
DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete();
// Delete the player last.
// Finally, delete the account itself. DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete();
DatabaseManager.getAccountDatastore()
.find(Account.class) // Finally, delete the account itself.
.filter(Filters.eq("id", target.getId())) DatabaseManager.getAccountDatastore()
.delete(); .find(Account.class)
} .filter(Filters.eq("id", target.getId()))
.delete();
public static <T> Stream<T> getByGameClass(Class<T> classType) { }
return DatabaseManager.getGameDatastore().find(classType).stream();
} public static <T> Stream<T> getByGameClass(Class<T> classType) {
return DatabaseManager.getGameDatastore().find(classType).stream();
@Deprecated(forRemoval = true) }
public static List<Player> getAllPlayers() {
return DatabaseManager.getGameDatastore().find(Player.class).stream().toList(); @Deprecated(forRemoval = true)
} public static List<Player> getAllPlayers() {
return DatabaseManager.getGameDatastore().find(Player.class).stream().toList();
public static Player getPlayerByUid(int id) { }
return DatabaseManager.getGameDatastore()
.find(Player.class) public static Player getPlayerByUid(int id) {
.filter(Filters.eq("_id", id)) return DatabaseManager.getGameDatastore()
.first(); .find(Player.class)
} .filter(Filters.eq("_id", id))
.first();
@Deprecated }
public static Player getPlayerByAccount(Account account) {
return DatabaseManager.getGameDatastore() @Deprecated
.find(Player.class) public static Player getPlayerByAccount(Account account) {
.filter(Filters.eq("accountId", account.getId())) return DatabaseManager.getGameDatastore()
.first(); .find(Player.class)
} .filter(Filters.eq("accountId", account.getId()))
.first();
public static Player getPlayerByAccount(Account account, Class<? extends Player> playerClass) { }
return DatabaseManager.getGameDatastore()
.find(playerClass) public static Player getPlayerByAccount(Account account, Class<? extends Player> playerClass) {
.filter(Filters.eq("accountId", account.getId())) return DatabaseManager.getGameDatastore()
.first(); .find(playerClass)
} .filter(Filters.eq("accountId", account.getId()))
.first();
public static boolean checkIfPlayerExists(int uid) { }
return DatabaseManager.getGameDatastore()
.find(Player.class) public static boolean checkIfPlayerExists(int uid) {
.filter(Filters.eq("_id", uid)) return DatabaseManager.getGameDatastore()
.count() .find(Player.class)
> 0; .filter(Filters.eq("_id", uid))
} .count()
> 0;
public static synchronized Player generatePlayerUid(Player character, int reservedId) { }
// Check if reserved id
int id; public static synchronized Player generatePlayerUid(Player character, int reservedId) {
if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { // Check if reserved id
id = reservedId; int id;
character.setUid(id); if (reservedId > 0 && !checkIfPlayerExists(reservedId)) {
} else { id = reservedId;
do { character.setUid(id);
id = DatabaseManager.getNextId(character); } else {
} while (checkIfPlayerExists(id)); do {
character.setUid(id); id = DatabaseManager.getNextId(character);
} } while (checkIfPlayerExists(id));
// Save to database character.setUid(id);
DatabaseManager.getGameDatastore().save(character); }
return character; // Save to database
} DatabaseManager.getGameDatastore().save(character);
return character;
public static synchronized int getNextPlayerId(int reservedId) { }
// Check if reserved id
int id; public static synchronized int getNextPlayerId(int reservedId) {
if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { // Check if reserved id
id = reservedId; int id;
} else { if (reservedId > 0 && !checkIfPlayerExists(reservedId)) {
do { id = reservedId;
id = DatabaseManager.getNextId(Player.class); } else {
} while (checkIfPlayerExists(id)); do {
} id = DatabaseManager.getNextId(Player.class);
return id; } while (checkIfPlayerExists(id));
} }
return id;
public static void savePlayer(Player character) { }
DatabaseManager.getGameDatastore().save(character);
} public static void savePlayer(Player character) {
DatabaseManager.getGameDatastore().save(character);
public static void saveAvatar(Avatar avatar) { }
DatabaseManager.getGameDatastore().save(avatar);
} public static void saveAvatar(Avatar avatar) {
DatabaseManager.getGameDatastore().save(avatar);
public static List<Avatar> getAvatars(Player player) { }
return DatabaseManager.getGameDatastore()
.find(Avatar.class) public static List<Avatar> getAvatars(Player player) {
.filter(Filters.eq("ownerId", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(Avatar.class)
.toList(); .filter(Filters.eq("ownerId", player.getUid()))
} .stream()
.toList();
public static void saveItem(GameItem item) { }
DatabaseManager.getGameDatastore().save(item);
} public static void saveItem(GameItem item) {
DatabaseManager.getGameDatastore().save(item);
public static boolean deleteItem(GameItem item) { }
DeleteResult result = DatabaseManager.getGameDatastore().delete(item);
return result.wasAcknowledged(); public static boolean deleteItem(GameItem item) {
} DeleteResult result = DatabaseManager.getGameDatastore().delete(item);
return result.wasAcknowledged();
public static List<GameItem> getInventoryItems(Player player) { }
return DatabaseManager.getGameDatastore()
.find(GameItem.class) public static List<GameItem> getInventoryItems(Player player) {
.filter(Filters.eq("ownerId", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(GameItem.class)
.toList(); .filter(Filters.eq("ownerId", player.getUid()))
} .stream()
.toList();
public static List<Friendship> getFriends(Player player) { }
return DatabaseManager.getGameDatastore()
.find(Friendship.class) public static List<Friendship> getFriends(Player player) {
.filter(Filters.eq("ownerId", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(Friendship.class)
.toList(); .filter(Filters.eq("ownerId", player.getUid()))
} .stream()
.toList();
public static List<Friendship> getReverseFriends(Player player) { }
return DatabaseManager.getGameDatastore()
.find(Friendship.class) public static List<Friendship> getReverseFriends(Player player) {
.filter(Filters.eq("friendId", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(Friendship.class)
.toList(); .filter(Filters.eq("friendId", player.getUid()))
} .stream()
.toList();
public static void saveFriendship(Friendship friendship) { }
DatabaseManager.getGameDatastore().save(friendship);
} public static void saveFriendship(Friendship friendship) {
DatabaseManager.getGameDatastore().save(friendship);
public static void deleteFriendship(Friendship friendship) { }
DatabaseManager.getGameDatastore().delete(friendship);
} public static void deleteFriendship(Friendship friendship) {
DatabaseManager.getGameDatastore().delete(friendship);
public static Friendship getReverseFriendship(Friendship friendship) { }
return DatabaseManager.getGameDatastore()
.find(Friendship.class) public static Friendship getReverseFriendship(Friendship friendship) {
.filter( return DatabaseManager.getGameDatastore()
Filters.and( .find(Friendship.class)
Filters.eq("ownerId", friendship.getFriendId()), .filter(
Filters.eq("friendId", friendship.getOwnerId()))) Filters.and(
.first(); Filters.eq("ownerId", friendship.getFriendId()),
} Filters.eq("friendId", friendship.getOwnerId())))
.first();
public static List<GachaRecord> getGachaRecords(int ownerId, int page, int gachaType) { }
return getGachaRecords(ownerId, page, gachaType, 10);
} public static List<GachaRecord> getGachaRecords(int ownerId, int page, int gachaType) {
return getGachaRecords(ownerId, page, gachaType, 10);
public static List<GachaRecord> getGachaRecords( }
int ownerId, int page, int gachaType, int pageSize) {
return DatabaseManager.getGameDatastore() public static List<GachaRecord> getGachaRecords(
.find(GachaRecord.class) int ownerId, int page, int gachaType, int pageSize) {
.filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) return DatabaseManager.getGameDatastore()
.iterator( .find(GachaRecord.class)
new FindOptions() .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType))
.sort(Sort.descending("transactionDate")) .iterator(
.skip(pageSize * page) new FindOptions()
.limit(pageSize)) .sort(Sort.descending("transactionDate"))
.toList(); .skip(pageSize * page)
} .limit(pageSize))
.toList();
public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType) { }
return getGachaRecordsMaxPage(ownerId, page, gachaType, 10);
} public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType) {
return getGachaRecordsMaxPage(ownerId, page, gachaType, 10);
public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize) { }
long count =
DatabaseManager.getGameDatastore() public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize) {
.find(GachaRecord.class) long count =
.filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType)) DatabaseManager.getGameDatastore()
.count(); .find(GachaRecord.class)
return count / 10 + (count % 10 > 0 ? 1 : 0); .filter(Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType))
} .count();
return count / 10 + (count % 10 > 0 ? 1 : 0);
public static void saveGachaRecord(GachaRecord gachaRecord) { }
DatabaseManager.getGameDatastore().save(gachaRecord);
} public static void saveGachaRecord(GachaRecord gachaRecord) {
DatabaseManager.getGameDatastore().save(gachaRecord);
public static List<Mail> getAllMail(Player player) { }
return DatabaseManager.getGameDatastore()
.find(Mail.class) public static List<Mail> getAllMail(Player player) {
.filter(Filters.eq("ownerUid", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(Mail.class)
.toList(); .filter(Filters.eq("ownerUid", player.getUid()))
} .stream()
.toList();
public static void saveMail(Mail mail) { }
DatabaseManager.getGameDatastore().save(mail);
} public static void saveMail(Mail mail) {
DatabaseManager.getGameDatastore().save(mail);
public static boolean deleteMail(Mail mail) { }
DeleteResult result = DatabaseManager.getGameDatastore().delete(mail);
return result.wasAcknowledged(); public static boolean deleteMail(Mail mail) {
} DeleteResult result = DatabaseManager.getGameDatastore().delete(mail);
return result.wasAcknowledged();
public static List<GameMainQuest> getAllQuests(Player player) { }
return DatabaseManager.getGameDatastore()
.find(GameMainQuest.class) public static List<GameMainQuest> getAllQuests(Player player) {
.filter(Filters.eq("ownerUid", player.getUid())) return DatabaseManager.getGameDatastore()
.stream() .find(GameMainQuest.class)
.toList(); .filter(Filters.eq("ownerUid", player.getUid()))
} .stream()
.toList();
public static void saveQuest(GameMainQuest quest) { }
DatabaseManager.getGameDatastore().save(quest);
} public static void saveQuest(GameMainQuest quest) {
DatabaseManager.getGameDatastore().save(quest);
public static boolean deleteQuest(GameMainQuest quest) { }
return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged();
} public static boolean deleteQuest(GameMainQuest quest) {
return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged();
public static GameHome getHomeByUid(int id) { }
return DatabaseManager.getGameDatastore()
.find(GameHome.class) public static GameHome getHomeByUid(int id) {
.filter(Filters.eq("ownerUid", id)) return DatabaseManager.getGameDatastore()
.first(); .find(GameHome.class)
} .filter(Filters.eq("ownerUid", id))
.first();
public static void saveHome(GameHome gameHome) { }
DatabaseManager.getGameDatastore().save(gameHome);
} public static void saveHome(GameHome gameHome) {
DatabaseManager.getGameDatastore().save(gameHome);
public static BattlePassManager loadBattlePass(Player player) { }
BattlePassManager manager =
DatabaseManager.getGameDatastore() public static BattlePassManager loadBattlePass(Player player) {
.find(BattlePassManager.class) BattlePassManager manager =
.filter(Filters.eq("ownerUid", player.getUid())) DatabaseManager.getGameDatastore()
.first(); .find(BattlePassManager.class)
if (manager == null) { .filter(Filters.eq("ownerUid", player.getUid()))
manager = new BattlePassManager(player); .first();
manager.save(); if (manager == null) {
} else { manager = new BattlePassManager(player);
manager.setPlayer(player); manager.save();
} } else {
return manager; manager.setPlayer(player);
} }
return manager;
public static void saveBattlePass(BattlePassManager manager) { }
DatabaseManager.getGameDatastore().save(manager);
} public static void saveBattlePass(BattlePassManager manager) {
DatabaseManager.getGameDatastore().save(manager);
public static PlayerActivityData getPlayerActivityData(int uid, int activityId) { }
return DatabaseManager.getGameDatastore()
.find(PlayerActivityData.class) public static PlayerActivityData getPlayerActivityData(int uid, int activityId) {
.filter(Filters.and(Filters.eq("uid", uid), Filters.eq("activityId", activityId))) return DatabaseManager.getGameDatastore()
.first(); .find(PlayerActivityData.class)
} .filter(Filters.and(Filters.eq("uid", uid), Filters.eq("activityId", activityId)))
.first();
public static void savePlayerActivityData(PlayerActivityData playerActivityData) { }
DatabaseManager.getGameDatastore().save(playerActivityData);
} public static void savePlayerActivityData(PlayerActivityData playerActivityData) {
DatabaseManager.getGameDatastore().save(playerActivityData);
public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) { }
return DatabaseManager.getGameDatastore()
.find(MusicGameBeatmap.class) public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) {
.filter(Filters.eq("musicShareId", musicShareId)) return DatabaseManager.getGameDatastore()
.first(); .find(MusicGameBeatmap.class)
} .filter(Filters.eq("musicShareId", musicShareId))
.first();
public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) { }
DatabaseManager.getGameDatastore().save(musicGameBeatmap);
} public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) {
DatabaseManager.getGameDatastore().save(musicGameBeatmap);
public static Achievements getAchievementData(int uid) { }
return DatabaseManager.getGameDatastore()
.find(Achievements.class) public static Achievements getAchievementData(int uid) {
.filter(Filters.and(Filters.eq("uid", uid))) return DatabaseManager.getGameDatastore()
.first(); .find(Achievements.class)
} .filter(Filters.and(Filters.eq("uid", uid)))
.first();
public static void saveAchievementData(Achievements achievements) { }
DatabaseManager.getGameDatastore().save(achievements);
} public static void saveAchievementData(Achievements achievements) {
} DatabaseManager.getGameDatastore().save(achievements);
}
public static void saveGroupInstance(SceneGroupInstance instance) {
DatabaseManager.getGameDatastore().save(instance);
}
public static SceneGroupInstance loadGroupInstance(int groupId, Player owner) {
return DatabaseManager.getGameDatastore().find(SceneGroupInstance.class)
.filter(Filters.and(Filters.eq("ownerUid", owner.getUid()),
Filters.eq("groupId", groupId))).first();
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,18 @@
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.utils.Utils;
public class BasicDungeonSettleListener implements DungeonSettleListener {
public class BasicDungeonSettleListener implements DungeonSettleListener {
@Override
@Override public void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) {
public void onDungeonSettle(Scene scene) { var scene = dungeonManager.getScene();
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var dungeonData = dungeonManager.getDungeonData();
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge())); var time = scene.getSceneTimeSeconds() - dungeonManager.getStartSceneTime() ;
} // TODO time taken and chests handling
} DungeonEndStats stats = new DungeonEndStats(scene.getKilledMonsterCount(), time, 0, endReason);
scene.broadcastPacket(new PacketDungeonSettleNotify(new BaseDungeonResult(dungeonData, stats)));
}
}

View File

@ -1,332 +1,314 @@
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.dungeon.DungeonData; import emu.grasscutter.data.excels.dungeon.DungeonData;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData; import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler; import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult; import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType; import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ActivityType; import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestContent; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify; import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.*; import lombok.Getter;
import java.util.stream.Collectors; import lombok.NonNull;
import java.util.stream.IntStream; import lombok.val;
import javax.annotation.Nullable;
import lombok.Getter; import javax.annotation.Nullable;
import lombok.NonNull; import java.util.*;
import lombok.val; import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* TODO handle time limits TODO handle respawn points TODO handle team wipes and respawns TODO check /**
* monster level and levelConfigMap * TODO handle time limits
*/ * TODO handle respawn points
public class DungeonManager { * TODO handle team wipes and respawns
* TODO check monster level and levelConfigMap
@Getter private final Scene scene; */
@Getter private final DungeonData dungeonData; public final class DungeonManager {
@Getter private final DungeonPassConfigData passConfigData; @Getter private final Scene scene;
@Getter private final DungeonData dungeonData;
@Getter private final int[] finishedConditions; @Getter private final DungeonPassConfigData passConfigData;
private final IntSet rewardedPlayers = new IntOpenHashSet();
private final Set<Integer> activeDungeonWayPoints = new HashSet<>(); @Getter private final int[] finishedConditions;
private boolean ended = false; private final IntSet rewardedPlayers = new IntOpenHashSet();
private int newestWayPoint = 0; private final Set<Integer> activeDungeonWayPoints = new HashSet<>();
@Getter private int startSceneTime = 0; private boolean ended = false;
private int newestWayPoint = 0;
DungeonTrialTeam trialTeam = null; @Getter private int startSceneTime = 0;
public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) { DungeonTrialTeam trialTeam = null;
this.scene = scene;
this.dungeonData = dungeonData; public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) {
this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond()); this.scene = scene;
this.finishedConditions = new int[passConfigData.getConds().size()]; this.dungeonData = dungeonData;
this.scene.setDungeonManager(this); this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond());
} this.finishedConditions = new int[passConfigData.getConds().size()];
this.scene.setDungeonManager(this);
public void triggerEvent(DungeonPassConditionType conditionType, int... params) { }
if (ended) {
return; public void triggerEvent(DungeonPassConditionType conditionType, int... params) {
} if (ended) {
for (int i = 0; i < passConfigData.getConds().size(); i++) { return;
var cond = passConfigData.getConds().get(i); }
if (conditionType == cond.getCondType()) { for (int i = 0; i < passConfigData.getConds().size(); i++) {
if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) { var cond = passConfigData.getConds().get(i);
finishedConditions[i] = 1; if (conditionType == cond.getCondType()) {
} if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) {
} finishedConditions[i] = 1;
} }
if (isFinishedSuccessfully()) { }
finishDungeon(); }
}
} if (isFinishedSuccessfully()) {
finishDungeon();
public boolean isFinishedSuccessfully() { }
return LogicType.calculate(passConfigData.getLogicType(), finishedConditions);
} }
public int getLevelForMonster(int id) { public boolean isFinishedSuccessfully() {
// TODO should use levelConfigMap? and how? return LogicType.calculate(passConfigData.getLogicType(), finishedConditions);
return dungeonData.getShowLevel(); }
}
public int getLevelForMonster(int id) {
public boolean activateRespawnPoint(int pointId) { //TODO should use levelConfigMap? and how?
val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId); return dungeonData.getShowLevel();
}
if (respawnPoint == null) {
Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId); public boolean activateRespawnPoint(int pointId) {
return false; val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId);
}
if (respawnPoint == null) {
scene.broadcastPacket( Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId);
new PacketDungeonWayPointNotify( return false;
activeDungeonWayPoints.add(pointId), activeDungeonWayPoints)); }
newestWayPoint = pointId;
scene.broadcastPacket(new PacketDungeonWayPointNotify(activeDungeonWayPoints.add(pointId), activeDungeonWayPoints));
Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId); newestWayPoint = pointId;
return true;
} Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId);
return true;
@Nullable public Position getRespawnLocation() { }
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return null; @Nullable
} public Position getRespawnLocation() {
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos(); return null;
} }
var pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
public Position getRespawnRotation() { return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos();
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid }
return null;
} public Position getRespawnRotation() {
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return pointData.getRot() != null ? pointData.getRot() : null; return null;
} }
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) { return pointData.getRot() != null ? pointData.getRot() : null;
if (!isFinishedSuccessfully() }
|| dungeonData.getRewardPreviewData() == null
|| dungeonData.getRewardPreviewData().getPreviewItems().length == 0) { public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) {
return false; if (!isFinishedSuccessfully() || dungeonData.getRewardPreviewData() == null || dungeonData.getRewardPreviewData().getPreviewItems().length == 0) {
} return false;
}
// Already rewarded
if (rewardedPlayers.contains(player.getUid())) { // Already rewarded
return false; if (rewardedPlayers.contains(player.getUid())) {
} return false;
}
if (!handleCost(player, useCondensed)) {
return false;
} if (!handleCost(player, useCondensed)) {
return false;
// Get and roll rewards. }
List<GameItem> rewards = new ArrayList<>(this.rollRewards(useCondensed));
// Add rewards to player and send notification. // Get and roll rewards.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); List<GameItem> rewards = new ArrayList<>(this.rollRewards(useCondensed));
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); // Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
rewardedPlayers.add(player.getUid()); player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET)); rewardedPlayers.add(player.getUid());
return true;
} scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET));
return true;
public boolean handleCost(Player player, boolean useCondensed) { }
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (resinCost == 0) { public boolean handleCost(Player player, boolean useCondensed) {
return true; int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
} if (resinCost == 0) {
if (useCondensed) { return true;
// Check if condensed resin is usable here. }
// For this, we use the following logic for now: if (useCondensed) {
// The normal resin cost of the dungeon has to be 20. // Check if condensed resin is usable here.
if (resinCost != 20) { // For this, we use the following logic for now:
return false; // The normal resin cost of the dungeon has to be 20.
} if (resinCost != 20) {
return false;
// Spend the condensed resin and only proceed if the transaction succeeds. }
return player.getResinManager().useCondensedResin(1);
} else if (dungeonData.getStatueCostID() == 106) { // Spend the condensed resin and only proceed if the transaction succeeds.
// Spend the resin and only proceed if the transaction succeeds. return player.getResinManager().useCondensedResin(1);
return player.getResinManager().useResin(resinCost); } else if (dungeonData.getStatueCostID() == 106) {
} // Spend the resin and only proceed if the transaction succeeds.
return true; return player.getResinManager().useResin(resinCost);
} }
return true;
private List<GameItem> rollRewards(boolean useCondensed) { }
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.dungeonData.getId(); private List<GameItem> rollRewards(boolean useCondensed) {
// If we have specific drop data for this dungeon, we use it. List<GameItem> rewards = new ArrayList<>();
if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) { int dungeonId = this.dungeonData.getId();
List<DungeonDropEntry> dropEntries = GameData.getDungeonDropDataMap().get(dungeonId); // If we have specific drop data for this dungeon, we use it.
if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) {
// Roll for each drop group. List<DungeonDropEntry> dropEntries = GameData.getDungeonDropDataMap().get(dungeonId);
for (var entry : dropEntries) {
// Determine the number of drops we get for this entry. // Roll for each drop group.
int start = entry.getCounts().get(0); for (var entry : dropEntries) {
int end = entry.getCounts().get(entry.getCounts().size() - 1); // Determine the number of drops we get for this entry.
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList()); int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1);
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
if (useCondensed) { int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
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; // 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 // Roll items for this group.
// items, // Here, we have to handle stacking, or the client will not display results correctly.
// we roll them separately. If not, we stack them. This should work out in practice, at // For now, we use the following logic: If the possible drop item are a list of multiple items,
// least // we roll them separately. If not, we stack them. This should work out in practice, at least
// for the currently existing set of dungeons. // for the currently existing set of dungeons.
if (entry.getItems().size() == 1) { if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount)); rewards.add(new GameItem(entry.getItems().get(0), amount));
} else { } else {
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size()); // int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex); // int itemId = entry.getItems().get(itemIndex);
int itemId = int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities()); rewards.add(new GameItem(itemId, 1));
rewards.add(new GameItem(itemId, 1)); }
} }
} }
} }
} // Otherwise, we fall back to the preview data.
// Otherwise, we fall back to the preview data. else {
else { Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
Grasscutter.getLogger() for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) {
.info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId); rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) { }
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); }
}
} return rewards;
}
return rewards;
} public void applyTrialTeam(Player player) {
if (getDungeonData() == null) return;
public void applyTrialTeam(Player player) {
if (getDungeonData() == null) return; switch (getDungeonData().getType()) {
// case DUNGEON_PLOT is handled by quest execs
switch (getDungeonData().getType()) { case DUNGEON_ACTIVITY -> {
// case DUNGEON_PLOT is handled by quest execs switch (getDungeonData().getPlayType()) {
case DUNGEON_ACTIVITY -> { case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> {
switch (getDungeonData().getPlayType()) { val activityHandler = player.getActivityManager()
case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> { .getActivityHandlerAs(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
val activityHandler = activityHandler.ifPresent(trialAvatarActivityHandler ->
player this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
.getActivityManager() }
.getActivityHandlerAs( }
ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class); }
activityHandler.ifPresent( case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO
trialAvatarActivityHandler -> }
this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
} if (this.trialTeam != null) {
} player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds);
} }
case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO }
}
if (this.trialTeam != null) { public void unsetTrialTeam(Player player){
player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds); if (this.trialTeam == null) return;
}
} player.getTeamManager().removeTrialAvatar();
this.trialTeam = null;
public void unsetTrialTeam(Player player) { }
if (this.trialTeam == null) {
return; public void startDungeon() {
} this.startSceneTime = scene.getSceneTimeSeconds();
player.getTeamManager().removeTrialAvatar(); scene.getPlayers().forEach(p -> {
this.trialTeam = null; p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId());
} applyTrialTeam(p);
});
public void startDungeon() { }
this.startSceneTime = scene.getSceneTimeSeconds();
scene public void finishDungeon() {
.getPlayers() notifyEndDungeon(true);
.forEach( endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED);
p -> { }
p.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId()); public void notifyEndDungeon(boolean successfully) {
applyTrialTeam(p); scene.getPlayers().forEach(p -> {
}); // Quest trigger
} p.getQuestManager().queueEvent(successfully ?
QuestContent.QUEST_CONTENT_FINISH_DUNGEON : QuestContent.QUEST_CONTENT_FAIL_DUNGEON,
public void finishDungeon() { dungeonData.getId());
notifyEndDungeon(true);
endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED); // Battle pass trigger
} if (dungeonData.getType().isCountsToBattlepass() && successfully) {
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
public void notifyEndDungeon(boolean successfully) { }
scene });
.getPlayers() scene.getScriptManager().callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
.forEach( }
p -> {
// Quest trigger public void quitDungeon() {
p.getQuestManager() notifyEndDungeon(false);
.queueEvent( endDungeon(BaseDungeonResult.DungeonEndReason.QUIT);
successfully }
? QuestContent.QUEST_CONTENT_FINISH_DUNGEON
: QuestContent.QUEST_CONTENT_FAIL_DUNGEON, public void failDungeon() {
dungeonData.getId()); notifyEndDungeon(false);
endDungeon(BaseDungeonResult.DungeonEndReason.FAILED);
// Battle pass trigger }
if (dungeonData.getType().isCountsToBattlepass() && successfully) {
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
} if (scene.getDungeonSettleListeners() != null) {
}); scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
scene }
.getScriptManager() ended = true;
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); }
}
public void restartDungeon() {
public void quitDungeon() { this.scene.setKilledMonsterCount(0);
notifyEndDungeon(false); this.rewardedPlayers.clear();
endDungeon(BaseDungeonResult.DungeonEndReason.QUIT); Arrays.fill(finishedConditions, 0);
} this.ended = false;
this.activeDungeonWayPoints.clear();
public void failDungeon() { }
notifyEndDungeon(false);
endDungeon(BaseDungeonResult.DungeonEndReason.FAILED); public void cleanUpScene() {
} this.scene.setDungeonManager(null);
this.scene.setKilledMonsterCount(0);
public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) { }
if (scene.getDungeonSettleListeners() != null) { }
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
}
ended = true;
}
public void restartDungeon() {
this.scene.setKilledMonsterCount(0);
this.rewardedPlayers.clear();
Arrays.fill(finishedConditions, 0);
this.ended = false;
this.activeDungeonWayPoints.clear();
}
public void cleanUpScene() {
this.scene.setDungeonManager(null);
this.scene.setKilledMonsterCount(0);
}
}

View File

@ -1,7 +1,7 @@
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
public interface DungeonSettleListener { public interface DungeonSettleListener {
void onDungeonSettle(Scene scene); void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
} }

View File

@ -1,115 +1,157 @@
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.data.excels.dungeon.DungeonData;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp; import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.utils.Position; import emu.grasscutter.server.game.GameServer;
import java.util.List; import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.utils.Position;
public final class DungeonSystem extends BaseGameSystem { import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
private static final BasicDungeonSettleListener basicDungeonSettleObserver = import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
new BasicDungeonSettleListener(); import lombok.val;
import org.reflections.Reflections;
public DungeonSystem(GameServer server) {
super(server); import java.util.List;
}
public class DungeonSystem extends BaseGameSystem {
public void getEntryInfo(Player player, int pointId) { private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener();
var entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId); private final Int2ObjectMap<DungeonBaseHandler> passCondHandlers;
if (entry == null) { public DungeonSystem(GameServer server) {
// Error super(server);
player.sendPacket(new PacketDungeonEntryInfoRsp()); this.passCondHandlers = new Int2ObjectOpenHashMap<>();
return; registerHandlers();
} }
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData())); public void registerHandlers() {
} this.registerHandlers(this.passCondHandlers, "emu.grasscutter.game.dungeons.pass_condition", DungeonBaseHandler.class);
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
var data = GameData.getDungeonDataMap().get(dungeonId); public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
if (data == null) { Reflections reflections = new Reflections(packageName);
return false; var handlerClasses = reflections.getSubTypesOf(clazz);
}
for (var obj : handlerClasses) {
Grasscutter.getLogger() this.registerPacketHandler(map, obj);
.debug( }
"{}({}) is trying to enter dungeon {}", }
player.getNickname(),
player.getUid(), public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
dungeonId); try {
DungeonValue opcode = handlerClass.getAnnotation(DungeonValue.class);
var sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId); if (opcode == null || opcode.value() == null) {
return;
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) { }
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, data.getId()); map.put(opcode.value().ordinal(), handlerClass.getDeclaredConstructor().newInstance());
} } catch (Exception e) {
e.printStackTrace();
player.getScene().setPrevScenePoint(pointId); }
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId)); }
return true;
} public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
/** used in tower dungeons handoff */
public boolean handoffDungeon( if (entry == null) {
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) { // Error
var data = GameData.getDungeonDataMap().get(dungeonId); player.sendPacket(new PacketDungeonEntryInfoRsp());
if (data == null) { return;
return false; }
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
Grasscutter.getLogger() }
.debug(
"{}({}) is trying to enter tower dungeon {}", public boolean triggerCondition(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
player.getNickname(), var handler = passCondHandlers.get(condition.getCondType().ordinal());
player.getUid(),
dungeonId); if (handler == null) {
Grasscutter.getLogger().debug("Could not trigger condition {} at {}", condition.getCondType(), params);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) { return false;
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver); }
}
return handler.execute(condition, params);
return true; }
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
public void exitDungeon(Player player) { DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
var scene = player.getScene();
if (data == null) {
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) { return false;
return; }
} Grasscutter.getLogger().info("{}({}) is trying to enter dungeon {}" ,player.getNickname(),player.getUid(),dungeonId);
// Get previous scene int sceneId = data.getSceneId();
var prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3; var scene = player.getScene();
scene.setPrevScene(sceneId);
// Get previous position
var dungeonData = scene.getDungeonData(); if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
var prevPos = new Position(GameConstants.START_POSITION); scene = player.getScene();
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
if (dungeonData != null) { }
var entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
scene.setPrevScenePoint(pointId);
if (entry != null) { return true;
prevPos.set(entry.getPointData().getTranPos()); }
}
} /**
* used in tower dungeons handoff
// clean temp team if it has */
player.getTeamManager().cleanTemporaryTeam(); public boolean handoffDungeon(Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
player.getTowerManager().clearEntry(); DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
// Transfer player back to world if (data == null) {
player.getWorld().transferPlayerToScene(player, prevScene, prevPos); return false;
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp)); }
} 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
val dungeonManager = scene.getDungeonManager();
DungeonData dungeonData = dungeonManager != null ? dungeonManager.getDungeonData() : null;
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());
}
if(!dungeonManager.isFinishedSuccessfully()){
dungeonManager.quitDungeon();
}
dungeonManager.unsetTrialTeam(player);
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
}
}

View File

@ -1,31 +1,38 @@
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify; import emu.grasscutter.game.dungeons.dungeon_results.TowerResult;
import emu.grasscutter.utils.Utils; import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
public class TowerDungeonSettleListener implements DungeonSettleListener {
public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(Scene scene) { @Override
if (scene.getScriptManager().getVariables().containsKey("stage") public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
&& scene.getScriptManager().getVariables().get("stage") == 1) { var scene = dungeonManager.getScene();
return; var dungeonData = dungeonManager.getDungeonData();
} if (scene.getLoadedGroups().stream().anyMatch(g -> {
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var variables = scene.getScriptManager().getVariables(g.id);
var towerManager = scene.getPlayers().get(0).getTowerManager(); return variables != null && variables.containsKey("stage") && variables.get("stage") == 1;
})) {
towerManager.notifyCurLevelRecordChangeWhenDone(3); return;
scene.broadcastPacket( }
new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor())); var towerManager = scene.getPlayers().get(0).getTowerManager();
scene.broadcastPacket( towerManager.notifyCurLevelRecordChangeWhenDone(3);
new PacketDungeonSettleNotify( scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(
scene.getChallenge(), towerManager.getCurrentFloorId(),
towerManager.hasNextFloor(), 3,
towerManager.hasNextLevel(), towerManager.canEnterScheduleFloor()
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId())); ));
}
} var challenge = scene.getChallenge();
var dungeonStats = new DungeonEndStats(scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge);
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
}
}

View File

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

View File

@ -1,37 +1,36 @@
package emu.grasscutter.game.dungeons.challenge.factory; package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.game.world.Scene;
import java.util.ArrayList; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List; import lombok.val;
public class ChallengeFactory { import java.util.ArrayList;
import java.util.List;
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
public abstract class ChallengeFactory {
static { private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler());
challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler()); static {
challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler()); challengeFactoryHandlers.add(new KillAndGuardChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler()); challengeFactoryHandlers.add(new KillMonsterCountChallengeFactoryHandler());
} challengeFactoryHandlers.add(new KillMonsterInTimeChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler());
public static WorldChallenge getChallenge( challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler());
int param1, challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler());
int param2, }
int param3,
int param4, public static WorldChallenge getChallenge(int localChallengeId, int challengeDataId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group){
int param5, val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeDataId);
int param6, val challengeType = challengeData.getChallengeType();
Scene scene,
SceneGroup group) { for(var handler : challengeFactoryHandlers){
for (var handler : challengeFactoryHandlers) { if(!handler.isThisType(challengeType)){
if (!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)) { continue;
continue; }
} return handler.build(localChallengeId, challengeDataId, param3, param4, param5, param6, scene, group);
return handler.build(param1, param2, param3, param4, param5, param6, scene, group); }
} return null;
return null; }
} }
}

View File

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

View File

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

View File

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

View File

@ -1,17 +1,18 @@
package emu.grasscutter.game.dungeons.challenge.factory; package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
import lombok.val; import lombok.val;
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler { import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1,188,234101003,12,3030,0 // ActiveChallenge with 1,188,234101003,12,3030,0
@ -19,24 +20,15 @@ public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHand
} }
@Override /*TODO check param4 == monstesToKill*/ @Override /*TODO check param4 == monstesToKill*/
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int monstersToKill, int gadgetCFGId, int unused, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int groupId,
int monstersToKill,
int gadgetCFGId,
int unused,
Scene scene,
SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId); val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge( return new WorldChallenge(
scene, scene, realGroup,
realGroup,
challengeId, // Id challengeId, // Id
challengeIndex, // Index challengeIndex, // Index
List.of(monstersToKill, 0), List.of(monstersToKill, 0),
0, // Limit 0, // Limit
monstersToKill, // Goal monstersToKill, // Goal
List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId))); List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId)));
} }
} }

View File

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

View File

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

View File

@ -5,10 +5,11 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
import lombok.val; import lombok.val;
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler { import java.util.List;
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1, 1, 241033003, 15, 0, 0 // ActiveChallenge with 1, 1, 241033003, 15, 0, 0
@ -16,24 +17,16 @@ public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactory
} }
@Override @Override
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int goal, int param5, int param6, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int groupId,
int goal,
int param5,
int param6,
Scene scene,
SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId); val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge( return new WorldChallenge(
scene, scene, realGroup,
realGroup,
challengeId, // Id challengeId, // Id
challengeIndex, // Index challengeIndex, // Index
List.of(goal, groupId), List.of(goal, groupId),
0, // Limit 0, // Limit
goal, // Goal goal, // Goal
List.of(new KillMonsterCountTrigger())); List.of(new KillMonsterCountTrigger())
);
} }
} }

View File

@ -1,40 +1,33 @@
package emu.grasscutter.game.dungeons.challenge.factory; package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List; import lombok.val;
import lombok.val;
import java.util.List;
public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
public boolean isThisType(ChallengeType challengeType) { @Override
// ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0 public boolean isThisType(ChallengeType challengeType) {
return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME; // ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0
} return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME;
}
@Override
public WorldChallenge build( @Override
int challengeIndex, public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCfgId, int param6, Scene scene, SceneGroup group) {
int challengeId, val realGroup = scene.getScriptManager().getGroupById(groupId);
int timeLimit, return new WorldChallenge(
int groupId, scene, realGroup,
int targetCfgId, challengeId, // Id
int param6, challengeIndex, // Index
Scene scene, List.of(timeLimit),
SceneGroup group) { timeLimit, // Limit
val realGroup = scene.getScriptManager().getGroupById(groupId); 0, // Goal
return new WorldChallenge( List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger())
scene, );
realGroup, }
challengeId, // Id }
challengeIndex, // Index
List.of(timeLimit),
timeLimit, // Limit
0, // Goal
List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger()));
}
}

View File

@ -6,37 +6,30 @@ import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
import lombok.val; import lombok.val;
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler { import java.util.List;
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 180,180,45,133108061,1,0 // ActiveChallenge with 180,180,45,133108061,1,0
// ActiveChallenge Fast with 1001, 5, 15, 240004005, 10, 0 // ActiveChallenge Fast with 1001, 5, 15, 240004005, 10, 0
return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME ||
|| challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST; challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST;
} }
@Override @Override
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCount, int param6, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int timeLimit,
int groupId,
int targetCount,
int param6,
Scene scene,
SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId); val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge( return new WorldChallenge(
scene, scene, realGroup,
realGroup,
challengeId, // Id challengeId, // Id
challengeIndex, // Index challengeIndex, // Index
List.of(targetCount, timeLimit), List.of(targetCount, timeLimit),
timeLimit, // Limit timeLimit, // Limit
targetCount, // Goal targetCount, // Goal
List.of(new KillMonsterCountTrigger(), new InTimeTrigger())); List.of(new KillMonsterCountTrigger(), new InTimeTrigger())
);
} }
} }

View File

@ -1,14 +1,15 @@
package emu.grasscutter.game.dungeons.challenge.factory; package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ForTimeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.ForTimeTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List; import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE;
public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler { public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
@ -18,23 +19,15 @@ public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler {
} }
@Override @Override
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int timeToSurvive, int unused4, int unused5, int unused6, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int timeToSurvive,
int unused4,
int unused5,
int unused6,
Scene scene,
SceneGroup group) {
return new WorldChallenge( return new WorldChallenge(
scene, scene, group,
group, challengeId, // Id
challengeId, // Id challengeIndex, // Index
challengeIndex, // Index List.of(timeToSurvive),
List.of(timeToSurvive), timeToSurvive, // Limit
timeToSurvive, // Limit 0, // Goal
0, // Goal List.of(new ForTimeTrigger())
List.of(new ForTimeTrigger())); );
} }
} }

View File

@ -1,15 +1,16 @@
package emu.grasscutter.game.dungeons.challenge.factory; package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType; import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List; import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME;
public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHandler { public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
@ -21,23 +22,15 @@ public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHan
} }
@Override @Override
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int param4, int triggerTag, int triggerCount, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int timeLimit,
int param4,
int triggerTag,
int triggerCount,
Scene scene,
SceneGroup group) {
return new WorldChallenge( return new WorldChallenge(
scene, scene, group,
group,
challengeId, // Id challengeId, // Id
challengeIndex, // Index challengeIndex, // Index
List.of(timeLimit, triggerCount), List.of(timeLimit, triggerCount),
timeLimit, // Limit timeLimit, // Limit
triggerCount, // Goal triggerCount, // Goal
List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag)))); List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag)))
);
} }
} }

View File

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

View File

@ -1,26 +1,38 @@
package emu.grasscutter.game.dungeons.challenge.trigger; package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class GuardTrigger extends KillMonsterTrigger {
@Override public class GuardTrigger extends ChallengeTrigger {
public void onBegin(WorldChallenge challenge) { private final int entityToProtectCFGId;
super.onBegin(challenge); private int lastSendPercent = 100;
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100)); public GuardTrigger(int entityToProtectCFGId){
} this.entityToProtectCFGId = entityToProtectCFGId;
}
@Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { public void onBegin(WorldChallenge challenge) {
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId()); challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId()); }
int percent = (int) (curHp / maxHp);
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent)); @Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
if (percent <= 0) { if(gadget.getConfigId() != entityToProtectCFGId){
challenge.fail(); return;
} }
} 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);
if(percent!=lastSendPercent) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
lastSendPercent = percent;
}
if(percent <= 0){
challenge.fail();
}
}
}

View File

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

View File

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

View File

@ -1,58 +1,63 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.ConfigGadget; import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.Getter; import lombok.Getter;
public abstract class EntityBaseGadget extends GameEntity { public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
protected final Position position; protected final Position position;
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
protected final Position rotation; protected final Position rotation;
public EntityBaseGadget(Scene scene) { public EntityBaseGadget(Scene scene) {
this(scene, null, null); this(scene, null, null);
} }
public EntityBaseGadget(Scene scene, Position position, Position rotation) { public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene); super(scene);
this.position = position != null ? position.clone() : new Position(); this.position = position != null ? position.clone() : new Position();
this.rotation = rotation != null ? rotation.clone() : new Position(); this.rotation = rotation != null ? rotation.clone() : new Position();
} }
public abstract int getGadgetId(); public abstract int getGadgetId();
@Override @Override
public void onDeath(int killerId) { public int getEntityTypeId() {
super.onDeath(killerId); // Invoke super class's onDeath() method. return this.getGadgetId();
} }
protected void fillFightProps(ConfigGadget configGadget) { @Override
if (configGadget == null || configGadget.getCombat() == null) { public void onDeath(int killerId) {
return; super.onDeath(killerId); // Invoke super class's onDeath() method.
} }
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty(); protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
var targetHp = combatProperties.getHP(); return;
setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp); }
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp); var combatData = configGadget.getCombat();
if (combatProperties.isInvincible()) { var combatProperties = combatData.getProperty();
targetHp = Float.POSITIVE_INFINITY;
} var targetHp = combatProperties.getHP();
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp); setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp);
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp);
var atk = combatProperties.getAttack(); if (combatProperties.isInvincible()) {
setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk); targetHp = Float.POSITIVE_INFINITY;
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk); }
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp);
var def = combatProperties.getDefence();
setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def); var atk = combatProperties.getAttack();
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def); setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk);
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk);
setLockHP(combatProperties.isLockHP());
} var def = combatProperties.getDefence();
} setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def);
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def);
setLockHP(combatProperties.isLockHP());
}
}

View File

@ -1,206 +1,281 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ConfigGadget; import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*; import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.entity.gadget.platform.BaseRoute;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.utils.Position; import emu.grasscutter.scripts.EntityControllerScriptManager;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.scripts.constants.EventType;
import it.unimi.dsi.fastutil.ints.Int2FloatMap; import emu.grasscutter.scripts.data.SceneGadget;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import emu.grasscutter.scripts.data.ScriptArgs;
import java.util.Optional; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import javax.annotation.Nullable; import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify;
import lombok.Getter; import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify;
import lombok.Setter; import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import lombok.ToString; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
@ToString(callSuper = true) import it.unimi.dsi.fastutil.ints.Int2FloatMap;
public class EntityGadget extends EntityBaseGadget { import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
@Getter private final GadgetData gadgetData; import lombok.Getter;
import lombok.Setter;
@Getter(onMethod_ = @Override, lazy = true) import lombok.ToString;
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
import javax.annotation.Nullable;
@Getter(onMethod_ = @Override) import java.util.ArrayList;
@Setter import java.util.List;
private int gadgetId;
@ToString(callSuper = true)
@Getter @Setter private int state; public class EntityGadget extends EntityBaseGadget {
@Getter @Setter private int pointType; @Getter private final GadgetData gadgetData;
@Getter private GadgetContent content; @Getter(onMethod = @__(@Override)) @Setter
@Getter @Setter private SceneGadget metaGadget; private int gadgetId;
@Nullable @Getter private final ConfigGadget configGadget; @Getter private final Position bornPos;
@Getter private final Position bornRot;
public EntityGadget(Scene scene, int gadgetId, Position pos) { @Getter @Setter private GameEntity owner = null;
this(scene, gadgetId, pos, null, null); @Getter @Setter private List<GameEntity> children = new ArrayList<>();
}
@Getter private int state;
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) { @Getter @Setter private int pointType;
this(scene, gadgetId, pos, rot, null); @Getter private GadgetContent content;
} @Getter(onMethod = @__(@Override), lazy = true)
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
public EntityGadget( @Getter @Setter private SceneGadget metaGadget;
Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { @Nullable @Getter
super(scene, pos, rot); private ConfigEntityGadget configGadget;
this.gadgetData = GameData.getGadgetDataMap().get(gadgetId); @Getter @Setter private BaseRoute routeConfig;
this.configGadget =
Optional.ofNullable(this.gadgetData) @Getter @Setter private int stopValue = 0; //Controller related, inited to zero
.map(GadgetData::getJsonName) @Getter @Setter private int startValue = 0; //Controller related, inited to zero
.map(GameData.getGadgetConfigData()::get) @Getter @Setter private int ticksSinceChange;
.orElse(null);
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId; public EntityGadget(Scene scene, int gadgetId, Position pos) {
this.content = content; this(scene, gadgetId, pos, null, null);
fillFightProps(configGadget); }
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
public void updateState(int state) { this(scene, gadgetId, pos, rot, null);
this.setState(state); }
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene() public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
.getScriptManager() super(scene, pos, rot);
.callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId()));
} this.gadgetData = GameData.getGadgetDataMap().get(gadgetId);
if (gadgetData != null && gadgetData.getJsonName() != null) {
@Deprecated(forRemoval = true) // Dont use! this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName());
public void setContent(GadgetContent content) { }
this.content = this.content == null ? content : this.content;
} this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId;
// TODO refactor this.content = content;
public void buildContent() { this.bornPos = this.getPosition().clone();
if (this.getContent() != null this.bornRot = this.getRotation().clone();
|| this.getGadgetData() == null this.fillFightProps(configGadget);
|| this.getGadgetData().getType() == null) {
return; if(GameData.getGadgetMappingMap().containsKey(gadgetId)) {
} String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController();
this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName));
this.content = }
switch (this.getGadgetData().getType()) { }
case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this); public void setState(int state) {
case Worktop -> new GadgetWorktop(this); this.state = state;
case RewardStatue -> new GadgetRewardStatue(this); //Cache the gadget state
case Chest -> new GadgetChest(this); if(metaGadget != null && metaGadget.group != null) {
case Gadget -> new GadgetObject(this); var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.group.id);
default -> null; if(instance != null) instance.cacheGadgetState(metaGadget, state);
}; }
} }
@Override public void updateState(int state) {
public void onInteract(Player player, GadgetInteractReq interactReq) { if(state == this.getState()) return; //Don't triggers events
if (this.getContent() == null) {
return; this.setState(state);
} ticksSinceChange = getScene().getSceneTimeSeconds();
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
boolean shouldDelete = this.getContent().onInteract(player, interactReq); getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_STATE_CHANGE, state, this.getConfigId()));
}
if (shouldDelete) {
this.getScene().killEntity(this); @Deprecated(forRemoval = true) // Dont use!
} public void setContent(GadgetContent content) {
} this.content = this.content == null ? content : this.content;
}
@Override
public void onCreate() { // TODO refactor
// Lua event public void buildContent() {
getScene() if (this.getContent() != null || this.getGadgetData() == null || this.getGadgetData().getType() == null) {
.getScriptManager() return;
.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId())); }
}
this.content = switch (this.getGadgetData().getType()) {
@Override case GatherPoint -> new GadgetGatherPoint(this);
public void onDeath(int killerId) { case GatherObject -> new GadgetGatherObject(this);
super.onDeath(killerId); // Invoke super class's onDeath() method. case Worktop, SealGadget -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
if (this.getSpawnEntry() != null) { case Chest -> new GadgetChest(this);
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); case Gadget -> new GadgetObject(this);
} default -> null;
if (getScene().getChallenge() != null) { };
getScene().getChallenge().onGadgetDeath(this); }
}
getScene() @Override
.getScriptManager() public void onInteract(Player player, GadgetInteractReq interactReq) {
.callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId())); if (this.getContent() == null) {
} return;
}
@Override
public SceneEntityInfo toProto() { boolean shouldDelete = this.getContent().onInteract(player, interactReq);
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder() if (shouldDelete) {
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) this.getScene().killEntity(this);
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) }
.setAiInfo( }
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder()) @Override
.build(); public void onCreate() {
// Lua event
SceneEntityInfo.Builder entityInfo = getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_CREATE, this.getConfigId()));
SceneEntityInfo.newBuilder() }
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) @Override
.setMotionInfo( public void onRemoved() {
MotionInfo.newBuilder() super.onRemoved();
.setPos(getPosition().toProto()) if(!children.isEmpty()) {
.setRot(getRotation().toProto()) getScene().removeEntities(children, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
.setSpeed(Vector.newBuilder())) children.clear();
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) }
.setEntityClientData(EntityClientData.newBuilder()) }
.setEntityAuthorityInfo(authority)
.setLifeState(1); @Override
public void onDeath(int killerId) {
PropPair pair = super.onDeath(killerId); // Invoke super class's onDeath() method.
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId()) if (this.getSpawnEntry() != null) {
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1)) this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
.build(); }
entityInfo.addPropList(pair); if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
// We do not use the getter to null check because the getter will create a fight prop map if it }
// is null getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_GADGET_DIE, this.getConfigId()));
if (this.fightProperties != null) {
addAllFightPropsToEntityInfo(entityInfo); SceneGroupInstance groupInstance = getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId());
} if(groupInstance != null && metaGadget != null)
groupInstance.getDeadEntities().add(metaGadget.config_id);
SceneGadgetInfo.Builder gadgetInfo = }
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId()) public boolean startPlatform(){
.setGroupId(this.getGroupId()) if(routeConfig == null){
.setConfigId(this.getConfigId()) return false;
.setGadgetState(this.getState()) }
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); if(routeConfig.isStarted()){
return true;
if (this.metaGadget != null) { }
gadgetInfo.setDraftId(this.metaGadget.draft_id); getScene().broadcastPacket(new PacketSceneTimeNotify(getScene()));
} routeConfig.startRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStartRouteNotify(this));
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo); return true;
} }
entityInfo.setGadget(gadgetInfo); public boolean stopPlatform(){
if(routeConfig == null){
return entityInfo.build(); return false;
} }
}
if(!routeConfig.isStarted()){
return true;
}
routeConfig.stopRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStopRouteNotify(this));
return true;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(bornPos.toProto()))
.setBornPos(bornPos.toProto())
.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(owner != null){
gadgetInfo.setOwnerEntityId(owner.getId());
}
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
if(routeConfig!=null){
gadgetInfo.setPlatform(getPlatformInfo());
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
public PlatformInfoOuterClass.PlatformInfo.Builder getPlatformInfo(){
if(routeConfig != null){
return routeConfig.toProto();
}
return PlatformInfoOuterClass.PlatformInfo.newBuilder();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,148 +1,133 @@
package emu.grasscutter.game.managers.blossom; package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.monster.MonsterData; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.data.excels.world.WorldLevelData; import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.scripts.data.SceneBossChest;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneBossChest; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.utils.Position;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.utils.Utils;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import java.util.ArrayDeque;
import java.util.ArrayDeque; import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List; import java.util.Queue;
import java.util.Queue;
public final class BlossomActivity {
public class BlossomActivity {
private final SceneGroup tempSceneGroup;
private static final int BLOOMING_GADGET_ID = 70210109; private final WorldChallenge challenge;
private final SceneGroup tempSceneGroup; private final EntityGadget gadget;
private final WorldChallenge challenge; private EntityGadget chest;
private final EntityGadget gadget; private int step;
private final int goal; private final int goal;
private final int worldLevel; private int generatedCount;
private final List<EntityMonster> activeMonsters = new ArrayList<>(); private final int worldLevel;
private final Queue<Integer> candidateMonsters = new ArrayDeque<>(); private boolean pass=false;
private EntityGadget chest; private final List<EntityMonster> activeMonsters = new ArrayList<>();
private int step; private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
private int generatedCount; private static final int BLOOMING_GADGET_ID = 70210109;
private boolean pass = false; public BlossomActivity(EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) {
this.tempSceneGroup = new SceneGroup();
public BlossomActivity( this.tempSceneGroup.id = entityGadget.getId();
EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) { this.gadget=entityGadget;
this.tempSceneGroup = new SceneGroup(); this.step=0;
this.tempSceneGroup.id = entityGadget.getId(); this.goal = monsters.size();
this.gadget = entityGadget; this.candidateMonsters.addAll(monsters);
this.step = 0; this.worldLevel = worldLevel;
this.goal = monsters.size(); ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>();
this.candidateMonsters.addAll(monsters); this.challenge = new WorldChallenge(entityGadget.getScene(),
this.worldLevel = worldLevel; tempSceneGroup,
ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>(); 1,
this.challenge = 1,
new WorldChallenge( List.of(goal, timeout),
entityGadget.getScene(), timeout,
tempSceneGroup, goal, challengeTriggers);
1, challengeTriggers.add(new KillMonsterCountTrigger());
1, //this.challengeTriggers.add(new InTimeTrigger());
List.of(goal, timeout), }
timeout, public WorldChallenge getChallenge() {
goal, return this.challenge;
challengeTriggers); }
challengeTriggers.add(new KillMonsterTrigger()); public void setMonsters(List<EntityMonster> monsters) {
// this.challengeTriggers.add(new InTimeTrigger()); this.activeMonsters.clear();
} this.activeMonsters.addAll(monsters);
for (EntityMonster monster : monsters) {
public WorldChallenge getChallenge() { monster.setGroupId(this.tempSceneGroup.id);
return this.challenge; }
} }
public int getAliveMonstersCount() {
public void setMonsters(List<EntityMonster> monsters) { int count=0;
this.activeMonsters.clear(); for (EntityMonster monster: activeMonsters) {
this.activeMonsters.addAll(monsters); if (monster.isAlive()) {
for (EntityMonster monster : monsters) { count++;
monster.setGroupId(this.tempSceneGroup.id); }
} }
} return count;
}
public int getAliveMonstersCount() { public boolean getPass() {
int count = 0; return pass;
for (EntityMonster monster : activeMonsters) { }
if (monster.isAlive()) { public void start() {
count++; challenge.start();
} }
} public void onTick() {
return count; Scene scene = gadget.getScene();
} Position pos = gadget.getPosition();
if (getAliveMonstersCount() <= 2) {
public boolean getPass() { if (generatedCount<goal) {
return pass; step++;
}
var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
public void start() { int worldLevelOverride = 0;
challenge.start(); if (worldLevelData != null) {
} worldLevelOverride = worldLevelData.getMonsterLevel();
}
public void onTick() {
Scene scene = gadget.getScene(); List<EntityMonster> newMonsters = new ArrayList<>();
Position pos = gadget.getPosition(); int willSpawn = Utils.randomRange(3,5);
if (getAliveMonstersCount() <= 2) { if (generatedCount+willSpawn>goal) {
if (generatedCount < goal) { willSpawn = goal - generatedCount;
step++; }
generatedCount+=willSpawn;
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel); for (int i = 0; i < willSpawn; i++) {
int worldLevelOverride = 0; var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll());
if (worldLevelData != null) { int level = scene.getEntityLevel(1, worldLevelOverride);
worldLevelOverride = worldLevelData.getMonsterLevel(); EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
} scene.addEntity(entity);
newMonsters.add(entity);
List<EntityMonster> newMonsters = new ArrayList<>(); }
int willSpawn = Utils.randomRange(3, 5); setMonsters(newMonsters);
if (generatedCount + willSpawn > goal) { }else {
willSpawn = goal - generatedCount; if (getAliveMonstersCount() == 0) {
} this.pass = true;
generatedCount += willSpawn; this.challenge.done();
for (int i = 0; i < willSpawn; i++) { }
MonsterData monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll()); }
int level = scene.getEntityLevel(1, worldLevelOverride); }
EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level); }
scene.addEntity(entity); public EntityGadget getGadget() {
newMonsters.add(entity); return gadget;
} }
setMonsters(newMonsters); public EntityGadget getChest() {
} else { if (chest==null) {
if (getAliveMonstersCount() == 0) { EntityGadget rewardGadget = new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
this.pass = true; SceneGadget metaGadget = new SceneGadget();
this.challenge.done(); metaGadget.boss_chest = new SceneBossChest();
} metaGadget.boss_chest.resin = 20;
} rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY);
} rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY);
} rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY);
rewardGadget.setMetaGadget(metaGadget);
public EntityGadget getGadget() { rewardGadget.buildContent();
return gadget; chest = rewardGadget;
} }
return chest;
public EntityGadget getChest() { }
if (chest == null) { }
EntityGadget rewardGadget =
new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
SceneGadget metaGadget = new SceneGadget();
metaGadget.boss_chest = new SceneBossChest();
metaGadget.boss_chest.resin = 20;
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY);
rewardGadget.setMetaGadget(metaGadget);
rewardGadget.buildContent();
chest = rewardGadget;
}
return chest;
}
}

View File

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

View File

@ -294,7 +294,7 @@ public class StaminaManager extends BasePlayerManager {
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
// Target Player // Target Player
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getUnlimitedStamina()) { if (!GAME_OPTIONS.staminaUsage || session.getPlayer().isUnlimitedStamina()) {
newStamina = getMaxCharacterStamina(); newStamina = getMaxCharacterStamina();
} }

View File

@ -45,7 +45,6 @@ import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.quest.enums.QuestContent; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerData;
import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.tower.TowerManager;
@ -117,12 +116,13 @@ public class Player {
@Getter @Setter private int sceneId; @Getter @Setter private int sceneId;
@Getter @Setter private int regionId; @Getter @Setter private int regionId;
@Getter private int mainCharacterId; @Getter private int mainCharacterId;
@Setter private boolean godmode; // Getter is inGodmode @Getter @Setter private boolean inGodMode;
private boolean stamina; // Getter is getUnlimitedStamina, Setter is setUnlimitedStamina @Getter @Setter private boolean unlimitedStamina;
@Getter private Set<Integer> nameCardList; @Getter private Set<Integer> nameCardList;
@Getter private Set<Integer> flyCloakList; @Getter private Set<Integer> flyCloakList;
@Getter private Set<Integer> costumeList; @Getter private Set<Integer> costumeList;
@Getter private Set<Integer> personalLineList;
@Getter @Setter private Set<Integer> rewardedLevels; @Getter @Setter private Set<Integer> rewardedLevels;
@Getter @Setter private Set<Integer> homeRewardedLevels; @Getter @Setter private Set<Integer> homeRewardedLevels;
@Getter @Setter private Set<Integer> realmList; @Getter @Setter private Set<Integer> realmList;
@ -793,18 +793,6 @@ public class Player {
this.save(); this.save();
} }
public boolean getUnlimitedStamina() {
return stamina;
}
public void setUnlimitedStamina(boolean stamina) {
this.stamina = stamina;
}
public boolean inGodmode() {
return godmode;
}
public boolean hasSentLoginPackets() { public boolean hasSentLoginPackets() {
return hasSentLoginPackets; return hasSentLoginPackets;
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +1,72 @@
package emu.grasscutter.game.props; package emu.grasscutter.game.props;
import emu.grasscutter.utils.Utils; import java.util.HashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import java.util.Map;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.stream.Stream;
import java.util.HashMap;
import java.util.Map; import emu.grasscutter.scripts.constants.IntValueEnum;
import java.util.stream.Stream; import emu.grasscutter.utils.Utils;
import lombok.Getter; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum ElementType { import lombok.Getter;
None(0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Fire( public enum ElementType implements IntValueEnum {
1, None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, Fire (1, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2", 1),
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, Water (2, FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2", 2),
10101, Grass (3, FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, 10501, "TeamResonance_Grass_Lv2", 7),
"TeamResonance_Fire_Lv2", Electric (4, FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2", 6),
2), Ice (5, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2", 4),
Water( Frozen (6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY),
2, Wind (7, FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2", 3),
FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, Rock (8, FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2", 5),
FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, AntiFire (9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
10201, Default (255, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent");
"TeamResonance_Water_Lv2",
3), private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>();
Grass( private static final Map<String, ElementType> stringMap = new HashMap<>();
3,
FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, static {
FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY, // Create bindings for each value.
10501, Stream.of(ElementType.values()).forEach(entry -> {
"TeamResonance_Grass_Lv2", map.put(entry.getValue(), entry);
8), stringMap.put(entry.name(), entry);
Electric( });
4, }
FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY,
FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, @Getter private final int value;
10401, @Getter private final int teamResonanceId;
"TeamResonance_Electric_Lv2", @Getter private final FightProperty curEnergyProp;
7), @Getter private final FightProperty maxEnergyProp;
Ice( @Getter private final int depotIndex;
5, @Getter private final int configHash;
FightProperty.FIGHT_PROP_CUR_ICE_ENERGY,
FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) {
10601, this(value, curEnergyProp, maxEnergyProp, 0, null, 1);
"TeamResonance_Ice_Lv2", }
5),
Frozen(6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName) {
Wind( this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1);
7, }
FightProperty.FIGHT_PROP_CUR_WIND_ENERGY,
FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName, int depotIndex) {
10301, this.value = value;
"TeamResonance_Wind_Lv2", this.curEnergyProp = curEnergyProp;
4), this.maxEnergyProp = maxEnergyProp;
Rock( this.teamResonanceId = teamResonanceId;
8, this.depotIndex = depotIndex;
FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, if (configName != null) {
FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, this.configHash = Utils.abilityHash(configName);
10701, } else {
"TeamResonance_Rock_Lv2", this.configHash = 0;
6), }
AntiFire(9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), }
Default(
255, public static ElementType getTypeByValue(int value) {
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, return map.getOrDefault(value, None);
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, }
10801,
"TeamResonance_AllDifferent"); public static ElementType getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>(); }
private static final Map<String, ElementType> stringMap = new HashMap<>(); }
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
@Getter private final int value;
@Getter private final int teamResonanceId;
@Getter private final FightProperty curEnergyProp;
@Getter private final FightProperty maxEnergyProp;
@Getter private final int depotValue;
@Getter private final int configHash;
ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) {
this(value, curEnergyProp, maxEnergyProp, 0, null, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName) {
this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName,
int depotValue) {
this.value = value;
this.curEnergyProp = curEnergyProp;
this.maxEnergyProp = maxEnergyProp;
this.teamResonanceId = teamResonanceId;
this.depotValue = depotValue;
if (configName != null) {
this.configHash = Utils.abilityHash(configName);
} else {
this.configHash = 0;
}
}
public static ElementType getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static ElementType getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
}

View File

@ -1,10 +0,0 @@
package emu.grasscutter.game.quest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValue {
QuestTrigger value();
}

View File

@ -1,22 +1,23 @@
package emu.grasscutter.game.quest.conditions; package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData; import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond; import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val; import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK) @QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK)
public class ConditionPersonalLineUnlock extends BaseCondition { public class ConditionPersonalLineUnlock extends BaseCondition {
@Override @Override
public boolean execute( public boolean execute(
Player owner, Player owner,
QuestData questData, QuestData questData,
QuestData.QuestAcceptCondition condition, QuestData.QuestAcceptCondition condition,
String paramStr, String paramStr,
int... params) { int... params
val personalLineId = condition.getParam()[0]; ) {
return owner.getPersonalLineList().contains(personalLineId); var personalLineId = condition.getParam()[0];
} return owner.getPersonalLineList().contains(personalLineId);
} }
}

View File

@ -1,17 +1,17 @@
package emu.grasscutter.game.quest.exec; package emu.grasscutter.game.quest.exec;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.QuestData; import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec; import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec; import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler; import emu.grasscutter.game.quest.handlers.QuestExecHandler;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY) @QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY)
public class ExecAddCurAvatarEnergy extends QuestExecHandler { public class ExecAddCurAvatarEnergy extends QuestExecHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
Grasscutter.getLogger().info("Energy refilled"); Grasscutter.getLogger().info("Energy refilled");
return quest.getOwner().getEnergyManager().refillEntityAvatarEnergy(); return quest.getOwner().getEnergyManager().refillActiveEnergy();
} }
} }

View File

@ -1,21 +1,21 @@
package emu.grasscutter.game.quest.exec; package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData; import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec; import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec; import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler; import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays; import java.util.Arrays;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS) @QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS)
public class ExecAddQuestProgress extends QuestExecHandler { public final class ExecAddQuestProgress extends QuestExecHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
var param = var param =
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray(); Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]); quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]);
return true; return true;
} }
} }

View File

@ -1,21 +1,20 @@
package emu.grasscutter.game.quest.exec; package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData; import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec; import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec; import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler; import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays; import java.util.Arrays;
import lombok.val;
@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE)
@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE) public class ExecSetOpenState extends QuestExecHandler {
public class ExecSetOpenState extends QuestExecHandler { @Override
@Override public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { var param =
val param = Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]);
quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]); return true;
return true; }
} }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +1,86 @@
package emu.grasscutter.game.world; package emu.grasscutter.game.world;
import dev.morphia.annotations.Entity; import java.util.HashSet;
import dev.morphia.annotations.Id; import java.util.Map;
import dev.morphia.annotations.Indexed; import java.util.Set;
import emu.grasscutter.database.DatabaseHelper; import java.util.concurrent.ConcurrentHashMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.scripts.data.SceneGadget; import org.bson.types.ObjectId;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.HashSet; import dev.morphia.annotations.Entity;
import java.util.Map; import dev.morphia.annotations.Id;
import java.util.Set; import dev.morphia.annotations.Indexed;
import java.util.concurrent.ConcurrentHashMap; import emu.grasscutter.database.DatabaseHelper;
import lombok.Getter; import emu.grasscutter.game.player.Player;
import lombok.Setter; import emu.grasscutter.scripts.data.SceneGadget;
import org.bson.types.ObjectId; import emu.grasscutter.scripts.data.SceneGroup;
import lombok.Getter;
@Entity(value = "group_instances", useDiscriminator = false) import lombok.Setter;
public class SceneGroupInstance {
@Id private ObjectId id; @Entity(value = "group_instances", useDiscriminator = false)
public final class SceneGroupInstance {
@Indexed private int ownerUid; // This group is owned by the host player @Id private ObjectId id;
@Getter private int groupId;
@Indexed private int ownerUid; //This group is owned by the host player
@Getter private transient SceneGroup luaGroup; @Getter private int groupId;
@Getter @Setter private int targetSuiteId;
@Getter @Setter private int activeSuiteId; @Getter private transient SceneGroup luaGroup;
@Getter private Set<Integer> deadEntities; // Config_ids @Getter @Setter private int targetSuiteId;
private boolean isCached; @Getter @Setter private int activeSuiteId;
@Getter private Set<Integer> deadEntities; //Config_ids
@Getter private Map<Integer, Integer> cachedGadgetStates; private boolean isCached;
@Getter private Map<String, Integer> cachedVariables;
@Getter private Map<Integer, Integer> cachedGadgetStates;
@Getter @Setter private int lastTimeRefreshed; @Getter private Map<String, Integer> cachedVariables;
public SceneGroupInstance(SceneGroup group, Player owner) { @Getter @Setter private int lastTimeRefreshed;
this.luaGroup = group;
this.groupId = group.id; public SceneGroupInstance(SceneGroup group, Player owner) {
this.targetSuiteId = 0; this.luaGroup = group;
this.activeSuiteId = 0; this.groupId = group.id;
this.lastTimeRefreshed = 0; this.targetSuiteId = 0;
this.ownerUid = owner.getUid(); this.activeSuiteId = 0;
this.deadEntities = new HashSet<>(); this.lastTimeRefreshed = 0;
this.cachedGadgetStates = new ConcurrentHashMap<>(); this.ownerUid = owner.getUid();
this.cachedVariables = new ConcurrentHashMap<>(); this.deadEntities = new HashSet<>();
this.cachedGadgetStates = new ConcurrentHashMap<>();
this.isCached = this.cachedVariables = new ConcurrentHashMap<>();
false; // This is true when the group is not loaded on scene but caches suite data
} this.isCached = false; //This is true when the group is not loaded on scene but caches suite data
}
@Deprecated // Morphia only!
SceneGroupInstance() { @Deprecated // Morphia only!
this.cachedVariables = new ConcurrentHashMap<>(); SceneGroupInstance(){
this.deadEntities = new HashSet<>(); this.cachedVariables = new ConcurrentHashMap<>();
this.cachedGadgetStates = new ConcurrentHashMap<>(); this.deadEntities = new HashSet<>();
} this.cachedGadgetStates = new ConcurrentHashMap<>();
}
public void setLuaGroup(SceneGroup group) {
this.luaGroup = group; public void setLuaGroup(SceneGroup group) {
this.groupId = group.id; this.luaGroup = group;
} this.groupId = group.id;
}
public boolean isCached() {
return this.isCached; public boolean isCached() {
} return this.isCached;
}
public void setCached(boolean value) {
this.isCached = value; public void setCached(boolean value) {
save(); // Save each time a group is registered or unregistered this.isCached = value;
} save(); //Save each time a group is registered or unregistered
}
public void cacheGadgetState(SceneGadget g, int state) {
if (g.persistent) // Only cache when is persistent public void cacheGadgetState(SceneGadget g, int state) {
cachedGadgetStates.put(g.config_id, state); if(g.persistent) //Only cache when is persistent
} cachedGadgetStates.put(g.config_id, state);
}
public int getCachedGadgetState(SceneGadget g) {
Integer state = cachedGadgetStates.getOrDefault(g.config_id, null); public int getCachedGadgetState(SceneGadget g) {
return (state == null) ? g.state : state; Integer state = cachedGadgetStates.getOrDefault(g.config_id, null);
} return (state == null) ? g.state : state;
}
public void save() {
DatabaseHelper.saveGroupInstance(this); public void save() {
} DatabaseHelper.saveGroupInstance(this);
} }
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,66 @@
package emu.grasscutter.scripts; package emu.grasscutter.scripts;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import java.util.HashMap; import java.util.HashMap;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import emu.grasscutter.utils.Position;
import lombok.val;
public class ScriptUtils { import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
public static HashMap<Object, Object> toMap(LuaTable table) {
HashMap<Object, Object> map = new HashMap<>(); public interface ScriptUtils {
LuaValue[] rootKeys = table.keys(); static HashMap<Object, Object> toMap(LuaTable table) {
for (LuaValue k : rootKeys) { HashMap<Object, Object> map = new HashMap<>();
if (table.get(k).istable()) { LuaValue[] rootKeys = table.keys();
map.put(k, toMap(table.get(k).checktable())); for (LuaValue k : rootKeys) {
} else { if (table.get(k).istable()) {
map.put(k, table.get(k)); map.put(k, toMap(table.get(k).checktable()));
} } else {
} map.put(k, table.get(k));
return map; }
} }
return map;
public static void print(LuaTable table) { }
Grasscutter.getLogger().info(toMap(table).toString());
} static void print(LuaTable table) {
} Grasscutter.getLogger().info(toMap(table).toString());
}
/**
* Converts a position object into a Lua table.
*
* @param position The position object to convert.
* @return The Lua table.
*/
static LuaTable posToLua(Position position) {
var result = new LuaTable();
if (position != null) {
result.set("x", position.getX());
result.set("y", position.getY());
result.set("z", position.getZ());
} else {
result.set("x", 0);
result.set("y", 0);
result.set("z", 0);
}
return result;
}
/**
* Converts a Lua table into a position object.
*
* @param position The Lua table to convert.
* @return The position object.
*/
static Position luaToPos(LuaValue position) {
var result = new Position();
if (position != null && !position.isnil()) {
result.setX(position.get("x").optint(0));
result.setY(position.get("y").optint(0));
result.setZ(position.get("z").optint(0));
}
return result;
}
}

View File

@ -1,15 +1,16 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@ToString @ToString
@Setter @Setter
public class SceneConfig { public class SceneConfig {
public Position vision_anchor; public Position vision_anchor;
public Position born_pos; public Position born_pos;
public Position born_rot; public Position born_rot;
public Position begin_pos; public Position begin_pos;
public Position size; public Position size;
} public float die_y;
}

View File

@ -1,170 +1,183 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.List; import lombok.Getter;
import java.util.Map; import lombok.Setter;
import java.util.Optional; import lombok.ToString;
import java.util.stream.Collectors; import org.luaj.vm2.LuaValue;
import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.Bindings;
import javax.script.ScriptException; import javax.script.CompiledScript;
import lombok.Setter; import javax.script.ScriptException;
import lombok.ToString;
import org.luaj.vm2.LuaValue; import java.util.ArrayList;
import java.util.List;
@ToString import java.util.Map;
@Setter import java.util.Optional;
public class SceneGroup { import java.util.Random;
public transient int import java.util.stream.Collectors;
block_id; // Not an actual variable in the scripts but we will keep it here for reference
@ToString
public int id; @Setter
public int refresh_id; public final class SceneGroup {
public Position pos; public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference
public Map<Integer, SceneMonster> monsters; // <ConfigId, Monster> public int id;
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets> public int refresh_id;
public Map<String, SceneTrigger> triggers; public Position pos;
public Map<Integer, SceneRegion> regions;
public List<SceneSuite> suites; public Map<Integer, SceneMonster> monsters; // <ConfigId, Monster>
public List<SceneVar> variables; public Map<Integer, SceneNPC> npcs; // <ConfigId, Npc>
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
public SceneBusiness business; public Map<String, SceneTrigger> triggers;
public SceneGarbage garbages; public Map<Integer, SceneRegion> regions;
public SceneInitConfig init_config; public List<SceneSuite> suites;
public List<SceneVar> variables;
private transient boolean loaded; // Not an actual variable in the scripts either
private transient CompiledScript script; public SceneBusiness business;
private transient Bindings bindings; public SceneGarbage garbages;
public SceneInitConfig init_config;
public static SceneGroup of(int groupId) { @Getter public boolean dynamic_load = false;
var group = new SceneGroup();
group.id = groupId; public SceneReplaceable is_replaceable;
return group;
} private transient boolean loaded; // Not an actual variable in the scripts either
private transient CompiledScript script;
public boolean isLoaded() { private transient Bindings bindings;
return this.loaded; public static SceneGroup of(int groupId) {
} var group = new SceneGroup();
group.id = groupId;
public void setLoaded(boolean loaded) { return group;
this.loaded = loaded; }
}
public boolean isLoaded() {
public int getBusinessType() { return this.loaded;
return this.business == null ? 0 : this.business.type; }
}
public void setLoaded(boolean loaded) {
public List<SceneGadget> getGarbageGadgets() { this.loaded = loaded;
return this.garbages == null ? null : this.garbages.gadgets; }
}
public int getBusinessType() {
public CompiledScript getScript() { return this.business == null ? 0 : this.business.type;
return this.script; }
}
public List<SceneGadget> getGarbageGadgets() {
public SceneSuite getSuiteByIndex(int index) { return this.garbages == null ? null : this.garbages.gadgets;
return this.suites.get(index - 1); }
}
public CompiledScript getScript() {
public Bindings getBindings() { return this.script;
return this.bindings; }
}
public SceneSuite getSuiteByIndex(int index) {
public synchronized SceneGroup load(int sceneId) { if(index < 1 || index > suites.size()) {
if (this.loaded) { return null;
return this; }
} return this.suites.get(index - 1);
// Set flag here so if there is no script, we don't call this function over and over again. }
this.setLoaded(true);
public Bindings getBindings() {
this.bindings = ScriptLoader.getEngine().createBindings(); return this.bindings;
}
CompiledScript cs =
ScriptLoader.getScript( public synchronized SceneGroup load(int sceneId) {
"Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua"); if (this.loaded) {
return this;
if (cs == null) { }
return this; // Set flag here so if there is no script, we don't call this function over and over again.
} this.setLoaded(true);
this.script = cs; this.bindings = ScriptLoader.getEngine().createBindings();
// Eval script CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua");
try {
cs.eval(this.bindings); if (cs == null) {
return this;
// Set }
this.monsters =
ScriptLoader.getSerializer() this.script = cs;
.toList(SceneMonster.class, this.bindings.get("monsters"))
.stream() // Eval script
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); try {
this.monsters.values().forEach(m -> m.group = this); cs.eval(this.bindings);
this.gadgets = // Set
ScriptLoader.getSerializer() this.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, this.bindings.get("monsters")).stream()
.toList(SceneGadget.class, this.bindings.get("gadgets")) .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
.stream() this.monsters.values().forEach(m -> m.group = this);
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.gadgets.values().forEach(m -> m.group = this); this.npcs = ScriptLoader.getSerializer().toList(SceneNPC.class, this.bindings.get("npcs")).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.triggers = this.npcs.values().forEach(m -> m.group = this);
ScriptLoader.getSerializer()
.toList(SceneTrigger.class, this.bindings.get("triggers")) this.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, this.bindings.get("gadgets")).stream()
.stream() .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
.collect(Collectors.toMap(x -> x.name, y -> y, (a, b) -> a)); this.gadgets.values().forEach(m -> m.group = this);
this.triggers.values().forEach(t -> t.currentGroup = this);
this.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, this.bindings.get("triggers")).stream()
this.suites = .collect(Collectors.toMap(SceneTrigger::getName, y -> y, (a, b) -> a));
ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites")); this.triggers.values().forEach(t -> t.currentGroup = this);
this.regions =
ScriptLoader.getSerializer() this.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites"));
.toList(SceneRegion.class, this.bindings.get("regions")) this.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, this.bindings.get("regions")).stream()
.stream() .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); this.regions.values().forEach(m -> m.group = this);
this.regions.values().forEach(m -> m.group = this);
this.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, this.bindings.get("init_config"));
this.init_config =
ScriptLoader.getSerializer() // Garbages // TODO: fix properly later
.toObject(SceneInitConfig.class, this.bindings.get("init_config")); Object garbagesValue = this.bindings.get("garbages");
if (garbagesValue instanceof LuaValue garbagesTable) {
// Garbages // TODO: fix properly later this.garbages = new SceneGarbage();
Object garbagesValue = this.bindings.get("garbages"); if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) {
if (garbagesValue instanceof LuaValue garbagesTable) { this.garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable());
this.garbages = new SceneGarbage(); this.garbages.gadgets.forEach(m -> m.group = this);
if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { }
this.garbages.gadgets = }
ScriptLoader.getSerializer()
.toList( // Add variables to suite
SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable()); this.variables = ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables"));
this.garbages.gadgets.forEach(m -> m.group = this);
} // Add monsters and gadgets to suite
} this.suites.forEach(i -> i.init(this));
// Add variables to suite } catch (ScriptException e) {
this.variables = Grasscutter.getLogger().error("An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e);
ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables")); }
// Add monsters and gadgets to suite Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId);
this.suites.forEach(i -> i.init(this)); return this;
}
} catch (ScriptException e) {
Grasscutter.getLogger() public int findInitSuiteIndex(int exclude_index) { //TODO: Investigate end index
.error( if (init_config == null) return 1;
"An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e); if (init_config.io_type == 1) return init_config.suite; //IO TYPE FLOW
} if (init_config.rand_suite) {
if (suites.size() == 1) {
Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId); return init_config.suite;
return this; } else {
} List<Integer> randSuiteList = new ArrayList<>();
for (int i = 0; i < suites.size(); i++) {
public Optional<SceneBossChest> searchBossChestInGroup() { if (i == exclude_index) continue;
return this.gadgets.values().stream()
.filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0) var suite = suites.get(i);
.map(g -> g.boss_chest) for(int j = 0; j < suite.rand_weight; j++) randSuiteList.add(i);
.findFirst(); }
}
} return randSuiteList.get(new Random().nextInt(randSuiteList.size()));
}
}
return init_config.suite;
}
public Optional<SceneBossChest> searchBossChestInGroup() {
return this.gadgets.values().stream()
.filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0)
.map(g -> g.boss_chest)
.findFirst();
}
}

View File

@ -1,12 +1,13 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@ToString @ToString
@Setter @Setter
public class SceneInitConfig { public final class SceneInitConfig {
public int suite; public int suite;
public int end_suite; public int end_suite;
public boolean rand_suite; public int io_type;
} public boolean rand_suite;
}

View File

@ -1,19 +1,15 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@ToString @ToString
@Setter @Setter
public class SceneMonster extends SceneObject { public class SceneMonster extends SceneObject{
public int monster_id; public int monster_id;
public int pose_id; public int pose_id;
public int drop_id; public int drop_id;
public int special_name_id; public boolean disableWander;
public String drop_tag; public int title_id;
public int climate_area_id; public int special_name_id;
public boolean disableWander; }
public int title_id;
public int[] affix;
public int mark_flag;
}

View File

@ -1,18 +1,19 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@ToString @ToString
@Setter @Setter
public class SceneObject { public abstract class SceneObject {
public int level; public int level;
public int config_id; public int config_id;
public int area_id; public int area_id;
public int vision_level = 0;
public Position pos;
public Position rot; public Position pos;
/** not set by lua */ public Position rot;
public transient SceneGroup group; /** not set by lua */
} public transient SceneGroup group;
}

View File

@ -1,60 +1,63 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Setter;
import lombok.ToString; import lombok.Setter;
import lombok.ToString;
@ToString
@Setter @ToString
public class SceneSuite { @Setter
// make it refer the default empty list to avoid NPE caused by some group public class SceneSuite {
public List<Integer> monsters = List.of(); // make it refer the default empty list to avoid NPE caused by some group
public List<Integer> gadgets = List.of(); public List<Integer> monsters = List.of();
public List<String> triggers = List.of(); public List<Integer> gadgets = List.of();
public List<Integer> regions = List.of(); public List<String> triggers = List.of();
public int rand_weight; public List<Integer> regions = List.of();
public int[] npcs; public int rand_weight;
public transient List<SceneMonster> sceneMonsters = List.of(); public boolean ban_refresh = false;
public transient List<SceneGadget> sceneGadgets = List.of();
public transient List<SceneTrigger> sceneTriggers = List.of(); public transient List<SceneMonster> sceneMonsters = List.of();
public transient List<SceneRegion> sceneRegions = List.of(); public transient List<SceneGadget> sceneGadgets = List.of();
public transient List<SceneTrigger> sceneTriggers = List.of();
public void init(SceneGroup sceneGroup) { public transient List<SceneRegion> sceneRegions = List.of();
if (sceneGroup.monsters != null && this.monsters != null) {
this.sceneMonsters = public void init(SceneGroup sceneGroup) {
new ArrayList<>( if(sceneGroup.monsters != null){
this.monsters.stream() this.sceneMonsters = new ArrayList<>(
.filter(sceneGroup.monsters::containsKey) this.monsters.stream()
.map(sceneGroup.monsters::get) .filter(sceneGroup.monsters::containsKey)
.toList()); .map(sceneGroup.monsters::get)
} .toList()
);
if (sceneGroup.gadgets != null && this.gadgets != null) { }
this.sceneGadgets =
new ArrayList<>( if(sceneGroup.gadgets != null){
this.gadgets.stream() this.sceneGadgets = new ArrayList<>(
.filter(sceneGroup.gadgets::containsKey) this.gadgets.stream()
.map(sceneGroup.gadgets::get) .filter(sceneGroup.gadgets::containsKey)
.toList()); .map(sceneGroup.gadgets::get)
} .toList()
);
if (sceneGroup.triggers != null && this.triggers != null) { }
this.sceneTriggers =
new ArrayList<>( if(sceneGroup.triggers != null) {
this.triggers.stream() this.sceneTriggers = new ArrayList<>(
.filter(sceneGroup.triggers::containsKey) this.triggers.stream()
.map(sceneGroup.triggers::get) .filter(sceneGroup.triggers::containsKey)
.toList()); .map(sceneGroup.triggers::get)
} .toList()
if (sceneGroup.regions != null && this.regions != null) { );
this.sceneRegions = }
new ArrayList<>( if(sceneGroup.regions != null) {
this.regions.stream() this.sceneRegions = new ArrayList<>(
.filter(sceneGroup.regions::containsKey) this.regions.stream()
.map(sceneGroup.regions::get) .filter(sceneGroup.regions::containsKey)
.toList()); .map(sceneGroup.regions::get)
} .toList()
} );
} }
}
}

View File

@ -1,59 +1,45 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
import lombok.Setter; import lombok.*;
@Setter @Setter
public class SceneTrigger { @Getter
public String name; @NoArgsConstructor
public int config_id; // todo find way to deserialize from lua with final fields, maybe with the help of Builder?
public int event; public final class SceneTrigger {
public String source; private String name;
public String condition; private int config_id;
public String action; private int event;
public boolean forbid_guest; private int trigger_count = 1;
public int trigger_count; private String source;
public String tlog_tag; private String condition;
private String action;
public transient SceneGroup currentGroup; private String tag;
@Override public transient SceneGroup currentGroup;
public boolean equals(Object obj) {
if (obj instanceof SceneTrigger sceneTrigger) { @Override
return this.name.equals(sceneTrigger.name); public boolean equals(Object obj) {
} if (obj instanceof SceneTrigger sceneTrigger){
return super.equals(obj); return this.name.equals(sceneTrigger.name);
} } else return super.equals(obj);
}
@Override
public int hashCode() { @Override
return name.hashCode(); public int hashCode() {
} return name.hashCode();
}
@Override
public String toString() { @Override
return "SceneTrigger{" public String toString() {
+ "name='" return "SceneTrigger{" +
+ name "name='" + name + '\'' +
+ '\'' ", config_id=" + config_id +
+ ", config_id=" ", event=" + event +
+ config_id ", source='" + source + '\'' +
+ ", event=" ", condition='" + condition + '\'' +
+ event ", action='" + action + '\'' +
+ ", source='" ", trigger_count='" + trigger_count + '\'' +
+ source '}';
+ '\'' }
+ ", condition='" }
+ condition
+ '\''
+ ", action='"
+ action
+ '\''
+ ", forbid_guest='"
+ forbid_guest
+ '\''
+ ", trigger_count='"
+ trigger_count
+ '\''
+ '}';
}
}

View File

@ -1,65 +1,90 @@
package emu.grasscutter.scripts.data; package emu.grasscutter.scripts.data;
public class ScriptArgs { public class ScriptArgs {
public int param1; public int param1;
public int param2; public int param2;
public int param3; public int param3;
public int source_eid; // Source entity public int source_eid; // Source entity
public int target_eid; public int target_eid;
public int group_id;
public ScriptArgs() {} public String source; // source string, used for timers
public int type; // lua event type, used by scripts and the ScriptManager
public ScriptArgs(int param1) {
this.param1 = param1; public ScriptArgs(int groupId, int eventType) {
} this(groupId, eventType, 0,0);
}
public ScriptArgs(int param1, int param2) {
this.param1 = param1; public ScriptArgs(int groupId, int eventType, int param1) {
this.param2 = param2; this(groupId, eventType, param1,0);
} }
public int getParam1() { public ScriptArgs(int groupId, int eventType, int param1, int param2) {
return param1; this.type = eventType;
} this.param1 = param1;
this.param2 = param2;
public ScriptArgs setParam1(int param1) { this.group_id = groupId;
this.param1 = param1; }
return this;
} public int getParam1() {
return param1;
public int getParam2() { }
return param2;
} public ScriptArgs setParam1(int param1) {
this.param1 = param1;
public ScriptArgs setParam2(int param2) { return this;
this.param2 = param2; }
return this;
} public int getParam2() {
return param2;
public int getParam3() { }
return param3;
} public ScriptArgs setParam2(int param2) {
this.param2 = param2;
public ScriptArgs setParam3(int param3) { return this;
this.param3 = param3; }
return this;
} public int getParam3() {
return param3;
public int getSourceEntityId() { }
return source_eid;
} public ScriptArgs setParam3(int param3) {
this.param3 = param3;
public ScriptArgs setSourceEntityId(int source_eid) { return this;
this.source_eid = source_eid; }
return this;
} public int getSourceEntityId() {
return source_eid;
public int getTargetEntityId() { }
return target_eid;
} public ScriptArgs setSourceEntityId(int source_eid) {
this.source_eid = source_eid;
public ScriptArgs setTargetEntityId(int target_eid) { return this;
this.target_eid = target_eid; }
return this;
} public int getTargetEntityId() {
} return target_eid;
}
public ScriptArgs setTargetEntityId(int target_eid) {
this.target_eid = target_eid;
return this;
}
public String getEventSource() {
return source;
}
public ScriptArgs setEventSource(String source) {
this.source = source;
return this;
}
public int getGroupId() {
return group_id;
}
public ScriptArgs setGroupId(int group_id) {
this.group_id = group_id;
return this;
}
}

View File

@ -1,99 +1,92 @@
package emu.grasscutter.scripts.service; package emu.grasscutter.scripts.service;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.scripts.SceneScriptManager; import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.scripts.listener.ScriptMonsterListener; import emu.grasscutter.scripts.listener.ScriptMonsterListener;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger; import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ScriptMonsterTideService { import java.util.concurrent.atomic.AtomicInteger;
private final SceneScriptManager sceneScriptManager;
private final SceneGroup currentGroup; public final class ScriptMonsterTideService {
private final AtomicInteger monsterAlive; private final SceneScriptManager sceneScriptManager;
private final AtomicInteger monsterTideCount; private final SceneGroup currentGroup;
private final AtomicInteger monsterKillCount; private final AtomicInteger monsterAlive;
private final int monsterSceneLimit; private final AtomicInteger monsterTideCount;
private final ConcurrentLinkedQueue<Integer> monsterConfigOrders; private final AtomicInteger monsterKillCount;
private final OnMonsterCreated onMonsterCreated = new OnMonsterCreated(); private final int monsterSceneLimit;
private final OnMonsterDead onMonsterDead = new OnMonsterDead(); private final ConcurrentLinkedQueue<Integer> monsterConfigOrders;
private final List<Integer> monsterConfigIds;
public ScriptMonsterTideService( private final OnMonsterCreated onMonsterCreated= new OnMonsterCreated();
SceneScriptManager sceneScriptManager, private final OnMonsterDead onMonsterDead= new OnMonsterDead();
SceneGroup group,
int tideCount, public ScriptMonsterTideService(SceneScriptManager sceneScriptManager,
int monsterSceneLimit, SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){
Integer[] ordersConfigId) { this.sceneScriptManager = sceneScriptManager;
this.sceneScriptManager = sceneScriptManager; this.currentGroup = group;
this.currentGroup = group; this.monsterSceneLimit = monsterSceneLimit;
this.monsterSceneLimit = monsterSceneLimit; this.monsterTideCount = new AtomicInteger(tideCount);
this.monsterTideCount = new AtomicInteger(tideCount); this.monsterKillCount = new AtomicInteger(0);
this.monsterKillCount = new AtomicInteger(0); this.monsterAlive = new AtomicInteger(0);
this.monsterAlive = new AtomicInteger(0); this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId));
this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); this.monsterConfigIds = List.of(ordersConfigId);
this.sceneScriptManager this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(onMonsterCreated);
.getScriptMonsterSpawnService() this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead);
.addMonsterCreatedListener(onMonsterCreated); // spawn the first turn
this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); for (int i = 0; i < this.monsterSceneLimit; i++) {
// spawn the first turn sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster()));
for (int i = 0; i < this.monsterSceneLimit; i++) { }
sceneScriptManager.addEntity( }
this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster()));
} public class OnMonsterCreated implements ScriptMonsterListener{
} @Override
public void onNotify(EntityMonster sceneMonster) {
public SceneMonster getNextMonster() { if(monsterConfigIds.contains(sceneMonster.getConfigId()) && monsterSceneLimit > 0){
var nextId = this.monsterConfigOrders.poll(); monsterAlive.incrementAndGet();
if (currentGroup.monsters.containsKey(nextId)) { monsterTideCount.decrementAndGet();
return currentGroup.monsters.get(nextId); }
} }
// TODO some monster config_id do not exist in groups, so temporarily set it to the first }
return currentGroup.monsters.values().stream().findFirst().orElse(null);
} public SceneMonster getNextMonster(){
var nextId = this.monsterConfigOrders.poll();
public void unload() { if(currentGroup.monsters.containsKey(nextId)){
this.sceneScriptManager return currentGroup.monsters.get(nextId);
.getScriptMonsterSpawnService() }
.removeMonsterCreatedListener(onMonsterCreated); // TODO some monster config_id do not exist in groups, so temporarily set it to the first
this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead); return currentGroup.monsters.values().stream().findFirst().orElse(null);
} }
public class OnMonsterCreated implements ScriptMonsterListener { public class OnMonsterDead implements ScriptMonsterListener {
@Override @Override
public void onNotify(EntityMonster sceneMonster) { public void onNotify(EntityMonster sceneMonster) {
if (monsterSceneLimit > 0) { if (monsterSceneLimit <= 0) {
monsterAlive.incrementAndGet(); return;
monsterTideCount.decrementAndGet(); }
} if (monsterAlive.decrementAndGet() >= monsterSceneLimit) {
} // maybe not happen
} return;
}
public class OnMonsterDead implements ScriptMonsterListener { monsterKillCount.incrementAndGet();
@Override if (monsterTideCount.get() > 0) {
public void onNotify(EntityMonster sceneMonster) { // add more
if (monsterSceneLimit <= 0) { sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster()));
return; }
} // spawn the last turn of monsters
if (monsterAlive.decrementAndGet() >= monsterSceneLimit) { // fix the 5-2
// maybe not happen sceneScriptManager.callEvent(new ScriptArgs(currentGroup.id, EventType.EVENT_MONSTER_TIDE_DIE, monsterKillCount.get()));
return; }
}
monsterKillCount.incrementAndGet(); }
if (monsterTideCount.get() > 0) {
// add more public void unload(){
sceneScriptManager.addEntity( this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterCreatedListener(onMonsterCreated);
sceneScriptManager.createMonster( this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead);
currentGroup.id, currentGroup.block_id, getNextMonster())); }
} }
// spawn the last turn of monsters
// fix the 5-2
sceneScriptManager.callEvent(
EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(monsterKillCount.get()));
}
}
}

View File

@ -1,30 +1,24 @@
package emu.grasscutter.server.event.entity; package emu.grasscutter.server.event.entity;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.server.event.Cancellable; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.server.event.types.EntityEvent; import emu.grasscutter.server.event.Cancellable;
import javax.annotation.Nullable; import emu.grasscutter.server.event.types.EntityEvent;
import lombok.Getter;
public final class EntityDamageEvent extends EntityEvent implements Cancellable { import lombok.Setter;
@Nullable private final GameEntity damager;
private float damage; import javax.annotation.Nullable;
public EntityDamageEvent(GameEntity entity, float damage, @Nullable GameEntity damager) { public final class EntityDamageEvent extends EntityEvent implements Cancellable {
super(entity); @Getter @Setter private float damage;
@Getter @Setter private ElementType attackElementType;
this.damage = damage; @Getter @Nullable private final GameEntity damager;
this.damager = damager;
} public EntityDamageEvent(GameEntity entity, float damage, ElementType attackElementType, @Nullable GameEntity damager) {
super(entity);
public float getDamage() {
return this.damage; this.damage = damage;
} this.attackElementType = attackElementType;
this.damager = damager;
public void setDamage(float damage) { }
this.damage = damage; }
}
@Nullable public GameEntity getDamager() {
return this.damager;
}
}

View File

@ -1,281 +1,279 @@
package emu.grasscutter.server.game; package emu.grasscutter.server.game;
import static emu.grasscutter.config.Configuration.GAME_INFO; import static emu.grasscutter.config.Configuration.GAME_INFO;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.battlepass.BattlePassSystem; import emu.grasscutter.game.battlepass.BattlePassSystem;
import emu.grasscutter.game.chat.ChatSystem; import emu.grasscutter.game.chat.ChatSystem;
import emu.grasscutter.game.chat.ChatSystemHandler; import emu.grasscutter.game.chat.ChatSystemHandler;
import emu.grasscutter.game.combine.CombineManger; import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.DropSystem; import emu.grasscutter.game.drop.DropSystem;
import emu.grasscutter.game.dungeons.DungeonSystem; import emu.grasscutter.game.dungeons.DungeonSystem;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.expedition.ExpeditionSystem;
import emu.grasscutter.game.expedition.ExpeditionSystem; import emu.grasscutter.game.gacha.GachaSystem;
import emu.grasscutter.game.gacha.GachaSystem; import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingCompoundManager; import emu.grasscutter.game.managers.cooking.CookingManager;
import emu.grasscutter.game.managers.cooking.CookingManager; import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.energy.EnergyManager; import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.QuestSystem;
import emu.grasscutter.game.quest.QuestSystem; import emu.grasscutter.game.shop.ShopSystem;
import emu.grasscutter.game.shop.ShopSystem; import emu.grasscutter.game.systems.AnnouncementSystem;
import emu.grasscutter.game.systems.AnnouncementSystem; import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.game.systems.InventorySystem; import emu.grasscutter.game.systems.MultiplayerSystem;
import emu.grasscutter.game.systems.MultiplayerSystem; import emu.grasscutter.game.tower.TowerSystem;
import emu.grasscutter.game.tower.TowerSystem; import emu.grasscutter.game.world.World;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.WorldDataSystem;
import emu.grasscutter.game.world.WorldDataSystem; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.server.event.game.ServerTickEvent;
import emu.grasscutter.server.event.game.ServerTickEvent; import emu.grasscutter.server.event.internal.ServerStartEvent;
import emu.grasscutter.server.event.internal.ServerStartEvent; import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.server.event.internal.ServerStopEvent; import emu.grasscutter.server.event.types.ServerEvent;
import emu.grasscutter.server.event.types.ServerEvent; import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.server.scheduler.ServerTaskScheduler; import emu.grasscutter.task.TaskMap;
import emu.grasscutter.task.TaskMap; import java.net.InetSocketAddress;
import java.net.InetSocketAddress; import java.time.Instant;
import java.time.Instant; import java.time.OffsetDateTime;
import java.time.OffsetDateTime; import java.util.*;
import java.util.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap; import kcp.highway.ChannelConfig;
import kcp.highway.ChannelConfig; import kcp.highway.KcpServer;
import kcp.highway.KcpServer; import lombok.Getter;
import lombok.Getter;
@Getter
@Getter public final class GameServer extends KcpServer {
public final class GameServer extends KcpServer { // Game server base
// Game server base private final InetSocketAddress address;
private final InetSocketAddress address; private final GameServerPacketHandler packetHandler;
private final GameServerPacketHandler packetHandler; private final Map<Integer, Player> players;
private final Map<Integer, Player> players; private final Set<World> worlds;
private final Set<World> worlds;
// Server systems
// Server systems private final InventorySystem inventorySystem;
private final InventorySystem inventorySystem; private final GachaSystem gachaSystem;
private final GachaSystem gachaSystem; private final ShopSystem shopSystem;
private final ShopSystem shopSystem; private final MultiplayerSystem multiplayerSystem;
private final MultiplayerSystem multiplayerSystem; private final DungeonSystem dungeonSystem;
private final DungeonSystem dungeonSystem; private final ExpeditionSystem expeditionSystem;
private final ExpeditionSystem expeditionSystem; private final DropSystem dropSystem;
private final DropSystem dropSystem; private final WorldDataSystem worldDataSystem;
private final WorldDataSystem worldDataSystem; private final BattlePassSystem battlePassSystem;
private final BattlePassSystem battlePassSystem; private final CombineManger combineSystem;
private final CombineManger combineSystem; private final TowerSystem towerSystem;
private final TowerSystem towerSystem; private final AnnouncementSystem announcementSystem;
private final AnnouncementSystem announcementSystem; private final QuestSystem questSystem;
private final QuestSystem questSystem;
// Extra
// Extra private final ServerTaskScheduler scheduler;
private final ServerTaskScheduler scheduler; private final TaskMap taskMap;
private final TaskMap taskMap;
private ChatSystemHandler chatManager;
private ChatSystemHandler chatManager;
public GameServer() {
public GameServer() { this(getAdapterInetSocketAddress());
this(getAdapterInetSocketAddress()); }
}
public GameServer(InetSocketAddress address) {
public GameServer(InetSocketAddress address) { ChannelConfig channelConfig = new ChannelConfig();
ChannelConfig channelConfig = new ChannelConfig(); channelConfig.nodelay(true, GAME_INFO.kcpInterval, 2, true);
channelConfig.nodelay(true, GAME_INFO.kcpInterval, 2, true); channelConfig.setMtu(1400);
channelConfig.setMtu(1400); channelConfig.setSndwnd(256);
channelConfig.setSndwnd(256); channelConfig.setRcvwnd(256);
channelConfig.setRcvwnd(256); channelConfig.setTimeoutMillis(30 * 1000); // 30s
channelConfig.setTimeoutMillis(30 * 1000); // 30s channelConfig.setUseConvChannel(true);
channelConfig.setUseConvChannel(true); channelConfig.setAckNoDelay(false);
channelConfig.setAckNoDelay(false);
this.init(GameSessionManager.getListener(), channelConfig, address);
this.init(GameSessionManager.getListener(), channelConfig, address);
EnergyManager.initialize();
DungeonChallenge.initialize(); StaminaManager.initialize();
EnergyManager.initialize(); CookingManager.initialize();
StaminaManager.initialize(); CookingCompoundManager.initialize();
CookingManager.initialize(); CombineManger.initialize();
CookingCompoundManager.initialize();
CombineManger.initialize(); // Game Server base
this.address = address;
// Game Server base this.packetHandler = new GameServerPacketHandler(PacketHandler.class);
this.address = address; this.players = new ConcurrentHashMap<>();
this.packetHandler = new GameServerPacketHandler(PacketHandler.class); this.worlds = Collections.synchronizedSet(new HashSet<>());
this.players = new ConcurrentHashMap<>();
this.worlds = Collections.synchronizedSet(new HashSet<>()); // Extra
this.scheduler = new ServerTaskScheduler();
// Extra this.taskMap = new TaskMap(true);
this.scheduler = new ServerTaskScheduler();
this.taskMap = new TaskMap(true); // Create game systems
this.inventorySystem = new InventorySystem(this);
// Create game systems this.gachaSystem = new GachaSystem(this);
this.inventorySystem = new InventorySystem(this); this.shopSystem = new ShopSystem(this);
this.gachaSystem = new GachaSystem(this); this.multiplayerSystem = new MultiplayerSystem(this);
this.shopSystem = new ShopSystem(this); this.dungeonSystem = new DungeonSystem(this);
this.multiplayerSystem = new MultiplayerSystem(this); this.dropSystem = new DropSystem(this);
this.dungeonSystem = new DungeonSystem(this); this.expeditionSystem = new ExpeditionSystem(this);
this.dropSystem = new DropSystem(this); this.combineSystem = new CombineManger(this);
this.expeditionSystem = new ExpeditionSystem(this); this.towerSystem = new TowerSystem(this);
this.combineSystem = new CombineManger(this); this.worldDataSystem = new WorldDataSystem(this);
this.towerSystem = new TowerSystem(this); this.battlePassSystem = new BattlePassSystem(this);
this.worldDataSystem = new WorldDataSystem(this); this.announcementSystem = new AnnouncementSystem(this);
this.battlePassSystem = new BattlePassSystem(this); this.questSystem = new QuestSystem(this);
this.announcementSystem = new AnnouncementSystem(this);
this.questSystem = new QuestSystem(this); // Chata manager
this.chatManager = new ChatSystem(this);
// Chata manager
this.chatManager = new ChatSystem(this); // Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
// Hook into shutdown event. }
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
} private static InetSocketAddress getAdapterInetSocketAddress() {
InetSocketAddress inetSocketAddress;
private static InetSocketAddress getAdapterInetSocketAddress() { if (GAME_INFO.bindAddress.equals("")) {
InetSocketAddress inetSocketAddress; inetSocketAddress = new InetSocketAddress(GAME_INFO.bindPort);
if (GAME_INFO.bindAddress.equals("")) { } else {
inetSocketAddress = new InetSocketAddress(GAME_INFO.bindPort); inetSocketAddress = new InetSocketAddress(GAME_INFO.bindAddress, GAME_INFO.bindPort);
} else { }
inetSocketAddress = new InetSocketAddress(GAME_INFO.bindAddress, GAME_INFO.bindPort); return inetSocketAddress;
} }
return inetSocketAddress;
} @Deprecated
public ChatSystemHandler getChatManager() {
@Deprecated return chatManager;
public ChatSystemHandler getChatManager() { }
return chatManager;
} @Deprecated
public void setChatManager(ChatSystemHandler chatManager) {
@Deprecated this.chatManager = chatManager;
public void setChatManager(ChatSystemHandler chatManager) { }
this.chatManager = chatManager;
} public ChatSystemHandler getChatSystem() {
return chatManager;
public ChatSystemHandler getChatSystem() { }
return chatManager;
} public void setChatSystem(ChatSystemHandler chatManager) {
this.chatManager = chatManager;
public void setChatSystem(ChatSystemHandler chatManager) { }
this.chatManager = chatManager;
} public void registerPlayer(Player player) {
getPlayers().put(player.getUid(), player);
public void registerPlayer(Player player) { }
getPlayers().put(player.getUid(), player);
} public Player getPlayerByUid(int id) {
return this.getPlayerByUid(id, false);
public Player getPlayerByUid(int id) { }
return this.getPlayerByUid(id, false);
} public Player getPlayerByUid(int id, boolean allowOfflinePlayers) {
// Console check
public Player getPlayerByUid(int id, boolean allowOfflinePlayers) { if (id == GameConstants.SERVER_CONSOLE_UID) {
// Console check return null;
if (id == GameConstants.SERVER_CONSOLE_UID) { }
return null;
} // Get from online players
Player player = this.getPlayers().get(id);
// Get from online players
Player player = this.getPlayers().get(id); if (!allowOfflinePlayers) {
return player;
if (!allowOfflinePlayers) { }
return player;
} // Check database if character isnt here
if (player == null) {
// Check database if character isnt here player = DatabaseHelper.getPlayerByUid(id);
if (player == null) { }
player = DatabaseHelper.getPlayerByUid(id);
} return player;
}
return player;
} public Player getPlayerByAccountId(String accountId) {
Optional<Player> playerOpt =
public Player getPlayerByAccountId(String accountId) { getPlayers().values().stream()
Optional<Player> playerOpt = .filter(player -> player.getAccount().getId().equals(accountId))
getPlayers().values().stream() .findFirst();
.filter(player -> player.getAccount().getId().equals(accountId)) return playerOpt.orElse(null);
.findFirst(); }
return playerOpt.orElse(null);
} public SocialDetail.Builder getSocialDetailByUid(int id) {
// Get from online players
public SocialDetail.Builder getSocialDetailByUid(int id) { Player player = this.getPlayerByUid(id, true);
// Get from online players
Player player = this.getPlayerByUid(id, true); if (player == null) {
return null;
if (player == null) { }
return null;
} return player.getSocialDetail();
}
return player.getSocialDetail();
} public Account getAccountByName(String username) {
Optional<Player> playerOpt =
public Account getAccountByName(String username) { getPlayers().values().stream()
Optional<Player> playerOpt = .filter(player -> player.getAccount().getUsername().equals(username))
getPlayers().values().stream() .findFirst();
.filter(player -> player.getAccount().getUsername().equals(username)) if (playerOpt.isPresent()) {
.findFirst(); return playerOpt.get().getAccount();
if (playerOpt.isPresent()) { }
return playerOpt.get().getAccount(); return DatabaseHelper.getAccountByName(username);
} }
return DatabaseHelper.getAccountByName(username);
} public synchronized void onTick() {
var tickStart = Instant.now();
public synchronized void onTick() {
var tickStart = Instant.now(); // Tick worlds.
this.worlds.removeIf(World::onTick);
// Tick worlds.
this.worlds.removeIf(World::onTick); // Tick players.
this.players.values().forEach(Player::onTick);
// Tick players.
this.players.values().forEach(Player::onTick); // Tick scheduler.
this.getScheduler().runTasks();
// Tick scheduler.
this.getScheduler().runTasks(); // Call server tick event.
ServerTickEvent event = new ServerTickEvent(tickStart, Instant.now());
// Call server tick event. event.call();
ServerTickEvent event = new ServerTickEvent(tickStart, Instant.now()); }
event.call();
} public void registerWorld(World world) {
this.getWorlds().add(world);
public void registerWorld(World world) { }
this.getWorlds().add(world);
} public void deregisterWorld(World world) {
// TODO Auto-generated method stub
public void deregisterWorld(World world) {
// TODO Auto-generated method stub }
} public void start() {
// Schedule game loop.
public void start() { Timer gameLoop = new Timer();
// Schedule game loop. gameLoop.scheduleAtFixedRate(
Timer gameLoop = new Timer(); new TimerTask() {
gameLoop.scheduleAtFixedRate( @Override
new TimerTask() { public void run() {
@Override try {
public void run() { onTick();
try { } catch (Exception e) {
onTick(); Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e);
} catch (Exception e) { }
Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e); }
} },
} new Date(),
}, 1000L);
new Date(), Grasscutter.getLogger().info(translate("messages.status.free_software"));
1000L); Grasscutter.getLogger()
Grasscutter.getLogger().info(translate("messages.status.free_software")); .info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort()));
Grasscutter.getLogger() ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
.info(translate("messages.game.address_bind", GAME_INFO.accessAddress, address.getPort())); event.call();
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); }
event.call();
} public void onServerShutdown() {
ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
public void onServerShutdown() { event.call();
ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now());
event.call(); // Kick and save all players
List<Player> list = new ArrayList<>(this.getPlayers().size());
// Kick and save all players list.addAll(this.getPlayers().values());
List<Player> list = new ArrayList<>(this.getPlayers().size());
list.addAll(this.getPlayers().values()); for (Player player : list) {
player.getSession().close();
for (Player player : list) { }
player.getSession().close(); }
} }
}
}

View File

@ -1,41 +1,26 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GameData; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.data.excels.QuestData.QuestCondition; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass; import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; @Opcodes(PacketOpcodes.AddQuestContentProgressReq)
import java.util.List; public class HandlerAddQuestContentProgressReq extends PacketHandler {
import java.util.stream.Stream; @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
@Opcodes(PacketOpcodes.AddQuestContentProgressReq) var req = AddQuestContentProgressReq.parseFrom(payload);
public class HandlerAddQuestContentProgressReq extends PacketHandler {
// Find all conditions in quest that are the same as the given one
@Override var type = QuestContent.getContentTriggerByValue(req.getContentType());
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { if(type != null) {
var req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload); session.getPlayer().getQuestManager().queueEvent(type, req.getParam());
// Find all conditions in quest that are the same as the given one }
Stream<QuestCondition> finishCond =
GameData.getQuestDataMap().get(req.getParam()).getFinishCond().stream(); session.send(new PacketAddQuestContentProgressRsp(req.getContentType()));
Stream<QuestCondition> acceptCond = }
GameData.getQuestDataMap().get(req.getParam()).getAcceptCond().stream();
Stream<QuestCondition> failCond = }
GameData.getQuestDataMap().get(req.getParam()).getFailCond().stream();
List<QuestCondition> allCondMatch =
Stream.concat(Stream.concat(acceptCond, failCond), finishCond)
.filter(p -> p.getType().getValue() == req.getContentType())
.toList();
for (QuestCondition cond : allCondMatch) {
session
.getPlayer()
.getQuestManager()
.triggerEvent(
QuestTrigger.getContentTriggerByValue(req.getContentType()), cond.getParam());
}
session.send(new PacketAddQuestContentProgressRsp(req.getContentType()));
}
}

View File

@ -1,69 +1,39 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.GameConstants; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameData; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.data.excels.world.WorldAreaData; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp;
import emu.grasscutter.net.packet.PacketOpcodes; import lombok.val;
import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; /**
import emu.grasscutter.server.game.GameSession; * Changes the currently active avatars Element if possible
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify; */
import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp; @Opcodes(PacketOpcodes.AvatarChangeElementTypeReq)
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify; public class HandlerAvatarChangeElementTypeReq extends PacketHandler {
import emu.grasscutter.server.packet.send.PacketAvatarSkillDepotChangeNotify;
@Override
@Opcodes(PacketOpcodes.AvatarChangeElementTypeReq) public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public class HandlerAvatarChangeElementTypeReq extends PacketHandler { var req = AvatarChangeElementTypeReq.parseFrom(payload);
var area = GameData.getWorldAreaDataMap().get(req.getAreaId());
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { if (area == null || area.getElementType() == null || area.getElementType().getDepotIndex() <= 0) {
AvatarChangeElementTypeReq req = AvatarChangeElementTypeReq.parseFrom(payload); session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
WorldAreaData area = GameData.getWorldAreaDataMap().get(req.getAreaId()); }
if (area == null val avatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar();
|| area.getElementType() == null if (!avatar.changeElement(area.getElementType())) {
|| area.getElementType().getDepotValue() <= 0) { session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); return;
return; }
}
// Success
// Get current avatar, should be one of the main characters session.send(new PacketAvatarChangeElementTypeRsp());
EntityAvatar mainCharacterEntity = }
session.getPlayer().getTeamManager().getCurrentAvatarEntity();
Avatar mainCharacter = mainCharacterEntity.getAvatar(); }
int skillDepotId = area.getElementType().getDepotValue();
switch (mainCharacter.getAvatarId()) {
case GameConstants.MAIN_CHARACTER_MALE -> skillDepotId += 500;
case GameConstants.MAIN_CHARACTER_FEMALE -> skillDepotId += 700;
default -> {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
}
}
// Sanity checks for skill depots
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(skillDepotId);
if (skillDepot == null || skillDepot.getId() == mainCharacter.getSkillDepotId()) {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return;
}
// Set skill depot
mainCharacter.setSkillDepotData(skillDepot);
// Success
session.send(new PacketAvatarChangeElementTypeRsp());
// Ability change packet
session.send(new PacketAvatarSkillDepotChangeNotify(mainCharacter));
session.send(new PacketAbilityChangeNotify(mainCharacterEntity));
session.send(new PacketAvatarFightPropNotify(mainCharacter));
}
}

View File

@ -1,191 +1,191 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo; import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass;
import emu.grasscutter.server.event.entity.EntityMoveEvent; import emu.grasscutter.server.event.entity.EntityMoveEvent;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
@Opcodes(PacketOpcodes.CombatInvocationsNotify) @Opcodes(PacketOpcodes.CombatInvocationsNotify)
public class HandlerCombatInvocationsNotify extends PacketHandler { public class HandlerCombatInvocationsNotify extends PacketHandler {
private float cachedLandingSpeed = 0; private float cachedLandingSpeed = 0;
private long cachedLandingTimeMillisecond = 0; private long cachedLandingTimeMillisecond = 0;
private boolean monitorLandingEvent = false; private boolean monitorLandingEvent = false;
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload);
for (CombatInvokeEntry entry : notif.getInvokeListList()) { for (CombatInvokeEntry entry : notif.getInvokeListList()) {
// Handle combat invoke // Handle combat invoke
switch (entry.getArgumentType()) { switch (entry.getArgumentType()) {
case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> { case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> {
EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData());
AttackResult attackResult = hitInfo.getAttackResult(); AttackResult attackResult = hitInfo.getAttackResult();
Player player = session.getPlayer(); Player player = session.getPlayer();
// Check if the player is invulnerable. // Check if the player is invulnerable.
if (attackResult.getAttackerId() if (attackResult.getAttackerId()
!= player.getTeamManager().getCurrentAvatarEntity().getId() != player.getTeamManager().getCurrentAvatarEntity().getId()
&& player.getAbilityManager().isAbilityInvulnerable()) break; && player.getAbilityManager().isAbilityInvulnerable()) break;
// Handle damage // Handle damage
player.getAttackResults().add(attackResult); player.getAttackResults().add(attackResult);
player.getEnergyManager().handleAttackHit(hitInfo); player.getEnergyManager().handleAttackHit(hitInfo);
} }
case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> { case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> {
// Handle movement // Handle movement
EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData());
GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId());
if (entity != null) { if (entity != null) {
// Move player // Move player
MotionInfo motionInfo = moveInfo.getMotionInfo(); MotionInfo motionInfo = moveInfo.getMotionInfo();
MotionState motionState = motionInfo.getState(); MotionState motionState = motionInfo.getState();
// Call entity move event. // Call entity move event.
EntityMoveEvent event = EntityMoveEvent event =
new EntityMoveEvent( new EntityMoveEvent(
entity, entity,
new Position(motionInfo.getPos()), new Position(motionInfo.getPos()),
new Position(motionInfo.getRot()), new Position(motionInfo.getRot()),
motionState); motionState);
event.call(); event.call();
entity.move(event.getPosition(), event.getRotation()); entity.move(event.getPosition(), event.getRotation());
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
entity.setMotionState(motionState); entity.setMotionState(motionState);
session session
.getPlayer() .getPlayer()
.getStaminaManager() .getStaminaManager()
.handleCombatInvocationsNotify(session, moveInfo, entity); .handleCombatInvocationsNotify(session, moveInfo, entity);
// TODO: handle MOTION_FIGHT landing which has a different damage factor // TODO: handle MOTION_FIGHT landing which has a different damage factor
// Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful.
// May need the height when starting plunge attack. // May need the height when starting plunge attack.
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets.
// Cache land speed for later use. // Cache land speed for later use.
if (motionState == MotionState.MOTION_STATE_LAND_SPEED) { if (motionState == MotionState.MOTION_STATE_LAND_SPEED) {
cachedLandingSpeed = motionInfo.getSpeed().getY(); cachedLandingSpeed = motionInfo.getSpeed().getY();
cachedLandingTimeMillisecond = System.currentTimeMillis(); cachedLandingTimeMillisecond = System.currentTimeMillis();
monitorLandingEvent = true; monitorLandingEvent = true;
} }
if (monitorLandingEvent) { if (monitorLandingEvent) {
if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) { if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) {
monitorLandingEvent = false; monitorLandingEvent = false;
handleFallOnGround(session, entity, motionState); handleFallOnGround(session, entity, motionState);
} }
} }
// MOTION_STATE_NOTIFY = Dont send to other players // MOTION_STATE_NOTIFY = Dont send to other players
if (motionState == MotionState.MOTION_STATE_NOTIFY) { if (motionState == MotionState.MOTION_STATE_NOTIFY) {
continue; continue;
} }
} }
} }
case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> { case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> {
EvtAnimatorParameterInfo paramInfo = EvtAnimatorParameterInfo paramInfo =
EvtAnimatorParameterInfo.parseFrom(entry.getCombatData()); EvtAnimatorParameterInfo.parseFrom(entry.getCombatData());
if (paramInfo.getIsServerCache()) { if (paramInfo.getIsServerCache()) {
paramInfo = paramInfo.toBuilder().setIsServerCache(false).build(); paramInfo = paramInfo.toBuilder().setIsServerCache(false).build();
entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build(); entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build();
} }
} }
default -> {} default -> {}
} }
session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry);
} }
} }
private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) {
if (session.getPlayer().inGodmode()) { if (session.getPlayer().isInGodMode()) {
return; return;
} }
// People have reported that after plunge attack (client sends a FIGHT instead of // People have reported that after plunge attack (client sends a FIGHT instead of
// FALL_ON_GROUND) they will die // FALL_ON_GROUND) they will die
// if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping
// again. // again.
// A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet.
// 200ms seems to be a reasonable delay. // 200ms seems to be a reasonable delay.
int maxDelay = 200; int maxDelay = 200;
long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond;
Grasscutter.getLogger() Grasscutter.getLogger()
.trace( .trace(
"MOTION_FALL_ON_GROUND received after " "MOTION_FALL_ON_GROUND received after "
+ actualDelay + actualDelay
+ "/" + "/"
+ maxDelay + maxDelay
+ "ms." + "ms."
+ (actualDelay > maxDelay ? " Discard" : "")); + (actualDelay > maxDelay ? " Discard" : ""));
if (actualDelay > maxDelay) { if (actualDelay > maxDelay) {
return; return;
} }
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float damageFactor = 0; float damageFactor = 0;
if (cachedLandingSpeed < -23.5) { if (cachedLandingSpeed < -23.5) {
damageFactor = 0.33f; damageFactor = 0.33f;
} }
if (cachedLandingSpeed < -25) { if (cachedLandingSpeed < -25) {
damageFactor = 0.5f; damageFactor = 0.5f;
} }
if (cachedLandingSpeed < -26.5) { if (cachedLandingSpeed < -26.5) {
damageFactor = 0.66f; damageFactor = 0.66f;
} }
if (cachedLandingSpeed < -28) { if (cachedLandingSpeed < -28) {
damageFactor = 1f; damageFactor = 1f;
} }
float damage = maxHP * damageFactor; float damage = maxHP * damageFactor;
float newHP = currentHP - damage; float newHP = currentHP - damage;
if (newHP < 0) { if (newHP < 0) {
newHP = 0; newHP = 0;
} }
if (damageFactor > 0) { if (damageFactor > 0) {
Grasscutter.getLogger() Grasscutter.getLogger()
.debug( .debug(
currentHP currentHP
+ "/" + "/"
+ maxHP + maxHP
+ "\tLandingSpeed: " + "\tLandingSpeed: "
+ cachedLandingSpeed + cachedLandingSpeed
+ "\tDamageFactor: " + "\tDamageFactor: "
+ damageFactor + damageFactor
+ "\tDamage: " + "\tDamage: "
+ damage + damage
+ "\tNewHP: " + "\tNewHP: "
+ newHP); + newHP);
} else { } else {
Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage"); Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage");
} }
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
entity entity
.getWorld() .getWorld()
.broadcastPacket( .broadcastPacket(
new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
if (newHP == 0) { if (newHP == 0) {
session session
.getPlayer() .getPlayer()
.getStaminaManager() .getStaminaManager()
.killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL); .killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL);
} }
cachedLandingSpeed = 0; cachedLandingSpeed = 0;
} }
} }

View File

@ -1,28 +1,29 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify;
import emu.grasscutter.server.game.GameSession;
@Opcodes(PacketOpcodes.EvtDoSkillSuccNotify)
public class HandlerEvtDoSkillSuccNotify extends PacketHandler { @Opcodes(PacketOpcodes.EvtDoSkillSuccNotify)
public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { @Override
EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload);
var player = session.getPlayer();
int skillId = notify.getSkillId(); var player = session.getPlayer();
int casterId = notify.getCasterId(); int skillId = notify.getSkillId();
int casterId = notify.getCasterId();
// Call skill perform in the player's ability manager.
player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId); // Call skill perform in the player's ability manager.
player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId);
// Handle skill notify in other managers.
player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); // Handle skill notify in other managers.
player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
player.getQuestManager().triggerEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0); player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
} player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0);
} }
}

View File

@ -1,29 +1,29 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ExecuteGadgetLuaReqOuterClass.ExecuteGadgetLuaReq; import emu.grasscutter.net.proto.ExecuteGadgetLuaReqOuterClass.ExecuteGadgetLuaReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketExecuteGadgetLuaRsp; import emu.grasscutter.server.packet.send.PacketExecuteGadgetLuaRsp;
@Opcodes(PacketOpcodes.ExecuteGadgetLuaReq) @Opcodes(PacketOpcodes.ExecuteGadgetLuaReq)
public class HandlerExecuteGadgetLuaReq extends PacketHandler { public class HandlerExecuteGadgetLuaReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
ExecuteGadgetLuaReq req = ExecuteGadgetLuaReq.parseFrom(payload); ExecuteGadgetLuaReq req = ExecuteGadgetLuaReq.parseFrom(payload);
Player player = session.getPlayer(); Player player = session.getPlayer();
GameEntity entity = player.getScene().getEntities().get(req.getSourceEntityId()); GameEntity entity = player.getScene().getEntities().get(req.getSourceEntityId());
int result = 1; int result = 1;
if (entity instanceof EntityGadget gadget) if (entity instanceof EntityGadget gadget)
result = gadget.onClientExecuteRequest(req.getParam1(), req.getParam2(), req.getParam3()); result = gadget.onClientExecuteRequest(req.getParam1(), req.getParam2(), req.getParam3());
player.sendPacket(new PacketExecuteGadgetLuaRsp(result)); player.sendPacket(new PacketExecuteGadgetLuaRsp(result));
} }
} }

View File

@ -1,22 +1,23 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.server.game.GameSession;
@Opcodes(PacketOpcodes.GadgetInteractReq)
public class HandlerGadgetInteractReq extends PacketHandler { @Opcodes(PacketOpcodes.GadgetInteractReq)
public class HandlerGadgetInteractReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { @Override
GadgetInteractReq req = GadgetInteractReq.parseFrom(payload); public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
GadgetInteractReq req = GadgetInteractReq.parseFrom(payload);
session
.getPlayer() session
.getQuestManager() .getPlayer()
.triggerEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId()); .getQuestManager()
session.getPlayer().interactWith(req.getGadgetEntityId(), req); .queueEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId());
} session.getPlayer().interactWith(req.getGadgetEntityId(), req);
} }
}

View File

@ -1,67 +1,66 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler; import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler;
import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData; import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData;
import emu.grasscutter.game.props.ActivityType; import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass; import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass.MusicGameSettleReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp; import emu.grasscutter.server.packet.send.PacketActivityInfoNotify;
import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp;
@Opcodes(PacketOpcodes.MusicGameSettleReq) import lombok.val;
public class HandlerMusicGameSettleReq extends PacketHandler {
@Opcodes(PacketOpcodes.MusicGameSettleReq)
@Override public class HandlerMusicGameSettleReq extends PacketHandler {
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = MusicGameSettleReqOuterClass.MusicGameSettleReq.parseFrom(payload); @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var playerData = val req = MusicGameSettleReq.parseFrom(payload);
session
.getPlayer() val activityManager = session.getPlayer().getActivityManager();
.getActivityManager()
.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); val playerDataOpt = activityManager.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME);
if (playerData.isEmpty()) { if (playerDataOpt.isEmpty()) {
return; session.send(new PacketMusicGameSettleRsp(RetcodeOuterClass.Retcode.RET_MUSIC_GAME_LEVEL_CONFIG_NOT_FOUND, req));
} return;
var handler = (MusicGameActivityHandler) playerData.get().getActivityHandler(); }
boolean isNewRecord = false;
// check if custom beatmap val playerData = playerDataOpt.get();
val handler = (MusicGameActivityHandler) playerData.getActivityHandler();
if (req.getUgcGuid() == 0) { boolean isNewRecord = false;
session
.getPlayer() // check if custom beatmap
.getActivityManager() if (req.getUgcGuid() == 0) {
.triggerWatcher( session.getPlayer().getActivityManager().triggerWatcher(
WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE, WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE,
String.valueOf(req.getMusicBasicId()), String.valueOf(req.getMusicBasicId()),
String.valueOf(req.getScore())); String.valueOf(req.getScore())
);
isNewRecord =
handler.setMusicGameRecord( isNewRecord = handler.setMusicGameRecord(playerData,
playerData.get(), MusicGamePlayerData.MusicGameRecord.of()
MusicGamePlayerData.MusicGameRecord.of() .musicId(req.getMusicBasicId())
.musicId(req.getMusicBasicId()) .maxCombo(req.getMaxCombo())
.maxCombo(req.getMaxCombo()) .maxScore(req.getScore())
.maxScore(req.getScore()) .build());
.build());
// update activity info
// update activity info session.send(new PacketActivityInfoNotify(handler.toProto(playerData, activityManager.getConditionExecutor())));
session.send(new PacketActivityInfoNotify(handler.toProto(playerData.get()))); } else {
} else { handler.setMusicGameCustomBeatmapRecord(playerData,
handler.setMusicGameCustomBeatmapRecord( MusicGamePlayerData.CustomBeatmapRecord.of()
playerData.get(), .musicShareId(req.getUgcGuid())
MusicGamePlayerData.CustomBeatmapRecord.of() .score(req.getMaxCombo())
.musicShareId(req.getUgcGuid()) .settle(req.getIsSaveScore())
.score(req.getMaxCombo()) .build());
// .settle(req.getSuccess()) }
.build());
}
session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord));
session.send( }
new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord));
} }
}

View File

@ -1,66 +1,67 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.MainQuestData; import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.binout.MainQuestData.TalkData; import emu.grasscutter.data.binout.MainQuestData.TalkData;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq;
import emu.grasscutter.server.packet.send.PacketNpcTalkRsp; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketNpcTalkRsp;
@Opcodes(PacketOpcodes.NpcTalkReq)
public class HandlerNpcTalkReq extends PacketHandler { @Opcodes(PacketOpcodes.NpcTalkReq)
@Override public class HandlerNpcTalkReq extends PacketHandler {
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { @Override
NpcTalkReq req = NpcTalkReq.parseFrom(payload); public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
NpcTalkReq req = NpcTalkReq.parseFrom(payload);
// Check if mainQuest exists
// remove last 2 digits to get a mainQuestId // Check if mainQuest exists
int talkId = req.getTalkId(); // remove last 2 digits to get a mainQuestId
int mainQuestId = talkId / 100; int talkId = req.getTalkId();
MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId); int mainQuestId = talkId / 100;
MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId);
if (mainQuestData != null) {
// This talk is associated with a quest. Handle it. if (mainQuestData != null) {
// If the quest has no talk data defined on it, create one. // This talk is associated with a quest. Handle it.
TalkData talkForQuest = new TalkData(talkId, ""); // If the quest has no talk data defined on it, create one.
if (mainQuestData.getTalks() != null) { TalkData talkForQuest = new TalkData(talkId, "");
var talks = mainQuestData.getTalks().stream().filter(p -> p.getId() == talkId).toList(); if (mainQuestData.getTalks() != null) {
var talks = mainQuestData.getTalks().stream().filter(p -> p.getId() == talkId).toList();
if (talks.size() > 0) {
talkForQuest = talks.get(0); if (talks.size() > 0) {
} talkForQuest = talks.get(0);
} }
}
// Add to the list of done talks for this quest.
var mainQuest = session.getPlayer().getQuestManager().getMainQuestById(mainQuestId); // Add to the list of done talks for this quest.
if (mainQuest != null) { var mainQuest = session.getPlayer().getQuestManager().getMainQuestById(mainQuestId);
session if (mainQuest != null) {
.getPlayer() session
.getQuestManager() .getPlayer()
.getMainQuestById(mainQuestId) .getQuestManager()
.getTalks() .getMainQuestById(mainQuestId)
.put(talkId, talkForQuest); .getTalks()
} .put(talkId, talkForQuest);
}
// Fire quest triggers.
session // Fire quest triggers.
.getPlayer() session
.getQuestManager() .getPlayer()
.triggerEvent( .getQuestManager()
QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0); .queueEvent(
session QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0);
.getPlayer() session
.getQuestManager() .getPlayer()
.triggerEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0); .getQuestManager()
session .queueEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0);
.getPlayer() session
.getQuestManager() .getPlayer()
.triggerEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(), 0); .getQuestManager()
} .queueEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(), 0);
}
session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId()));
} session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId()));
} }
}

View File

@ -1,24 +1,25 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp;
@Opcodes(PacketOpcodes.PostEnterSceneReq)
public class HandlerPostEnterSceneReq extends PacketHandler { @Opcodes(PacketOpcodes.PostEnterSceneReq)
public class HandlerPostEnterSceneReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { @Override
if (session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM) { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
session if (session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM) {
.getPlayer() session
.getQuestManager() .getPlayer()
.triggerEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0); .getQuestManager()
} .queueEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0);
}
session.send(new PacketPostEnterSceneRsp(session.getPlayer()));
} session.send(new PacketPostEnterSceneRsp(session.getPlayer()));
} }
}

View File

@ -1,40 +1,39 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq; import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp;
@Opcodes(PacketOpcodes.SelectWorktopOptionReq)
public class HandlerSelectWorktopOptionReq extends PacketHandler { @Opcodes(PacketOpcodes.SelectWorktopOptionReq)
public class HandlerSelectWorktopOptionReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { @Override
SelectWorktopOptionReq req = SelectWorktopOptionReq.parseFrom(payload); public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
SelectWorktopOptionReq req = SelectWorktopOptionReq.parseFrom(payload);
try {
GameEntity entity = session.getPlayer().getScene().getEntityById(req.getGadgetEntityId()); try {
GameEntity entity = session.getPlayer().getScene().getEntityById(req.getGadgetEntityId());
if (!(entity instanceof EntityGadget)) {
return; if (!(entity instanceof EntityGadget)) {
} return;
session.getPlayer().getScene().selectWorktopOptionWith(req); }
session session.getPlayer().getScene().selectWorktopOptionWith(req);
.getPlayer() session.getPlayer().getScene().getScriptManager().callEvent(
.getScene() new ScriptArgs(entity.getGroupId(), EventType.EVENT_SELECT_OPTION, entity.getConfigId(), req.getOptionId())
.getScriptManager() );
.callEvent( session.getPlayer().getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_WORKTOP_SELECT, entity.getConfigId(), req.getOptionId());
EventType.EVENT_SELECT_OPTION, } finally {
new ScriptArgs(entity.getConfigId(), req.getOptionId())); // Always send packet
} finally { session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId()));
// Always send packet }
session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); }
}
} }
}

View File

@ -14,7 +14,7 @@ public class HandlerSkipPlayerGameTimeReq extends PacketHandler {
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = SkipPlayerGameTimeReqOuterClass.SkipPlayerGameTimeReq.parseFrom(payload); var req = SkipPlayerGameTimeReqOuterClass.SkipPlayerGameTimeReq.parseFrom(payload);
var player = session.getPlayer(); var player = session.getPlayer();
player.getScene().setTime(req.getGameTime()); player.updatePlayerGameTime(req.getGameTime());
player.getScene().broadcastPacket(new PacketPlayerGameTimeNotify(player)); player.getScene().broadcastPacket(new PacketPlayerGameTimeNotify(player));
player.sendPacket(new PacketSkipPlayerGameTimeRsp(req)); player.sendPacket(new PacketSkipPlayerGameTimeRsp(req));
} }

View File

@ -1,34 +1,27 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.entity.platform.EntityPlatform; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.game.entity.gadget.platform.AbilityRoute;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.UpdateAbilityCreatedMovingPlatformNotifyOuterClass; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.net.proto.UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify;
@Opcodes(PacketOpcodes.UpdateAbilityCreatedMovingPlatformNotify)
@Opcodes(PacketOpcodes.UpdateAbilityCreatedMovingPlatformNotify) public class HandlerUpdateAbilityCreatedMovingPlatformNotify extends PacketHandler {
public class HandlerUpdateAbilityCreatedMovingPlatformNotify extends PacketHandler { @Override
@Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { var notify = UpdateAbilityCreatedMovingPlatformNotify.parseFrom(payload);
var notify = var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId());
UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify
.parseFrom(payload); if (!(entity instanceof EntityGadget entityGadget) || !(entityGadget.getRouteConfig() instanceof AbilityRoute)) {
var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId()); return;
}
if (!(entity instanceof EntityPlatform)) {
return; switch (notify.getOpType()) {
} case OP_TYPE_ACTIVATE -> entityGadget.startPlatform();
case OP_TYPE_DEACTIVATE -> entityGadget.stopPlatform();
var scene = ((EntityPlatform) entity).getOwner().getScene(); }
}
switch (notify.getOpType()) { }
case OP_TYPE_ACTIVATE -> scene.broadcastPacket(
new PacketPlatformStartRouteNotify((EntityPlatform) entity, scene));
case OP_TYPE_DEACTIVATE -> scene.broadcastPacket(
new PacketPlatformStopRouteNotify((EntityPlatform) entity, scene));
}
}
}

View File

@ -1,18 +1,18 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeGameTimeRspOuterClass.ChangeGameTimeRsp; import emu.grasscutter.net.proto.ChangeGameTimeRspOuterClass.ChangeGameTimeRsp;
public class PacketChangeGameTimeRsp extends BasePacket { public class PacketChangeGameTimeRsp extends BasePacket {
public PacketChangeGameTimeRsp(Player player) { public PacketChangeGameTimeRsp(Player player) {
super(PacketOpcodes.ChangeGameTimeRsp); super(PacketOpcodes.ChangeGameTimeRsp);
ChangeGameTimeRsp proto = ChangeGameTimeRsp proto =
ChangeGameTimeRsp.newBuilder().setCurGameTime(player.getScene().getTime()).build(); ChangeGameTimeRsp.newBuilder().setCurGameTime(player.getWorld().getGameTime()).build();
this.setData(proto); this.setData(proto);
} }
} }

View File

@ -1,64 +1,13 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass.DungeonSettleNotify;
import emu.grasscutter.net.proto.ItemParamOuterClass; public class PacketDungeonSettleNotify extends BasePacket {
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify; public PacketDungeonSettleNotify(BaseDungeonResult result) {
super(PacketOpcodes.DungeonSettleNotify);
public class PacketDungeonSettleNotify extends BasePacket {
this.setData(result.getProto());
public PacketDungeonSettleNotify(WorldChallenge challenge) { }
super(PacketOpcodes.DungeonSettleNotify); }
DungeonSettleNotify proto =
DungeonSettleNotify.newBuilder()
.setDungeonId(challenge.getScene().getDungeonData().getId())
.setIsSuccess(challenge.isSuccess())
.setCloseTime(challenge.getScene().getAutoCloseTime())
.setResult(challenge.isSuccess() ? 1 : 0)
.build();
this.setData(proto);
}
public PacketDungeonSettleNotify(
WorldChallenge challenge, boolean canJump, boolean hasNextLevel, int nextFloorId) {
super(PacketOpcodes.DungeonSettleNotify);
var continueStatus =
TowerLevelEndNotify.ContinueStateType.CONTINUE_STATE_TYPE_CAN_NOT_CONTINUE_VALUE;
if (challenge.isSuccess() && canJump) {
continueStatus =
hasNextLevel
? TowerLevelEndNotify.ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_LEVEL_VALUE
: TowerLevelEndNotify.ContinueStateType
.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_FLOOR_VALUE;
}
var towerLevelEndNotify =
TowerLevelEndNotify.newBuilder()
.setIsSuccess(challenge.isSuccess())
.setContinueState(continueStatus)
.addFinishedStarCondList(1)
.addFinishedStarCondList(2)
.addFinishedStarCondList(3)
.addRewardItemList(
ItemParamOuterClass.ItemParam.newBuilder().setItemId(201).setCount(1000).build());
if (nextFloorId > 0 && canJump) {
towerLevelEndNotify.setNextFloorId(nextFloorId);
}
DungeonSettleNotify proto =
DungeonSettleNotify.newBuilder()
.setDungeonId(challenge.getScene().getDungeonData().getId())
.setIsSuccess(challenge.isSuccess())
.setCloseTime(challenge.getScene().getAutoCloseTime())
.setResult(challenge.isSuccess() ? 1 : 0)
.setTowerLevelEndNotify(towerLevelEndNotify.build())
.build();
this.setData(proto);
}
}

View File

@ -1,22 +1,33 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.EntityFightPropUpdateNotifyOuterClass.EntityFightPropUpdateNotify; import emu.grasscutter.net.proto.EntityFightPropUpdateNotifyOuterClass.EntityFightPropUpdateNotify;
public class PacketEntityFightPropUpdateNotify extends BasePacket { import java.util.Collection;
public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) { public class PacketEntityFightPropUpdateNotify extends BasePacket {
super(PacketOpcodes.EntityFightPropUpdateNotify); public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) {
super(PacketOpcodes.EntityFightPropUpdateNotify);
EntityFightPropUpdateNotify proto =
EntityFightPropUpdateNotify.newBuilder() EntityFightPropUpdateNotify proto =
.setEntityId(entity.getId()) EntityFightPropUpdateNotify.newBuilder()
.putFightPropMap(prop.getId(), entity.getFightProperty(prop)) .setEntityId(entity.getId())
.build(); .putFightPropMap(prop.getId(), entity.getFightProperty(prop))
.build();
this.setData(proto);
} this.setData(proto);
} }
public PacketEntityFightPropUpdateNotify(GameEntity entity, Collection<FightProperty> props) {
super(PacketOpcodes.EntityFightPropUpdateNotify);
var protoBuilder = EntityFightPropUpdateNotify.newBuilder()
.setEntityId(entity.getId());
props.forEach(p -> protoBuilder.putFightPropMap(p.getId(), entity.getFightProperty(p)));
this.setData(protoBuilder);
}
}

View File

@ -1,26 +1,26 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.enums.ParentQuestState; import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify; import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify;
public class PacketFinishedParentQuestNotify extends BasePacket { public class PacketFinishedParentQuestNotify extends BasePacket {
public PacketFinishedParentQuestNotify(Player player) { public PacketFinishedParentQuestNotify(Player player) {
super(PacketOpcodes.FinishedParentQuestNotify, true); super(PacketOpcodes.FinishedParentQuestNotify, true);
FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder(); FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder();
for (GameMainQuest mainQuest : player.getQuestManager().getMainQuests().values()) { for (GameMainQuest mainQuest : player.getQuestManager().getMainQuests().values()) {
// Canceled Quests do not appear in this packet //Canceled Quests do not appear in this packet
if (mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_CANCELED) { if (mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_CANCELED) {
proto.addParentQuestList(mainQuest.toProto()); proto.addParentQuestList(mainQuest.toProto(false));
} }
} }
this.setData(proto); this.setData(proto);
} }
} }

View File

@ -1,31 +1,33 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify;
import java.util.List;
import java.util.List;
public class PacketFinishedParentQuestUpdateNotify extends BasePacket {
public class PacketFinishedParentQuestUpdateNotify extends BasePacket {
public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) {
super(PacketOpcodes.FinishedParentQuestUpdateNotify); public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) {
super(PacketOpcodes.FinishedParentQuestUpdateNotify);
FinishedParentQuestUpdateNotify proto =
FinishedParentQuestUpdateNotify.newBuilder().addParentQuestList(quest.toProto()).build(); FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder()
.addParentQuestList(quest.toProto(true))
this.setData(proto); .build();
}
this.setData(proto);
public PacketFinishedParentQuestUpdateNotify(List<GameMainQuest> quests) { }
super(PacketOpcodes.FinishedParentQuestUpdateNotify);
public PacketFinishedParentQuestUpdateNotify(List<GameMainQuest> quests) {
var proto = FinishedParentQuestUpdateNotify.newBuilder(); super(PacketOpcodes.FinishedParentQuestUpdateNotify);
for (GameMainQuest mainQuest : quests) { var proto = FinishedParentQuestUpdateNotify.newBuilder();
proto.addParentQuestList(mainQuest.toProto());
} for (GameMainQuest mainQuest : quests) {
proto.build(); proto.addParentQuestList(mainQuest.toProto(true));
this.setData(proto); }
} proto.build();
} this.setData(proto);
}
}

View File

@ -1,11 +1,11 @@
package emu.grasscutter.server.packet.send; //package emu.grasscutter.server.packet.send;
//
import emu.grasscutter.net.packet.BasePacket; //import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; //import emu.grasscutter.net.packet.PacketOpcodes;
//
public class PacketHomeUnknown2Rsp extends BasePacket { //public class PacketHomeUnknown2Rsp extends BasePacket {
//
public PacketHomeUnknown2Rsp() { // public PacketHomeUnknown2Rsp() {
super(PacketOpcodes.Unk2700_KIIOGMKFNNP_ServerRsp); // super(PacketOpcodes.Unk2700_KIIOGMKFNNP_ServerRsp);
} // }
} //}

View File

@ -1,18 +1,32 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass; import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass;
import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass;
public class PacketMusicGameSettleRsp extends BasePacket { import emu.grasscutter.net.proto.RetcodeOuterClass;
public PacketMusicGameSettleRsp(int musicBasicId, long musicShareId, boolean isNewRecord) { public class PacketMusicGameSettleRsp extends BasePacket {
super(PacketOpcodes.MusicGameSettleRsp);
public PacketMusicGameSettleRsp(int musicBasicId, long musicShareId, boolean isNewRecord) {
var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder(); super(PacketOpcodes.MusicGameSettleRsp);
proto.setMusicBasicId(musicBasicId).setUgcGuid(musicShareId).setIsNewRecord(isNewRecord); var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder();
this.setData(proto); proto.setMusicBasicId(musicBasicId).setUgcGuid(musicShareId).setIsNewRecord(isNewRecord);
}
} this.setData(proto);
}
public PacketMusicGameSettleRsp(RetcodeOuterClass.Retcode errorCode, MusicGameSettleReqOuterClass.MusicGameSettleReq req) {
super(PacketOpcodes.MusicGameSettleRsp);
var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder()
.setRetcode(errorCode.getNumber())
.setMusicBasicId(req.getMusicBasicId())
.setUgcGuid(req.getUgcGuid());
this.setData(proto);
}
}

View File

@ -1,22 +1,20 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.entity.platform.EntityPlatform; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.PlatformStartRouteNotifyOuterClass.PlatformStartRouteNotify;
import emu.grasscutter.net.proto.PlatformStartRouteNotifyOuterClass; import lombok.val;
public class PacketPlatformStartRouteNotify extends BasePacket { public class PacketPlatformStartRouteNotify extends BasePacket {
public PacketPlatformStartRouteNotify(EntityPlatform entity, Scene scene) { public PacketPlatformStartRouteNotify(EntityGadget gadgetEntity) {
super(PacketOpcodes.PlatformStartRouteNotify); super(PacketOpcodes.PlatformStartRouteNotify);
var notify = val notify = PlatformStartRouteNotify.newBuilder()
PlatformStartRouteNotifyOuterClass.PlatformStartRouteNotify.newBuilder() .setEntityId(gadgetEntity.getId())
.setEntityId(entity.getId()) .setSceneTime(gadgetEntity.getScene().getSceneTime())
.setSceneTime(scene.getSceneTime()) .setPlatform(gadgetEntity.getPlatformInfo());
.setPlatform(entity.onStartRoute())
.build(); this.setData(notify);
}
this.setData(notify); }
}
}

View File

@ -1,22 +1,20 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.entity.platform.EntityPlatform; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.PlatformStopRouteNotifyOuterClass;
import emu.grasscutter.net.proto.PlatformStopRouteNotifyOuterClass;
public class PacketPlatformStopRouteNotify extends BasePacket {
public class PacketPlatformStopRouteNotify extends BasePacket { public PacketPlatformStopRouteNotify(EntityGadget gadgetEntity) {
public PacketPlatformStopRouteNotify(EntityPlatform entity, Scene scene) { super(PacketOpcodes.PlatformStopRouteNotify);
super(PacketOpcodes.PlatformStopRouteNotify);
var notify = PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder()
var notify = .setPlatform(gadgetEntity.getPlatformInfo())
PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder() .setSceneTime(gadgetEntity.getScene().getSceneTime())
.setPlatform(entity.onStopRoute()) .setEntityId(gadgetEntity.getId())
.setSceneTime(scene.getSceneTime()) .build();
.setEntityId(entity.getId())
.build(); this.setData(notify);
}
this.setData(notify); }
}
}

View File

@ -1,21 +1,21 @@
package emu.grasscutter.server.packet.send; package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.PlayerGameTimeNotifyOuterClass.PlayerGameTimeNotify; import emu.grasscutter.net.proto.PlayerGameTimeNotifyOuterClass.PlayerGameTimeNotify;
public class PacketPlayerGameTimeNotify extends BasePacket { public class PacketPlayerGameTimeNotify extends BasePacket {
public PacketPlayerGameTimeNotify(Player player) { public PacketPlayerGameTimeNotify(Player player) {
super(PacketOpcodes.PlayerGameTimeNotify); super(PacketOpcodes.PlayerGameTimeNotify);
PlayerGameTimeNotify proto = PlayerGameTimeNotify proto =
PlayerGameTimeNotify.newBuilder() PlayerGameTimeNotify.newBuilder()
.setGameTime(player.getScene().getTime()) .setGameTime(player.getWorld().getGameTime())
.setUid(player.getUid()) .setUid(player.getUid())
.build(); .build();
this.setData(proto); this.setData(proto);
} }
} }

File diff suppressed because it is too large Load Diff