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

@ -117,7 +117,7 @@ public final class EntityCommand implements CommandHandler {
} }
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(

View File

@ -180,9 +180,9 @@ public final class SetPropCommand implements CommandHandler {
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 =
@ -194,7 +194,7 @@ public final class SetPropCommand implements CommandHandler {
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);

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;
} }
} }
} }

View File

@ -48,7 +48,10 @@ public final class GameData {
@Getter @Getter
private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>(); private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>();
@Getter private static final Map<String, ConfigGadget> gadgetConfigData = new HashMap<>(); @Getter private static final Map<String, ConfigEntityAvatar> avatarConfigData = new HashMap<>();
@Getter private static final Map<String, ConfigEntityGadget> gadgetConfigData = new HashMap<>();
@Getter private static final Map<String, ConfigEntityMonster> monsterConfigData = new HashMap<>();
@Getter private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>(); @Getter private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
@ -102,6 +105,8 @@ public final class GameData {
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap =
new Int2ObjectLinkedOpenHashMap<>(); new Int2ObjectLinkedOpenHashMap<>();
@Getter private static final Int2ObjectMap<AvatarReplaceCostumeData> avatarReplaceCostumeDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap =
new Int2ObjectLinkedOpenHashMap<>(); new Int2ObjectLinkedOpenHashMap<>();
@ -221,6 +226,8 @@ public final class GameData {
@Getter @Getter
private static final Int2ObjectMap<GatherData> gatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<GatherData> gatherDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Deprecated // This is to prevent people from using this map. This is for the resource loader only!
private static final Int2ObjectMap<GuideTriggerData> guideTriggerDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap = private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
@ -246,6 +253,8 @@ public final class GameData {
@Getter @Getter
private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap =
new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<MonsterSpecialNameData> monsterSpecialNameDataMap =
new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap = private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap =
@ -403,9 +412,8 @@ public final class GameData {
@Getter private static final List<OpenStateData> openStateList = new ArrayList<>(); @Getter private static final List<OpenStateData> openStateList = new ArrayList<>();
@Getter private static final Map<Integer, List<Integer>> scenePointsPerScene = new HashMap<>(); @Getter private static final Map<Integer, List<Integer>> scenePointsPerScene = new HashMap<>();
@Getter private static final Map<String, ScriptSceneData> scriptSceneDataMap = new HashMap<>(); @Getter private static final Map<String, ScriptSceneData> scriptSceneDataMap = new HashMap<>();
@Getter private static final Map<String, GuideTriggerData> guideTriggerDataStringMap = new HashMap<>();
@Getter @Getter private static final Map<String, ConfigLevelEntity> configLevelEntityDataMap = new HashMap<>();
private static final Map<String, ConfigLevelEntity> configLevelEntityDataMap = new HashMap<>();
@Getter @Getter
private static final Int2ObjectMap<IntSet> proudSkillGroupLevels = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<IntSet> proudSkillGroupLevels = new Int2ObjectOpenHashMap<>();

View File

@ -8,6 +8,10 @@ import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.*;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.config.ConfigEntityAvatar;
import emu.grasscutter.data.binout.config.ConfigEntityBase;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.binout.config.ConfigEntityMonster;
import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.PointData;
import emu.grasscutter.game.managers.blossom.BlossomConfig; import emu.grasscutter.game.managers.blossom.BlossomConfig;
import emu.grasscutter.game.quest.QuestEncryptionKey; import emu.grasscutter.game.quest.QuestEncryptionKey;
@ -85,6 +89,7 @@ public class ResourceLoader {
if (loadedAll) return; if (loadedAll) return;
Grasscutter.getLogger().info(translate("messages.status.resources.loading")); Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
loadConfigData();
// Load ability lists // Load ability lists
loadAbilityEmbryos(); loadAbilityEmbryos();
loadOpenConfig(); loadOpenConfig();
@ -94,7 +99,6 @@ public class ResourceLoader {
// Process into depots // Process into depots
GameDepot.load(); GameDepot.load();
// Load spawn data and quests // Load spawn data and quests
loadGadgetConfigData();
loadSpawnData(); loadSpawnData();
loadQuests(); loadQuests();
loadScriptSceneData(); loadScriptSceneData();
@ -575,24 +579,44 @@ public class ResourceLoader {
} }
} }
private static void loadGadgetConfigData() { private static void loadConfigData(){
try { loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class);
Files.newDirectoryStream(getResourcePath("BinOutput/Gadget/"), "*.json") loadConfigData(GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class);
.forEach( loadConfigDataMap(GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class);
path -> { }
try {
GameData.getGadgetConfigData()
.putAll(JsonUtils.loadToMap(path, String.class, ConfigGadget.class));
} catch (Exception e) {
Grasscutter.getLogger()
.error("failed to load ConfigGadget entries for " + path.toString(), e);
}
});
Grasscutter.getLogger() private static <T extends ConfigEntityBase> void loadConfigData(Map<String,T> targetMap, String folderPath, Class<T> configClass) {
.debug("Loaded {} ConfigGadget entries.", GameData.getGadgetConfigData().size()); val className = configClass.getName();
try(val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) {
stream.forEach(path -> {
try {
val name = path.getFileName().toString().replace(".json", "");
targetMap.put(name, JsonUtils.loadToClass(path, configClass));
} catch (Exception e) {
Grasscutter.getLogger().error("failed to load {} entries for {}", className, path.toString(), e);
}
});
Grasscutter.getLogger().debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className);
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().error("Failed to load ConfigGadget folder."); Grasscutter.getLogger().error("Failed to load {} folder.", className);
}
}
private static <T extends ConfigEntityBase> void loadConfigDataMap(Map<String,T> targetMap, String folderPath, Class<T> configClass) {
val className = configClass.getName();
try(val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) {
stream.forEach(path -> {
try {
targetMap.putAll(JsonUtils.loadToMap(path, String.class, configClass));
} catch (Exception e) {
Grasscutter.getLogger().error("failed to load {} entries for {}", className, path.toString(), e);
}
});
Grasscutter.getLogger().debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className);
} catch (IOException e) {
Grasscutter.getLogger().error("Failed to load {} folder.", className);
} }
} }

View File

@ -1,6 +1,7 @@
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.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.dungeon.DailyDungeonData; import emu.grasscutter.data.excels.dungeon.DailyDungeonData;
@ -10,29 +11,25 @@ import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class PointData { public final class PointData {
@Getter @Setter private int id; @Getter @Setter private int id;
private String $type; private String $type;
@Getter private Position tranPos; @Getter private Position tranPos;
@Getter private Position pos;
@Getter private Position rot;
@Getter private Position size;
@SerializedName( @SerializedName(value="dungeonIds", alternate={"JHHFPGJNMIN"})
value = "dungeonIds", @Getter private int[] dungeonIds;
alternate = {"JHHFPGJNMIN"})
@Getter
private int[] dungeonIds;
@SerializedName( @SerializedName(value="dungeonRandomList", alternate={"OIBKFJNBLHO"})
value = "dungeonRandomList", @Getter private int[] dungeonRandomList;
alternate = {"OIBKFJNBLHO"})
@Getter
private int[] dungeonRandomList;
@SerializedName( @SerializedName(value="groupIDs", alternate={"HFOBOOHKBGF"})
value = "tranSceneId", @Getter private int[] groupIDs;
alternate = {"JHBICGBAPIH"})
@Getter @SerializedName(value="tranSceneId", alternate={"JHBICGBAPIH"})
@Setter @Getter @Setter private int tranSceneId;
private int tranSceneId;
public String getType() { public String getType() {
return $type; return $type;

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,6 +1,11 @@
package emu.grasscutter.data.excels.monster; package emu.grasscutter.data.excels.monster;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
@ -9,26 +14,12 @@ import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.excels.GadgetData;
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 java.util.List;
import java.util.Set;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW) @ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW)
@Getter @Getter
public class MonsterData extends GameResource { public class MonsterData extends GameResource {
public static Set<FightProperty> definedFightProperties = 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);
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);
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
@ -51,10 +42,8 @@ public class MonsterData extends GameResource {
@SerializedName("hpBase") @SerializedName("hpBase")
private float baseHp; private float baseHp;
@SerializedName("attackBase") @SerializedName("attackBase")
private float baseAttack; private float baseAttack;
@SerializedName("defenseBase") @SerializedName("defenseBase")
private float baseDefense; private float baseDefense;
@ -74,6 +63,38 @@ public class MonsterData extends GameResource {
private int weaponId; private int weaponId;
private MonsterDescribeData describeData; private MonsterDescribeData describeData;
private int specialNameId; // will only be set if describe data is available
@Override
public void onLoad() {
for (int id : this.equips) {
if (id == 0) {
continue;
}
GadgetData gadget = GameData.getGadgetDataMap().get(id);
if (gadget == null) {
continue;
}
if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) {
this.weaponId = id;
}
}
this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId());
if (this.describeData == null){
return;
}
for(Entry<Integer, MonsterSpecialNameData> entry: GameData.getMonsterSpecialNameDataMap().entrySet()) {
if (entry.getValue().getSpecialNameLabId() == this.getDescribeData().getSpecialNameLabId()){
this.specialNameId = entry.getKey();
break;
}
}
}
public float getFightProperty(FightProperty prop) { public float getFightProperty(FightProperty prop) {
return switch (prop) { return switch (prop) {
case FIGHT_PROP_BASE_HP -> this.baseHp; case FIGHT_PROP_BASE_HP -> this.baseHp;
@ -91,24 +112,6 @@ public class MonsterData extends GameResource {
}; };
} }
@Override
public void onLoad() {
this.describeData = GameData.getMonsterDescribeDataMap().get(this.getDescribeId());
for (int id : this.equips) {
if (id == 0) {
continue;
}
GadgetData gadget = GameData.getGadgetDataMap().get(id);
if (gadget == null) {
continue;
}
if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) {
this.weaponId = id;
}
}
}
@Getter @Getter
public class HpDrops { public class HpDrops {
private int DropId; private int DropId;

View File

@ -1,5 +1,6 @@
package emu.grasscutter.data.excels.monster; package emu.grasscutter.data.excels.monster;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
@ -12,6 +13,9 @@ public class MonsterDescribeData extends GameResource {
private int id; private int id;
private long nameTextMapHash; private long nameTextMapHash;
private int titleID; @SerializedName(value = "titleId", alternate={"titleID"})
private int specialNameLabID; private int titleId;
@SerializedName(value = "specialNameLabId", alternate={"specialNameLabID"})
private int specialNameLabId;
private MonsterSpecialNameData specialNameData;
} }

View File

@ -21,6 +21,8 @@ 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 emu.grasscutter.game.world.SceneGroupInstance;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -449,4 +451,14 @@ public final class DatabaseHelper {
public static void saveAchievementData(Achievements achievements) { public static void saveAchievementData(Achievements achievements) {
DatabaseManager.getGameDatastore().save(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

@ -103,6 +103,13 @@ public class PlayerActivityData {
int curProgress; int curProgress;
boolean isTakenReward; boolean isTakenReward;
/**
* @return True when the progress of this watcher has reached the total progress.
*/
public boolean isFinished(){
return this.curProgress >= this.totalProgress;
}
public static WatcherInfo init(ActivityWatcher watcher) { public static WatcherInfo init(ActivityWatcher watcher) {
return WatcherInfo.of() return WatcherInfo.of()
.watcherId(watcher.getWatcherId()) .watcherId(watcher.getWatcherId())

View File

@ -49,6 +49,8 @@ import lombok.Setter;
import lombok.val; import lombok.val;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import javax.annotation.Nonnull;
@Entity(value = "avatars", useDiscriminator = false) @Entity(value = "avatars", useDiscriminator = false)
public class Avatar { public class Avatar {
@Transient @Getter private final Int2ObjectMap<GameItem> equips; @Transient @Getter private final Int2ObjectMap<GameItem> equips;
@ -234,7 +236,23 @@ public class Avatar {
this.skillDepot = skillDepot; // Used while loading this from the database this.skillDepot = skillDepot; // Used while loading this from the database
} }
/**
* Changes this avatar's skill depot.
* Does not notify the player of the change.
*
* @param skillDepot The new skill depot.
*/
public void setSkillDepotData(AvatarSkillDepotData skillDepot) { public void setSkillDepotData(AvatarSkillDepotData skillDepot) {
this.setSkillDepotData(skillDepot, false);
}
/**
* Changes this avatar's skill depot.
*
* @param skillDepot The new skill depot.
* @param notify Whether to notify the player of the change.
*/
public void setSkillDepotData(AvatarSkillDepotData skillDepot, boolean notify) {
// Set id and depot // Set id and depot
this.skillDepotId = skillDepot.getId(); this.skillDepotId = skillDepot.getId();
this.skillDepot = skillDepot; this.skillDepot = skillDepot;
@ -251,6 +269,38 @@ public class Avatar {
.filter(proudSkillId -> GameData.getProudSkillDataMap().containsKey(proudSkillId)) .filter(proudSkillId -> GameData.getProudSkillDataMap().containsKey(proudSkillId))
.forEach(proudSkillId -> this.proudSkillList.add(proudSkillId)); .forEach(proudSkillId -> this.proudSkillList.add(proudSkillId));
this.recalcStats(); this.recalcStats();
// Send the depot change notification.
if (notify) this.owner.sendPacket(new PacketAvatarSkillDepotChangeNotify(this));
}
/**
* Changes the avatar's element to the target element, if the character has values for it set in the candSkillDepot
*
* @param elementTypeToChange element to change to
* @return false if failed or already using that element, true if it actually changed
*/
public boolean changeElement(@Nonnull ElementType elementTypeToChange) {
var candSkillDepotIdsList = this.avatarData.getCandSkillDepotIds();
var candSkillDepotIndex = elementTypeToChange.getDepotIndex();
// if no candidate skill to change or index out of bound
if (candSkillDepotIdsList == null ||
candSkillDepotIndex >= candSkillDepotIdsList.size()) {
return false;
}
var candSkillDepotId = candSkillDepotIdsList.get(candSkillDepotIndex);
// Sanity checks for skill depots
val skillDepot = GameData.getAvatarSkillDepotDataMap().get(candSkillDepotId);
if (skillDepot == null || skillDepot.getId() == skillDepotId) {
return false;
}
// Set skill depot
setSkillDepotData(skillDepot, true);
return true;
} }
public List<Integer> getFetterList() { public List<Integer> getFetterList() {

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(Scene scene) { public void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) {
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var scene = dungeonManager.getScene();
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge())); var dungeonData = dungeonManager.getDungeonData();
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

@ -24,20 +24,22 @@ 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 java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.val; import lombok.val;
/** import javax.annotation.Nullable;
* TODO handle time limits TODO handle respawn points TODO handle team wipes and respawns TODO check import java.util.*;
* monster level and levelConfigMap import java.util.stream.Collectors;
*/ import java.util.stream.IntStream;
public class DungeonManager {
/**
* TODO handle time limits
* TODO handle respawn points
* TODO handle team wipes and respawns
* TODO check monster level and levelConfigMap
*/
public final class DungeonManager {
@Getter private final Scene scene; @Getter private final Scene scene;
@Getter private final DungeonData dungeonData; @Getter private final DungeonData dungeonData;
@Getter private final DungeonPassConfigData passConfigData; @Getter private final DungeonPassConfigData passConfigData;
@ -69,12 +71,14 @@ public class DungeonManager {
if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) { if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) {
finishedConditions[i] = 1; finishedConditions[i] = 1;
} }
} }
} }
if (isFinishedSuccessfully()) { if (isFinishedSuccessfully()) {
finishDungeon(); finishDungeon();
} }
} }
public boolean isFinishedSuccessfully() { public boolean isFinishedSuccessfully() {
@ -82,7 +86,7 @@ public class DungeonManager {
} }
public int getLevelForMonster(int id) { public int getLevelForMonster(int id) {
// TODO should use levelConfigMap? and how? //TODO should use levelConfigMap? and how?
return dungeonData.getShowLevel(); return dungeonData.getShowLevel();
} }
@ -94,20 +98,19 @@ public class DungeonManager {
return false; return false;
} }
scene.broadcastPacket( scene.broadcastPacket(new PacketDungeonWayPointNotify(activeDungeonWayPoints.add(pointId), activeDungeonWayPoints));
new PacketDungeonWayPointNotify(
activeDungeonWayPoints.add(pointId), activeDungeonWayPoints));
newestWayPoint = pointId; newestWayPoint = pointId;
Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId); Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId);
return true; return true;
} }
@Nullable public Position getRespawnLocation() { @Nullable
public Position getRespawnLocation() {
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return null; return null;
} }
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData(); var pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos(); return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos();
} }
@ -120,9 +123,7 @@ public class DungeonManager {
} }
public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) { public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) {
if (!isFinishedSuccessfully() if (!isFinishedSuccessfully() || dungeonData.getRewardPreviewData() == null || dungeonData.getRewardPreviewData().getPreviewItems().length == 0) {
|| dungeonData.getRewardPreviewData() == null
|| dungeonData.getRewardPreviewData().getPreviewItems().length == 0) {
return false; return false;
} }
@ -131,6 +132,7 @@ public class DungeonManager {
return false; return false;
} }
if (!handleCost(player, useCondensed)) { if (!handleCost(player, useCondensed)) {
return false; return false;
} }
@ -196,10 +198,8 @@ public class DungeonManager {
// Roll items for this group. // Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly. // 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 // For now, we use the following logic: If the possible drop item are a list of multiple items,
// items, // we roll them separately. If not, we stack them. This should work out in practice, at 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));
@ -207,8 +207,7 @@ public class DungeonManager {
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));
} }
} }
@ -216,8 +215,7 @@ public class DungeonManager {
} }
// Otherwise, we fall back to the preview data. // Otherwise, we fall back to the preview data.
else { else {
Grasscutter.getLogger() Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
.info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) { for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
} }
@ -230,46 +228,38 @@ public class DungeonManager {
if (getDungeonData() == null) return; if (getDungeonData() == null) return;
switch (getDungeonData().getType()) { switch (getDungeonData().getType()) {
// case DUNGEON_PLOT is handled by quest execs // case DUNGEON_PLOT is handled by quest execs
case DUNGEON_ACTIVITY -> { case DUNGEON_ACTIVITY -> {
switch (getDungeonData().getPlayType()) { switch (getDungeonData().getPlayType()) {
case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> { case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> {
val activityHandler = val activityHandler = player.getActivityManager()
player .getActivityHandlerAs(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
.getActivityManager() activityHandler.ifPresent(trialAvatarActivityHandler ->
.getActivityHandlerAs( this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
activityHandler.ifPresent(
trialAvatarActivityHandler ->
this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
} }
} }
} }
case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO
} }
if (this.trialTeam != null) { if (this.trialTeam != null) {
player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds); player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds);
} }
} }
public void unsetTrialTeam(Player player) { public void unsetTrialTeam(Player player){
if (this.trialTeam == null) { if (this.trialTeam == null) return;
return;
}
player.getTeamManager().removeTrialAvatar(); player.getTeamManager().removeTrialAvatar();
this.trialTeam = null; this.trialTeam = null;
} }
public void startDungeon() { public void startDungeon() {
this.startSceneTime = scene.getSceneTimeSeconds(); this.startSceneTime = scene.getSceneTimeSeconds();
scene scene.getPlayers().forEach(p -> {
.getPlayers() p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId());
.forEach( applyTrialTeam(p);
p -> { });
p.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId());
applyTrialTeam(p);
});
} }
public void finishDungeon() { public void finishDungeon() {
@ -278,26 +268,18 @@ public class DungeonManager {
} }
public void notifyEndDungeon(boolean successfully) { public void notifyEndDungeon(boolean successfully) {
scene scene.getPlayers().forEach(p -> {
.getPlayers() // Quest trigger
.forEach( p.getQuestManager().queueEvent(successfully ?
p -> { QuestContent.QUEST_CONTENT_FINISH_DUNGEON : QuestContent.QUEST_CONTENT_FAIL_DUNGEON,
// Quest trigger dungeonData.getId());
p.getQuestManager()
.queueEvent(
successfully
? QuestContent.QUEST_CONTENT_FINISH_DUNGEON
: QuestContent.QUEST_CONTENT_FAIL_DUNGEON,
dungeonData.getId());
// Battle pass trigger // Battle pass trigger
if (dungeonData.getType().isCountsToBattlepass() && successfully) { if (dungeonData.getType().isCountsToBattlepass() && successfully) {
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
} }
}); });
scene scene.getScriptManager().callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
.getScriptManager()
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
} }
public void quitDungeon() { public void quitDungeon() {

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

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

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

View File

@ -2,11 +2,14 @@ package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
@ -14,6 +17,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val;
@Getter @Getter
@Setter @Setter
@ -78,26 +82,33 @@ public class WorldChallenge {
} }
public void done() { public void done() {
if (!inProgress()) { if (!this.inProgress()) return;
return; this.finish(true);
}
finish(true);
this.getScene()
.getScriptManager()
.callEvent(
EventType.EVENT_CHALLENGE_SUCCESS,
// TODO record the time in PARAM2 and used in action
new ScriptArgs().setParam2(finishedTime));
challengeTriggers.forEach(t -> t.onFinish(this)); var scene = this.getScene();
var dungeonManager = scene.getDungeonManager();
if (dungeonManager != null && dungeonManager.getDungeonData() != null) {
scene.getPlayers().forEach(p -> p.getActivityManager().triggerWatcher(
WatcherTriggerType.TRIGGER_FINISH_CHALLENGE,
String.valueOf(dungeonManager.getDungeonData().getId()),
String.valueOf(this.getGroup().id),
String.valueOf(this.getChallengeId())
));
}
scene.getScriptManager().callEvent(
// TODO record the time in PARAM2 and used in action
new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS).setParam2(finishedTime));
this.getScene().triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE, getChallengeId(), getChallengeIndex());
this.challengeTriggers.forEach(t -> t.onFinish(this));
} }
public void fail() { public void fail(){
if (!inProgress()) { if (!this.inProgress()) return;
return; this.finish(true);
}
finish(false); this.getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL));
this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null);
challengeTriggers.forEach(t -> t.onFinish(this)); challengeTriggers.forEach(t -> t.onFinish(this));
} }
@ -132,6 +143,18 @@ public class WorldChallenge {
this.challengeTriggers.forEach(t -> t.onGadgetDeath(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) { public void onGadgetDamage(EntityGadget gadget) {
if (!inProgress()) { if (!inProgress()) {
return; return;

View File

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

@ -6,10 +6,11 @@ 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;
public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler { import java.util.List;
public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override @Override
public boolean isThisType(ChallengeType challengeType) { public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0 // ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0
@ -17,24 +18,16 @@ public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactor
} }
@Override @Override
public WorldChallenge build( public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCfgId, int param6, Scene scene, SceneGroup group) {
int challengeIndex,
int challengeId,
int timeLimit,
int groupId,
int targetCfgId,
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(timeLimit),
List.of(timeLimit), timeLimit, // Limit
timeLimit, // Limit 0, // Goal
0, // Goal List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger())
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

@ -3,17 +3,14 @@ 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 abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {} public void onBegin(WorldChallenge challenge) { }
public void onFinish(WorldChallenge challenge) { }
public void onFinish(WorldChallenge challenge) {} public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { }
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) { }
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {} public void onCheckTimeout(WorldChallenge challenge) { }
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { }
public void onGadgetDeath(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

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

View File

@ -3,21 +3,19 @@ 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 { @AllArgsConstructor
public class KillMonsterTrigger extends ChallengeTrigger{
private int monsterCfgId;
@Override @Override
public void onBegin(WorldChallenge challenge) { public void onBegin(WorldChallenge challenge) {
challenge challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
} }
@Override @Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
var newScore = challenge.increaseScore(); if(monster.getConfigId() == monsterCfgId){
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done(); challenge.done();
} }
} }

View File

@ -52,18 +52,25 @@ public class EntityAvatar extends GameEntity {
public EntityAvatar(Scene scene, Avatar avatar) { public EntityAvatar(Scene scene, Avatar avatar) {
super(scene); super(scene);
this.avatar = avatar; this.avatar = avatar;
this.avatar.setCurrentEnergy(); this.avatar.setCurrentEnergy();
if (getScene() != null) { if (getScene() != null) {
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
GameItem weapon = getAvatar().getWeapon(); var weapon = getAvatar().getWeapon();
if (weapon != null) { if (weapon != null) {
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON)); weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
} }
} }
} }
@Override
public int getEntityTypeId() {
return this.getAvatar().getAvatarId();
}
public Player getPlayer() { public Player getPlayer() {
return this.avatar.getPlayer(); return this.avatar.getPlayer();
} }
@ -153,6 +160,22 @@ public class EntityAvatar extends GameEntity {
} }
} }
/**
* Adds a fixed amount of energy to the current avatar.
*
* @param amount The amount of energy to add.
* @return True if the energy was added, false if the energy was not added.
*/
public boolean addEnergy(float amount) {
var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
var curEnergy = this.getFightProperty(curEnergyProp);
if (curEnergy == amount) return false;
this.getAvatar().setCurrentEnergy(curEnergyProp, amount);
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
return true;
}
public void addEnergy(float amount, PropChangeReason reason) { public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false); this.addEnergy(amount, reason, false);
} }

View File

@ -1,6 +1,6 @@
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;
@ -25,12 +25,17 @@ public abstract class EntityBaseGadget extends GameEntity {
public abstract int getGadgetId(); public abstract int getGadgetId();
@Override
public int getEntityTypeId() {
return this.getGadgetId();
}
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method. super.onDeath(killerId); // Invoke super class's onDeath() method.
} }
protected void fillFightProps(ConfigGadget configGadget) { protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) { if (configGadget == null || configGadget.getCombat() == null) {
return; return;
} }

View File

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

View File

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

View File

@ -30,6 +30,11 @@ public class EntityNPC extends GameEntity {
this.metaNpc = metaNPC; this.metaNpc = metaNPC;
} }
@Override
public int getEntityTypeId() {
return this.metaNpc.npc_id;
}
@Override @Override
public Int2FloatMap getFightProperties() { public Int2FloatMap getFightProperties() {
return null; return null;

View File

@ -20,15 +20,21 @@ public class EntityRegion extends GameEntity {
public EntityRegion(Scene scene, SceneRegion region) { public EntityRegion(Scene scene, SceneRegion region) {
super(scene); super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION); this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION);
setGroupId(region.group.id); this.setGroupId(region.group.id);
setBlockId(region.group.block_id); this.setBlockId(region.group.block_id);
setConfigId(region.config_id); this.setConfigId(region.config_id);
this.position = region.pos.clone(); this.position = region.pos.clone();
this.entities = ConcurrentHashMap.newKeySet(); this.entities = ConcurrentHashMap.newKeySet();
this.metaRegion = region; this.metaRegion = region;
} }
@Override
public int getEntityTypeId() {
return this.metaRegion.config_id;
}
public void addEntity(GameEntity entity) { public void addEntity(GameEntity entity) {
if (this.getEntities().contains(entity.getId())) { if (this.getEntities().contains(entity.getId())) {
return; return;

View File

@ -1,6 +1,5 @@
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;
@ -10,25 +9,22 @@ 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 EntityPlatform platformGadget; @Getter private EntityGadget platformGadget;
public EntitySolarIsotomaClientGadget( public EntitySolarIsotomaClientGadget(Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
super(scene, player, notify); super(scene, player, notify);
} }
@Override @Override
public void onCreate() { public void onCreate() {
// Create solar isotoma elevator and send to all. //Create solar isotoma elevator and send to all.
this.platformGadget = this.platformGadget = new EntitySolarIsotomaElevatorPlatform(this, getScene(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
new EntitySolarIsotomaElevatorPlatform(
this, getScene(), getOwner(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
getScene().addEntity(this.platformGadget); getScene().addEntity(this.platformGadget);
} }
@Override @Override
public void onRemoved() { public void onRemoved() {
// Remove solar isotoma elevator entity. //Remove solar isotoma elevator entity.
getScene().removeEntity(this.platformGadget); getScene().removeEntity(this.platformGadget);
} }
} }

View File

@ -1,7 +1,7 @@
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;
@ -25,17 +25,17 @@ 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 java.util.List;
import javax.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import javax.annotation.Nullable;
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;
@ -43,10 +43,9 @@ public class EntityVehicle extends EntityBaseGadget {
@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);
@ -64,7 +63,7 @@ public class EntityVehicle extends EntityBaseGadget {
} }
@Override @Override
protected void fillFightProps(ConfigGadget configGadget) { protected void fillFightProps(ConfigEntityGadget 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);
@ -73,49 +72,37 @@ public class EntityVehicle extends EntityBaseGadget {
@Override @Override
public SceneEntityInfo toProto() { public SceneEntityInfo toProto() {
VehicleInfo vehicle = VehicleInfo vehicle = VehicleInfo.newBuilder()
VehicleInfo.newBuilder() .setOwnerUid(this.owner.getUid())
.setOwnerUid(this.owner.getUid()) .setCurStamina(getCurStamina())
.setCurStamina(getCurStamina()) .build();
.build();
EntityAuthorityInfo authority = EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
EntityAuthorityInfo.newBuilder() .setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto()))
.setAiInfo( .setBornPos(getPosition().toProto())
SceneEntityAiInfo.newBuilder() .build();
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
SceneGadgetInfo.newBuilder() .setGadgetId(this.getGadgetId())
.setGadgetId(this.getGadgetId()) .setAuthorityPeerId(this.getOwner().getPeerId())
.setAuthorityPeerId(this.getOwner().getPeerId()) .setIsEnableInteract(true)
.setIsEnableInteract(true) .setVehicleInfo(vehicle);
.setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
SceneEntityInfo.newBuilder() .setEntityId(getId())
.setEntityId(getId()) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.setMotionInfo( .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
MotionInfo.newBuilder() .setGadget(gadgetInfo)
.setPos(getPosition().toProto()) .setEntityAuthorityInfo(authority)
.setRot(getRotation().toProto()) .setLifeState(1);
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair pair = PropPair.newBuilder()
PropPair.newBuilder() .setType(PlayerProperty.PROP_LEVEL.getId())
.setType(PlayerProperty.PROP_LEVEL.getId()) .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47)) .build();
.build();
this.addAllFightPropsToEntityInfo(entityInfo); this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(pair); entityInfo.addPropList(pair);

View File

@ -1,6 +1,7 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
@ -12,6 +13,7 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.server.event.entity.EntityDamageEvent; import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.server.event.entity.EntityDeathEvent; import emu.grasscutter.server.event.entity.EntityDeathEvent;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@ -39,6 +41,10 @@ public abstract class GameEntity {
@Getter @Setter private boolean lockHP; @Getter @Setter private boolean lockHP;
// Lua controller for specific actions
@Getter @Setter private EntityController entityController;
@Getter private ElementType lastAttackType = ElementType.None;
// Abilities // Abilities
private Object2FloatMap<String> metaOverrideMap; private Object2FloatMap<String> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers; private Int2ObjectMap<String> metaModifiers;
@ -52,6 +58,8 @@ public abstract class GameEntity {
return this.getId() >> 24; return this.getId() >> 24;
} }
public abstract int getEntityTypeId();
public World getWorld() { public World getWorld() {
return this.getScene().getWorld(); return this.getScene().getWorld();
} }
@ -115,15 +123,12 @@ public abstract class GameEntity {
} }
protected MotionInfo getMotionInfo() { protected MotionInfo getMotionInfo() {
MotionInfo proto = return MotionInfo.newBuilder()
MotionInfo.newBuilder() .setPos(this.getPosition().toProto())
.setPos(this.getPosition().toProto()) .setRot(this.getRotation().toProto())
.setRot(this.getRotation().toProto()) .setSpeed(Vector.newBuilder())
.setSpeed(Vector.newBuilder()) .setState(this.getMotionState())
.setState(this.getMotionState()) .build();
.build();
return proto;
} }
public float heal(float amount) { public float heal(float amount) {
@ -149,10 +154,10 @@ public abstract class GameEntity {
} }
public void damage(float amount) { public void damage(float amount) {
this.damage(amount, 0); this.damage(amount, 0, ElementType.None);
} }
public void damage(float amount, int killerId) { public void damage(float amount, int killerId, ElementType attackType) {
// Check if the entity has properties. // Check if the entity has properties.
if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) { if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) {
return; return;
@ -160,7 +165,7 @@ public abstract class GameEntity {
// Invoke entity damage event. // Invoke entity damage event.
EntityDamageEvent event = EntityDamageEvent event =
new EntityDamageEvent(this, amount, this.getScene().getEntityById(killerId)); new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId));
event.call(); event.call();
if (event.isCanceled()) { if (event.isCanceled()) {
return; // If the event is canceled, do not damage the entity. return; // If the event is canceled, do not damage the entity.
@ -190,6 +195,17 @@ public abstract class GameEntity {
} }
} }
/**
* Runs the Lua callbacks for {@link EntityDamageEvent}.
*
* @param event The damage event.
*/
public void runLuaCallbacks(EntityDamageEvent event) {
if (entityController != null) {
entityController.onBeHurt(this, event.getAttackElementType(), true);//todo is host handling
}
}
/** /**
* Move this entity to a new position. * Move this entity to a new position.
* *
@ -215,6 +231,19 @@ public abstract class GameEntity {
public void onRemoved() {} 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 * Called when this entity dies
* *
@ -224,6 +253,11 @@ public abstract class GameEntity {
// Invoke entity death event. // Invoke entity death event.
EntityDeathEvent event = new EntityDeathEvent(this, killerId); EntityDeathEvent event = new EntityDeathEvent(this, killerId);
event.call(); event.call();
// Run Lua callbacks.
if (entityController != null) {
entityController.onDie(this, getLastAttackType());
}
} }
public abstract SceneEntityInfo toProto(); public abstract SceneEntityInfo toProto();

View File

@ -5,19 +5,22 @@ 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.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetRewardStatue extends GadgetContent { public final class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) { public GadgetRewardStatue(EntityGadget gadget) {
super(gadget); super(gadget);
} }
public boolean onInteract(Player player, GadgetInteractReq req) { public boolean onInteract(Player player, GadgetInteractReq req) {
if (player.getScene().getChallenge() != null var dungeonManager = player.getScene().getDungeonManager();
&& player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
dungeonChallenge.getStatueDrops(player, req); if (player.getScene().getChallenge() instanceof DungeonChallenge) {
var useCondensed = req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE;
dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId());
} }
player.sendPacket( player.sendPacket(

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,38 +1,23 @@
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.props.PlayerProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform { public class EntitySolarIsotomaElevatorPlatform extends EntityGadget {
public EntitySolarIsotomaElevatorPlatform( public EntitySolarIsotomaElevatorPlatform(EntitySolarIsotomaClientGadget isotoma, Scene scene, int gadgetId, Position pos, Position rot) {
EntitySolarIsotomaClientGadget isotoma, super(scene, gadgetId, pos, rot);
Scene scene, setOwner(isotoma);
Player player, this.setRouteConfig(new AbilityRoute(rot, false, false, pos));
int gadgetId, this.setContent(new GadgetAbility(this, isotoma));
Position pos,
Position rot) {
super(
isotoma,
scene,
player,
gadgetId,
pos,
rot,
MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY);
} }
@Override @Override
protected void fillFightProps(ConfigGadget configGadget) { protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) { if (configGadget == null || configGadget.getCombat() == null) {
return; return;
} }
@ -40,10 +25,10 @@ public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform {
var combatProperties = combatData.getProperty(); var combatProperties = combatData.getProperty();
if (combatProperties.isUseCreatorProperty()) { if (combatProperties.isUseCreatorProperty()) {
// If useCreatorProperty == true, use owner's property; //If useCreatorProperty == true, use owner's property;
GameEntity ownerAvatar = getScene().getEntityById(getGadget().getOwnerEntityId()); GameEntity ownerEntity = getOwner();
if (ownerAvatar != null) { if (ownerEntity != null) {
getFightProperties().putAll(ownerAvatar.getFightProperties()); getFightProperties().putAll(ownerEntity.getFightProperties());
return; return;
} else { } else {
Grasscutter.getLogger().warn("Why gadget owner is null?"); Grasscutter.getLogger().warn("Why gadget owner is null?");
@ -52,102 +37,4 @@ public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform {
super.fillFightProps(configGadget); 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,11 +1,9 @@
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.data.excels.world.WorldLevelData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger; import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
@ -15,54 +13,48 @@ import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; 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 class BlossomActivity { public final class BlossomActivity {
private static final int BLOOMING_GADGET_ID = 70210109;
private final SceneGroup tempSceneGroup; private final SceneGroup tempSceneGroup;
private final WorldChallenge challenge; private final WorldChallenge challenge;
private final EntityGadget gadget; private final EntityGadget gadget;
private final int goal;
private final int worldLevel;
private final List<EntityMonster> activeMonsters = new ArrayList<>();
private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
private EntityGadget chest; private EntityGadget chest;
private int step; private int step;
private final int goal;
private int generatedCount; private int generatedCount;
private boolean pass = false; private final int worldLevel;
private boolean pass=false;
public BlossomActivity( private final List<EntityMonster> activeMonsters = new ArrayList<>();
EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) { private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
private static final int BLOOMING_GADGET_ID = 70210109;
public BlossomActivity(EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) {
this.tempSceneGroup = new SceneGroup(); this.tempSceneGroup = new SceneGroup();
this.tempSceneGroup.id = entityGadget.getId(); this.tempSceneGroup.id = entityGadget.getId();
this.gadget = entityGadget; this.gadget=entityGadget;
this.step = 0; this.step=0;
this.goal = monsters.size(); this.goal = monsters.size();
this.candidateMonsters.addAll(monsters); this.candidateMonsters.addAll(monsters);
this.worldLevel = worldLevel; this.worldLevel = worldLevel;
ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>(); ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>();
this.challenge = this.challenge = new WorldChallenge(entityGadget.getScene(),
new WorldChallenge( tempSceneGroup,
entityGadget.getScene(), 1,
tempSceneGroup, 1,
1, List.of(goal, timeout),
1, timeout,
List.of(goal, timeout), goal, challengeTriggers);
timeout, challengeTriggers.add(new KillMonsterCountTrigger());
goal, //this.challengeTriggers.add(new InTimeTrigger());
challengeTriggers);
challengeTriggers.add(new KillMonsterTrigger());
// this.challengeTriggers.add(new InTimeTrigger());
} }
public WorldChallenge getChallenge() { public WorldChallenge getChallenge() {
return this.challenge; return this.challenge;
} }
public void setMonsters(List<EntityMonster> monsters) { public void setMonsters(List<EntityMonster> monsters) {
this.activeMonsters.clear(); this.activeMonsters.clear();
this.activeMonsters.addAll(monsters); this.activeMonsters.addAll(monsters);
@ -70,53 +62,49 @@ public class BlossomActivity {
monster.setGroupId(this.tempSceneGroup.id); monster.setGroupId(this.tempSceneGroup.id);
} }
} }
public int getAliveMonstersCount() { public int getAliveMonstersCount() {
int count = 0; int count=0;
for (EntityMonster monster : activeMonsters) { for (EntityMonster monster: activeMonsters) {
if (monster.isAlive()) { if (monster.isAlive()) {
count++; count++;
} }
} }
return count; return count;
} }
public boolean getPass() { public boolean getPass() {
return pass; return pass;
} }
public void start() { public void start() {
challenge.start(); challenge.start();
} }
public void onTick() { public void onTick() {
Scene scene = gadget.getScene(); Scene scene = gadget.getScene();
Position pos = gadget.getPosition(); Position pos = gadget.getPosition();
if (getAliveMonstersCount() <= 2) { if (getAliveMonstersCount() <= 2) {
if (generatedCount < goal) { if (generatedCount<goal) {
step++; step++;
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel); var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
int worldLevelOverride = 0; int worldLevelOverride = 0;
if (worldLevelData != null) { if (worldLevelData != null) {
worldLevelOverride = worldLevelData.getMonsterLevel(); worldLevelOverride = worldLevelData.getMonsterLevel();
} }
List<EntityMonster> newMonsters = new ArrayList<>(); List<EntityMonster> newMonsters = new ArrayList<>();
int willSpawn = Utils.randomRange(3, 5); int willSpawn = Utils.randomRange(3,5);
if (generatedCount + willSpawn > goal) { if (generatedCount+willSpawn>goal) {
willSpawn = goal - generatedCount; willSpawn = goal - generatedCount;
} }
generatedCount += willSpawn; generatedCount+=willSpawn;
for (int i = 0; i < willSpawn; i++) { for (int i = 0; i < willSpawn; i++) {
MonsterData monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll()); var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll());
int level = scene.getEntityLevel(1, worldLevelOverride); int level = scene.getEntityLevel(1, worldLevelOverride);
EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level); EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
scene.addEntity(entity); scene.addEntity(entity);
newMonsters.add(entity); newMonsters.add(entity);
} }
setMonsters(newMonsters); setMonsters(newMonsters);
} else { }else {
if (getAliveMonstersCount() == 0) { if (getAliveMonstersCount() == 0) {
this.pass = true; this.pass = true;
this.challenge.done(); this.challenge.done();
@ -124,15 +112,12 @@ public class BlossomActivity {
} }
} }
} }
public EntityGadget getGadget() { public EntityGadget getGadget() {
return gadget; return gadget;
} }
public EntityGadget getChest() { public EntityGadget getChest() {
if (chest == null) { if (chest==null) {
EntityGadget rewardGadget = EntityGadget rewardGadget = new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
SceneGadget metaGadget = new SceneGadget(); SceneGadget metaGadget = new SceneGadget();
metaGadget.boss_chest = new SceneBossChest(); metaGadget.boss_chest = new SceneBossChest();
metaGadget.boss_chest.resin = 20; metaGadget.boss_chest.resin = 20;

View File

@ -30,6 +30,8 @@ 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 lombok.Getter;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -40,7 +42,7 @@ public class EnergyManager extends BasePlayerManager {
private static final Int2ObjectMap<List<SkillParticleGenerationInfo>> private static final Int2ObjectMap<List<SkillParticleGenerationInfo>>
skillParticleGenerationData = new Int2ObjectOpenHashMap<>(); skillParticleGenerationData = new Int2ObjectOpenHashMap<>();
private final Object2IntMap<EntityAvatar> avatarNormalProbabilities; private final Object2IntMap<EntityAvatar> avatarNormalProbabilities;
private boolean energyUsage; // Should energy usage be enabled for this player? @Getter private boolean energyUsage; // Should energy usage be enabled for this player?
public EnergyManager(Player player) { public EnergyManager(Player player) {
super(player); super(player);
@ -381,8 +383,28 @@ public class EnergyManager extends BasePlayerManager {
.findFirst(); .findFirst();
} }
public boolean getEnergyUsage() { /**
return this.energyUsage; * Refills the energy of the active avatar.
*
* @return True if the energy was refilled, false otherwise.
*/
public boolean refillActiveEnergy() {
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) { public void setEnergyUsage(boolean energyUsage) {

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

@ -5,14 +5,18 @@ import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.OpenStateData; import emu.grasscutter.data.excels.OpenStateData;
import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType; import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
import lombok.val;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
// @Entity // @Entity
public class PlayerProgressManager extends BasePlayerDataManager { public final class PlayerProgressManager extends BasePlayerDataManager {
/****************************************************************************************************************** /******************************************************************************************************************
****************************************************************************************************************** ******************************************************************************************************************
* OPEN STATES * OPEN STATES
@ -145,6 +149,13 @@ public class PlayerProgressManager extends BasePlayerDataManager {
this.player.sendPacket(new PacketSetOpenStateRsp(openState, value)); this.player.sendPacket(new PacketSetOpenStateRsp(openState, value));
} }
/**
* This force sets an open state, ignoring all conditions and permissions
*/
public void forceSetOpenState(int openState, int value) {
this.setOpenState(openState, value);
}
/********** /**********
* Triggered unlocking of open states (unlock states whose conditions have been met.) * Triggered unlocking of open states (unlock states whose conditions have been met.)
**********/ **********/
@ -221,7 +232,7 @@ public class PlayerProgressManager extends BasePlayerDataManager {
// Fire quest trigger for trans point unlock. // Fire quest trigger for trans point unlock.
this.player this.player
.getQuestManager() .getQuestManager()
.triggerEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId); .queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
// Send packet. // Send packet.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId)); this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));
@ -235,4 +246,35 @@ public class PlayerProgressManager extends BasePlayerDataManager {
// Send packet. // Send packet.
this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId)); 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);
}
} }

View File

@ -40,7 +40,7 @@ import lombok.Setter;
import lombok.val; import lombok.val;
@Entity @Entity
public class TeamManager extends BasePlayerDataManager { public final class TeamManager extends BasePlayerDataManager {
@Transient private final List<EntityAvatar> avatars; @Transient private final List<EntityAvatar> avatars;
@Transient @Getter private final Set<EntityBaseGadget> gadgets; @Transient @Getter private final Set<EntityBaseGadget> gadgets;
@Transient @Getter private final IntSet teamResonances; @Transient @Getter private final IntSet teamResonances;
@ -658,6 +658,17 @@ public class TeamManager extends BasePlayerDataManager {
this.getPlayer().sendPacket(new PacketChangeAvatarRsp(guid)); this.getPlayer().sendPacket(new PacketChangeAvatarRsp(guid));
} }
/**
* Applies 10% of the avatar's max HP as damage.
* This occurs when the avatar is killed by the void.
*/
public void applyVoidDamage() {
this.getActiveTeam().forEach(entity -> {
entity.damage(entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f);
player.sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
});
}
public void onAvatarDie(long dieGuid) { public void onAvatarDie(long dieGuid) {
EntityAvatar deadAvatar = this.getCurrentAvatarEntity(); EntityAvatar deadAvatar = this.getCurrentAvatarEntity();

View File

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

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

@ -15,8 +15,9 @@ public class ConditionPersonalLineUnlock extends BaseCondition {
QuestData questData, QuestData questData,
QuestData.QuestAcceptCondition condition, QuestData.QuestAcceptCondition condition,
String paramStr, String paramStr,
int... params) { int... params
val personalLineId = condition.getParam()[0]; ) {
var personalLineId = condition.getParam()[0];
return owner.getPersonalLineList().contains(personalLineId); return owner.getPersonalLineList().contains(personalLineId);
} }
} }

View File

@ -12,6 +12,6 @@ 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

@ -8,7 +8,7 @@ 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 =

View File

@ -6,13 +6,12 @@ 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) {
val 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().forceSetOpenState(param[0], param[1]); quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]);

View File

@ -19,20 +19,23 @@ import emu.grasscutter.game.entity.gadget.GadgetWorktop;
import emu.grasscutter.game.managers.blossom.BlossomManager; import emu.grasscutter.game.managers.blossom.BlossomManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.TeamInfo; import emu.grasscutter.game.player.TeamInfo;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.*;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.QuestGroupSuite; import emu.grasscutter.game.quest.QuestGroupSuite;
import emu.grasscutter.game.world.data.TeleportProperties;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.EnterTypeOuterClass;
import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass; import emu.grasscutter.net.proto.SelectWorktopOptionReqOuterClass;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager; import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.KahnsSort;
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 java.util.*; import java.util.*;
@ -41,8 +44,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val;
public class Scene { public final class Scene {
@Getter private final World world; @Getter private final World world;
@Getter private final SceneData sceneData; @Getter private final SceneData sceneData;
@Getter private final List<Player> players; @Getter private final List<Player> players;
@ -115,6 +119,13 @@ public class Scene {
.orElse(null); .orElse(null);
} }
public GameEntity getEntityByConfigId(int configId, int groupId) {
return this.entities.values().stream()
.filter(x -> x.getConfigId() == configId && x.getGroupId() == groupId)
.findFirst()
.orElse(null);
}
/** /**
* Sets the scene's pause state. Sends the current scene's time to all players. * Sets the scene's pause state. Sends the current scene's time to all players.
* *
@ -338,6 +349,7 @@ public class Scene {
public void handleAttack(AttackResult result) { public void handleAttack(AttackResult result) {
// GameEntity attacker = getEntityById(result.getAttackerId()); // GameEntity attacker = getEntityById(result.getAttackerId());
GameEntity target = getEntityById(result.getDefenseId()); GameEntity target = getEntityById(result.getDefenseId());
ElementType attackType = ElementType.getTypeByValue(result.getElementType());
if (target == null) { if (target == null) {
return; return;
@ -345,13 +357,13 @@ public class Scene {
// Godmode check // Godmode check
if (target instanceof EntityAvatar) { if (target instanceof EntityAvatar) {
if (((EntityAvatar) target).getPlayer().inGodmode()) { if (((EntityAvatar) target).getPlayer().isInGodMode()) {
return; return;
} }
} }
// Sanity check // Sanity check
target.damage(result.getDamage(), result.getAttackerId()); target.damage(result.getDamage(), result.getAttackerId(), attackType);
} }
public void killEntity(GameEntity target) { public void killEntity(GameEntity target) {
@ -399,17 +411,19 @@ public class Scene {
} }
public void onTick() { public void onTick() {
// disable script for home // Disable ticking for the player's home world.
if (this.getSceneType() == SceneType.SCENE_HOME_WORLD if (this.getSceneType() == SceneType.SCENE_HOME_WORLD
|| this.getSceneType() == SceneType.SCENE_HOME_ROOM) { || this.getSceneType() == SceneType.SCENE_HOME_ROOM) {
return; return;
} }
if (this.getScriptManager().isInit()) { if (this.getScriptManager().isInit()) {
this.checkBlocks(); this.checkBlocks();
} else { } else {
// TEMPORARY // TEMPORARY
this.checkSpawns(); this.checkSpawns();
} }
// Triggers // Triggers
this.scriptManager.checkRegions(); this.scriptManager.checkRegions();
@ -417,14 +431,134 @@ public class Scene {
challenge.onCheckTimeOut(); challenge.onCheckTimeOut();
} }
blossomManager.onTick(); this.blossomManager.onTick();
checkNpcGroup(); checkNpcGroup();
this.finishLoading();
this.checkPlayerRespawn();
if (this.tickCount++ % 10 == 0)
broadcastPacket(new PacketSceneTimeNotify(this));
}
/**
* Validates a player's current position.
* Teleports the player if the player is out of bounds.
*/
private void checkPlayerRespawn() {
var diePos = this.getScriptManager().getConfig().die_y;
// Check players in the scene.
this.players.forEach(player -> {
if (this.getScriptManager().getConfig() == null) return;
// Check if we need a respawn
if (diePos >= player.getPosition().getY()) {
//Respawn the player.
this.respawnPlayer(player);
}
});
// Check entities in the scene.
this.getEntities().forEach((id, entity) -> {
if (diePos >= entity.getPosition().getY()){
this.killEntity(entity);
}
});
}
/**
* @return The script's default location, or the player's location.
*/
public Position getDefaultLocation(Player player) {
val defaultPosition = getScriptManager().getConfig().born_pos;
return defaultPosition != null ? defaultPosition : player.getPosition();
}
/**
* @return The script's default rotation, or the player's rotation.
*/
private Position getDefaultRot(Player player) {
var defaultRotation = this.getScriptManager().getConfig().born_rot;
return defaultRotation != null ? defaultRotation : player.getRotation();
}
/**
* Gets the respawn position for the player.
*
* @param player The player to get the respawn position for.
* @return The respawn position for the player.
*/
private Position getRespawnLocation(Player player) {
// TODO: Get the last valid location the player stood on.
var lastCheckpointPos = dungeonManager != null ? dungeonManager.getRespawnLocation() : null;
return lastCheckpointPos != null ? lastCheckpointPos : getDefaultLocation(player);
}
/**
* Gets the respawn rotation for the player.
*
* @param player The player to get the respawn rotation for.
* @return The respawn rotation for the player.
*/
private Position getRespawnRotation(Player player) {
var lastCheckpointRot = this.dungeonManager != null ? this.dungeonManager.getRespawnRotation() : null;
return lastCheckpointRot != null ? lastCheckpointRot : this.getDefaultRot(player);
}
/**
* Teleports the player to the respawn location.
*
* @param player The player to respawn.
* @return true if the player was successfully respawned, false otherwise.
*/
public boolean respawnPlayer(Player player) {
// Apply void damage as a penalty.
player.getTeamManager().applyVoidDamage();
// TODO: Respawn the player at the last valid location.
var targetPos = getRespawnLocation(player);
var targetRot = getRespawnRotation(player);
var teleportProps = TeleportProperties.builder()
.sceneId(getId())
.teleportTo(targetPos)
.teleportRot(targetRot)
.teleportType(PlayerTeleportEvent.TeleportType.INTERNAL)
.enterType(EnterTypeOuterClass.EnterType.ENTER_TYPE_GOTO)
.enterReason(dungeonManager != null ? EnterReason.DungeonReviveOnWaypoint : EnterReason.Revival);
return this.getWorld().transferPlayerToScene(player, teleportProps.build());
}
/**
* Invoked when the scene finishes loading.
* Runs all callbacks that were added with {@link #runWhenFinished(Runnable)}.
*/
public void finishLoading() {
if (this.finishedLoading) return;
this.finishedLoading = true;
this.afterLoadedCallbacks.forEach(Runnable::run);
this.afterLoadedCallbacks.clear();
}
/**
* Adds a callback to be executed when the scene is finished loading.
* If the scene is already finished loading, the callback will be executed immediately.
*
* @param runnable The callback to be executed.
*/
public void runWhenFinished(Runnable runnable) {
if (this.isFinishedLoading()) {
runnable.run();return;
}
this.afterLoadedCallbacks.add(runnable);
} }
public int getEntityLevel(int baseLevel, int worldLevelOverride) { public int getEntityLevel(int baseLevel, int worldLevelOverride) {
int level = worldLevelOverride > 0 ? worldLevelOverride + baseLevel - 22 : baseLevel; int level = worldLevelOverride > 0 ? worldLevelOverride + baseLevel - 22 : baseLevel;
level = level >= 100 ? 100 : level; level = Math.min(level, 100);
level = level <= 0 ? 1 : level; level = level <= 0 ? 1 : level;
return level; return level;
@ -543,11 +677,12 @@ public class Scene {
} }
if (toAdd.size() > 0) { if (toAdd.size() > 0) {
toAdd.stream().forEach(this::addEntityDirectly); toAdd.forEach(this::addEntityDirectly);
this.broadcastPacket(new PacketSceneEntityAppearNotify(toAdd, VisionType.VISION_TYPE_BORN)); this.broadcastPacket(new PacketSceneEntityAppearNotify(toAdd, VisionType.VISION_TYPE_BORN));
} }
if (toRemove.size() > 0) { if (toRemove.size() > 0) {
toRemove.stream().forEach(this::removeEntityDirectly); toRemove.forEach(this::removeEntityDirectly);
this.broadcastPacket( this.broadcastPacket(
new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE));
blossomManager.recycleGadgetEntity(toRemove); blossomManager.recycleGadgetEntity(toRemove);
@ -568,8 +703,9 @@ public class Scene {
return true; return true;
} }
private synchronized boolean loadBlock(SceneBlock block) { public synchronized boolean loadBlock(SceneBlock block) {
if (this.loadedBlocks.contains(block)) return false; if (this.loadedBlocks.contains(block)) return false;
this.onLoadBlock(block, this.players); this.onLoadBlock(block, this.players);
this.loadedBlocks.add(block); this.loadedBlocks.add(block);
return true; return true;
@ -578,7 +714,7 @@ public class Scene {
public synchronized void checkBlocks() { public synchronized void checkBlocks() {
Set<SceneBlock> visible = Set<SceneBlock> visible =
this.players.stream() this.players.stream()
.map(player -> this.getPlayerActiveBlocks(player)) .map(this::getPlayerActiveBlocks)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@ -635,65 +771,146 @@ public class Scene {
Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id);
} }
public int loadDynamicGroup(int group_id) {
SceneGroup group = getScriptManager().getGroupById(group_id);
if(group == null || getScriptManager().getGroupInstanceById(group_id) != null) return -1; //Group not found or already instanced
onLoadGroup(new ArrayList<>(List.of(group)));
if(GameData.getGroupReplacements().containsKey(group_id)) onRegisterGroups();
if (group.init_config == null) return -1;
return group.init_config.suite;
}
public boolean unregisterDynamicGroup(int groupId){
var group = getScriptManager().getGroupById(groupId);
if (group == null) return false;
var block = getScriptManager().getBlocks().get(group.block_id);
this.unloadGroup(block, groupId);
return true;
}
public void onRegisterGroups() {
var sceneGroups = this.loadedGroups;
var sceneGroupMap = sceneGroups.stream()
.collect(Collectors.toMap(item -> item.id, item -> item));
var sceneGroupsIds = sceneGroups.stream()
.map(group -> group.id)
.toList();
var dynamicGroups = sceneGroups.stream()
.filter(group -> group.dynamic_load)
.map(group -> group.id)
.toList();
//Create the graph
var nodes = new ArrayList<KahnsSort.Node>();
var groupList = new ArrayList<Integer>();
GameData.getGroupReplacements().values().stream().filter(replacement -> dynamicGroups.contains(replacement.id)).forEach(replacement -> {
Grasscutter.getLogger().info("Graph ordering replacement {}", replacement);
replacement.replace_groups.forEach(group -> {
nodes.add(new KahnsSort.Node(replacement.id, group));
if (!groupList.contains(group))
groupList.add(group);
});
if (!groupList.contains(replacement.id))
groupList.add(replacement.id);
});
KahnsSort.Graph graph = new KahnsSort.Graph(nodes, groupList);
List<Integer> dynamicGroupsOrdered = KahnsSort.doSort(graph);
if (dynamicGroupsOrdered == null) throw new RuntimeException("Invalid group replacement graph");
// Now we can start unloading and loading groups :D
dynamicGroupsOrdered.forEach(group -> {
if (GameData.getGroupReplacements().containsKey((int)group)) { //isGroupJoinReplacement
var data = GameData.getGroupReplacements().get((int)group);
var sceneGroupReplacement = this.loadedGroups.stream().filter(g -> g.id == group).findFirst().orElseThrow();
if (sceneGroupReplacement.is_replaceable != null) {
var it = data.replace_groups.iterator();
while (it.hasNext()) {
var replace_group = it.next();
if (!sceneGroupsIds.contains(replace_group)) continue;
// Check if we can replace this group
SceneGroup sceneGroup = sceneGroupMap.get(replace_group);
if (sceneGroup != null && sceneGroup.is_replaceable != null &&
((sceneGroup.is_replaceable.value &&
sceneGroup.is_replaceable.version <= sceneGroupReplacement.is_replaceable.version) ||
sceneGroup.is_replaceable.new_bin_only)) {
this.unloadGroup(scriptManager.getBlocks().get(sceneGroup.block_id), replace_group);
it.remove();
Grasscutter.getLogger().info("Graph ordering: unloaded {}", replace_group);
}
}
}
}
});
}
public void loadTriggerFromGroup(SceneGroup group, String triggerName) { public void loadTriggerFromGroup(SceneGroup group, String triggerName) {
// Load triggers and regions // Load triggers and regions
getScriptManager() this.getScriptManager().registerTrigger(group.triggers.values().stream()
.registerTrigger( .filter(p -> p.getName().contains(triggerName)).toList());
group.triggers.values().stream().filter(p -> p.name.contains(triggerName)).toList());
group.regions.values().stream() group.regions.values().stream()
.filter(q -> q.config_id == Integer.parseInt(triggerName.substring(13))) .filter(q -> q.config_id == Integer.parseInt(triggerName.substring(13)))
.map(region -> new EntityRegion(this, region)) .map(region -> new EntityRegion(this, region))
.forEach(getScriptManager()::registerRegion); .forEach(getScriptManager()::registerRegion);
} }
public void onLoadGroup(List<SceneGroup> groups) { public void onLoadGroup(List<SceneGroup> groups) {
if (groups == null || groups.isEmpty()) { if (groups == null || groups.isEmpty()) {
return; return;
} }
for (SceneGroup group : groups) {
for (var group : groups) {
if(this.loadedGroups.contains(group)) continue;
// We load the script files for the groups here // We load the script files for the groups here
this.getScriptManager().loadGroupFromScript(group); this.getScriptManager().loadGroupFromScript(group);
if (!this.scriptManager.getLoadedGroupSetPerBlock().containsKey(group.block_id))
this.onLoadBlock(scriptManager.getBlocks().get(group.block_id), players);
this.scriptManager.getLoadedGroupSetPerBlock().get(group.block_id).add(group);
} }
// Spawn gadgets AFTER triggers are added // Spawn gadgets AFTER triggers are added
// TODO // TODO
var entities = new ArrayList<GameEntity>(); var entities = new ArrayList<GameEntity>();
for (SceneGroup group : groups) { for (var group : groups) {
if(this.loadedGroups.contains(group)) continue;
if (group.init_config == null) { if (group.init_config == null) {
continue; continue;
} }
var groupInstance = this.getScriptManager().getGroupInstanceById(group.id);
var cachedInstance = this.getScriptManager().getCachedGroupInstanceById(group.id);
if (cachedInstance != null) {
cachedInstance.setLuaGroup(group);
groupInstance = cachedInstance;
}
// Load garbages // Load garbages
List<SceneGadget> garbageGadgets = group.getGarbageGadgets(); var garbageGadgets = group.getGarbageGadgets();
if (garbageGadgets != null) { if (garbageGadgets != null) {
entities.addAll( entities.addAll(garbageGadgets.stream()
garbageGadgets.stream() .map(g -> scriptManager.createGadget(group.id, group.block_id, g))
.map(g -> scriptManager.createGadget(group.id, group.block_id, g)) .filter(Objects::nonNull).toList());
.filter(Objects::nonNull)
.toList());
} }
// Load suites // Load suites
int suite = group.init_config.suite; //int suite = group.findInitSuiteIndex(0);
this.getScriptManager().refreshGroup(groupInstance, 0, false); //This is what the official server does
if (suite == 0 || group.suites == null || group.suites.size() == 0) { this.loadedGroups.add(group);
continue;
}
// just load the 'init' suite, avoid spawn the suite added by AddExtraGroupSuite etc.
var suiteData = group.getSuiteByIndex(suite);
suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger);
entities.addAll(scriptManager.getGadgetsInGroupSuite(group, suiteData));
entities.addAll(scriptManager.getMonstersInGroupSuite(group, suiteData));
scriptManager.registerRegionInGroupSuite(group, suiteData);
} }
scriptManager.meetEntities(entities); this.scriptManager.meetEntities(entities);
// scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null); groups.forEach(g -> scriptManager.callEvent(new ScriptArgs(g.id, EventType.EVENT_GROUP_LOAD, g.id)));
// groups.forEach(g -> scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null));
Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size());
} }
@ -718,6 +935,47 @@ public class Scene {
scriptManager.getLoadedGroupSetPerBlock().remove(block.id); scriptManager.getLoadedGroupSetPerBlock().remove(block.id);
Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id);
} }
/**
* Unloads a Lua group.
*
* @param block The block that contains the group.
* @param groupId The group ID.
*/
public void unloadGroup(SceneBlock block, int groupId) {
var toRemove = this.getEntities().values().stream()
.filter(e -> e != null && (
e.getBlockId() == block.id &&
e.getGroupId() == groupId)
).toList();
if (toRemove.size() > 0) {
toRemove.forEach(this::removeEntityDirectly);
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE));
}
var group = block.groups.get(groupId);
if (group.triggers != null) {
group.triggers.values().forEach(
this.getScriptManager()::deregisterTrigger);
}
if (group.regions != null) {
group.regions.values().forEach(
this.getScriptManager()::deregisterRegion);
}
this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group);
this.loadedGroups.remove(group);
if (this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).isEmpty()) {
this.scriptManager.getLoadedGroupSetPerBlock().remove(block.id);
Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id);
}
this.broadcastPacket(new PacketGroupUnloadNotify(List.of(groupId)));
this.scriptManager.unregisterGroup(group);
}
// Gadgets // Gadgets
public void onPlayerCreateGadget(EntityClientGadget gadget) { public void onPlayerCreateGadget(EntityClientGadget gadget) {
@ -738,7 +996,7 @@ public class Scene {
public void onPlayerDestroyGadget(int entityId) { public void onPlayerDestroyGadget(int entityId) {
GameEntity entity = getEntities().get(entityId); GameEntity entity = getEntities().get(entityId);
if (entity == null || !(entity instanceof EntityClientGadget gadget)) { if (!(entity instanceof EntityClientGadget gadget)) {
return; return;
} }
@ -834,18 +1092,38 @@ public class Scene {
return; return;
} }
sceneGroupSuite.forEach( sceneGroupSuite.forEach(i -> {
i -> { var group = scriptManager.getGroupById(i.getGroup());
var group = scriptManager.getGroupById(i.getGroup()); if (group == null) return;
if (group == null) {
return; var groupInstance = scriptManager.getGroupInstanceById(i.getGroup());
} var suite = group.getSuiteByIndex(i.getSuite());
var suite = group.getSuiteByIndex(i.getSuite()); if (suite == null || groupInstance == null) {
if (suite == null) { return;
return; }
}
scriptManager.addGroupSuite(group, suite); scriptManager.refreshGroup(groupInstance, i.getSuite(), false);
}); });
}
/**
* Adds an unlocked force to the scene.
*
* @param force The ID of the force to unlock.
*/
public void unlockForce(int force) {
this.unlockedForces.add(force);
this.broadcastPacket(new PacketSceneForceUnlockNotify(force, true));
}
/**
* Removes an unlocked force from the scene.
*
* @param force The ID of the force to lock.
*/
public void lockForce(int force) {
this.unlockedForces.remove(force);
this.broadcastPacket(new PacketSceneForceLockNotify(force));
} }
public void selectWorktopOptionWith(SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq req) { public void selectWorktopOptionWith(SelectWorktopOptionReqOuterClass.SelectWorktopOptionReq req) {

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,14 @@ package emu.grasscutter.scripts;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import java.util.HashMap; import java.util.HashMap;
import emu.grasscutter.utils.Position;
import lombok.val;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
public class ScriptUtils { public interface ScriptUtils {
static HashMap<Object, Object> toMap(LuaTable table) {
public static HashMap<Object, Object> toMap(LuaTable table) {
HashMap<Object, Object> map = new HashMap<>(); HashMap<Object, Object> map = new HashMap<>();
LuaValue[] rootKeys = table.keys(); LuaValue[] rootKeys = table.keys();
for (LuaValue k : rootKeys) { for (LuaValue k : rootKeys) {
@ -20,7 +22,45 @@ public class ScriptUtils {
return map; return map;
} }
public static void print(LuaTable table) { static void print(LuaTable table) {
Grasscutter.getLogger().info(toMap(table).toString()); 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

@ -12,4 +12,5 @@ public class SceneConfig {
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

@ -3,28 +3,33 @@ 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 java.util.Optional;
import java.util.stream.Collectors;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
@ToString @ToString
@Setter @Setter
public class SceneGroup { public final class SceneGroup {
public transient int public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference
block_id; // Not an actual variable in the scripts but we will keep it here for reference
public int id; public int id;
public int refresh_id; public int refresh_id;
public Position pos; public Position pos;
public Map<Integer, SceneMonster> monsters; // <ConfigId, Monster> public Map<Integer, SceneMonster> monsters; // <ConfigId, Monster>
public Map<Integer, SceneNPC> npcs; // <ConfigId, Npc>
public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets> public Map<Integer, SceneGadget> gadgets; // <ConfigId, Gadgets>
public Map<String, SceneTrigger> triggers; public Map<String, SceneTrigger> triggers;
public Map<Integer, SceneRegion> regions; public Map<Integer, SceneRegion> regions;
@ -34,11 +39,13 @@ public class SceneGroup {
public SceneBusiness business; public SceneBusiness business;
public SceneGarbage garbages; public SceneGarbage garbages;
public SceneInitConfig init_config; public SceneInitConfig init_config;
@Getter public boolean dynamic_load = false;
public SceneReplaceable is_replaceable;
private transient boolean loaded; // Not an actual variable in the scripts either private transient boolean loaded; // Not an actual variable in the scripts either
private transient CompiledScript script; private transient CompiledScript script;
private transient Bindings bindings; private transient Bindings bindings;
public static SceneGroup of(int groupId) { public static SceneGroup of(int groupId) {
var group = new SceneGroup(); var group = new SceneGroup();
group.id = groupId; group.id = groupId;
@ -66,6 +73,9 @@ public class SceneGroup {
} }
public SceneSuite getSuiteByIndex(int index) { public SceneSuite getSuiteByIndex(int index) {
if(index < 1 || index > suites.size()) {
return null;
}
return this.suites.get(index - 1); return this.suites.get(index - 1);
} }
@ -82,9 +92,7 @@ public class SceneGroup {
this.bindings = ScriptLoader.getEngine().createBindings(); this.bindings = ScriptLoader.getEngine().createBindings();
CompiledScript cs = CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua");
ScriptLoader.getScript(
"Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua");
if (cs == null) { if (cs == null) {
return this; return this;
@ -97,74 +105,79 @@ public class SceneGroup {
cs.eval(this.bindings); cs.eval(this.bindings);
// Set // Set
this.monsters = this.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, this.bindings.get("monsters")).stream()
ScriptLoader.getSerializer() .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
.toList(SceneMonster.class, this.bindings.get("monsters"))
.stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.monsters.values().forEach(m -> m.group = this); this.monsters.values().forEach(m -> m.group = this);
this.gadgets = this.npcs = ScriptLoader.getSerializer().toList(SceneNPC.class, this.bindings.get("npcs")).stream()
ScriptLoader.getSerializer() .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
.toList(SceneGadget.class, this.bindings.get("gadgets")) this.npcs.values().forEach(m -> m.group = this);
.stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a)); this.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, this.bindings.get("gadgets")).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
this.gadgets.values().forEach(m -> m.group = this); this.gadgets.values().forEach(m -> m.group = this);
this.triggers = this.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, this.bindings.get("triggers")).stream()
ScriptLoader.getSerializer() .collect(Collectors.toMap(SceneTrigger::getName, y -> y, (a, b) -> a));
.toList(SceneTrigger.class, this.bindings.get("triggers"))
.stream()
.collect(Collectors.toMap(x -> x.name, y -> y, (a, b) -> a));
this.triggers.values().forEach(t -> t.currentGroup = this); this.triggers.values().forEach(t -> t.currentGroup = this);
this.suites = this.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites"));
ScriptLoader.getSerializer().toList(SceneSuite.class, this.bindings.get("suites")); this.regions = ScriptLoader.getSerializer().toList(SceneRegion.class, this.bindings.get("regions")).stream()
this.regions = .collect(Collectors.toMap(x -> x.config_id, y -> y, (a, b) -> a));
ScriptLoader.getSerializer()
.toList(SceneRegion.class, this.bindings.get("regions"))
.stream()
.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 = this.init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, this.bindings.get("init_config"));
ScriptLoader.getSerializer()
.toObject(SceneInitConfig.class, this.bindings.get("init_config"));
// Garbages // TODO: fix properly later // Garbages // TODO: fix properly later
Object garbagesValue = this.bindings.get("garbages"); Object garbagesValue = this.bindings.get("garbages");
if (garbagesValue instanceof LuaValue garbagesTable) { if (garbagesValue instanceof LuaValue garbagesTable) {
this.garbages = new SceneGarbage(); this.garbages = new SceneGarbage();
if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) { if (garbagesTable.checktable().get("gadgets") != LuaValue.NIL) {
this.garbages.gadgets = this.garbages.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable());
ScriptLoader.getSerializer()
.toList(
SceneGadget.class, garbagesTable.checktable().get("gadgets").checktable());
this.garbages.gadgets.forEach(m -> m.group = this); this.garbages.gadgets.forEach(m -> m.group = this);
} }
} }
// Add variables to suite // Add variables to suite
this.variables = this.variables = ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables"));
ScriptLoader.getSerializer().toList(SceneVar.class, this.bindings.get("variables"));
// Add monsters and gadgets to suite // Add monsters and gadgets to suite
this.suites.forEach(i -> i.init(this)); this.suites.forEach(i -> i.init(this));
} catch (ScriptException e) { } catch (ScriptException e) {
Grasscutter.getLogger() Grasscutter.getLogger().error("An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e);
.error(
"An error occurred while loading group " + this.id + " in scene " + sceneId + ".", e);
} }
Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId); Grasscutter.getLogger().debug("Successfully loaded group {} in scene {}.", this.id, sceneId);
return this; return this;
} }
public int findInitSuiteIndex(int exclude_index) { //TODO: Investigate end index
if (init_config == null) return 1;
if (init_config.io_type == 1) return init_config.suite; //IO TYPE FLOW
if (init_config.rand_suite) {
if (suites.size() == 1) {
return init_config.suite;
} else {
List<Integer> randSuiteList = new ArrayList<>();
for (int i = 0; i < suites.size(); i++) {
if (i == exclude_index) continue;
var suite = suites.get(i);
for(int j = 0; j < suite.rand_weight; j++) randSuiteList.add(i);
}
return randSuiteList.get(new Random().nextInt(randSuiteList.size()));
}
}
return init_config.suite;
}
public Optional<SceneBossChest> searchBossChestInGroup() { public Optional<SceneBossChest> searchBossChestInGroup() {
return this.gadgets.values().stream() return this.gadgets.values().stream()
.filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0) .filter(g -> g.boss_chest != null && g.boss_chest.monster_config_id > 0)
.map(g -> g.boss_chest) .map(g -> g.boss_chest)
.findFirst(); .findFirst();
} }
} }

View File

@ -5,8 +5,9 @@ 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 int io_type;
public boolean rand_suite; public boolean rand_suite;
} }

View File

@ -5,15 +5,11 @@ 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 String drop_tag;
public int climate_area_id;
public boolean disableWander; public boolean disableWander;
public int title_id; public int title_id;
public int[] affix; public int special_name_id;
public int mark_flag;
} }

View File

@ -6,10 +6,11 @@ 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 pos;
public Position rot; public Position rot;

View File

@ -2,6 +2,7 @@ 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.Setter;
import lombok.ToString; import lombok.ToString;
@ -14,7 +15,8 @@ public class SceneSuite {
public List<String> triggers = List.of(); public List<String> triggers = List.of();
public List<Integer> regions = List.of(); public List<Integer> regions = List.of();
public int rand_weight; public int rand_weight;
public int[] npcs;
public boolean ban_refresh = false;
public transient List<SceneMonster> sceneMonsters = List.of(); public transient List<SceneMonster> sceneMonsters = List.of();
public transient List<SceneGadget> sceneGadgets = List.of(); public transient List<SceneGadget> sceneGadgets = List.of();
@ -22,39 +24,40 @@ public class SceneSuite {
public transient List<SceneRegion> sceneRegions = List.of(); public transient List<SceneRegion> sceneRegions = List.of();
public void init(SceneGroup sceneGroup) { public void init(SceneGroup sceneGroup) {
if (sceneGroup.monsters != null && this.monsters != null) { if(sceneGroup.monsters != null){
this.sceneMonsters = this.sceneMonsters = new ArrayList<>(
new ArrayList<>( this.monsters.stream()
this.monsters.stream() .filter(sceneGroup.monsters::containsKey)
.filter(sceneGroup.monsters::containsKey) .map(sceneGroup.monsters::get)
.map(sceneGroup.monsters::get) .toList()
.toList()); );
} }
if (sceneGroup.gadgets != null && this.gadgets != null) { if(sceneGroup.gadgets != null){
this.sceneGadgets = this.sceneGadgets = new ArrayList<>(
new ArrayList<>( this.gadgets.stream()
this.gadgets.stream() .filter(sceneGroup.gadgets::containsKey)
.filter(sceneGroup.gadgets::containsKey) .map(sceneGroup.gadgets::get)
.map(sceneGroup.gadgets::get) .toList()
.toList()); );
} }
if (sceneGroup.triggers != null && this.triggers != null) { if(sceneGroup.triggers != null) {
this.sceneTriggers = this.sceneTriggers = new ArrayList<>(
new ArrayList<>( this.triggers.stream()
this.triggers.stream() .filter(sceneGroup.triggers::containsKey)
.filter(sceneGroup.triggers::containsKey) .map(sceneGroup.triggers::get)
.map(sceneGroup.triggers::get) .toList()
.toList()); );
} }
if (sceneGroup.regions != null && this.regions != null) { if(sceneGroup.regions != null) {
this.sceneRegions = this.sceneRegions = new ArrayList<>(
new ArrayList<>( this.regions.stream()
this.regions.stream() .filter(sceneGroup.regions::containsKey)
.filter(sceneGroup.regions::containsKey) .map(sceneGroup.regions::get)
.map(sceneGroup.regions::get) .toList()
.toList()); );
} }
} }
} }

View File

@ -1,27 +1,28 @@
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;
private String tag;
public transient SceneGroup currentGroup; public transient SceneGroup currentGroup;
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof SceneTrigger sceneTrigger) { if (obj instanceof SceneTrigger sceneTrigger){
return this.name.equals(sceneTrigger.name); return this.name.equals(sceneTrigger.name);
} } else return super.equals(obj);
return super.equals(obj);
} }
@Override @Override
@ -31,29 +32,14 @@ public class SceneTrigger {
@Override @Override
public String toString() { public String toString() {
return "SceneTrigger{" return "SceneTrigger{" +
+ "name='" "name='" + name + '\'' +
+ name ", config_id=" + config_id +
+ '\'' ", event=" + event +
+ ", config_id=" ", source='" + source + '\'' +
+ config_id ", condition='" + condition + '\'' +
+ ", event=" ", action='" + action + '\'' +
+ event ", trigger_count='" + trigger_count + '\'' +
+ ", source='" '}';
+ source
+ '\''
+ ", condition='"
+ condition
+ '\''
+ ", action='"
+ action
+ '\''
+ ", forbid_guest='"
+ forbid_guest
+ '\''
+ ", trigger_count='"
+ trigger_count
+ '\''
+ '}';
} }
} }

View File

@ -6,16 +6,23 @@ public class ScriptArgs {
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 String source; // source string, used for timers
public int type; // lua event type, used by scripts and the ScriptManager
public ScriptArgs() {} public ScriptArgs(int groupId, int eventType) {
this(groupId, eventType, 0,0);
public ScriptArgs(int param1) {
this.param1 = param1;
} }
public ScriptArgs(int param1, int param2) { public ScriptArgs(int groupId, int eventType, int param1) {
this(groupId, eventType, param1,0);
}
public ScriptArgs(int groupId, int eventType, int param1, int param2) {
this.type = eventType;
this.param1 = param1; this.param1 = param1;
this.param2 = param2; this.param2 = param2;
this.group_id = groupId;
} }
public int getParam1() { public int getParam1() {
@ -62,4 +69,22 @@ public class ScriptArgs {
this.target_eid = target_eid; this.target_eid = target_eid;
return this; 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

@ -7,11 +7,13 @@ 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class ScriptMonsterTideService { public final class ScriptMonsterTideService {
private final SceneScriptManager sceneScriptManager; private final SceneScriptManager sceneScriptManager;
private final SceneGroup currentGroup; private final SceneGroup currentGroup;
private final AtomicInteger monsterAlive; private final AtomicInteger monsterAlive;
@ -19,15 +21,12 @@ public class ScriptMonsterTideService {
private final AtomicInteger monsterKillCount; private final AtomicInteger monsterKillCount;
private final int monsterSceneLimit; private final int monsterSceneLimit;
private final ConcurrentLinkedQueue<Integer> monsterConfigOrders; private final ConcurrentLinkedQueue<Integer> monsterConfigOrders;
private final OnMonsterCreated onMonsterCreated = new OnMonsterCreated(); private final List<Integer> monsterConfigIds;
private final OnMonsterDead onMonsterDead = new OnMonsterDead(); private final OnMonsterCreated onMonsterCreated= new OnMonsterCreated();
private final OnMonsterDead onMonsterDead= new OnMonsterDead();
public ScriptMonsterTideService( public ScriptMonsterTideService(SceneScriptManager sceneScriptManager,
SceneScriptManager sceneScriptManager, SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){
SceneGroup group,
int tideCount,
int monsterSceneLimit,
Integer[] ordersConfigId) {
this.sceneScriptManager = sceneScriptManager; this.sceneScriptManager = sceneScriptManager;
this.currentGroup = group; this.currentGroup = group;
this.monsterSceneLimit = monsterSceneLimit; this.monsterSceneLimit = monsterSceneLimit;
@ -35,44 +34,35 @@ public class ScriptMonsterTideService {
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()
.addMonsterCreatedListener(onMonsterCreated);
this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead);
// spawn the first turn // spawn the first turn
for (int i = 0; i < this.monsterSceneLimit; i++) { for (int i = 0; i < this.monsterSceneLimit; i++) {
sceneScriptManager.addEntity( sceneScriptManager.addEntity(this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster()));
this.sceneScriptManager.createMonster(group.id, group.block_id, getNextMonster()));
} }
} }
public SceneMonster getNextMonster() { public class OnMonsterCreated implements ScriptMonsterListener{
var nextId = this.monsterConfigOrders.poll();
if (currentGroup.monsters.containsKey(nextId)) {
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 void unload() {
this.sceneScriptManager
.getScriptMonsterSpawnService()
.removeMonsterCreatedListener(onMonsterCreated);
this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead);
}
public class OnMonsterCreated implements ScriptMonsterListener {
@Override @Override
public void onNotify(EntityMonster sceneMonster) { public void onNotify(EntityMonster sceneMonster) {
if (monsterSceneLimit > 0) { if(monsterConfigIds.contains(sceneMonster.getConfigId()) && monsterSceneLimit > 0){
monsterAlive.incrementAndGet(); monsterAlive.incrementAndGet();
monsterTideCount.decrementAndGet(); monsterTideCount.decrementAndGet();
} }
} }
} }
public SceneMonster getNextMonster(){
var nextId = this.monsterConfigOrders.poll();
if(currentGroup.monsters.containsKey(nextId)){
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 class OnMonsterDead implements ScriptMonsterListener { public class OnMonsterDead implements ScriptMonsterListener {
@Override @Override
public void onNotify(EntityMonster sceneMonster) { public void onNotify(EntityMonster sceneMonster) {
@ -86,14 +76,17 @@ public class ScriptMonsterTideService {
monsterKillCount.incrementAndGet(); monsterKillCount.incrementAndGet();
if (monsterTideCount.get() > 0) { if (monsterTideCount.get() > 0) {
// add more // add more
sceneScriptManager.addEntity( sceneScriptManager.addEntity(sceneScriptManager.createMonster(currentGroup.id, currentGroup.block_id, getNextMonster()));
sceneScriptManager.createMonster(
currentGroup.id, currentGroup.block_id, getNextMonster()));
} }
// spawn the last turn of monsters // spawn the last turn of monsters
// fix the 5-2 // fix the 5-2
sceneScriptManager.callEvent( sceneScriptManager.callEvent(new ScriptArgs(currentGroup.id, EventType.EVENT_MONSTER_TIDE_DIE, monsterKillCount.get()));
EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(monsterKillCount.get()));
} }
}
public void unload(){
this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterCreatedListener(onMonsterCreated);
this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead);
} }
} }

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.game.props.ElementType;
import emu.grasscutter.server.event.Cancellable; import emu.grasscutter.server.event.Cancellable;
import emu.grasscutter.server.event.types.EntityEvent; import emu.grasscutter.server.event.types.EntityEvent;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public final class EntityDamageEvent extends EntityEvent implements Cancellable { public final class EntityDamageEvent extends EntityEvent implements Cancellable {
@Nullable private final GameEntity damager; @Getter @Setter private float damage;
private float damage; @Getter @Setter private ElementType attackElementType;
@Getter @Nullable private final GameEntity damager;
public EntityDamageEvent(GameEntity entity, float damage, @Nullable GameEntity damager) { public EntityDamageEvent(GameEntity entity, float damage, ElementType attackElementType, @Nullable GameEntity damager) {
super(entity); super(entity);
this.damage = damage; this.damage = damage;
this.attackElementType = attackElementType;
this.damager = damager; this.damager = damager;
} }
public float getDamage() {
return this.damage;
}
public void setDamage(float damage) {
this.damage = damage;
}
@Nullable public GameEntity getDamager() {
return this.damager;
}
} }

View File

@ -13,7 +13,6 @@ 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;
@ -91,7 +90,6 @@ public final class GameServer extends KcpServer {
this.init(GameSessionManager.getListener(), channelConfig, address); this.init(GameSessionManager.getListener(), channelConfig, address);
DungeonChallenge.initialize();
EnergyManager.initialize(); EnergyManager.initialize();
StaminaManager.initialize(); StaminaManager.initialize();
CookingManager.initialize(); CookingManager.initialize();

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.game.quest.enums.QuestTrigger;
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.AddQuestContentProgressReqOuterClass; import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp;
import java.util.List;
import java.util.stream.Stream;
@Opcodes(PacketOpcodes.AddQuestContentProgressReq) @Opcodes(PacketOpcodes.AddQuestContentProgressReq)
public class HandlerAddQuestContentProgressReq extends PacketHandler { public class HandlerAddQuestContentProgressReq 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 req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload); var req = AddQuestContentProgressReq.parseFrom(payload);
// Find all conditions in quest that are the same as the given one // Find all conditions in quest that are the same as the given one
Stream<QuestCondition> finishCond = var type = QuestContent.getContentTriggerByValue(req.getContentType());
GameData.getQuestDataMap().get(req.getParam()).getFinishCond().stream(); if(type != null) {
Stream<QuestCondition> acceptCond = session.getPlayer().getQuestManager().queueEvent(type, req.getParam());
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())); 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.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.world.WorldAreaData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
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.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq; import emu.grasscutter.net.proto.AvatarChangeElementTypeReqOuterClass.AvatarChangeElementTypeReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp; import emu.grasscutter.server.packet.send.PacketAvatarChangeElementTypeRsp;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify; import lombok.val;
import emu.grasscutter.server.packet.send.PacketAvatarSkillDepotChangeNotify;
/**
* Changes the currently active avatars Element if possible
*/
@Opcodes(PacketOpcodes.AvatarChangeElementTypeReq) @Opcodes(PacketOpcodes.AvatarChangeElementTypeReq)
public class HandlerAvatarChangeElementTypeReq extends PacketHandler { public class HandlerAvatarChangeElementTypeReq 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 {
AvatarChangeElementTypeReq req = AvatarChangeElementTypeReq.parseFrom(payload); var req = AvatarChangeElementTypeReq.parseFrom(payload);
var area = GameData.getWorldAreaDataMap().get(req.getAreaId());
WorldAreaData area = GameData.getWorldAreaDataMap().get(req.getAreaId()); if (area == null || area.getElementType() == null || area.getElementType().getDepotIndex() <= 0) {
if (area == null
|| area.getElementType() == null
|| area.getElementType().getDepotValue() <= 0) {
session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE)); session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return; return;
} }
// Get current avatar, should be one of the main characters val avatar = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar();
EntityAvatar mainCharacterEntity = if (!avatar.changeElement(area.getElementType())) {
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)); session.send(new PacketAvatarChangeElementTypeRsp(Retcode.RET_SVR_ERROR_VALUE));
return; return;
} }
// Set skill depot
mainCharacter.setSkillDepotData(skillDepot);
// Success // Success
session.send(new PacketAvatarChangeElementTypeRsp()); session.send(new PacketAvatarChangeElementTypeRsp());
// Ability change packet
session.send(new PacketAvatarSkillDepotChangeNotify(mainCharacter));
session.send(new PacketAbilityChangeNotify(mainCharacterEntity));
session.send(new PacketAvatarFightPropNotify(mainCharacter));
} }
} }

View File

@ -116,7 +116,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
} }
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

View File

@ -1,5 +1,6 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.quest.enums.QuestContent;
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;
@ -23,6 +24,6 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
// Handle skill notify in other managers. // Handle skill notify in other managers.
player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
player.getQuestManager().triggerEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0); player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId, 0);
} }
} }

View File

@ -1,5 +1,6 @@
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.quest.enums.QuestContent;
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;
@ -16,7 +17,7 @@ public class HandlerGadgetInteractReq extends PacketHandler {
session session
.getPlayer() .getPlayer()
.getQuestManager() .getQuestManager()
.triggerEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId()); .queueEvent(QuestContent.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId());
session.getPlayer().interactWith(req.getGadgetEntityId(), req); session.getPlayer().interactWith(req.getGadgetEntityId(), req);
} }
} }

View File

@ -7,61 +7,60 @@ 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.net.proto.RetcodeOuterClass;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketActivityInfoNotify; import emu.grasscutter.server.packet.send.PacketActivityInfoNotify;
import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp; import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp;
import lombok.val;
@Opcodes(PacketOpcodes.MusicGameSettleReq) @Opcodes(PacketOpcodes.MusicGameSettleReq)
public class HandlerMusicGameSettleReq extends PacketHandler { public class HandlerMusicGameSettleReq 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 req = MusicGameSettleReqOuterClass.MusicGameSettleReq.parseFrom(payload); val req = MusicGameSettleReq.parseFrom(payload);
var playerData = val activityManager = session.getPlayer().getActivityManager();
session
.getPlayer() val playerDataOpt = activityManager.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME);
.getActivityManager() if (playerDataOpt.isEmpty()) {
.getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME); session.send(new PacketMusicGameSettleRsp(RetcodeOuterClass.Retcode.RET_MUSIC_GAME_LEVEL_CONFIG_NOT_FOUND, req));
if (playerData.isEmpty()) {
return; return;
} }
var handler = (MusicGameActivityHandler) playerData.get().getActivityHandler();
val playerData = playerDataOpt.get();
val handler = (MusicGameActivityHandler) playerData.getActivityHandler();
boolean isNewRecord = false; boolean isNewRecord = false;
// check if custom beatmap // check if custom beatmap
if (req.getUgcGuid() == 0) { if (req.getUgcGuid() == 0) {
session session.getPlayer().getActivityManager().triggerWatcher(
.getPlayer() WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE,
.getActivityManager() String.valueOf(req.getMusicBasicId()),
.triggerWatcher( String.valueOf(req.getScore())
WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE, );
String.valueOf(req.getMusicBasicId()),
String.valueOf(req.getScore()));
isNewRecord = isNewRecord = handler.setMusicGameRecord(playerData,
handler.setMusicGameRecord( MusicGamePlayerData.MusicGameRecord.of()
playerData.get(), .musicId(req.getMusicBasicId())
MusicGamePlayerData.MusicGameRecord.of() .maxCombo(req.getMaxCombo())
.musicId(req.getMusicBasicId()) .maxScore(req.getScore())
.maxCombo(req.getMaxCombo()) .build());
.maxScore(req.getScore())
.build());
// update activity info // update activity info
session.send(new PacketActivityInfoNotify(handler.toProto(playerData.get()))); session.send(new PacketActivityInfoNotify(handler.toProto(playerData, activityManager.getConditionExecutor())));
} else { } else {
handler.setMusicGameCustomBeatmapRecord( handler.setMusicGameCustomBeatmapRecord(playerData,
playerData.get(), MusicGamePlayerData.CustomBeatmapRecord.of()
MusicGamePlayerData.CustomBeatmapRecord.of() .musicShareId(req.getUgcGuid())
.musicShareId(req.getUgcGuid()) .score(req.getMaxCombo())
.score(req.getMaxCombo()) .settle(req.getIsSaveScore())
// .settle(req.getSuccess()) .build());
.build());
} }
session.send(
new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord)); session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getUgcGuid(), isNewRecord));
} }
} }

View File

@ -3,6 +3,7 @@ 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.game.quest.enums.QuestContent;
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;
@ -49,16 +50,16 @@ public class HandlerNpcTalkReq extends PacketHandler {
session session
.getPlayer() .getPlayer()
.getQuestManager() .getQuestManager()
.triggerEvent( .queueEvent(
QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0); QuestContent.QUEST_CONTENT_COMPLETE_ANY_TALK, String.valueOf(req.getTalkId()), 0, 0);
session session
.getPlayer() .getPlayer()
.getQuestManager() .getQuestManager()
.triggerEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0); .queueEvent(QuestContent.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(), 0);
session session
.getPlayer() .getPlayer()
.getQuestManager() .getQuestManager()
.triggerEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(), 0); .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,6 +1,7 @@
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.game.quest.enums.QuestContent;
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;
@ -16,7 +17,7 @@ public class HandlerPostEnterSceneReq extends PacketHandler {
session session
.getPlayer() .getPlayer()
.getQuestManager() .getQuestManager()
.triggerEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0); .queueEvent(QuestContent.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(), 0);
} }
session.send(new PacketPostEnterSceneRsp(session.getPlayer())); session.send(new PacketPostEnterSceneRsp(session.getPlayer()));

View File

@ -2,12 +2,13 @@ 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.quest.enums.QuestContent;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
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.net.packet.PacketHandler;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp; import emu.grasscutter.server.packet.send.PacketSelectWorktopOptionRsp;
@ -25,16 +26,14 @@ public class HandlerSelectWorktopOptionReq extends PacketHandler {
return; return;
} }
session.getPlayer().getScene().selectWorktopOptionWith(req); session.getPlayer().getScene().selectWorktopOptionWith(req);
session session.getPlayer().getScene().getScriptManager().callEvent(
.getPlayer() new ScriptArgs(entity.getGroupId(), EventType.EVENT_SELECT_OPTION, entity.getConfigId(), req.getOptionId())
.getScene() );
.getScriptManager() session.getPlayer().getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_WORKTOP_SELECT, entity.getConfigId(), req.getOptionId());
.callEvent(
EventType.EVENT_SELECT_OPTION,
new ScriptArgs(entity.getConfigId(), req.getOptionId()));
} finally { } finally {
// Always send packet // Always send packet
session.send(new PacketSelectWorktopOptionRsp(req.getGadgetEntityId(), req.getOptionId())); 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.game.entity.gadget.platform.AbilityRoute;
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.UpdateAbilityCreatedMovingPlatformNotifyOuterClass; import emu.grasscutter.net.proto.UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify;
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 = var notify = UpdateAbilityCreatedMovingPlatformNotify.parseFrom(payload);
UpdateAbilityCreatedMovingPlatformNotifyOuterClass.UpdateAbilityCreatedMovingPlatformNotify
.parseFrom(payload);
var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId()); var entity = session.getPlayer().getScene().getEntityById(notify.getEntityId());
if (!(entity instanceof EntityPlatform)) { if (!(entity instanceof EntityGadget entityGadget) || !(entityGadget.getRouteConfig() instanceof AbilityRoute)) {
return; return;
} }
var scene = ((EntityPlatform) entity).getOwner().getScene();
switch (notify.getOpType()) { switch (notify.getOpType()) {
case OP_TYPE_ACTIVATE -> scene.broadcastPacket( case OP_TYPE_ACTIVATE -> entityGadget.startPlatform();
new PacketPlatformStartRouteNotify((EntityPlatform) entity, scene)); case OP_TYPE_DEACTIVATE -> entityGadget.stopPlatform();
case OP_TYPE_DEACTIVATE -> scene.broadcastPacket(
new PacketPlatformStopRouteNotify((EntityPlatform) entity, scene));
} }
} }
} }

View File

@ -11,7 +11,7 @@ public class PacketChangeGameTimeRsp extends BasePacket {
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;
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify;
public class PacketDungeonSettleNotify extends BasePacket { public class PacketDungeonSettleNotify extends BasePacket {
public PacketDungeonSettleNotify(BaseDungeonResult result) {
public PacketDungeonSettleNotify(WorldChallenge challenge) {
super(PacketOpcodes.DungeonSettleNotify); super(PacketOpcodes.DungeonSettleNotify);
DungeonSettleNotify proto = this.setData(result.getProto());
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

@ -6,8 +6,9 @@ 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 class PacketEntityFightPropUpdateNotify extends BasePacket {
public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) { public PacketEntityFightPropUpdateNotify(GameEntity entity, FightProperty prop) {
super(PacketOpcodes.EntityFightPropUpdateNotify); super(PacketOpcodes.EntityFightPropUpdateNotify);
@ -19,4 +20,14 @@ public class PacketEntityFightPropUpdateNotify extends BasePacket {
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

@ -15,9 +15,9 @@ public class PacketFinishedParentQuestNotify extends BasePacket {
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));
} }
} }

View File

@ -4,6 +4,7 @@ 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 {
@ -11,8 +12,9 @@ public class PacketFinishedParentQuestUpdateNotify extends BasePacket {
public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) {
super(PacketOpcodes.FinishedParentQuestUpdateNotify); super(PacketOpcodes.FinishedParentQuestUpdateNotify);
FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder()
FinishedParentQuestUpdateNotify.newBuilder().addParentQuestList(quest.toProto()).build(); .addParentQuestList(quest.toProto(true))
.build();
this.setData(proto); this.setData(proto);
} }
@ -23,7 +25,7 @@ public class PacketFinishedParentQuestUpdateNotify extends BasePacket {
var proto = FinishedParentQuestUpdateNotify.newBuilder(); var proto = FinishedParentQuestUpdateNotify.newBuilder();
for (GameMainQuest mainQuest : quests) { for (GameMainQuest mainQuest : quests) {
proto.addParentQuestList(mainQuest.toProto()); proto.addParentQuestList(mainQuest.toProto(true));
} }
proto.build(); proto.build();
this.setData(proto); 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

@ -2,7 +2,9 @@ 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.MusicGameSettleReqOuterClass;
import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass; import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass;
public class PacketMusicGameSettleRsp extends BasePacket { public class PacketMusicGameSettleRsp extends BasePacket {
@ -13,6 +15,18 @@ public class PacketMusicGameSettleRsp extends BasePacket {
proto.setMusicBasicId(musicBasicId).setUgcGuid(musicShareId).setIsNewRecord(isNewRecord); 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); this.setData(proto);
} }
} }

View File

@ -1,21 +1,19 @@
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; import emu.grasscutter.net.proto.PlatformStartRouteNotifyOuterClass.PlatformStartRouteNotify;
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,21 +1,19 @@
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(EntityPlatform entity, Scene scene) { public PacketPlatformStopRouteNotify(EntityGadget gadgetEntity) {
super(PacketOpcodes.PlatformStopRouteNotify); super(PacketOpcodes.PlatformStopRouteNotify);
var notify = var notify = PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder()
PlatformStopRouteNotifyOuterClass.PlatformStopRouteNotify.newBuilder() .setPlatform(gadgetEntity.getPlatformInfo())
.setPlatform(entity.onStopRoute()) .setSceneTime(gadgetEntity.getScene().getSceneTime())
.setSceneTime(scene.getSceneTime()) .setEntityId(gadgetEntity.getId())
.setEntityId(entity.getId()) .build();
.build();
this.setData(notify); this.setData(notify);
} }

View File

@ -12,7 +12,7 @@ public class PacketPlayerGameTimeNotify extends BasePacket {
PlayerGameTimeNotify proto = PlayerGameTimeNotify proto =
PlayerGameTimeNotify.newBuilder() PlayerGameTimeNotify.newBuilder()
.setGameTime(player.getScene().getTime()) .setGameTime(player.getWorld().getGameTime())
.setUid(player.getUid()) .setUid(player.getUid())
.build(); .build();

View File

@ -344,7 +344,7 @@ public final class Language {
"Cache modified %d, textmap modified %d".formatted(cacheModified, textmapsModified)); "Cache modified %d, textmap modified %d".formatted(cacheModified, textmapsModified));
if (textmapsModified < cacheModified) { if (textmapsModified < cacheModified) {
// Try loading from cache // Try loading from cache
Grasscutter.getLogger().info("Loading cached 'TextMaps'..."); Grasscutter.getLogger().debug("Loading cached 'TextMaps'...");
textMapStrings = loadTextMapsCache(); textMapStrings = loadTextMapsCache();
return; return;
} }