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:
赵怡然
2022-08-21 14:19:59 +08:00
committed by GitHub
parent 957296fa2d
commit abd1e7569e
21 changed files with 670 additions and 261 deletions

View File

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

View File

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

View File

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

View File

@@ -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)];
}
}