Commit 0bfa1fa0 authored by Akka's avatar Akka Committed by GitHub
Browse files

Merge pull request #8 from Grasscutters/development

Development
parents 65861c3c 056b5b80
This diff is collapsed.
......@@ -70,7 +70,7 @@ dependencies {
implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.8'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0'
implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2'
implementation group: 'org.reflections', name: 'reflections', version: '0.10.2'
......
{
"scheduleId" : 1,
"scheduleStartTime" : "2022-05-01T00:00:00+08:00",
"nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00"
}
\ No newline at end of file
......@@ -16,7 +16,6 @@ public final class Config {
public String KEY_FOLDER = "./keys/";
public String SCRIPTS_FOLDER = "./resources/Scripts/";
public String PLUGINS_FOLDER = "./plugins/";
public String LANGUAGE_FOLDER = "./languages/";
public ServerDebugMode DebugMode = ServerDebugMode.NONE; // ALL, MISSING, NONE
public ServerRunMode RunMode = ServerRunMode.HYBRID; // HYBRID, DISPATCH_ONLY, GAME_ONLY
......
......@@ -9,28 +9,121 @@ import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.inventory.EquipType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static java.util.Map.entry;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "giveart", usage = "giveart <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description")
public final class GiveArtifactCommand implements CommandHandler {
private static final Map<String, Map<EquipType, Integer>> mainPropMap = Map.ofEntries(
entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))),
entry("hp%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10980), entry(EquipType.EQUIP_RING, 50980), entry(EquipType.EQUIP_DRESS, 30980))),
entry("atk", Map.ofEntries(entry(EquipType.EQUIP_NECKLACE, 12001))),
entry("atk%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10990), entry(EquipType.EQUIP_RING, 50990), entry(EquipType.EQUIP_DRESS, 30990))),
entry("def%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10970), entry(EquipType.EQUIP_RING, 50970), entry(EquipType.EQUIP_DRESS, 30970))),
entry("er", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10960))),
entry("em", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10950), entry(EquipType.EQUIP_RING, 50880), entry(EquipType.EQUIP_DRESS, 30930))),
entry("hb", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30940))),
entry("cdmg", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30950))),
entry("cr", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30960))),
entry("phys%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50890))),
entry("dendro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50900))),
entry("geo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50910))),
entry("anemo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50920))),
entry("hydro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50930))),
entry("cryo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50940))),
entry("electro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50950))),
entry("pyro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50960)))
);
private static final Map<String, String> appendPropMap = Map.ofEntries(
entry("hp", "0102"),
entry("hp%", "0103"),
entry("atk", "0105"),
entry("atk%", "0106"),
entry("def", "0108"),
entry("def%", "0109"),
entry("er", "0123"),
entry("em", "0124"),
entry("cr", "0120"),
entry("cdmg", "0122")
);
private int getAppendPropId(String substatText, ItemData itemData) {
int res;
// If the given substat text is an integer, we just use that
// as the append prop ID.
try {
res = Integer.parseInt(substatText);
return res;
}
catch (NumberFormatException ignores) {
// No need to handle this here. We just continue with the
// possibility of the argument being a substat string.
}
// If the argument was not an integer, we try to determine
// the append prop ID from the given text + artifact information.
// A substat string has the format `substat_tier`, with the
// `_tier` part being optional.
String[] substatArgs = substatText.split("_");
String substatType;
int substatTier;
if (substatArgs.length == 1) {
substatType = substatArgs[0];
substatTier =
itemData.getRankLevel() == 1 ? 2
: itemData.getRankLevel() == 2 ? 3
: 4;
}
else if (substatArgs.length == 2) {
substatType = substatArgs[0];
substatTier = Integer.parseInt(substatArgs[1]);
}
else {
throw new IllegalArgumentException();
}
// Check if the specified tier is legal for the artifact rarity.
if (substatTier < 1 || substatTier > 4) {
throw new IllegalArgumentException();
}
if (itemData.getRankLevel() == 1 && substatTier > 2) {
throw new IllegalArgumentException();
}
if (itemData.getRankLevel() == 2 && substatTier > 3) {
throw new IllegalArgumentException();
}
// Check if the given substat type string is a legal stat.
if (!appendPropMap.containsKey(substatType)) {
throw new IllegalArgumentException();
}
// Build the append prop ID.
return Integer.parseInt(Integer.toString(itemData.getRankLevel()) + appendPropMap.get(substatType) + Integer.toString(substatTier));
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
// Sanity checks
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() < 2) {
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.usage"));
return;
}
// Get the artifact piece ID from the arguments.
int itemId;
try {
itemId = Integer.parseInt(args.remove(0));
......@@ -38,20 +131,35 @@ public final class GiveArtifactCommand implements CommandHandler {
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error"));
return;
}
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) {
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error"));
return;
}
// Get the main stat from the arguments.
// If the given argument is an integer, we use that.
// If not, we check if the argument string is in the main prop map.
String mainPropIdString = args.remove(0);
int mainPropId;
try {
mainPropId = Integer.parseInt(args.remove(0));
mainPropId = Integer.parseInt(mainPropIdString);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate("commands.generic.execution.argument_error"));
mainPropId = -1;
}
if (mainPropMap.containsKey(mainPropIdString) && mainPropMap.get(mainPropIdString).containsKey(itemData.getEquipType())) {
mainPropId = mainPropMap.get(mainPropIdString).get(itemData.getEquipType());
}
if (mainPropId == -1) {
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
return;
}
// Get the level from the arguments.
int level = 1;
try {
int last = Integer.parseInt(args.get(args.size()-1));
......@@ -62,9 +170,13 @@ public final class GiveArtifactCommand implements CommandHandler {
} catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic
}
List<Integer> appendPropIdList = new ArrayList<>();
// Get substats.
ArrayList<Integer> appendPropIdList = new ArrayList<>();
try {
// Every remaining argument is a substat.
args.forEach(it -> {
// The substat syntax permits specifying a number of rolls for the given
// substat. Split the string into stat and number if that is the case here.
String[] arr;
int n = 1;
if ((arr = it.split(",")).length == 2) {
......@@ -74,13 +186,19 @@ public final class GiveArtifactCommand implements CommandHandler {
n = 200;
}
}
appendPropIdList.addAll(Collections.nCopies(n, Integer.parseInt(it)));
// Determine the substat ID.
int appendPropId = getAppendPropId(it, itemData);
// Add the current substat.
appendPropIdList.addAll(Collections.nCopies(n, appendPropId));
});
} catch (Exception ignored) {
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
return;
}
// Create item for the artifact.
GameItem item = new GameItem(itemData);
item.setLevel(level);
item.setMainPropId(mainPropId);
......
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.tower.TowerLevelRecord;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"},
description = "Unlock all levels of tower", permission = "player.tower")
public class UnlockTowerCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
unlockFloor(sender, sender.getServer().getTowerScheduleManager()
.getCurrentTowerScheduleData().getEntranceFloorId());
unlockFloor(sender, sender.getServer().getTowerScheduleManager()
.getScheduleFloors());
CommandHandler.sendMessage(sender, translate("commands.tower.unlock_done"));
}
public void unlockFloor(Player player, List<Integer> floors){
floors.stream()
.filter(id -> !player.getTowerManager().getRecordMap().containsKey(id))
.forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id)));
}
}
......@@ -9,6 +9,8 @@ import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.AbilityModifier;
import emu.grasscutter.data.custom.AbilityModifierEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.data.def.*;
......@@ -22,6 +24,7 @@ public class GameData {
// BinOutputs
private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
private static final Map<String, AbilityEmbryoEntry> abilityEmbryos = new HashMap<>();
private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>();
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
......@@ -70,6 +73,7 @@ public class GameData {
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
// Cache
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
......@@ -101,6 +105,10 @@ public class GameData {
return abilityEmbryos;
}
public static Map<String, AbilityModifierEntry> getAbilityModifiers() {
return abilityModifiers;
}
public static Map<String, OpenConfigEntry> getOpenConfigEntries() {
return openConfigEntries;
}
......@@ -320,4 +328,7 @@ public class GameData {
public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){
return towerLevelDataMap;
}
public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){
return towerScheduleDataMap;
}
}
......@@ -18,6 +18,11 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.PointData;
import emu.grasscutter.data.common.ScenePointConfig;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.AbilityModifier;
import emu.grasscutter.data.custom.AbilityModifier.AbilityConfigData;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType;
import emu.grasscutter.data.custom.AbilityModifierEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.game.world.SpawnDataEntry;
......@@ -47,6 +52,7 @@ public class ResourceLoader {
// Load ability lists
loadAbilityEmbryos();
loadOpenConfig();
loadAbilityModifiers();
// Load resources
loadResources();
// Process into depots
......@@ -244,6 +250,69 @@ public class ResourceLoader {
}
}
private static void loadAbilityModifiers() {
// Load from BinOutput
File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Ability/Temp/AvatarAbilities/"));
File[] files = folder.listFiles();
if (files == null) {
Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath());
return;
}
for (File file : files) {
List<AbilityConfigData> abilityConfigList = null;
try (FileReader fileReader = new FileReader(file)) {
abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType());
} catch (Exception e) {
e.printStackTrace();
continue;
}
for (AbilityConfigData data : abilityConfigList) {
if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) {
continue;
}
AbilityModifierEntry modifierEntry = new AbilityModifierEntry(data.Default.abilityName);
for (Entry<String, AbilityModifier> entry : data.Default.modifiers.entrySet()) {
AbilityModifier modifier = entry.getValue();
// Stare.
if (modifier.onAdded != null) {
for (AbilityModifierAction action : modifier.onAdded) {
if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnAdded().add(action);
}
}
}
if (modifier.onThinkInterval != null) {
for (AbilityModifierAction action : modifier.onThinkInterval) {
if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnThinkInterval().add(action);
}
}
}
if (modifier.onRemoved != null) {
for (AbilityModifierAction action : modifier.onRemoved) {
if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnRemoved().add(action);
}
}
}
}
GameData.getAbilityModifiers().put(modifierEntry.getName(), modifierEntry);
}
}
}
private static void loadSpawnData() {
// Read from cached file if exists
File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json");
......
package emu.grasscutter.data.custom;
import java.util.Map;
public class AbilityModifier {
public AbilityModifierAction[] onAdded;
public AbilityModifierAction[] onThinkInterval;
public AbilityModifierAction[] onRemoved;
public static class AbilityConfigData {
public AbilityData Default;
}
public static class AbilityData {
public String abilityName;
public Map<String, AbilityModifier> modifiers;
}
public static class AbilityModifierAction {
public String $type;
public AbilityModifierActionType type;
public String target;
public AbilityModifierValue amount;
public AbilityModifierValue amountByTargetCurrentHPRatio;
}
public static class AbilityModifierValue {
public boolean isFormula;
public boolean isDynamic;
public String dynamicKey;
}
public enum AbilityModifierActionType {
HealHP, ApplyModifier, LoseHP;
}
}
package emu.grasscutter.data.custom;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
public class AbilityModifierEntry {
private String name; // Custom value
public List<AbilityModifierAction> onModifierAdded;
public List<AbilityModifierAction> onThinkInterval;
public List<AbilityModifierAction> onRemoved;
public AbilityModifierEntry(String name) {
this.name = name;
this.onModifierAdded = new ArrayList<>();
this.onThinkInterval = new ArrayList<>();
this.onRemoved = new ArrayList<>();
}
public String getName() {
return name;
}
public List<AbilityModifierAction> getOnAdded() {
return onModifierAdded;
}
public List<AbilityModifierAction> getOnThinkInterval() {
return onThinkInterval;
}
public List<AbilityModifierAction> getOnRemoved() {
return onRemoved;
}
}
package emu.grasscutter.data.def;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
@ResourceType(name = "TowerScheduleExcelConfigData.json")
public class TowerScheduleData extends GameResource {
private int ScheduleId;
private List<Integer> EntranceFloorId;
private List<ScheduleDetail> Schedules;
private int MonthlyLevelConfigId;
@Override
public int getId() {
return ScheduleId;
}
@Override
public void onLoad() {
super.onLoad();
this.Schedules = this.Schedules.stream()
.filter(item -> item.getFloorList().size() > 0)
.toList();
}
public int getScheduleId() {
return ScheduleId;
}
public void setScheduleId(int scheduleId) {
ScheduleId = scheduleId;
}
public List<Integer> getEntranceFloorId() {
return EntranceFloorId;
}
public void setEntranceFloorId(List<Integer> entranceFloorId) {
EntranceFloorId = entranceFloorId;
}
public List<ScheduleDetail> getSchedules() {
return Schedules;
}
public void setSchedules(List<ScheduleDetail> schedules) {
Schedules = schedules;
}
public int getMonthlyLevelConfigId() {
return MonthlyLevelConfigId;
}
public void setMonthlyLevelConfigId(int monthlyLevelConfigId) {
MonthlyLevelConfigId = monthlyLevelConfigId;
}
public static class ScheduleDetail{
private List<Integer> FloorList;
public List<Integer> getFloorList() {
return FloorList;
}
public void setFloorList(List<Integer> floorList) {
FloorList = floorList;
}
}
}
package emu.grasscutter.game.ability;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.custom.AbilityModifier;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.custom.AbilityModifierEntry;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityInvokeArgumentOuterClass.AbilityInvokeArgument;
import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap;
import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
public class AbilityManager {
private Player player;
public AbilityManager(Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
//System.out.println(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_META_OVERRIDE_PARAM:
handleOverrideParam(invoke);
break;
case ABILITY_META_REINIT_OVERRIDEMAP:
handleReinitOverrideMap(invoke);
break;
case ABILITY_META_MODIFIER_CHANGE:
handleModifierChange(invoke);
break;
case ABILITY_MIXIN_COST_STAMINA:
handleMixinCostStamina(invoke);
break;
case ABILITY_ACTION_GENERATE_ELEM_BALL:
handleGenerateElemBall(invoke);
break;
default:
break;
}
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
GameEntity target = player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work properly anyways
if (data.getAction() == ModifierAction.ADDED && data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.REMOVED) {
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke) {
// Not the right way of doing this
if (Grasscutter.getConfig().OpenStamina) {
// getPlayer().getStaminaManager().updateStamina(getPlayer().getSession(), -450);
// TODO
// set flag in stamina/movement manager that specifies the player is currently using an alternate sprint
}
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
AbilityActionGenerateElemBall action = AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData());
if (action == null) {
return;
}
ItemData itemData = GameData.getItemDataMap().get(2024);
if (itemData == null) {
return; // Should never happen
}
EntityItem energyBall = new EntityItem(getPlayer().getScene(), getPlayer(), itemData, new Position(action.getPos()), 1);
energyBall.getRotation().set(action.getRot());
getPlayer().getScene().addEntity(energyBall);
}
private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {
case HealHP -> {
if (action.amount == null) {
return;
}
float healAmount = 0;
if (action.amount.isDynamic && action.amount.dynamicKey != null) {
healAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
}
if (healAmount > 0) {
target.heal(healAmount);
}
}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = 0;
if (action.amount.isDynamic && action.amount.dynamicKey != null) {
damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
}
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
}
}
}
......@@ -69,6 +69,7 @@ public class Avatar {
@Transient private Player owner;
@Transient private AvatarData data;
@Transient private AvatarSkillDepotData skillDepot;
@Transient private long guid; // Player unique id
private int avatarId; // Id of avatar
......@@ -103,8 +104,8 @@ public class Avatar {
private int nameCardRewardId;
private int nameCardId;
@Deprecated // Do not use. Morhpia only!
public Avatar() {
// Morhpia only!
this.equips = new Int2ObjectOpenHashMap<>();
this.fightProp = new Int2FloatOpenHashMap();
this.extraAbilityEmbryos = new HashSet<>();
......@@ -140,7 +141,7 @@ public class Avatar {
}
// Skill depot
this.setSkillDepot(getAvatarData().getSkillDepot());
this.setSkillDepotData(getAvatarData().getSkillDepot());
// Set stats
this.recalcStats();
......@@ -164,7 +165,8 @@ public class Avatar {
}
protected void setAvatarData(AvatarData data) {
this.data = data;
if (this.data != null) return;
this.data = data; // Used while loading this from the database
}
public int getOwnerId() {
......@@ -257,9 +259,19 @@ public class Avatar {
return skillDepotId;
}
public void setSkillDepot(AvatarSkillDepotData skillDepot) {
// Set id
public AvatarSkillDepotData getSkillDepot() {
return skillDepot;
}
protected void setSkillDepot(AvatarSkillDepotData skillDepot) {
if (this.skillDepot != null) return;
this.skillDepot = skillDepot; // Used while loading this from the database
}
public void setSkillDepotData(AvatarSkillDepotData skillDepot) {
// Set id and depot
this.skillDepotId = skillDepot.getId();
this.skillDepot = skillDepot;
// Clear, then add skills
getSkillLevelMap().clear();
if (skillDepot.getEnergySkill() > 0) {
......@@ -501,8 +513,8 @@ public class Avatar {
// Set energy usage
if (data.getSkillDepot() != null && data.getSkillDepot().getEnergySkillData() != null) {
ElementType element = data.getSkillDepot().getElementType();
this.setFightProperty(element.getEnergyProperty(), data.getSkillDepot().getEnergySkillData().getCostElemVal());
this.setFightProperty((element.getEnergyProperty().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal());
this.setFightProperty(element.getMaxEnergyProp(), data.getSkillDepot().getEnergySkillData().getCostElemVal());
this.setFightProperty((element.getMaxEnergyProp().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal());
}
// Artifacts
......
......@@ -5,6 +5,7 @@ import java.util.List;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GameItem;
......@@ -139,12 +140,14 @@ public class AvatarStorage implements Iterable<Avatar> {
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
if (avatarData == null) {
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
......
......@@ -12,12 +12,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone();
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId()));
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(),
true,
towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(),
3,
towerManager.canEnterScheduleFloor()
));
scene.broadcastPacket(new PacketDungeonSettleNotify(
scene.getChallenge(),
towerManager.hasNextFloor(),
towerManager.hasNextLevel(),
towerManager.getNextFloorId()
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()
));
}
......
......@@ -17,17 +17,22 @@ import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlo
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils;
......@@ -110,6 +115,35 @@ public class EntityAvatar extends GameEntity {
this.killedBy = killerId;
}
@Override
public float heal(float amount) {
float healed = super.heal(amount);
if (healed > 0f) {
getScene().broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, PropChangeReason.PROP_CHANGE_ABILITY, ChangeHpReason.ChangeHpAddAbility)
);
}
return healed;
}
public void addEnergy(float amount) {
FightProperty curEnergyProp = getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
FightProperty maxEnergyProp = getAvatar().getSkillDepot().getElementType().getMaxEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
float maxEnergy = this.getFightProperty(maxEnergyProp);
float newEnergy = Math.min(curEnergy + amount, maxEnergy);
if (newEnergy != curEnergy) {
setFightProperty(curEnergyProp, newEnergy);
getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(getAvatar(), curEnergyProp));
getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, PropChangeReason.PROP_CHANGE_ENERGY_BALL));
}
}
public SceneAvatarInfo getSceneAvatarInfo() {
SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder()
.setUid(this.getPlayer().getUid())
......
package emu.grasscutter.game.entity;
import java.util.HashMap;
import java.util.Map;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene;
......@@ -9,8 +12,11 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public abstract class GameEntity {
protected int id;
......@@ -25,6 +31,10 @@ public abstract class GameEntity {
private int lastMoveSceneTimeMs;
private int lastMoveReliableSeq;
// Abilities
private Map<String, Float> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers;
public GameEntity(Scene scene) {
this.scene = scene;
this.moveState = MotionState.MOTION_NONE;
......@@ -54,6 +64,20 @@ public abstract class GameEntity {
return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
}
public Map<String, Float> getMetaOverrideMap() {
if (this.metaOverrideMap == null) {
this.metaOverrideMap = new HashMap<>();
}
return this.metaOverrideMap;
}
public Int2ObjectMap<String> getMetaModifiers() {
if (this.metaModifiers == null) {
this.metaModifiers = new Int2ObjectOpenHashMap<>();
}
return this.metaModifiers;
}
public abstract Int2FloatOpenHashMap getFightProperties();
public abstract Position getPosition();
......@@ -146,4 +170,53 @@ public abstract class GameEntity {
public void setSpawnEntry(SpawnDataEntry spawnEntry) {
this.spawnEntry = spawnEntry;
}
public float heal(float amount) {
if (this.getFightProperties() == null) {
return 0f;
}
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (curHp >= maxHp) {
return 0f;
}
float healed = Math.min(maxHp - curHp, amount);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed);
getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
return healed;
}
public void damage(float amount) {
damage(amount, 0);
}
public void damage(float amount, int killerId) {
// Sanity check
if (getFightProperties() == null) {
return;
}
// Lose hp
addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -amount);
// Check if dead
boolean isDead = false;
if (getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true;
}
// Packets
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead
if (isDead) {
getScene().killEntity(this, 0);
}
}
}
......@@ -172,6 +172,9 @@ public class Inventory implements Iterable<GameItem> {
// Handle
this.addVirtualItem(item.getItemId(), item.getCount());
return item;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_ADSORBATE) {
player.getTeamManager().addEnergyToTeam(item);
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) {
// Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000;
......
......@@ -30,7 +30,7 @@ public class InvokeHandler<T> {
}
public synchronized void update(Player player) {
if (player.getWorld() == null) {
if (player.getWorld() == null || player.getScene() == null) {
this.entryListForwardAll.clear();
this.entryListForwardAllExceptCur.clear();
this.entryListForwardHost.clear();
......
......@@ -8,6 +8,7 @@ import emu.grasscutter.data.def.PlayerLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest;
import emu.grasscutter.game.ability.AbilityManager;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarProfileData;
import emu.grasscutter.game.avatar.AvatarStorage;
......@@ -89,6 +90,7 @@ public class Player {
@Transient private FriendsList friendsList;
@Transient private MailHandler mailHandler;
@Transient private MessageHandler messageHandler;
@Transient private AbilityManager abilityManager;
@Transient private SotSManager sotsManager;
......@@ -142,6 +144,7 @@ public class Player {
this.friendsList = new FriendsList(this);
this.mailHandler = new MailHandler(this);
this.towerManager = new TowerManager(this);
this.abilityManager = new AbilityManager(this);
this.pos = new Position();
this.rotation = new Position();
this.properties = new HashMap<>();
......@@ -1025,6 +1028,10 @@ public class Player {
public SotSManager getSotSManager() { return sotsManager; }
public AbilityManager getAbilityManager() {
return abilityManager;
}
public synchronized void onTick() {
// Check ping
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment