Commit 30c7bb94 authored by Melledy's avatar Melledy
Browse files

Merge branch 'dev-world-scripts' of https://github.com/Grasscutters/Grasscutter into development

parents 8e6aa50c 5a3e9bc3
package emu.grasscutter.game.entity;
import java.util.Arrays;
import java.util.List;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.ClientGadgetInfoOuterClass;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
......@@ -23,15 +20,17 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.ToString;
@ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget {
private final GadgetData data;
private final Position pos;
......@@ -39,7 +38,9 @@ public class EntityGadget extends EntityBaseGadget {
private int gadgetId;
private int state;
private IntSet worktopOptions;
private int pointType;
private GadgetContent content;
private SceneGadget metaGadget;
public EntityGadget(Scene scene, int gadgetId, Position pos) {
super(scene);
......@@ -50,19 +51,22 @@ public class EntityGadget extends EntityBaseGadget {
this.rot = new Position();
}
public EntityGadget(Scene scene, int gadgetId, Position pos, GadgetContent content) {
this(scene, gadgetId, pos);
this.content = content;
}
public GadgetData getGadgetData() {
return data;
}
@Override
public Position getPosition() {
// TODO Auto-generated method stub
return this.pos;
}
@Override
public Position getRotation() {
// TODO Auto-generated method stub
return this.rot;
}
......@@ -82,33 +86,72 @@ public class EntityGadget extends EntityBaseGadget {
this.state = state;
}
public IntSet getWorktopOptions() {
return worktopOptions;
public void updateState(int state){
this.setState(state);
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId()));
}
public void addWorktopOptions(int[] options) {
if (this.worktopOptions == null) {
this.worktopOptions = new IntOpenHashSet();
public int getPointType() {
return pointType;
}
Arrays.stream(options).forEach(this.worktopOptions::add);
public void setPointType(int pointType) {
this.pointType = pointType;
}
public GadgetContent getContent() {
return content;
}
public void removeWorktopOption(int option) {
if (this.worktopOptions == null) {
@Deprecated // Dont use!
public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content;
}
public SceneGadget getMetaGadget() {
return metaGadget;
}
public void setMetaGadget(SceneGadget metaGadget) {
this.metaGadget = metaGadget;
}
// TODO refactor
public void buildContent() {
if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) {
return;
}
this.worktopOptions.remove(option);
EntityType type = getGadgetData().getType();
GadgetContent content = switch (type) {
case GatherPoint -> new GadgetGatherPoint(this);
case Worktop -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this);
default -> null;
};
this.content = content;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
// TODO Auto-generated method stub
return null;
}
@Override
public void onDeath(int killerId) {
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId()));
}
@Override
public void onDeath(int killerId) {
if(getScene().getChallenge() != null){
getScene().getChallenge().onGadgetDeath(this);
}
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
}
@Override
......@@ -143,15 +186,16 @@ public class EntityGadget extends EntityBaseGadget {
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.getGadgetData().getType() == EntityType.Worktop && this.getWorktopOptions() != null) {
WorktopInfo worktop = WorktopInfo.newBuilder()
.addAllOptionList(this.getWorktopOptions())
.build();
gadgetInfo.setWorktop(worktop);
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
public void die() {
getScene().broadcastPacket(new PacketLifeStateChangeNotify(this, LifeState.LIFE_DEAD));
this.onDeath(0);
}
}
......@@ -4,13 +4,11 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
......@@ -25,6 +23,7 @@ import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
......@@ -112,6 +111,12 @@ public class EntityMonster extends GameEntity {
this.poseId = poseId;
}
@Override
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
}
@Override
public void damage(float amount, int killerId) {
// Get HP before damage.
......@@ -135,8 +140,8 @@ public class EntityMonster extends GameEntity {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
// first set the challenge data
if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) {
getScene().getChallenge().onMonsterDie(this);
if (getScene().getChallenge() != null) {
getScene().getChallenge().onMonsterDeath(this);
}
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) {
if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){
......@@ -144,7 +149,9 @@ public class EntityMonster extends GameEntity {
}
// prevent spawn monster after success
if(getScene().getChallenge() != null && getScene().getChallenge().inProgress()){
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null);
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}else if(getScene().getChallenge() == null){
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}
}
}
......
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.scripts.data.SceneNPC;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityNPC extends GameEntity{
private final Position position;
private final Position rotation;
private final SceneNPC metaNpc;
private final int suiteId;
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.config_id);
setGroupId(metaNPC.group.id);
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.pos.clone();
this.rotation = metaNPC.rot.clone();
this.metaNpc = metaNPC;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return null;
}
@Override
public Position getPosition() {
return position;
}
@Override
public Position getRotation() {
return rotation;
}
public int getSuiteId() {
return suiteId;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority = EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo = SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_NPC)
.setMotionInfo(MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(VectorOuterClass.Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
entityInfo.setNpc(SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
.setNpcId(metaNpc.npc_id)
.setBlockId(getBlockId())
.build());
return entityInfo.build();
}
}
......@@ -108,10 +108,6 @@ public abstract class GameEntity {
this.lastMoveReliableSeq = lastMoveReliableSeq;
}
public abstract SceneEntityInfo toProto();
public abstract void onDeath(int killerId);
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
......@@ -219,4 +215,21 @@ public abstract class GameEntity {
getScene().killEntity(this, killerId);
}
}
/**
* Called when this entity is added to the world
*/
public void onCreate() {
}
/**
* Called when this entity dies
* @param killerId Entity id of the entity that killed this entity
*/
public void onDeath(int killerId) {
}
public abstract SceneEntityInfo toProto();
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import static emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType.INTER_OP_START;
public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if(handler == null){
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName());
return false;
}
if(opType == INTER_OP_START && handler.isTwoStep()){
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_CHEST, INTER_OP_START));
return false;
}else{
var success = handler.onInteract(this, player);
if (!success){
return false;
}
getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_OPEN_CHEST));
// let the chest disappear
getGadget().die();
return true;
}
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if(getGadget().getMetaGadget() == null){
return;
}
var bossChest = getGadget().getMetaGadget().boss_chest;
if(bossChest != null){
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest(BossChestInfo.newBuilder()
.setMonsterConfigId(bossChest.monster_config_id)
.setResin(bossChest.resin)
.addAllQualifyUidList(players)
.addAllRemainUidList(players)
.build());
}
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
private final EntityGadget gadget;
public GadgetContent(EntityGadget gadget) {
this.gadget = gadget;
}
public EntityGadget getGadget() {
return gadget;
}
public abstract boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.GatherData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public class GadgetGatherPoint extends GadgetContent {
private GatherData gatherData;
public GadgetGatherPoint(EntityGadget gadget) {
super(gadget);
this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
}
public GatherData getGatherData() {
return gatherData;
}
public int getItemId() {
return getGatherData().getItemId();
}
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
GameItem item = new GameItem(gatherData.getItemId(), 1);
player.getInventory().addItem(item, ActionReason.Gather);
return true;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId())
.setIsForbidGuest(this.getGatherData().isForbidGuest())
.build();
gadgetInfo.setGatherGadget(gatherGadgetInfo);
}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
if (player.getScene().getChallenge() != null && player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
dungeonChallenge.getStatueDrops(player);
}
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_OPEN_STATUE));
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
}
}
package emu.grasscutter.game.entity.gadget;
import java.util.Arrays;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.InterOpTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.WorktopInfoOuterClass.WorktopInfo;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class GadgetWorktop extends GadgetContent {
private IntSet worktopOptions;
public GadgetWorktop(EntityGadget gadget) {
super(gadget);
}
public IntSet getWorktopOptions() {
return worktopOptions;
}
public void addWorktopOptions(int[] options) {
if (this.worktopOptions == null) {
this.worktopOptions = new IntOpenHashSet();
}
Arrays.stream(options).forEach(this.worktopOptions::add);
}
public void removeWorktopOption(int option) {
if (this.worktopOptions == null) {
return;
}
this.worktopOptions.remove(option);
}
public boolean onInteract(Player player, InterOpTypeOuterClass.InterOpType opType) {
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (this.worktopOptions == null) {
return;
}
WorktopInfo worktop = WorktopInfo.newBuilder()
.addAllOptionList(this.getWorktopOptions())
.build();
gadgetInfo.setWorktop(worktop);
}
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import java.util.ArrayList;
import java.util.List;
public class BossChestInteractHandler implements ChestInteractHandler{
@Override
public boolean isTwoStep() {
return true;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataManager();
var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id);
var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if(reward == null){
Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id);
return false;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.OpenWorldBossChest);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
return true;
}
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
public interface ChestInteractHandler {
boolean isTwoStep();
boolean onInteract(GadgetChest chest, Player player);
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.ChestReward;
import java.util.Random;
public class NormalChestInteractHandler implements ChestInteractHandler {
private final ChestReward chestReward;
public NormalChestInteractHandler(ChestReward rewardData){
this.chestReward = rewardData;
}
@Override
public boolean isTwoStep() {
return false;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
player.earnExp(chestReward.getAdvExp());
player.getInventory().addItem(201, chestReward.getResin());
var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5);
player.getInventory().addItem(202, (int)mora);
for(int i=0;i<chestReward.getContent().size();i++){
chest.getGadget().getScene().addItemEntity(chestReward.getContent().get(i).getItemId(), chestReward.getContent().get(i).getCount(), chest.getGadget());
}
var random = new Random(System.currentTimeMillis());
for(int i=0;i<chestReward.getRandomCount();i++){
var index = random.nextInt(chestReward.getRandomContent().size());
var item = chestReward.getRandomContent().get(index);
chest.getGadget().getScene().addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
}
return true;
}
}
......@@ -37,7 +37,6 @@ import emu.grasscutter.game.managers.mapmark.*;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.QuestManager;
......@@ -988,7 +987,8 @@ public class Player {
return this.getMailHandler().replaceMailByIndex(index, message);
}
public void interactWith(int gadgetEntityId, GadgetInteractReq request) {
public void interactWith(int gadgetEntityId, InterOpTypeOuterClass.InterOpType opType) {
GameEntity entity = getScene().getEntityById(gadgetEntityId);
if (entity == null) {
return;
......@@ -1016,11 +1016,15 @@ public class Player {
}
}
} else if (entity instanceof EntityGadget gadget) {
if (gadget.getGadgetData().getType() == EntityType.RewardStatue) {
if (scene.getChallenge() != null) {
scene.getChallenge().getStatueDrops(this, request);
if (gadget.getContent() == null) {
return;
}
this.sendPacket(new PacketGadgetInteractRsp(gadget, InteractType.INTERACT_TYPE_OPEN_STATUE));
boolean shouldDelete = gadget.getContent().onInteract(this, opType);
if (shouldDelete) {
entity.getScene().removeEntity(entity);
}
} else if (entity instanceof EntityMonster monster) {
insectCaptureManager.arrestSmallCreature(monster);
......
package emu.grasscutter.game.world;
import emu.grasscutter.game.inventory.ItemDef;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ChestReward {
List<String> objNames;
int advExp;
int resin;
int mora;
int sigil;
List<ItemDef> content;
int randomCount;
List<ItemDef> randomContent;
}
package emu.grasscutter.game.world;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.dungeons.DungeonSettleListener;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.Player;
......@@ -13,24 +13,24 @@ import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.scripts.SceneScriptManager;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.danilopianini.util.SpatialIndex;
import java.util.*;
import java.util.stream.Collectors;
public class Scene {
private final World world;
......@@ -49,12 +49,11 @@ public class Scene {
private int weather;
private SceneScriptManager scriptManager;
private DungeonChallenge challenge;
private WorldChallenge challenge;
private List<DungeonSettleListener> dungeonSettleListeners;
private DungeonData dungeonData;
private int prevScene; // Id of the previous scene
private int prevScenePoint;
public Scene(World world, SceneData sceneData) {
this.world = world;
this.sceneData = sceneData;
......@@ -198,11 +197,11 @@ public class Scene {
this.dungeonData = dungeonData;
}
public DungeonChallenge getChallenge() {
public WorldChallenge getChallenge() {
return challenge;
}
public void setChallenge(DungeonChallenge challenge) {
public void setChallenge(WorldChallenge challenge) {
this.challenge = challenge;
}
......@@ -310,6 +309,7 @@ public class Scene {
private void addEntityDirectly(GameEntity entity) {
getEntities().put(entity.getId(), entity);
entity.onCreate(); // Call entity create event
}
public synchronized void addEntity(GameEntity entity) {
......@@ -320,14 +320,21 @@ public class Scene {
public synchronized void addEntityToSingleClient(Player player, GameEntity entity) {
this.addEntityDirectly(entity);
player.sendPacket(new PacketSceneEntityAppearNotify(entity));
}
public void addEntities(Collection<? extends GameEntity> entities){
addEntities(entities, VisionType.VISION_TYPE_BORN);
}
public synchronized void addEntities(Collection<GameEntity> entities) {
public synchronized void addEntities(Collection<? extends GameEntity> entities, VisionType visionType) {
if(entities == null || entities.isEmpty()){
return;
}
for (GameEntity entity : entities) {
this.addEntityDirectly(entity);
}
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VISION_TYPE_BORN));
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, visionType));
}
private GameEntity removeEntityDirectly(GameEntity entity) {
......@@ -344,7 +351,14 @@ public class Scene {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType));
}
}
public synchronized void removeEntities(List<GameEntity> entity, VisionType visionType) {
var toRemove = entity.stream()
.map(this::removeEntityDirectly)
.toList();
if (toRemove.size() > 0) {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType));
}
}
public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) {
this.removeEntityDirectly(oldEntity);
this.addEntityDirectly(newEntity);
......@@ -424,9 +438,12 @@ public class Scene {
// TEMPORARY
this.checkSpawns();
}
// Triggers
this.getScriptManager().onTick();
this.scriptManager.checkRegions();
if(challenge != null){
challenge.onCheckTimeOut();
}
}
// TODO - Test
......@@ -503,17 +520,16 @@ public class Scene {
}
}
public List<SceneBlock> getPlayerActiveBlocks(Player player){
// consider the borders' entities of blocks, so we check if contains by index
return SceneIndexManager.queryNeighbors(getScriptManager().getBlocksIndex(),
player.getPos().toXZDoubleArray(), Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
}
public void checkBlocks() {
Set<SceneBlock> visible = new HashSet<>();
for (Player player : this.getPlayers()) {
for (SceneBlock block : getScriptManager().getBlocks()) {
if (!block.contains(player.getPos())) {
continue;
}
visible.add(block);
}
var blocks = getPlayerActiveBlocks(player);
visible.addAll(blocks);
}
Iterator<SceneBlock> it = this.getLoadedBlocks().iterator();
......@@ -527,59 +543,129 @@ public class Scene {
}
}
for (SceneBlock block : visible) {
for(var block : visible){
if (!this.getLoadedBlocks().contains(block)) {
this.onLoadBlock(block);
this.onLoadBlock(block, this.getPlayers());
this.getLoadedBlocks().add(block);
}else{
// dynamic load the groups for players in a loaded block
var toLoad = this.getPlayers().stream()
.filter(p -> block.contains(p.getPos()))
.map(p -> playerMeetGroups(p, block))
.flatMap(Collection::stream)
.toList();
onLoadGroup(toLoad);
}
for (Player player : this.getPlayers()) {
getScriptManager().meetEntities(loadNpcForPlayer(player, block));
}
}
// TODO optimize
public void onLoadBlock(SceneBlock block) {
for (SceneGroup group : block.groups) {
// We load the script files for the groups here
if (!group.isLoaded()) {
this.getScriptManager().loadGroupFromScript(group);
}
public List<SceneGroup> playerMeetGroups(Player player, SceneBlock block){
List<SceneGroup> sceneGroups = SceneIndexManager.queryNeighbors(block.sceneGroupIndex, player.getPos().toDoubleArray(),
Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
List<SceneGroup> groups = sceneGroups.stream()
.filter(group -> !scriptManager.getLoadedGroupSetPerBlock().get(block.id).contains(group))
.peek(group -> scriptManager.getLoadedGroupSetPerBlock().get(block.id).add(group))
.toList();
if (groups.size() == 0) {
return List.of();
}
group.triggers.forEach(getScriptManager()::registerTrigger);
group.regions.forEach(getScriptManager()::registerRegion);
return groups;
}
public void onLoadBlock(SceneBlock block, List<Player> players) {
this.getScriptManager().loadBlockFromScript(block);
scriptManager.getLoadedGroupSetPerBlock().put(block.id , new HashSet<>());
// the groups form here is not added in current scene
var groups = players.stream()
.filter(player -> block.contains(player.getPos()))
.map(p -> playerMeetGroups(p, block))
.flatMap(Collection::stream)
.toList();
onLoadGroup(groups);
Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id);
}
public void onLoadGroup(List<SceneGroup> groups){
if(groups == null || groups.isEmpty()){
return;
}
for (SceneGroup group : groups) {
// We load the script files for the groups here
this.getScriptManager().loadGroupFromScript(group);
}
// Spawn gadgets AFTER triggers are added
// TODO
for (SceneGroup group : block.groups) {
var entities = new ArrayList<GameEntity>();
for (SceneGroup group : groups) {
if (group.init_config == null) {
continue;
}
// Load garbages
List<SceneGadget> garbageGadgets = group.getGarbageGadgets();
if (garbageGadgets != null) {
entities.addAll(garbageGadgets.stream().map(g -> scriptManager.createGadget(group.id, group.block_id, g))
.filter(Objects::nonNull)
.toList());
}
// Load suites
int suite = group.init_config.suite;
if (suite == 0) {
if (suite == 0 || group.suites == null || group.suites.size() == 0) {
continue;
}
do {
this.getScriptManager().spawnGadgetsInGroup(group, suite);
suite++;
} while (suite < group.init_config.end_suite);
// 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(suiteData.sceneGadgets.stream()
.map(g -> scriptManager.createGadget(group.id, group.block_id, g))
.filter(Objects::nonNull)
.toList());
entities.addAll(suiteData.sceneMonsters.stream()
.map(mob -> scriptManager.createMonster(group.id, group.block_id, mob))
.filter(Objects::nonNull)
.toList());
}
scriptManager.meetEntities(entities);
//scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null);
//groups.forEach(g -> scriptManager.callEvent(EventType.EVENT_GROUP_LOAD, null));
Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size());
}
public void onUnloadBlock(SceneBlock block) {
List<GameEntity> toRemove = this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList();
List<GameEntity> toRemove = this.getEntities().values().stream()
.filter(e -> e.getBlockId() == block.id).toList();
if (toRemove.size() > 0) {
toRemove.stream().forEach(this::removeEntityDirectly);
toRemove.forEach(this::removeEntityDirectly);
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE));
}
for (SceneGroup group : block.groups) {
group.triggers.forEach(getScriptManager()::deregisterTrigger);
for (SceneGroup group : block.groups.values()) {
if(group.triggers != null){
group.triggers.values().forEach(getScriptManager()::deregisterTrigger);
}
if(group.regions != null){
group.regions.forEach(getScriptManager()::deregisterRegion);
}
}
scriptManager.getLoadedGroupSetPerBlock().remove(block.id);
Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id);
}
// Gadgets
......@@ -643,4 +729,65 @@ public class Scene {
player.getSession().send(packet);
}
}
public void addItemEntity(int itemId, int amount, GameEntity bornForm){
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
return;
}
if (itemData.isEquip()) {
float range = (3f + (.1f * amount));
for (int i = 0; i < amount; i++) {
Position pos = bornForm.getPosition().clone().addX((float) (Math.random() * range) - (range / 2)).addZ((float) (Math.random() * range) - (range / 2)).addZ(.9f);
EntityItem entity = new EntityItem(this, null, itemData, pos, 1);
addEntity(entity);
}
} else {
EntityItem entity = new EntityItem(this, null, itemData, bornForm.getPosition().clone().addZ(.9f), amount);
addEntity(entity);
}
}
public List<EntityNPC> loadNpcForPlayer(Player player, SceneBlock block){
if(!block.contains(player.getPos())){
return List.of();
}
var pos = player.getPos();
var data = GameData.getSceneNpcBornData().get(getId());
if(data == null){
return List.of();
}
var npcs = SceneIndexManager.queryNeighbors(data.getIndex(), pos.toDoubleArray(),
Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
var entityNPCS = npcs.stream().map(item -> {
var group = data.getGroups().get(item.getGroupId());
if(group == null){
group = SceneGroup.of(item.getGroupId());
data.getGroups().putIfAbsent(item.getGroupId(), group);
group.load(getId());
}
if(group.npc == null){
return null;
}
var npc = group.npc.get(item.getConfigId());
if(npc == null){
return null;
}
return getScriptManager().createNPC(npc, block.id, item.getSuiteIdList().get(0));
})
.filter(Objects::nonNull)
.filter(item -> getEntities().values().stream()
.filter(e -> e instanceof EntityNPC)
.noneMatch(e -> e.getConfigId() == item.getConfigId()))
.toList();
if(entityNPCS.size() > 0){
broadcastPacket(new PacketGroupSuiteNotify(entityNPCS));
}
return entityNPCS;
}
}
package emu.grasscutter.game.world;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.RewardPreviewData;
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.entity.gadget.chest.ChestInteractHandler;
import emu.grasscutter.game.entity.gadget.chest.NormalChestInteractHandler;
import emu.grasscutter.server.game.GameServer;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WorldDataManager {
private final GameServer gameServer;
private final Map<String, ChestInteractHandler> chestInteractHandlerMap; // chestType-Handler
public WorldDataManager(GameServer gameServer){
this.gameServer = gameServer;
this.chestInteractHandlerMap = new HashMap<>();
loadChestConfig();
}
public synchronized void loadChestConfig(){
// set the special chest first
chestInteractHandlerMap.put("SceneObj_Chest_Flora", new BossChestInteractHandler());
try(InputStream is = DataLoader.load("ChestReward.json"); InputStreamReader isr = new InputStreamReader(is)) {
List<ChestReward> chestReward = Grasscutter.getGsonFactory().fromJson(
isr,
TypeToken.getParameterized(List.class, ChestReward.class).getType());
chestReward.forEach(reward ->
reward.getObjNames().forEach(
name -> chestInteractHandlerMap.putIfAbsent(name, new NormalChestInteractHandler(reward))));
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load chest reward config.", e);
}
}
public Map<String, ChestInteractHandler> getChestInteractHandlerMap() {
return chestInteractHandlerMap;
}
public RewardPreviewData getRewardByBossId(int monsterId){
var investigationMonsterData = GameData.getInvestigationMonsterDataMap().values().parallelStream()
.filter(imd -> imd.getMonsterIdList() != null && !imd.getMonsterIdList().isEmpty())
.filter(imd -> imd.getMonsterIdList().get(0) == monsterId)
.findFirst();
if(investigationMonsterData.isEmpty()){
return null;
}
return GameData.getRewardPreviewDataMap().get(investigationMonsterData.get().getRewardPreviewId());
}
}
package emu.grasscutter.scripts;
import com.github.davidmoten.rtreemulti.Entry;
import com.github.davidmoten.rtreemulti.RTree;
import com.github.davidmoten.rtreemulti.geometry.Geometry;
import com.github.davidmoten.rtreemulti.geometry.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
public class SceneIndexManager {
public static <T> RTree<T, Geometry> buildIndex(int dimensions, Collection<T> elements, Function<T, Geometry> extractor){
RTree<T, Geometry> rtree = RTree.dimensions(dimensions).create();
return rtree.add(elements.stream().map(e -> Entry.entry(e, extractor.apply(e))).toList());
}
public static <T> List<T> queryNeighbors(RTree<T, Geometry> tree, double[] position, int range){
var result = new ArrayList<T>();
Rectangle rectangle = Rectangle.create(calRange(position, -range), calRange(position, range));
var queryResult = tree.search(rectangle);
queryResult.forEach(q -> result.add(q.value()));
return result;
}
private static double[] calRange(double[] position, int range){
var newPos = position.clone();
for(int i=0;i<newPos.length;i++){
newPos[i] += range;
}
return newPos;
}
}
package emu.grasscutter.scripts;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactory;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify;
import io.netty.util.concurrent.FastThreadLocal;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
public class ScriptLib {
public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class);
private final SceneScriptManager sceneScriptManager;
private final FastThreadLocal<SceneScriptManager> sceneScriptManager;
private final FastThreadLocal<SceneGroup> currentGroup;
public ScriptLib() {
this.sceneScriptManager = new FastThreadLocal<>();
this.currentGroup = new FastThreadLocal<>();
}
public void setSceneScriptManager(SceneScriptManager sceneScriptManager){
this.sceneScriptManager.set(sceneScriptManager);
}
public ScriptLib(SceneScriptManager sceneScriptManager) {
this.sceneScriptManager = sceneScriptManager;
public void removeSceneScriptManager(){
this.sceneScriptManager.remove();
}
public SceneScriptManager getSceneScriptManager() {
return sceneScriptManager;
// normally not null
return Optional.of(sceneScriptManager.get()).get();
}
private String printTable(LuaTable table){
......@@ -38,7 +49,15 @@ public class ScriptLib {
sb.append("}");
return sb.toString();
}
public void setCurrentGroup(SceneGroup currentGroup){
this.currentGroup.set(currentGroup);
}
public Optional<SceneGroup> getCurrentGroup(){
return Optional.of(this.currentGroup.get());
}
public void removeCurrentGroup(){
this.currentGroup.remove();
}
public int SetGadgetStateByConfigId(int configId, int gadgetState) {
logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}",
configId,gadgetState);
......@@ -49,33 +68,23 @@ public class ScriptLib {
return 1;
}
if (!(entity.get() instanceof EntityGadget)) {
return 1;
if (entity.get() instanceof EntityGadget entityGadget) {
entityGadget.updateState(gadgetState);
return 0;
}
EntityGadget gadget = (EntityGadget) entity.get();
gadget.setState(gadgetState);
getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState));
return 0;
return 1;
}
public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) {
logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}",
groupId,configId,gadgetState);
List<GameEntity> list = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getGroupId() == groupId).toList();
for (GameEntity entity : list) {
if (!(entity instanceof EntityGadget)) {
continue;
}
EntityGadget gadget = (EntityGadget) entity;
gadget.setState(gadgetState);
getSceneScriptManager().getScene().broadcastPacket(new PacketGadgetStateNotify(gadget, gadgetState));
}
getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getGroupId() == groupId)
.filter(e -> e instanceof EntityGadget)
.map(e -> (EntityGadget)e)
.forEach(e -> e.updateState(gadgetState));
return 0;
}
......@@ -83,42 +92,47 @@ public class ScriptLib {
public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) {
logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}",
groupId,configId,options);
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
if (entity.isEmpty()) {
if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) {
return 1;
}
if (!(entity.get() instanceof EntityGadget)) {
if (!(gadget.getContent() instanceof GadgetWorktop worktop)) {
return 1;
}
EntityGadget gadget = (EntityGadget) entity.get();
gadget.addWorktopOptions(options);
worktop.addWorktopOptions(options);
getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget));
return 0;
}
public int SetWorktopOptions(LuaTable table){
logger.debug("[LUA] Call SetWorktopOptions with {}", printTable(table));
// TODO
return 0;
}
public int DelWorktopOptionByGroupId(int groupId, int configId, int option) {
logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option);
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
if (entity.isEmpty()) {
if (entity.isEmpty() || !(entity.get() instanceof EntityGadget gadget)) {
return 1;
}
if (!(entity.get() instanceof EntityGadget)) {
if (!(gadget.getContent() instanceof GadgetWorktop worktop)) {
return 1;
}
EntityGadget gadget = (EntityGadget) entity.get();
gadget.removeWorktopOption(option);
worktop.removeWorktopOption(option);
getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget));
return 0;
}
......@@ -146,48 +160,102 @@ public class ScriptLib {
if (group == null || group.monsters == null) {
return 1;
}
var suiteData = group.getSuiteByIndex(suite);
if(suiteData == null){
return 1;
}
// avoid spawn wrong monster
if(getSceneScriptManager().getScene().getChallenge() != null)
if(!getSceneScriptManager().getScene().getChallenge().inProgress() ||
getSceneScriptManager().getScene().getChallenge().getGroup().id != groupId){
return 0;
}
this.getSceneScriptManager().spawnMonstersInGroup(group, suite);
this.getSceneScriptManager().addGroupSuite(group, suiteData);
return 0;
}
public int GoToGroupSuite(int groupId, int suite) {
logger.debug("[LUA] Call GoToGroupSuite with {},{}",
groupId,suite);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
if (group == null || group.monsters == null) {
return 1;
}
var suiteData = group.getSuiteByIndex(suite);
if(suiteData == null){
return 1;
}
// param3 (probably time limit for timed dungeons)
public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) {
logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}",
challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5);
for(var suiteItem : group.suites){
if(suiteData == suiteItem){
continue;
}
this.getSceneScriptManager().removeGroupSuite(group, suiteItem);
}
this.getSceneScriptManager().addGroupSuite(group, suiteData);
return 0;
}
public int RemoveExtraGroupSuite(int groupId, int suite) {
logger.debug("[LUA] Call RemoveExtraGroupSuite with {},{}",
groupId,suite);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
var objective = objectiveKills;
if (group == null || group.monsters == null) {
return 1;
}
var suiteData = group.getSuiteByIndex(suite);
if(suiteData == null){
return 1;
}
this.getSceneScriptManager().removeGroupSuite(group, suiteData);
if(group == null){
group = getSceneScriptManager().getGroupById(timeLimitOrGroupId);
objective = groupId;
return 0;
}
public int KillExtraGroupSuite(int groupId, int suite) {
logger.debug("[LUA] Call KillExtraGroupSuite with {},{}",
groupId,suite);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
if (group == null || group.monsters == null) {
return 1;
}
var suiteData = group.getSuiteByIndex(suite);
if(suiteData == null){
return 1;
}
this.getSceneScriptManager().removeGroupSuite(group, suiteData);
if(getSceneScriptManager().getScene().getChallenge() != null &&
getSceneScriptManager().getScene().getChallenge().inProgress())
{
return 0;
}
// param3 (probably time limit for timed dungeons)
public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) {
logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}",
challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5);
DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(),
group, challengeId, challengeIndex, objective);
var challenge = ChallengeFactory.getChallenge(
challengeId,
challengeIndex,
timeLimitOrGroupId,
groupId,
objectiveKills,
param5,
getSceneScriptManager().getScene(),
getCurrentGroup().get()
);
if(challenge == null){
return 1;
}
if(challenge instanceof DungeonChallenge dungeonChallenge){
// set if tower first stage (6-1)
challenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0);
dungeonChallenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0);
}
getSceneScriptManager().getScene().setChallenge(challenge);
challenge.start();
return 0;
}
......@@ -244,7 +312,7 @@ public class ScriptLib {
public int GetRegionEntityCount(LuaTable table) {
logger.debug("[LUA] Call GetRegionEntityCount with {}",
table);
printTable(table));
int regionId = table.get("region_eid").toint();
int entityType = table.get("entity_type").toint();
......@@ -267,12 +335,12 @@ public class ScriptLib {
// TODO record time
return 0;
}
public int GetGroupMonsterCount(int var1){
logger.debug("[LUA] Call GetGroupMonsterCount with {}",
var1);
public int GetGroupMonsterCount(){
logger.debug("[LUA] Call GetGroupMonsterCount ");
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id)
.filter(e -> e instanceof EntityMonster &&
e.getGroupId() == getCurrentGroup().map(sceneGroup -> sceneGroup.id).orElse(-1))
.count();
}
public int SetMonsterBattleByGroup(int var1, int var2, int var3){
......@@ -314,7 +382,7 @@ public class ScriptLib {
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint());
if(entity == null){
return 1;
return 0;
}
getSceneScriptManager().getScene().killEntity(entity, 0);
return 0;
......@@ -334,7 +402,11 @@ public class ScriptLib {
var configId = table.get("config_id").toint();
var delayTime = table.get("delay_time").toint();
getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime);
if(getCurrentGroup().isEmpty()){
return 1;
}
getSceneScriptManager().spawnMonstersByConfigId(getCurrentGroup().get(), configId, delayTime);
return 0;
}
......@@ -353,8 +425,81 @@ public class ScriptLib {
printTable(table));
var configId = table.get("config_id").toint();
//TODO
var group = getCurrentGroup();
if (group.isEmpty()) {
return 1;
}
var gadget = group.get().gadgets.get(configId);
var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget);
getSceneScriptManager().addEntity(entity);
return 0;
}
public int CheckRemainGadgetCountByGroupId(LuaTable table){
logger.debug("[LUA] Call CheckRemainGadgetCountByGroupId with {}",
printTable(table));
var groupId = table.get("group_id").toint();
var count = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == groupId)
.count();
return (int)count;
}
public int GetGadgetStateByConfigId(int groupId, int configId){
logger.debug("[LUA] Call GetGadgetStateByConfigId with {},{}",
groupId, configId);
if(groupId == 0){
groupId = getCurrentGroup().get().id;
}
final int realGroupId = groupId;
var gadget = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(g -> g instanceof EntityGadget entityGadget && entityGadget.getGroupId() == realGroupId)
.filter(g -> g.getConfigId() == configId)
.findFirst();
if(gadget.isEmpty()){
return 1;
}
return ((EntityGadget)gadget.get()).getState();
}
public int MarkPlayerAction(int var1, int var2, int var3, int var4){
logger.debug("[LUA] Call MarkPlayerAction with {},{},{},{}",
var1, var2,var3,var4);
return 0;
}
public int AddQuestProgress(String var1){
logger.debug("[LUA] Call AddQuestProgress with {}",
var1);
return 0;
}
/**
* change the state of gadget
*/
public int ChangeGroupGadget(LuaTable table){
logger.debug("[LUA] Call ChangeGroupGadget with {}",
printTable(table));
var configId = table.get("config_id").toint();
var state = table.get("state").toint();
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId);
if(entity == null){
return 1;
}
if (entity instanceof EntityGadget entityGadget) {
entityGadget.updateState(state);
return 0;
}
return 1;
}
}
package emu.grasscutter.scripts;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.script.LuajContext;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.scripts.constants.ScriptRegionShape;
import emu.grasscutter.scripts.data.SceneMeta;
import emu.grasscutter.scripts.serializer.LuaSerializer;
import emu.grasscutter.scripts.serializer.Serializer;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.script.LuajContext;
import javax.script.*;
import java.io.File;
import java.io.FileReader;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ScriptLoader {
private static ScriptEngineManager sm;
......@@ -34,8 +29,16 @@ public class ScriptLoader {
private static ScriptEngineFactory factory;
private static String fileType;
private static Serializer serializer;
private static Map<String, CompiledScript> scripts = new HashMap<>();
private static ScriptLib scriptLib;
private static LuaValue scriptLibLua;
/**
* suggest GC to remove it if the memory is less
*/
private static Map<String, SoftReference<CompiledScript>> scriptsCache = new ConcurrentHashMap<>();
/**
* sceneId - SceneMeta
*/
private static Map<Integer, SoftReference<SceneMeta>> sceneMetaCache = new ConcurrentHashMap<>();
public synchronized static void init() throws Exception {
if (sm != null) {
......@@ -67,6 +70,10 @@ public class ScriptLoader {
ctx.globals.set("EventType", CoerceJavaToLua.coerce(new EventType())); // TODO - make static class to avoid instantiating a new class every scene
ctx.globals.set("GadgetState", CoerceJavaToLua.coerce(new ScriptGadgetState()));
ctx.globals.set("RegionShape", CoerceJavaToLua.coerce(new ScriptRegionShape()));
scriptLib = new ScriptLib();
scriptLibLua = CoerceJavaToLua.coerce(scriptLib);
ctx.globals.set("ScriptLib", scriptLibLua);
}
public static ScriptEngine getEngine() {
......@@ -81,25 +88,50 @@ public class ScriptLoader {
return serializer;
}
public static ScriptLib getScriptLib() {
return scriptLib;
}
public static LuaValue getScriptLibLua() {
return scriptLibLua;
}
public static <T> Optional<T> tryGet(SoftReference<T> softReference){
try{
return Optional.ofNullable(softReference.get());
}catch (NullPointerException npe){
return Optional.empty();
}
}
public static CompiledScript getScriptByPath(String path) {
CompiledScript sc = scripts.get(path);
var sc = tryGet(scriptsCache.get(path));
if (sc.isPresent()) {
return sc.get();
}
Grasscutter.getLogger().info("Loaded " + path);
Grasscutter.getLogger().info("Loading script " + path);
if (sc == null) {
File file = new File(path);
if (!file.exists()) return null;
try (FileReader fr = new FileReader(file)) {
sc = ((Compilable) getEngine()).compile(fr);
scripts.put(path, sc);
var script = ((Compilable) getEngine()).compile(fr);
scriptsCache.put(path, new SoftReference<>(script));
return script;
} catch (Exception e) {
//e.printStackTrace();
Grasscutter.getLogger().error("Loading script {} failed!", path, e);
return null;
}
}
return sc;
public static SceneMeta getSceneMeta(int sceneId) {
return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> {
var instance = SceneMeta.of(sceneId);
sceneMetaCache.put(sceneId, new SoftReference<>(instance));
return instance;
});
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment