Continue merging quests (pt. 1)

Finished last at: `World.java`, line `player.setAvatarsAbilityForScene(newScene);`
This commit is contained in:
KingRainbow44 2023-04-09 13:25:16 -04:00
parent c64cc7d5e2
commit 97ee71bcf4
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
26 changed files with 6545 additions and 6479 deletions

View File

@ -1,5 +1,6 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
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.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
@ -14,7 +15,8 @@ public class CompoundData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
private int groupID; @SerializedName("groupID")
private int groupId;
private int rankLevel; private int rankLevel;
private boolean isDefaultUnlocked; private boolean isDefaultUnlocked;
private int costTime; private int costTime;

View File

@ -116,7 +116,7 @@ public class WorldChallenge {
public void fail() { public void fail() {
if (!this.inProgress()) return; if (!this.inProgress()) return;
this.finish(true); this.finish(false);
this.getScene() this.getScene()
.getScriptManager() .getScriptManager()
@ -127,7 +127,7 @@ public class WorldChallenge {
private void finish(boolean success) { private void finish(boolean success) {
this.progress = false; this.progress = false;
this.success = success; this.success = success;
this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L); this.finishedTime = (int) ((this.scene.getSceneTimeSeconds() - this.startedAt));
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this)); getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
} }

View File

@ -55,16 +55,13 @@ public class EntityAvatar extends GameEntity {
this.avatar = avatar; this.avatar = avatar;
this.avatar.setCurrentEnergy(); this.avatar.setCurrentEnergy();
if (scene != null) this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
if (getScene() != null) { GameItem weapon = this.getAvatar().getWeapon();
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
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 @Override
public int getEntityTypeId() { public int getEntityTypeId() {

View File

@ -2,10 +2,15 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.config.ConfigEntityGadget; import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.Getter; import lombok.Getter;
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_GADGET_HP_CHANGE;
public abstract class EntityBaseGadget extends GameEntity { public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
protected final Position position; protected final Position position;
@ -33,6 +38,29 @@ public abstract class EntityBaseGadget extends GameEntity {
@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.
getScene()
.getPlayers()
.forEach(
p ->
p.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_DESTROY_GADGET, this.getGadgetId()));
}
@Override
public void runLuaCallbacks(EntityDamageEvent event) {
super.runLuaCallbacks(event);
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(
this.getGroupId(),
EVENT_SPECIFIC_GADGET_HP_CHANGE,
getConfigId(),
getGadgetId())
.setSourceEntityId(getId())
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
.setEventSource(Integer.toString(getConfigId())));
} }
protected void fillFightProps(ConfigEntityGadget configGadget) { protected void fillFightProps(ConfigEntityGadget configGadget) {

View File

@ -157,6 +157,10 @@ public abstract class GameEntity {
this.damage(amount, 0, ElementType.None); this.damage(amount, 0, ElementType.None);
} }
public void damage(float amount, ElementType attackType) {
this.damage(amount, 0, attackType);
}
public void damage(float amount, int killerId, ElementType attackType) { 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)) {
@ -177,12 +181,15 @@ public abstract class GameEntity {
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage())); this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
} }
this.lastAttackType = attackType;
// Check if dead // Check if dead
boolean isDead = false; boolean isDead = false;
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true; isDead = true;
} }
this.runLuaCallbacks(event);
// Packets // Packets
this.getScene() this.getScene()

View File

@ -1,6 +1,8 @@
package emu.grasscutter.game.entity.gadget; package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
@ -15,15 +17,25 @@ import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
public class GadgetGatherObject extends GadgetContent { public final class GadgetGatherObject extends GadgetContent {
private int itemId; private int itemId;
private boolean isForbidGuest; private boolean isForbidGuest;
public GadgetGatherObject(EntityGadget gadget) { public GadgetGatherObject(EntityGadget gadget) {
super(gadget); super(gadget);
// overwrites the default spawn handling
if (gadget.getSpawnEntry() != null) { if (gadget.getSpawnEntry() != null) {
this.itemId = gadget.getSpawnEntry().getGatherItemId(); this.itemId = gadget.getSpawnEntry().getGatherItemId();
return;
}
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
if (gatherData != null) {
this.itemId = gatherData.getItemId();
this.isForbidGuest = gatherData.isForbidGuest();
} else {
Grasscutter.getLogger().error("invalid gather object: {}", gadget.getConfigId());
} }
} }

View File

@ -3,38 +3,45 @@ package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GatherData; import emu.grasscutter.data.excels.GatherData;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo; import emu.grasscutter.net.proto.GatherGadgetInfoOuterClass.GatherGadgetInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.utils.Utils;
public class GadgetGatherPoint extends GadgetContent { /** Spawner for the gather objects */
private final int itemId; public final class GadgetGatherPoint extends GadgetContent {
private boolean isForbidGuest; private final GatherData gatherData;
private final EntityGadget gatherObjectChild;
public GadgetGatherPoint(EntityGadget gadget) { public GadgetGatherPoint(EntityGadget gadget) {
super(gadget); super(gadget);
if (gadget.getSpawnEntry() != null) { this.gatherData = GameData.getGatherDataMap().get(gadget.getPointType());
this.itemId = gadget.getSpawnEntry().getGatherItemId();
} else { var scene = gadget.getScene();
GatherData gatherData = GameData.getGatherDataMap().get(gadget.getPointType()); gatherObjectChild = new EntityGadget(scene, gatherData.getGadgetId(), gadget.getBornPos());
this.itemId = gatherData.getItemId();
this.isForbidGuest = gatherData.isForbidGuest(); gatherObjectChild.setBlockId(gadget.getBlockId());
} gatherObjectChild.setConfigId(gadget.getConfigId());
gatherObjectChild.setGroupId(gadget.getGroupId());
gatherObjectChild.getRotation().set(gadget.getRotation());
gatherObjectChild.setState(gadget.getState());
gatherObjectChild.setPointType(gadget.getPointType());
gatherObjectChild.setMetaGadget(gadget.getMetaGadget());
gatherObjectChild.buildContent();
gadget.getChildren().add(gatherObjectChild);
scene.addEntity(gatherObjectChild);
} }
public int getItemId() { public int getItemId() {
return this.itemId; return this.gatherData.getItemId();
} }
public boolean isForbidGuest() { public boolean isForbidGuest() {
return isForbidGuest; return this.gatherData.isForbidGuest();
} }
public boolean onInteract(Player player, GadgetInteractReq req) { public boolean onInteract(Player player, GadgetInteractReq req) {
@ -46,6 +53,7 @@ public class GadgetGatherPoint extends GadgetContent {
} }
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
// todo does official use this for the spawners?
GatherGadgetInfo gatherGadgetInfo = GatherGadgetInfo gatherGadgetInfo =
GatherGadgetInfo.newBuilder() GatherGadgetInfo.newBuilder()
.setItemId(this.getItemId()) .setItemId(this.getItemId())
@ -54,30 +62,4 @@ public class GadgetGatherPoint extends GadgetContent {
gadgetInfo.setGatherGadget(gatherGadgetInfo); gadgetInfo.setGatherGadget(gatherGadgetInfo);
} }
public void dropItems(Player player) {
Scene scene = getGadget().getScene();
int times = Utils.randomRange(1, 2);
for (int i = 0; i < times; i++) {
EntityItem item =
new EntityItem(
scene,
player,
GameData.getItemDataMap().get(itemId),
getGadget()
.getPosition()
.clone()
.addY(2f)
.addX(Utils.randomFloatRange(-1f, 1f))
.addZ(Utils.randomFloatRange(-1f, 1f)),
1,
true);
scene.addEntity(item);
}
scene.killEntity(this.getGadget(), player.getTeamManager().getCurrentAvatarEntity().getId());
// Todo: add record
}
} }

View File

@ -11,7 +11,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Arrays; import java.util.Arrays;
public class GadgetWorktop extends GadgetContent { public final class GadgetWorktop extends GadgetContent {
private IntSet worktopOptions; private IntSet worktopOptions;
private WorktopWorktopOptionHandler handler; private WorktopWorktopOptionHandler handler;
@ -20,6 +20,9 @@ public class GadgetWorktop extends GadgetContent {
} }
public IntSet getWorktopOptions() { public IntSet getWorktopOptions() {
if (this.worktopOptions == null) {
this.worktopOptions = new IntOpenHashSet();
}
return worktopOptions; return worktopOptions;
} }

View File

@ -44,6 +44,12 @@ public class BossChestInteractHandler implements ChestInteractHandler {
var reward = worldDataManager.getRewardByBossId(monster.monster_id); var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if (reward == null) { if (reward == null) {
var dungeonManager = player.getScene().getDungeonManager();
if (dungeonManager != null) {
return dungeonManager.getStatueDrops(
player, useCondensedResin, chest.getGadget().getGroupId());
}
Grasscutter.getLogger() Grasscutter.getLogger()
.warn("Could not found the reward of boss monster {}", monster.monster_id); .warn("Could not found the reward of boss monster {}", monster.monster_id);
return false; return false;

View File

@ -2,6 +2,8 @@ package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
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;
@ -27,7 +29,7 @@ public enum EquipType {
}); });
} }
private final int value; @Getter private final int value;
EquipType(int value) { EquipType(int value) {
this.value = value; this.value = value;
@ -40,8 +42,4 @@ public enum EquipType {
public static EquipType getTypeByName(String name) { public static EquipType getTypeByName(String name) {
return stringMap.getOrDefault(name, EQUIP_NONE); return stringMap.getOrDefault(name, EQUIP_NONE);
} }
public int getValue() {
return value;
}
} }

View File

@ -57,6 +57,7 @@ public class GameItem {
@Getter @Setter private int equipCharacter; @Getter @Setter private int equipCharacter;
@Transient @Getter @Setter private int weaponEntityId; @Transient @Getter @Setter private int weaponEntityId;
@Transient @Getter private boolean newItem = false;
public GameItem() { public GameItem() {
// Morphia only // Morphia only
@ -116,6 +117,30 @@ public class GameItem {
} }
} }
public int getOwnerId() {
return ownerId;
}
public void setOwner(Player player) {
this.ownerId = player.getUid();
this.guid = player.getNextGameGuid();
}
public void checkIsNew(Inventory inventory) {
// display notification when player obtain new item
if (inventory.getItemByGuid(this.itemId) == null) {
this.newItem = true;
}
}
public ObjectId getObjectId() {
return id;
}
public ItemType getItemType() {
return this.itemData.getItemType();
}
public static int getMinPromoteLevel(int level) { public static int getMinPromoteLevel(int level) {
if (level > 80) { if (level > 80) {
return 6; return 6;
@ -133,23 +158,6 @@ public class GameItem {
return 0; return 0;
} }
public int getOwnerId() {
return ownerId;
}
public void setOwner(Player player) {
this.ownerId = player.getUid();
this.guid = player.getNextGameGuid();
}
public ObjectId getObjectId() {
return id;
}
public ItemType getItemType() {
return this.itemData.getItemType();
}
public int getEquipSlot() { public int getEquipSlot() {
return this.getItemData().getEquipType().getValue(); return this.getItemData().getEquipType().getValue();
} }
@ -353,7 +361,7 @@ public class GameItem {
return ItemHint.newBuilder() return ItemHint.newBuilder()
.setItemId(getItemId()) .setItemId(getItemId())
.setCount(getCount()) .setCount(getCount())
.setIsNew(false) .setIsNew(this.isNewItem())
.build(); .build();
} }

View File

@ -15,11 +15,9 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseAction.UseItemParams; import emu.grasscutter.game.props.ItemUseAction.UseItemParams;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify; import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemDelNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -96,12 +94,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
GameItem result = putItem(item); GameItem result = putItem(item);
if (result != null) { if (result != null) {
getPlayer() this.triggerAddItemEvents(result);
.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM,
result.getItemId(),
result.getCount());
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result)); getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
return true; return true;
} }
@ -110,18 +103,19 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
} }
public boolean addItem(GameItem item, ActionReason reason) { public boolean addItem(GameItem item, ActionReason reason) {
boolean result = addItem(item); return addItem(item, reason, false);
if (result && reason != null) {
getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
}
return result;
} }
public boolean addItem(GameItem item, ActionReason reason, boolean forceNotify) { public boolean addItem(GameItem item, ActionReason reason, boolean forceNotify) {
boolean result = addItem(item); boolean result = addItem(item);
if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) {
getPlayer()
.sendPacket(
new PacketAddNoGachaAvatarCardNotify(
(item.getItemId() % 1000) + 10000000, reason, item));
}
if (reason != null && (forceNotify || result)) { if (reason != null && (forceNotify || result)) {
getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason)); getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
} }
@ -155,12 +149,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
e.printStackTrace(); e.printStackTrace();
} }
if (result != null) { if (result != null) {
getPlayer() this.triggerAddItemEvents(result);
.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM,
result.getItemId(),
result.getCount());
changedItems.add(result); changedItems.add(result);
} }
} }
@ -168,11 +157,30 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
return; return;
} }
if (reason != null) { if (reason != null) {
getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason)); getPlayer().sendPacket(new PacketItemAddHintNotify(items, reason));
} }
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
} }
private void triggerAddItemEvents(GameItem result) {
getPlayer()
.getBattlePassManager()
.triggerMission(
WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
getPlayer()
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_OBTAIN_ITEM, result.getItemId(), result.getCount());
}
private void triggerRemItemEvents(GameItem item, int removeCount) {
getPlayer()
.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, item.getItemId(), removeCount);
getPlayer()
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_ITEM_LESS_THAN, item.getItemId(), item.getCount());
}
public void addItemParams(Collection<ItemParam> items) { public void addItemParams(Collection<ItemParam> items) {
addItems( addItems(
items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(),
@ -194,6 +202,8 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
var data = item.getItemData(); var data = item.getItemData();
if (data == null) return null; if (data == null) return null;
this.player.getProgressManager().addItemObtainedHistory(item.getItemId(), item.getCount());
if (data.isUseOnGain()) { if (data.isUseOnGain()) {
var params = new UseItemParams(this.player, data.getUseTarget()); var params = new UseItemParams(this.player, data.getUseTarget());
params.usedItemId = data.getId(); params.usedItemId = data.getId();
@ -267,6 +277,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
this.player.getCodex().checkAddedItem(item); this.player.getCodex().checkAddedItem(item);
// Set owner and guid FIRST! // Set owner and guid FIRST!
item.setOwner(this.player); item.setOwner(this.player);
item.checkIsNew(this);
// Put in item store // Put in item store
getItems().put(item.getGuid(), item); getItems().put(item.getGuid(), item);
if (tab != null) { if (tab != null) {
@ -467,9 +478,7 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
// Battle pass trigger // Battle pass trigger
int removeCount = Math.min(count, item.getCount()); int removeCount = Math.min(count, item.getCount());
getPlayer() this.triggerRemItemEvents(item, removeCount);
.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, item.getItemId(), removeCount);
// Update in db // Update in db
item.save(); item.save();

View File

@ -2,6 +2,8 @@ package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
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;
@ -27,7 +29,7 @@ public enum ItemQuality {
}); });
} }
private final int value; @Getter private final int value;
ItemQuality(int value) { ItemQuality(int value) {
this.value = value; this.value = value;
@ -40,8 +42,4 @@ public enum ItemQuality {
public static ItemQuality getTypeByName(String name) { public static ItemQuality getTypeByName(String name) {
return stringMap.getOrDefault(name, QUALITY_NONE); return stringMap.getOrDefault(name, QUALITY_NONE);
} }
public int getValue() {
return value;
}
} }

View File

@ -2,6 +2,8 @@ package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
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;
@ -27,7 +29,7 @@ public enum ItemType {
}); });
} }
private final int value; @Getter private final int value;
ItemType(int value) { ItemType(int value) {
this.value = value; this.value = value;
@ -40,8 +42,4 @@ public enum ItemType {
public static ItemType getTypeByName(String name) { public static ItemType getTypeByName(String name) {
return stringMap.getOrDefault(name, ITEM_NONE); return stringMap.getOrDefault(name, ITEM_NONE);
} }
public int getValue() {
return value;
}
} }

View File

@ -2,6 +2,8 @@ package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
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;
@ -61,7 +63,7 @@ public enum MaterialType {
}); });
} }
private final int value; @Getter private final int value;
MaterialType(int value) { MaterialType(int value) {
this.value = value; this.value = value;
@ -74,8 +76,4 @@ public enum MaterialType {
public static MaterialType getTypeByName(String name) { public static MaterialType getTypeByName(String name) {
return stringMap.getOrDefault(name, MATERIAL_NONE); return stringMap.getOrDefault(name, MATERIAL_NONE);
} }
public int getValue() {
return value;
}
} }

View File

@ -27,51 +27,13 @@ public class BlossomManager {
private final List<BlossomActivity> blossomActivities = new ArrayList<>(); private final List<BlossomActivity> blossomActivities = new ArrayList<>();
private final List<BlossomActivity> activeChests = new ArrayList<>(); private final List<BlossomActivity> activeChests = new ArrayList<>();
private final List<EntityGadget> createdEntity = new ArrayList<>(); private final List<EntityGadget> createdEntity = new ArrayList<>();
private final List<SpawnDataEntry> blossomConsumed = new ArrayList<>(); private final List<SpawnDataEntry> blossomConsumed = new ArrayList<>();
public BlossomManager(Scene scene) { public BlossomManager(Scene scene) {
this.scene = scene; this.scene = scene;
} }
private static Integer getPreviewReward(BlossomType type, int worldLevel) {
// TODO: blossoms should be based on their city
if (type == null) {
Grasscutter.getLogger().error("Illegal blossom type {}", type);
return null;
}
int blossomChestId = type.getBlossomChestId();
var dataMap = GameData.getBlossomRefreshExcelConfigDataMap();
for (var data : dataMap.values()) {
if (blossomChestId == data.getBlossomChestId()) {
var dropVecList = data.getDropVec();
if (worldLevel > dropVecList.length) {
Grasscutter.getLogger().error("Illegal world level {}", worldLevel);
return null;
}
return dropVecList[worldLevel].getPreviewReward();
}
}
Grasscutter.getLogger().error("Cannot find blossom type {}", type);
return null;
}
private static RewardPreviewData getRewardList(BlossomType type, int worldLevel) {
Integer previewReward = getPreviewReward(type, worldLevel);
if (previewReward == null) return null;
return GameData.getRewardPreviewDataMap().get((int) previewReward);
}
public static IntList getRandomMonstersID(int difficulty, int count) {
IntList result = new IntArrayList();
List<Integer> monsters =
GameDepot.getBlossomConfig().getMonsterIdsPerDifficulty().get(difficulty);
for (int i = 0; i < count; i++) {
result.add((int) monsters.get(Utils.randomRange(0, monsters.size() - 1)));
}
return result;
}
public void onTick() { public void onTick() {
synchronized (blossomActivities) { synchronized (blossomActivities) {
var it = blossomActivities.iterator(); var it = blossomActivities.iterator();
@ -201,6 +163,35 @@ public class BlossomManager {
return scene.getWorld().getWorldLevel(); return scene.getWorld().getWorldLevel();
} }
private static Integer getPreviewReward(BlossomType type, int worldLevel) {
// TODO: blossoms should be based on their city
if (type == null) {
Grasscutter.getLogger().error("Illegal blossom type {}", type);
return null;
}
int blossomChestId = type.getBlossomChestId();
var dataMap = GameData.getBlossomRefreshExcelConfigDataMap();
for (var data : dataMap.values()) {
if (blossomChestId == data.getBlossomChestId()) {
var dropVecList = data.getDropVec();
if (worldLevel > dropVecList.length) {
Grasscutter.getLogger().error("Illegal world level {}", worldLevel);
return null;
}
return dropVecList[worldLevel].getPreviewReward();
}
}
Grasscutter.getLogger().error("Cannot find blossom type {}", type);
return null;
}
private static RewardPreviewData getRewardList(BlossomType type, int worldLevel) {
Integer previewReward = getPreviewReward(type, worldLevel);
if (previewReward == null) return null;
return GameData.getRewardPreviewDataMap().get((int) previewReward);
}
public List<GameItem> onReward(Player player, EntityGadget chest, boolean useCondensedResin) { public List<GameItem> onReward(Player player, EntityGadget chest, boolean useCondensedResin) {
var resinManager = player.getResinManager(); var resinManager = player.getResinManager();
synchronized (activeChests) { synchronized (activeChests) {
@ -240,4 +231,14 @@ public class BlossomManager {
} }
return null; return null;
} }
public static IntList getRandomMonstersID(int difficulty, int count) {
IntList result = new IntArrayList();
List<Integer> monsters =
GameDepot.getBlossomConfig().getMonsterIdsPerDifficulty().get(difficulty);
for (int i = 0; i < count; i++) {
result.add((int) monsters.get(Utils.randomRange(0, monsters.size() - 1)));
}
return result;
}
} }

View File

@ -39,7 +39,7 @@ public class CookingCompoundManager extends BasePlayerManager {
if (compound.isDefaultUnlocked()) { if (compound.isDefaultUnlocked()) {
defaultUnlockedCompounds.add(id); defaultUnlockedCompounds.add(id);
} }
compoundGroups.computeIfAbsent(compound.getGroupID(), gid -> new HashSet<>()).add(id); compoundGroups.computeIfAbsent(compound.getGroupId(), gid -> new HashSet<>()).add(id);
}); });
// TODO:Because we haven't implemented fishing feature,unlock all compounds related to // TODO:Because we haven't implemented fishing feature,unlock all compounds related to
// fish.Besides,it should be bound to player rather than manager. // fish.Besides,it should be bound to player rather than manager.

View File

@ -412,9 +412,7 @@ public class EnergyManager extends BasePlayerManager {
public void setEnergyUsage(boolean energyUsage) { public void setEnergyUsage(boolean energyUsage) {
this.energyUsage = energyUsage; this.energyUsage = energyUsage;
if (!energyUsage) { // Refill team energy if usage is disabled if (!energyUsage) { // Refill team energy if usage is disabled
for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) { this.refillTeamEnergy(PropChangeReason.PROP_CHANGE_REASON_GM, true);
entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true);
}
} }
} }
} }

View File

@ -141,6 +141,7 @@ public class StaminaManager extends BasePlayerManager {
put(242301, 0.8f); put(242301, 0.8f);
put(542301, 0.8f); put(542301, 0.8f);
}}; }};
private final Logger logger = Grasscutter.getLogger(); private final Logger logger = Grasscutter.getLogger();
private final HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>(); private final HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>();
private final HashMap<String, AfterUpdateStaminaListener> afterUpdateStaminaListeners = new HashMap<>(); private final HashMap<String, AfterUpdateStaminaListener> afterUpdateStaminaListeners = new HashMap<>();
@ -414,13 +415,7 @@ public class StaminaManager extends BasePlayerManager {
// Internal handler // Internal handler
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
if (currentState == motionState) { if (currentState == motionState) return;
if (motionState.equals(MotionState.MOTION_STATE_CLIMB_JUMP)) {
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true);
}
return;
}
switch (motionState) { switch (motionState) {
case MOTION_STATE_CLIMB -> case MOTION_STATE_CLIMB ->
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true);
@ -440,6 +435,73 @@ public class StaminaManager extends BasePlayerManager {
updateStaminaRelative(session, consumption, true); updateStaminaRelative(session, consumption, true);
} }
private class SustainedStaminaHandler extends TimerTask {
public void run() {
boolean moving = isPlayerMoving();
int currentCharacterStamina = getCurrentCharacterStamina();
int maxCharacterStamina = getMaxCharacterStamina();
int currentVehicleStamina = getCurrentVehicleStamina();
int maxVehicleStamina = getMaxVehicleStamina();
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
logger.trace("Player moving: " + moving + ", stamina full: " +
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
boolean isCharacterStamina = true;
Consumption consumption;
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbConsumption();
} else if (MotionStatesCategorized.get("DASH").contains(currentState)) {
consumption = getDashConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlyConsumption();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = new Consumption(ConsumptionType.RUN);
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
consumption = getSkiffConsumption();
isCharacterStamina = false;
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = new Consumption(ConsumptionType.STANDBY);
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
consumption = getSwimConsumptions();
} else if (MotionStatesCategorized.get("WALK").contains(currentState)) {
consumption = new Consumption(ConsumptionType.WALK);
} else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) {
consumption = new Consumption();
} else if (MotionStatesCategorized.get("OTHER").contains(currentState)) {
consumption = getOtherConsumptions();
} else { // ignore
return;
}
if (consumption.amount < 0 && isCharacterStamina) {
// Do not apply reduction factor when recovering stamina
if (player.getTeamManager().getTeamResonances().contains(10301)) {
consumption.amount *= 0.85f;
}
}
// Delay 1 seconds before starts recovering stamina
if (consumption.amount != 0 && cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0
&& consumption.type != ConsumptionType.POWERED_FLY
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
if (staminaRecoverDelay < 5) {
// For others recover after 1 seconds (5 ticks) - as official server does.
staminaRecoverDelay++;
consumption.amount = 0;
logger.trace("Delaying recovery: " + staminaRecoverDelay);
}
}
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
}
}
previousState = currentState;
previousCoordinates = currentCoordinates.clone();
}
}
private void handleDrowning() { private void handleDrowning() {
// TODO: fix drowning waverider entity // TODO: fix drowning waverider entity
int stamina = getCurrentCharacterStamina(); int stamina = getCurrentCharacterStamina();
@ -452,6 +514,10 @@ public class StaminaManager extends BasePlayerManager {
} }
} }
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private Consumption getFightConsumption(int skillCasting) { private Consumption getFightConsumption(int skillCasting) {
// Talent moving // Talent moving
if (TalentMovements.contains(skillCasting)) { if (TalentMovements.contains(skillCasting)) {
@ -471,10 +537,6 @@ public class StaminaManager extends BasePlayerManager {
}; };
} }
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private Consumption getClimbConsumption() { private Consumption getClimbConsumption() {
Consumption consumption = new Consumption(); Consumption consumption = new Consumption();
if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) { if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) {
@ -552,6 +614,8 @@ public class StaminaManager extends BasePlayerManager {
return new Consumption(); return new Consumption();
} }
// Reduction getter
private float getTalentCostReductionFactor(HashMap<Integer, Float> talentReductionMap) { private float getTalentCostReductionFactor(HashMap<Integer, Float> talentReductionMap) {
// All known talents reductions are not stackable // All known talents reductions are not stackable
float reduction = 1; float reduction = 1;
@ -568,8 +632,6 @@ public class StaminaManager extends BasePlayerManager {
return reduction; return reduction;
} }
// Reduction getter
private float getFoodCostReductionFactor(HashMap<Integer, Float> foodReductionMap) { private float getFoodCostReductionFactor(HashMap<Integer, Float> foodReductionMap) {
// All known food reductions are not stackable // All known food reductions are not stackable
// TODO: Check consumed food (buff?) and return proper factor // TODO: Check consumed food (buff?) and return proper factor
@ -633,76 +695,11 @@ public class StaminaManager extends BasePlayerManager {
private Consumption getSwordCost(int skillId) { private Consumption getSwordCost(int skillId) {
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000);
// Character specific handling // Character specific handling
if (skillId == 10421) { switch (skillId) {
case 10421:
consumption.amount = -2500; consumption.amount = -2500;
break;
} }
return consumption; return consumption;
} }
private class SustainedStaminaHandler extends TimerTask {
public void run() {
boolean moving = isPlayerMoving();
int currentCharacterStamina = getCurrentCharacterStamina();
int maxCharacterStamina = getMaxCharacterStamina();
int currentVehicleStamina = getCurrentVehicleStamina();
int maxVehicleStamina = getMaxVehicleStamina();
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
logger.trace("Player moving: " + moving + ", stamina full: " +
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
boolean isCharacterStamina = true;
Consumption consumption;
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbConsumption();
} else if (MotionStatesCategorized.get("DASH").contains(currentState)) {
consumption = getDashConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlyConsumption();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = new Consumption(ConsumptionType.RUN);
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
consumption = getSkiffConsumption();
isCharacterStamina = false;
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = new Consumption(ConsumptionType.STANDBY);
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
consumption = getSwimConsumptions();
} else if (MotionStatesCategorized.get("WALK").contains(currentState)) {
consumption = new Consumption(ConsumptionType.WALK);
} else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) {
consumption = new Consumption();
} else if (MotionStatesCategorized.get("OTHER").contains(currentState)) {
consumption = getOtherConsumptions();
} else { // ignore
return;
}
if (consumption.amount < 0 && isCharacterStamina) {
// Do not apply reduction factor when recovering stamina
if (player.getTeamManager().getTeamResonances().contains(10301)) {
consumption.amount *= 0.85f;
}
}
// Delay 1 seconds before starts recovering stamina
if (consumption.amount != 0 && cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0
&& consumption.type != ConsumptionType.POWERED_FLY
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
if (staminaRecoverDelay < 5) {
// For others recover after 1 seconds (5 ticks) - as official server does.
staminaRecoverDelay++;
consumption.amount = 0;
logger.trace("Delaying recovery: " + staminaRecoverDelay);
}
}
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
}
}
previousState = currentState;
previousCoordinates = currentCoordinates.clone();
}
}
} }

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.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestCond; import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent; 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.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static emu.grasscutter.scripts.constants.EventType.EVENT_UNLOCK_TRANS_POINT;
// @Entity // @Entity
public final class PlayerProgressManager extends BasePlayerDataManager { public final class PlayerProgressManager extends BasePlayerDataManager {
/****************************************************************************************************************** /******************************************************************************************************************
@ -33,22 +37,25 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
GameData.getOpenStateList().stream() GameData.getOpenStateList().stream()
.filter( .filter(
s -> s ->
s.isDefaultState() // Actual default-opened states. s.isDefaultState() && !s.isAllowClientOpen() // Actual default-opened states.
|| ((s.getCond().size() == 1)
&& (s.getCond().get(0).getCondType()
== OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL)
&& (s.getCond().get(0).getParam() == 1))
// All states whose unlock we don't handle correctly yet. // All states whose unlock we don't handle correctly yet.
|| (s.getCond().stream() || (s.getCond().stream()
.filter( .anyMatch(
c -> c ->
c.getCondType() c.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL
== OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) || c.getCondType()
.count() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL))
== 0)
// Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a // Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a
// working chat. // working chat.
|| s.getId() == 1) || s.getId() == 1)
.filter( .filter(
s -> s ->
!BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist. !BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist.
.map(s -> s.getId()) .map(OpenStateData::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
public PlayerProgressManager(Player player) { public PlayerProgressManager(Player player) {
@ -88,6 +95,10 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
if (value != previousValue) { if (value != previousValue) {
this.player.getOpenStates().put(openState, value); this.player.getOpenStates().put(openState, value);
this.player
.getQuestManager()
.queueEvent(QuestCond.QUEST_COND_OPEN_STATE_EQUAL, openState, value);
if (sendNotify) { if (sendNotify) {
player.getSession().send(new PacketOpenStateChangeNotify(openState, value)); player.getSession().send(new PacketOpenStateChangeNotify(openState, value));
} }
@ -104,19 +115,31 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
private boolean areConditionsMet(OpenStateData openState) { private boolean areConditionsMet(OpenStateData openState) {
// Check all conditions and test if at least one of them is violated. // Check all conditions and test if at least one of them is violated.
for (var condition : openState.getCond()) { for (var condition : openState.getCond()) {
switch (condition.getCondType()) {
// For level conditions, check if the player has reached the necessary level. // For level conditions, check if the player has reached the necessary level.
if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) { case OPEN_STATE_COND_PLAYER_LEVEL -> {
if (this.player.getLevel() < condition.getParam()) { if (this.player.getLevel() < condition.getParam()) {
return false; return false;
} }
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) { }
// ToDo: Implement. case OPEN_STATE_COND_QUEST -> {
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) { // check sub quest id for quest finished met requirements
// ToDo: Implement. var quest = this.player.getQuestManager().getQuestById(condition.getParam());
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) { if (quest == null || quest.getState() != QuestState.QUEST_STATE_FINISHED) {
// ToDo: Implement. return false;
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) { }
}
case OPEN_STATE_COND_PARENT_QUEST -> {
// check main quest id for quest finished met requirements
// TODO not sure if its having or finished quest
var mainQuest = this.player.getQuestManager().getMainQuestById(condition.getParam());
if (mainQuest == null
|| mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED) {
return false;
}
}
// ToDo: Implement. // ToDo: Implement.
case OPEN_STATE_OFFERING_LEVEL, OPEN_STATE_CITY_REPUTATION_LEVEL -> {}
} }
} }
@ -229,6 +252,10 @@ public final class PlayerProgressManager extends BasePlayerDataManager {
this.player this.player
.getQuestManager() .getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId); .queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
this.player
.getScene()
.getScriptManager()
.callEvent(new ScriptArgs(0, EVENT_UNLOCK_TRANS_POINT, sceneId, pointId));
// Send packet. // Send packet.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId)); this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));

View File

@ -68,6 +68,8 @@ public final class TeamManager extends BasePlayerDataManager {
this.gadgets = new HashSet<>(); this.gadgets = new HashSet<>();
this.teamResonances = new IntOpenHashSet(); this.teamResonances = new IntOpenHashSet();
this.teamResonancesConfig = new IntOpenHashSet(); this.teamResonancesConfig = new IntOpenHashSet();
this.trialAvatars = new HashMap<>();
this.trialAvatarTeam = new TeamInfo();
} }
public TeamManager(Player player) { public TeamManager(Player player) {

View File

@ -14,12 +14,6 @@ public class ItemUseUnlockHomeModule extends ItemUseInt {
@Override @Override
public boolean useItem(UseItemParams params) { public boolean useItem(UseItemParams params) {
return true; return false;
}
@Override
public boolean postUseItem(UseItemParams params) {
params.player.addRealmList(this.i);
return true;
} }
} }

View File

@ -9,6 +9,7 @@ import emu.grasscutter.data.excels.*;
import emu.grasscutter.data.excels.codex.CodexAnimalData; import emu.grasscutter.data.excels.codex.CodexAnimalData;
import emu.grasscutter.data.excels.monster.MonsterData; import emu.grasscutter.data.excels.monster.MonsterData;
import emu.grasscutter.data.excels.world.WorldLevelData; import emu.grasscutter.data.excels.world.WorldLevelData;
import emu.grasscutter.data.server.Grid;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.dungeons.DungeonManager; import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.DungeonSettleListener;
@ -46,6 +47,8 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val; import lombok.val;
import javax.annotation.Nullable;
public final 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;
@ -126,6 +129,10 @@ public final class Scene {
.orElse(null); .orElse(null);
} }
@Nullable public Route getSceneRouteById(int routeId) {
return sceneRoutes.get(routeId);
}
/** /**
* 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.
* *
@ -218,8 +225,11 @@ public final class Scene {
// Deregister scene if not in use // Deregister scene if not in use
if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty) { if (this.getPlayerCount() <= 0 && !this.dontDestroyWhenEmpty) {
this.getScriptManager().onDestroy();
this.getWorld().deregisterScene(this); this.getWorld().deregisterScene(this);
} }
this.saveGroups();
} }
private void setupPlayerAvatars(Player player) { private void setupPlayerAvatars(Player player) {
@ -229,9 +239,14 @@ public final class Scene {
// Add new entities for player // Add new entities for player
TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo(); TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo();
for (int avatarId : teamInfo.getAvatars()) { for (int avatarId : teamInfo.getAvatars()) {
EntityAvatar entity = Avatar avatar = player.getAvatars().getAvatarById(avatarId);
new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatarId)); if (avatar == null) {
player.getTeamManager().getActiveTeam().add(entity); if (player.getTeamManager().isUsingTrialTeam()) {
avatar = player.getTeamManager().getTrialAvatars().get(avatarId);
}
if (avatar == null) continue;
}
player.getTeamManager().getActiveTeam().add(new EntityAvatar(player.getScene(), avatar));
} }
// Limit character index in case its out of bounds // Limit character index in case its out of bounds
@ -322,7 +337,12 @@ public final class Scene {
} }
public synchronized void removeEntities(List<GameEntity> entity, VisionType visionType) { public synchronized void removeEntities(List<GameEntity> entity, VisionType visionType) {
var toRemove = entity.stream().map(this::removeEntityDirectly).toList(); var toRemove =
entity.stream()
.filter(Objects::nonNull)
.map(this::removeEntityDirectly)
.filter(Objects::nonNull)
.toList();
if (toRemove.size() > 0) { if (toRemove.size() > 0) {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType)); this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, visionType));
} }
@ -408,17 +428,21 @@ public final class Scene {
// Death event // Death event
target.onDeath(attackerId); target.onDeath(attackerId);
this.triggerDungeonEvent(
DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER_COUNT, ++killedMonsterCount);
} }
public void onTick() { public void onTick() {
// Disable ticking for the player's home world. // 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) {
this.finishLoading();
return; return;
} }
if (this.getScriptManager().isInit()) { if (this.getScriptManager().isInit()) {
this.checkBlocks(); // this.checkBlocks();
this.checkGroups();
} else { } else {
// TEMPORARY // TEMPORARY
this.checkSpawns(); this.checkSpawns();
@ -431,7 +455,10 @@ public final class Scene {
challenge.onCheckTimeOut(); challenge.onCheckTimeOut();
} }
this.blossomManager.onTick(); var sceneTime = getSceneTimeSeconds();
getEntities().forEach((eid, e) -> e.onTick(sceneTime));
blossomManager.onTick();
checkNpcGroup(); checkNpcGroup();
@ -701,10 +728,17 @@ public final class Scene {
Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange); Grasscutter.getConfig().server.game.loadEntitiesForPlayerRange);
} }
private boolean unloadBlockIfNotVisible(Collection<SceneBlock> visible, SceneBlock block) { public Set<Integer> getPlayerActiveGroups(Player player) {
if (visible.contains(block)) return false; // consider the borders' entities of blocks, so we check if contains by index
this.onUnloadBlock(block); Position playerPosition = player.getPosition();
return true; Set<Integer> activeGroups = new HashSet<>();
for (int i = 0; i < 4; i++) {
Grid grid = getScriptManager().getGroupGrids().get(i);
activeGroups.addAll(grid.getNearbyGroups(i, playerPosition));
}
return activeGroups;
} }
public synchronized boolean loadBlock(SceneBlock block) { public synchronized boolean loadBlock(SceneBlock block) {
@ -715,63 +749,44 @@ public final class Scene {
return true; return true;
} }
public synchronized void checkBlocks() { public synchronized void checkGroups() {
Set<SceneBlock> visible = Set<Integer> visible =
this.players.stream() this.players.stream()
.map(this::getPlayerActiveBlocks) .map(player -> this.getPlayerActiveGroups(player))
.flatMap(Collection::stream) .flatMap(Collection::stream)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
this.loadedBlocks.removeIf(block -> unloadBlockIfNotVisible(visible, block)); Iterator<SceneGroup> it = this.loadedGroups.iterator();
while (it.hasNext()) {
SceneGroup group = it.next();
if (!visible.contains(group.id) && !group.dynamic_load)
unloadGroup(scriptManager.getBlocks().get(group.block_id), group.id);
}
List<SceneGroup> toLoad =
visible.stream() visible.stream()
.filter(block -> !this.loadBlock(block)) .filter(g -> this.loadedGroups.stream().filter(gr -> gr.id == g).count() == 0)
.forEach( .map(
block -> { g -> {
// dynamic load the groups for players in a loaded block for (var b : scriptManager.getBlocks().values()) {
var toLoad = loadBlock(b);
this.players.stream() SceneGroup group = b.groups.getOrDefault(g, null);
.filter(p -> block.contains(p.getPosition())) if (group != null && !group.dynamic_load) return group;
.map(p -> this.playerMeetGroups(p, block)) }
.flatMap(Collection::stream)
return null;
})
.filter(Objects::nonNull)
.toList(); .toList();
this.onLoadGroup(toLoad); this.onLoadGroup(toLoad);
}); if (!toLoad.isEmpty()) this.onRegisterGroups();
}
public List<SceneGroup> playerMeetGroups(Player player, SceneBlock block) {
List<SceneGroup> sceneGroups =
SceneIndexManager.queryNeighbors(
block.sceneGroupIndex,
player.getPosition().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();
}
return groups;
} }
public void onLoadBlock(SceneBlock block, List<Player> players) { public void onLoadBlock(SceneBlock block, List<Player> players) {
this.getScriptManager().loadBlockFromScript(block); this.getScriptManager().loadBlockFromScript(block);
scriptManager.getLoadedGroupSetPerBlock().put(block.id, new HashSet<>()); 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.getPosition()))
.map(p -> playerMeetGroups(p, block))
.flatMap(Collection::stream)
.toList();
onLoadGroup(groups);
Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id);
} }
@ -780,7 +795,7 @@ public final class Scene {
if (group == null || getScriptManager().getGroupInstanceById(group_id) != null) if (group == null || getScriptManager().getGroupInstanceById(group_id) != null)
return -1; // Group not found or already instanced return -1; // Group not found or already instanced
onLoadGroup(new ArrayList<>(List.of(group))); this.onLoadGroup(new ArrayList<>(List.of(group)));
if (GameData.getGroupReplacements().containsKey(group_id)) onRegisterGroups(); if (GameData.getGroupReplacements().containsKey(group_id)) onRegisterGroups();
@ -824,7 +839,6 @@ public final class Scene {
KahnsSort.Graph graph = new KahnsSort.Graph(nodes, groupList); KahnsSort.Graph graph = new KahnsSort.Graph(nodes, groupList);
List<Integer> dynamicGroupsOrdered = KahnsSort.doSort(graph); 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 // Now we can start unloading and loading groups :D
dynamicGroupsOrdered.forEach( dynamicGroupsOrdered.forEach(
@ -929,38 +943,10 @@ public final class Scene {
Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size()); Grasscutter.getLogger().info("Scene {} loaded {} group(s)", this.getId(), groups.size());
} }
public void onUnloadBlock(SceneBlock block) { public void unloadGroup(SceneBlock block, int group_id) {
List<GameEntity> toRemove = List<GameEntity> toRemove =
this.getEntities().values().stream().filter(e -> e.getBlockId() == block.id).toList();
if (toRemove.size() > 0) {
toRemove.forEach(this::removeEntityDirectly);
this.broadcastPacket(
new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE));
}
for (SceneGroup group : block.groups.values()) {
if (group.triggers != null) {
group.triggers.values().forEach(getScriptManager()::deregisterTrigger);
}
if (group.regions != null) {
group.regions.values().forEach(getScriptManager()::deregisterRegion);
}
}
scriptManager.getLoadedGroupSetPerBlock().remove(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() this.getEntities().values().stream()
.filter(e -> e != null && (e.getBlockId() == block.id && e.getGroupId() == groupId)) .filter(e -> e != null && (e.getBlockId() == block.id && e.getGroupId() == group_id))
.toList(); .toList();
if (toRemove.size() > 0) { if (toRemove.size() > 0) {
@ -969,15 +955,15 @@ public final class Scene {
new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE));
} }
var group = block.groups.get(groupId); var group = block.groups.get(group_id);
if (group.triggers != null) { if (group.triggers != null) {
group.triggers.values().forEach(this.getScriptManager()::deregisterTrigger); group.triggers.values().forEach(getScriptManager()::deregisterTrigger);
} }
if (group.regions != null) { if (group.regions != null) {
group.regions.values().forEach(this.getScriptManager()::deregisterRegion); group.regions.values().forEach(getScriptManager()::deregisterRegion);
} }
this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group); scriptManager.getLoadedGroupSetPerBlock().get(block.id).remove(group);
this.loadedGroups.remove(group); this.loadedGroups.remove(group);
if (this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).isEmpty()) { if (this.scriptManager.getLoadedGroupSetPerBlock().get(block.id).isEmpty()) {
@ -985,7 +971,7 @@ public final class Scene {
Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id);
} }
this.broadcastPacket(new PacketGroupUnloadNotify(List.of(groupId))); this.broadcastPacket(new PacketGroupUnloadNotify(List.of(group_id)));
this.scriptManager.unregisterGroup(group); this.scriptManager.unregisterGroup(group);
} }
@ -1155,4 +1141,8 @@ public final class Scene {
} }
} }
} }
public void saveGroups() {
this.getScriptManager().getCachedGroupInstances().values().forEach(SceneGroupInstance::save);
}
} }

View File

@ -217,9 +217,14 @@ public class World implements Iterable<Player> {
} }
public void deregisterScene(Scene scene) { public void deregisterScene(Scene scene) {
scene.saveGroups();
this.getScenes().remove(scene.getId()); this.getScenes().remove(scene.getId());
} }
public void save() {
this.getScenes().values().forEach(Scene::saveGroups);
}
public boolean transferPlayerToScene(Player player, int sceneId, Position pos) { public boolean transferPlayerToScene(Player player, int sceneId, Position pos) {
return this.transferPlayerToScene(player, sceneId, TeleportType.INTERNAL, null, pos); return this.transferPlayerToScene(player, sceneId, TeleportType.INTERNAL, null, pos);
} }
@ -299,6 +304,7 @@ public class World implements Iterable<Player> {
} }
Scene oldScene = null; Scene oldScene = null;
if (player.getScene() != null) { if (player.getScene() != null) {
oldScene = player.getScene(); oldScene = player.getScene();
@ -312,8 +318,13 @@ public class World implements Iterable<Player> {
var newScene = this.getSceneById(teleportProperties.getSceneId()); var newScene = this.getSceneById(teleportProperties.getSceneId());
newScene.addPlayer(player); newScene.addPlayer(player);
player.getTeamManager().applyAbilities(newScene); player.setAvatarsAbilityForScene(newScene);
// Dungeon
// Dungeon system is handling this already
// if(dungeonData!=null){
// var dungeonManager = new DungeonManager(newScene, dungeonData);
// dungeonManager.startDungeon();
// }
SceneConfig config = newScene.getScriptManager().getConfig(); SceneConfig config = newScene.getScriptManager().getConfig();
if (teleportProperties.getTeleportTo() == null && config != null) { if (teleportProperties.getTeleportTo() == null && config != null) {
if (config.born_pos != null) { if (config.born_pos != null) {