Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Levelup City Implementation #2281

Merged
merged 6 commits into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/emu/grasscutter/data/GameData.java
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ public final class GameData {
private static final Int2ObjectMap<WeaponPromoteData> weaponPromoteDataMap =
new Int2ObjectOpenHashMap<>();

@Getter
private static final Int2ObjectMap<StatuePromoteData> statuePromoteDataMap =
new Int2ObjectOpenHashMap<>();

@Getter
private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>();

Expand Down Expand Up @@ -567,6 +571,10 @@ public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteL
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
}

public static StatuePromoteData getStatuePromoteData(int cityId, int promoteLevel) {
return statuePromoteDataMap.get((cityId << 8) + promoteLevel);
}

public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) {
return reliquaryLevelDataMap.get((rankLevel << 8) + level);
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/emu/grasscutter/data/excels/StatuePromoteData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package emu.grasscutter.data.excels;

import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import lombok.Getter;
import lombok.Setter;

@ResourceType(name = "StatuePromoteExcelConfigData.json")
public class StatuePromoteData extends GameResource {
@Getter @Setter private int level;
@Getter @Setter private int cityId;
@Getter @Setter private ItemParamData[] costItems;
@Getter @Setter private int[] rewardIdList;
@Getter @Setter private int stamina;

@Override
public int getId() {
return (cityId << 8) + level;
}
}
28 changes: 28 additions & 0 deletions src/main/java/emu/grasscutter/game/city/CityInfoData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package emu.grasscutter.game.city;

import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.CityInfoOuterClass.CityInfo;
import lombok.Getter;
import lombok.Setter;

@Entity
public class CityInfoData {
@Getter @Setter private int cityId;

@Getter @Setter
private int level = 1; // level of the city (include level SotS, level Frostbearing Trees, etc.)

@Getter @Setter private int numCrystal = 0; // number of crystals in the city

public CityInfoData(int cityId) {
this.cityId = cityId;
}

public CityInfo toProto() {
return CityInfo.newBuilder()
.setCityId(cityId)
.setLevel(level)
.setCrystalNum(numCrystal)
.build();
}
}
96 changes: 96 additions & 0 deletions src/main/java/emu/grasscutter/game/managers/SotSManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@

import ch.qos.logback.classic.Logger;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.CityData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.game.city.CityInfoData;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLevelupCityRsp;
import emu.grasscutter.server.packet.send.PacketSceneForceUnlockNotify;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
Expand Down Expand Up @@ -208,4 +217,91 @@ public void run() {
}
}
}

public CityData getCityByAreaId(int areaId) {
return GameData.getCityDataMap().values().stream()
.filter(city -> city.getAreaIdVec().contains(areaId))
.findFirst()
.orElse(null);
}

public CityInfoData getCityInfo(int cityId) {
if (player.getCityInfoData() == null) player.setCityInfoData(new HashMap<>());
var cityInfo = player.getCityInfoData().get(cityId);
if (cityInfo == null) {
cityInfo = new CityInfoData(cityId);
player.getCityInfoData().put(cityId, cityInfo);
}
return cityInfo;
}

public void addCityInfo(CityInfoData cityInfoData) {
if (player.getCityInfoData() == null) player.setCityInfoData(new HashMap<>());

player.getCityInfoData().put(cityInfoData.getCityId(), cityInfoData);
}

public void levelUpSotS(int areaId, int sceneId, int itemNum) {
if (itemNum <= 0) return;

// search city by areaId
var city = this.getCityByAreaId(areaId);
if (city == null) return;
var cityId = city.getCityId();

// check data level up
var cityInfo = this.getCityInfo(cityId);
var nextStatuePromoteData = GameData.getStatuePromoteData(cityId, cityInfo.getLevel() + 1);
if (nextStatuePromoteData == null) return;
var nextLevelCrystal = nextStatuePromoteData.getCostItems()[0].getCount();

// delete item from inventory
var itemNumrequired = Math.min(itemNum, nextLevelCrystal - cityInfo.getNumCrystal());
player
.getInventory()
.removeItemById(nextStatuePromoteData.getCostItems()[0].getId(), itemNumrequired);

// update number oculi
cityInfo.setNumCrystal(cityInfo.getNumCrystal() + itemNumrequired);

// hanble quest
if (itemNumrequired >= 1)
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_CITY_LEVEL_UP, cityId, areaId);

// handle oculi overflow
if (cityInfo.getNumCrystal() >= nextLevelCrystal) {
cityInfo.setNumCrystal(cityInfo.getNumCrystal() - nextLevelCrystal);
cityInfo.setLevel(cityInfo.getLevel() + 1);

// update max stamina and notify client
player.setProperty(
PlayerProperty.PROP_MAX_STAMINA,
player.getProperty(PlayerProperty.PROP_MAX_STAMINA)
+ nextStatuePromoteData.getStamina() * 100,
true);

// Add items to inventory
if (nextStatuePromoteData.getRewardIdList() != null) {
for (var rewardId : nextStatuePromoteData.getRewardIdList()) {
RewardData rewardData = GameData.getRewardDataMap().get(rewardId);
if (rewardData == null) continue;

player
.getInventory()
.addItemParamDatas(rewardData.getRewardItemList(), ActionReason.CityLevelupReward);
}
}

// unlock forcescene
player.sendPacket(new PacketSceneForceUnlockNotify(1, true));
}

// update data
this.addCityInfo(cityInfo);

// Packets
player.sendPacket(
new PacketLevelupCityRsp(
sceneId, cityInfo.getLevel(), cityId, cityInfo.getNumCrystal(), areaId, 0));
}
}
8 changes: 7 additions & 1 deletion src/main/java/emu/grasscutter/game/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.city.CityInfoData;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList;
Expand All @@ -28,7 +29,6 @@
import emu.grasscutter.game.managers.FurnitureManager;
import emu.grasscutter.game.managers.ResinManager;
import emu.grasscutter.game.managers.SatiationManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.managers.cooking.ActiveCookCompoundData;
import emu.grasscutter.game.managers.cooking.CookingCompoundManager;
import emu.grasscutter.game.managers.cooking.CookingManager;
Expand All @@ -38,6 +38,7 @@
import emu.grasscutter.game.managers.forging.ForgingManager;
import emu.grasscutter.game.managers.mapmark.MapMark;
import emu.grasscutter.game.managers.mapmark.MapMarksManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.quest.QuestManager;
Expand Down Expand Up @@ -221,6 +222,8 @@ public class Player implements PlayerHook, FieldFetch {

@Getter @Setter private ElementType mainCharacterElement = ElementType.None;

@Getter @Setter private Map<Integer, CityInfoData> cityInfoData; // cityId -> CityData

@Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
public Player() {
Expand Down Expand Up @@ -267,6 +270,7 @@ public Player() {
this.chatEmojiIdList = new ArrayList<>();
this.playerProgress = new PlayerProgress();
this.activeQuestTimers = new HashSet<>();
this.cityInfoData = new HashMap<>();

this.attackResults = new LinkedBlockingQueue<>();
this.coopRequests = new Int2ObjectOpenHashMap<>();
Expand Down Expand Up @@ -1520,6 +1524,8 @@ private boolean setPropertyWithSanityCheck(PlayerProperty prop, int value, boole
PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP));
case PROP_PLAYER_LEVEL -> this.sendPacket(new PacketPlayerPropChangeReasonNotify(this, prop, currentValue, value,
PropChangeReason.PROP_CHANGE_REASON_LEVELUP));
case PROP_MAX_STAMINA -> this.sendPacket(new PacketPlayerPropChangeReasonNotify(this, prop, currentValue, value,
PropChangeReason.PROP_CHANGE_REASON_CITY_LEVELUP));

// TODO: Handle world level changing.
// case PROP_PLAYER_WORLD_LEVEL -> this.sendPacket(new PacketPlayerPropChangeReasonNotify(this, prop, currentValue, value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package emu.grasscutter.server.packet.recv;

import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.LevelupCityReqOuterClass.LevelupCityReq;
import emu.grasscutter.server.game.GameSession;

@Opcodes(PacketOpcodes.LevelupCityReq)
public class HandlerLevelupCityReq extends PacketHandler {

@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
LevelupCityReq req = LevelupCityReq.parseFrom(payload);

// Level up city
session
.getPlayer()
.getSotsManager()
.levelUpSotS(req.getAreaId(), req.getSceneId(), req.getItemNum());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CityInfoOuterClass.CityInfo;
import emu.grasscutter.net.proto.GetSceneAreaRspOuterClass.GetSceneAreaRsp;

public class PacketGetSceneAreaRsp extends BasePacket {
Expand All @@ -17,9 +16,9 @@ public PacketGetSceneAreaRsp(Player player, int sceneId) {
GetSceneAreaRsp.newBuilder()
.setSceneId(sceneId)
.addAllAreaIdList(player.getUnlockedSceneAreas(sceneId))
.addCityInfoList(CityInfo.newBuilder().setCityId(1).setLevel(1).build())
.addCityInfoList(CityInfo.newBuilder().setCityId(2).setLevel(1).build())
.addCityInfoList(CityInfo.newBuilder().setCityId(3).setLevel(1).build())
.addCityInfoList(player.getSotsManager().getCityInfo(1).toProto())
.addCityInfoList(player.getSotsManager().getCityInfo(2).toProto())
.addCityInfoList(player.getSotsManager().getCityInfo(3).toProto())
.build();

this.setData(p);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package emu.grasscutter.server.packet.send;

import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CityInfoOuterClass.CityInfo;
import emu.grasscutter.net.proto.LevelupCityRspOuterClass.LevelupCityRsp;

public class PacketLevelupCityRsp extends BasePacket {

public PacketLevelupCityRsp(
int sceneId, int level, int cityId, int crystalNum, int areaId, int retcode) {
super(PacketOpcodes.LevelupCityRsp);

LevelupCityRsp proto =
LevelupCityRsp.newBuilder()
.setSceneId(sceneId)
.setCityInfo(
CityInfo.newBuilder()
.setCityId(cityId)
.setLevel(level)
.setCrystalNum(crystalNum)
.build())
.setAreaId(areaId)
.setRetcode(retcode)
.build();

this.setData(proto);
}
}