Levelup City Implementation (#2281)

* add statue promo data

* implement levelup city feature

* fix get level city when enter game

* format code

* fix typo, remove some property in the player, add the field cityInfoData to player class
This commit is contained in:
Phong 2023-08-12 10:54:19 +07:00 committed by GitHub
parent d0dde1c9e2
commit bdc4b5af89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 214 additions and 5 deletions

View File

@ -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<>();
@ -567,6 +571,10 @@ public final class GameData {
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);
}

View File

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

View File

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

View File

@ -2,15 +2,24 @@ package emu.grasscutter.game.managers;
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;
@ -208,4 +217,91 @@ public class SotSManager extends BasePlayerManager {
}
}
}
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));
}
}

View File

@ -15,6 +15,7 @@ import emu.grasscutter.game.activity.ActivityManager;
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;
@ -28,7 +29,6 @@ import emu.grasscutter.game.mail.MailHandler;
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;
@ -38,6 +38,7 @@ import emu.grasscutter.game.managers.forging.ActiveForgeData;
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;
@ -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() {
@ -267,6 +270,7 @@ public class Player implements PlayerHook, FieldFetch {
this.chatEmojiIdList = new ArrayList<>();
this.playerProgress = new PlayerProgress();
this.activeQuestTimers = new HashSet<>();
this.cityInfoData = new HashMap<>();
this.attackResults = new LinkedBlockingQueue<>();
this.coopRequests = new Int2ObjectOpenHashMap<>();
@ -1520,6 +1524,8 @@ public class Player implements PlayerHook, FieldFetch {
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,

View File

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

View File

@ -3,7 +3,6 @@ package emu.grasscutter.server.packet.send;
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 {
@ -17,9 +16,9 @@ public class PacketGetSceneAreaRsp extends BasePacket {
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);

View File

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