mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2026-02-07 10:36:41 +01:00
Blossom Implement (#1606)
* Blossom! * rename * delete SpawnBlossomEntry.java * use MAP * use List * use LIST * use List * useCondensedResin * useCondensedResin * fix build * enhance * fix bug * REMOVE BOSS * fix condensed resin * fix condensed resin * use POSITIVE_INFINITY * use RewardPreviewData * fix build * fix resources * add BLOSSOM_MONSTER_FIGHTING_VOLUME * edit monster score * edit monster score * fix bug * fix bug * improve logic * fix monsters level * Deleted comment blocks * nitpick * Fix compilation problems * nitpick * Refactor + nitpick * Clean up overall diff to develop * Clean up other usage of condensed resin * Clean up overall diff to develop * Lombokify Scene.java * Missed an odd getter name * Unhardcode reward previews * EDIT NAME * remove leyline 1 * remove leyline 2 * Update BlossomManager.java Co-authored-by: AnimeGitB <AnimeGitB@bigblueball.in>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package emu.grasscutter.game.managers.blossom;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.MonsterData;
|
||||
import emu.grasscutter.data.excels.WorldLevelData;
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneBossChest;
|
||||
import emu.grasscutter.scripts.data.SceneGadget;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
public class BlossomActivity {
|
||||
|
||||
private final SceneGroup tempSceneGroup;
|
||||
private final WorldChallenge challenge;
|
||||
private final EntityGadget gadget;
|
||||
private EntityGadget chest;
|
||||
private int step;
|
||||
private final int goal;
|
||||
private int generatedCount;
|
||||
private final int worldLevel;
|
||||
private boolean pass=false;
|
||||
private final List<EntityMonster> activeMonsters = new ArrayList<>();
|
||||
private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
|
||||
private static final int BLOOMING_GADGET_ID = 70210109;
|
||||
public BlossomActivity(EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) {
|
||||
this.tempSceneGroup = new SceneGroup();
|
||||
this.tempSceneGroup.id = entityGadget.getId();
|
||||
this.gadget=entityGadget;
|
||||
this.step=0;
|
||||
this.goal = monsters.size();
|
||||
this.candidateMonsters.addAll(monsters);
|
||||
this.worldLevel = worldLevel;
|
||||
ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>();
|
||||
this.challenge = new WorldChallenge(entityGadget.getScene(),
|
||||
tempSceneGroup,
|
||||
1,
|
||||
1,
|
||||
List.of(goal, timeout),
|
||||
timeout,
|
||||
goal, challengeTriggers);
|
||||
challengeTriggers.add(new KillMonsterTrigger());
|
||||
//this.challengeTriggers.add(new InTimeTrigger());
|
||||
}
|
||||
public WorldChallenge getChallenge(){
|
||||
return this.challenge;
|
||||
}
|
||||
public void setMonsters(List<EntityMonster> monsters) {
|
||||
this.activeMonsters.clear();
|
||||
this.activeMonsters.addAll(monsters);
|
||||
for(EntityMonster monster : monsters){
|
||||
monster.setGroupId(this.tempSceneGroup.id);
|
||||
}
|
||||
}
|
||||
public int getAliveMonstersCount(){
|
||||
int count=0;
|
||||
for(EntityMonster monster: activeMonsters) {
|
||||
if(monster.isAlive()){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
public boolean getPass(){
|
||||
return pass;
|
||||
}
|
||||
public void start(){
|
||||
challenge.start();
|
||||
}
|
||||
public void onTick() {
|
||||
Scene scene = gadget.getScene();
|
||||
Position pos = gadget.getPosition();
|
||||
if(getAliveMonstersCount() <= 2){
|
||||
if(generatedCount<goal){
|
||||
step++;
|
||||
|
||||
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
|
||||
int worldLevelOverride = 0;
|
||||
if (worldLevelData != null) {
|
||||
worldLevelOverride = worldLevelData.getMonsterLevel();
|
||||
}
|
||||
|
||||
List<EntityMonster> newMonsters = new ArrayList<>();
|
||||
int willSpawn = Utils.randomRange(3,5);
|
||||
if(generatedCount+willSpawn>goal){
|
||||
willSpawn = goal - generatedCount;
|
||||
}
|
||||
generatedCount+=willSpawn;
|
||||
for (int i = 0; i < willSpawn; i++) {
|
||||
MonsterData monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll());
|
||||
int level = scene.getEntityLevel(1, worldLevelOverride);
|
||||
EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(40), level);
|
||||
scene.addEntity(entity);
|
||||
newMonsters.add(entity);
|
||||
}
|
||||
setMonsters(newMonsters);
|
||||
}else{
|
||||
if(getAliveMonstersCount() == 0) {
|
||||
this.pass = true;
|
||||
this.challenge.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public EntityGadget getGadget(){
|
||||
return gadget;
|
||||
}
|
||||
public EntityGadget getChest(){
|
||||
if(chest==null) {
|
||||
EntityGadget rewardGadget = new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
|
||||
SceneGadget metaGadget = new SceneGadget();
|
||||
metaGadget.boss_chest = new SceneBossChest();
|
||||
metaGadget.boss_chest.resin = 20;
|
||||
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY);
|
||||
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY);
|
||||
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY);
|
||||
rewardGadget.setMetaGadget(metaGadget);
|
||||
rewardGadget.buildContent();
|
||||
chest = rewardGadget;
|
||||
}
|
||||
return chest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package emu.grasscutter.game.managers.blossom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class BlossomConfig {
|
||||
@Getter private int monsterFightingVolume;
|
||||
// @Getter private Int2ObjectMap<IntList> monsterIdsPerDifficulty; // Need to wrangle Gson for this
|
||||
@Getter private Map<Integer, List<Integer>> monsterIdsPerDifficulty;
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package emu.grasscutter.game.managers.blossom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.GameDepot;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.excels.RewardPreviewData;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.game.world.SpawnDataEntry;
|
||||
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
|
||||
import emu.grasscutter.net.proto.BlossomBriefInfoOuterClass;
|
||||
import emu.grasscutter.net.proto.VisionTypeOuterClass;
|
||||
import emu.grasscutter.server.packet.send.PacketBlossomBriefInfoNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
public class BlossomManager {
|
||||
public BlossomManager(Scene scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
private final Scene scene;
|
||||
private final List<BlossomActivity> blossomActivities = new ArrayList<>();
|
||||
private final List<BlossomActivity> activeChests = new ArrayList<>();
|
||||
private final List<EntityGadget> createdEntity = new ArrayList<>();
|
||||
|
||||
private final List<SpawnDataEntry> blossomConsumed = new ArrayList<>();
|
||||
|
||||
public void onTick(){
|
||||
synchronized (blossomActivities){
|
||||
var it = blossomActivities.iterator();
|
||||
while(it.hasNext()){
|
||||
var active = it.next();
|
||||
active.onTick();
|
||||
if (active.getPass()) {
|
||||
EntityGadget chest = active.getChest();
|
||||
scene.addEntity(chest);
|
||||
scene.setChallenge(null);
|
||||
activeChests.add(active);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void recycleGadgetEntity(List<GameEntity> entities){
|
||||
for(var entity : entities){
|
||||
if(entity instanceof EntityGadget gadget){
|
||||
createdEntity.remove(gadget);
|
||||
}
|
||||
}
|
||||
notifyIcon();
|
||||
}
|
||||
|
||||
public void initBlossom(EntityGadget gadget){
|
||||
if(createdEntity.contains(gadget)){
|
||||
return;
|
||||
}
|
||||
if(blossomConsumed.contains(gadget.getSpawnEntry())){
|
||||
return;
|
||||
}
|
||||
var id = gadget.getGadgetId();
|
||||
if(BlossomType.valueOf(id)==null){
|
||||
return;
|
||||
}
|
||||
gadget.buildContent();
|
||||
gadget.setState(204);
|
||||
int worldLevel = getWorldLevel();
|
||||
GadgetWorktop gadgetWorktop = ((GadgetWorktop) gadget.getContent());
|
||||
gadgetWorktop.addWorktopOptions(new int[]{187});
|
||||
gadgetWorktop.setOnSelectWorktopOptionEvent((GadgetWorktop context, int option) -> {
|
||||
BlossomActivity activity;
|
||||
EntityGadget entityGadget = context.getGadget();
|
||||
synchronized (blossomActivities) {
|
||||
for (BlossomActivity i : this.blossomActivities) {
|
||||
if (i.getGadget() == entityGadget) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int volume=0;
|
||||
IntList monsters = new IntArrayList();
|
||||
while(true){
|
||||
var remain = GameDepot.getBlossomConfig().getMonsterFightingVolume() - volume;
|
||||
if(remain<=0){
|
||||
break;
|
||||
}
|
||||
var rand = Utils.randomRange(1,100);
|
||||
if(rand>85 && remain>=50){//15% ,generate strong monster
|
||||
monsters.addAll(getRandomMonstersID(2,1));
|
||||
volume+=50;
|
||||
}else if(rand>50 && remain>=20) {//35% ,generate normal monster
|
||||
monsters.addAll(getRandomMonstersID(1,1));
|
||||
volume+=20;
|
||||
}else{//50% ,generate weak monster
|
||||
monsters.addAll(getRandomMonstersID(0,1));
|
||||
volume+=10;
|
||||
}
|
||||
}
|
||||
|
||||
Grasscutter.getLogger().info("Blossom Monsters:"+monsters);
|
||||
|
||||
activity = new BlossomActivity(entityGadget, monsters, -1, worldLevel);
|
||||
blossomActivities.add(activity);
|
||||
}
|
||||
entityGadget.updateState(201);
|
||||
scene.setChallenge(activity.getChallenge());
|
||||
scene.removeEntity(entityGadget, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
|
||||
activity.start();
|
||||
return true;
|
||||
});
|
||||
createdEntity.add(gadget);
|
||||
notifyIcon();
|
||||
}
|
||||
|
||||
public void notifyIcon() {
|
||||
final int wl = getWorldLevel();
|
||||
final int worldLevel = (wl < 0) ? 0 : ((wl > 8) ? 8 : wl);
|
||||
final int monsterLevel = GameData.getWorldLevelDataMap().get(worldLevel).getMonsterLevel();
|
||||
List<BlossomBriefInfoOuterClass.BlossomBriefInfo> blossoms = new ArrayList<>();
|
||||
GameDepot.getSpawnLists().forEach((gridBlockId, spawnDataEntryList) -> {
|
||||
int sceneId = gridBlockId.getSceneId();
|
||||
spawnDataEntryList.stream()
|
||||
.map(SpawnDataEntry::getGroup)
|
||||
.map(SpawnGroupEntry::getSpawns)
|
||||
.flatMap(List::stream)
|
||||
.filter(spawn -> !blossomConsumed.contains(spawn))
|
||||
.filter(spawn -> BlossomType.valueOf(spawn.getGadgetId()) != null)
|
||||
.forEach(spawn -> {
|
||||
var type = BlossomType.valueOf(spawn.getGadgetId());
|
||||
int previewReward = getPreviewReward(type, worldLevel);
|
||||
blossoms.add(BlossomBriefInfoOuterClass.BlossomBriefInfo.newBuilder()
|
||||
.setSceneId(sceneId)
|
||||
.setPos(spawn.getPos().toProto())
|
||||
.setResin(20)
|
||||
.setMonsterLevel(monsterLevel)
|
||||
.setRewardId(previewReward)
|
||||
.setCircleCampId(type.getCircleCampId())
|
||||
.setRefreshId(type.getBlossomChestId()) // TODO: replace when using actual leylines
|
||||
.build()
|
||||
);
|
||||
});
|
||||
});
|
||||
scene.broadcastPacket(new PacketBlossomBriefInfoNotify(blossoms));
|
||||
}
|
||||
|
||||
public int 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) {
|
||||
var resinManager = player.getResinManager();
|
||||
synchronized (activeChests) {
|
||||
var it = activeChests.iterator();
|
||||
while (it.hasNext()) {
|
||||
var activeChest = it.next();
|
||||
if (activeChest.getChest() == chest) {
|
||||
boolean pay = useCondensedResin ? resinManager.useCondensedResin(1) : resinManager.useResin(20);
|
||||
if (pay) {
|
||||
int worldLevel = getWorldLevel();
|
||||
List<GameItem> items = new ArrayList<>();
|
||||
var gadget = activeChest.getGadget();
|
||||
var type = BlossomType.valueOf(gadget.getGadgetId());
|
||||
RewardPreviewData blossomRewards = getRewardList(type, worldLevel);
|
||||
if (blossomRewards == null) {
|
||||
Grasscutter.getLogger().error("Blossom could not support world level : "+worldLevel);
|
||||
return null;
|
||||
}
|
||||
var rewards = blossomRewards.getPreviewItems();
|
||||
for (ItemParamData blossomReward : rewards) {
|
||||
int rewardCount = blossomReward.getCount();
|
||||
if (useCondensedResin) {
|
||||
rewardCount += blossomReward.getCount(); // Double!
|
||||
}
|
||||
items.add(new GameItem(blossomReward.getItemId(),rewardCount));
|
||||
}
|
||||
it.remove();
|
||||
recycleGadgetEntity(List.of(gadget));
|
||||
blossomConsumed.add(gadget.getSpawnEntry());
|
||||
return items;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package emu.grasscutter.game.managers.blossom;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
public enum BlossomType {
|
||||
GOLD(70360056, 101001001, 1),
|
||||
BLUE(70360057, 101002003, 2);
|
||||
|
||||
@Getter private final int gadgetId;
|
||||
@Getter private final int circleCampId;
|
||||
@Getter private final int blossomChestId;
|
||||
|
||||
BlossomType(int gadgetId, int circleCampId, int blossomChestId) {
|
||||
this.gadgetId = gadgetId;
|
||||
this.circleCampId = circleCampId;
|
||||
this.blossomChestId = blossomChestId;
|
||||
}
|
||||
|
||||
private static final Int2ObjectMap<BlossomType> map = new Int2ObjectOpenHashMap<>(
|
||||
Stream.of(values()).collect(Collectors.toMap(x -> x.getGadgetId(), x -> x))
|
||||
);
|
||||
|
||||
public static BlossomType valueOf(int i) {
|
||||
return map.get(i);
|
||||
}
|
||||
|
||||
public static BlossomType random() {
|
||||
BlossomType[] values = values();
|
||||
return values[Utils.randomRange(0, values.length)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user