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
97 changed files with 14397 additions and 12921 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,65 +1,90 @@
package emu.grasscutter.scripts.data;
public class ScriptArgs {
public int param1;
public int param2;
public int param3;
public int source_eid; // Source entity
public int target_eid;
public ScriptArgs() {}
public ScriptArgs(int param1) {
this.param1 = param1;
}
public ScriptArgs(int param1, int param2) {
this.param1 = param1;
this.param2 = param2;
}
public int getParam1() {
return param1;
}
public ScriptArgs setParam1(int param1) {
this.param1 = param1;
return this;
}
public int getParam2() {
return param2;
}
public ScriptArgs setParam2(int param2) {
this.param2 = param2;
return this;
}
public int getParam3() {
return param3;
}
public ScriptArgs setParam3(int param3) {
this.param3 = param3;
return this;
}
public int getSourceEntityId() {
return source_eid;
}
public ScriptArgs setSourceEntityId(int source_eid) {
this.source_eid = source_eid;
return this;
}
public int getTargetEntityId() {
return target_eid;
}
public ScriptArgs setTargetEntityId(int target_eid) {
this.target_eid = target_eid;
return this;
}
}
package emu.grasscutter.scripts.data;
public class ScriptArgs {
public int param1;
public int param2;
public int param3;
public int source_eid; // Source entity
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(int groupId, int eventType) {
this(groupId, eventType, 0,0);
}
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.param2 = param2;
this.group_id = groupId;
}
public int getParam1() {
return param1;
}
public ScriptArgs setParam1(int param1) {
this.param1 = param1;
return this;
}
public int getParam2() {
return param2;
}
public ScriptArgs setParam2(int param2) {
this.param2 = param2;
return this;
}
public int getParam3() {
return param3;
}
public ScriptArgs setParam3(int param3) {
this.param3 = param3;
return this;
}
public int getSourceEntityId() {
return source_eid;
}
public ScriptArgs setSourceEntityId(int source_eid) {
this.source_eid = source_eid;
return this;
}
public int getTargetEntityId() {
return target_eid;
}
public ScriptArgs setTargetEntityId(int target_eid) {
this.target_eid = target_eid;
return this;
}
public String getEventSource() {
return source;
}
public ScriptArgs setEventSource(String source) {
this.source = source;
return this;
}
public int getGroupId() {
return group_id;
}
public ScriptArgs setGroupId(int group_id) {
this.group_id = group_id;
return this;
}
}

View File

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