Commit a8293102 authored by Melledy's avatar Melledy Committed by GitHub
Browse files

Merge branch 'development' into stable

parents 304b9cb8 ecf7a81a
...@@ -2,8 +2,9 @@ package emu.grasscutter.game.drop; ...@@ -2,8 +2,9 @@ package emu.grasscutter.game.drop;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
...@@ -17,7 +18,8 @@ import emu.grasscutter.utils.Utils; ...@@ -17,7 +18,8 @@ import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.io.FileReader; import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
...@@ -41,7 +43,7 @@ public class DropManager { ...@@ -41,7 +43,7 @@ public class DropManager {
} }
public synchronized void load() { public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Drop.json")) { try (Reader fileReader = new InputStreamReader(DataLoader.load("Drop.json"))) {
getDropData().clear(); getDropData().clear();
List<DropInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType()); List<DropInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType());
if(banners.size() > 0) { if(banners.size() > 0) {
...@@ -69,9 +71,7 @@ public class DropManager { ...@@ -69,9 +71,7 @@ public class DropManager {
} else { } else {
// target is null if items will be added are shared. no one could pick it up because of the combination(give + shared) // target is null if items will be added are shared. no one could pick it up because of the combination(give + shared)
// so it will be sent to all players' inventories directly. // so it will be sent to all players' inventories directly.
dropScene.getPlayers().forEach(x -> { dropScene.getPlayers().forEach(x -> x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
});
} }
} }
} }
......
package emu.grasscutter.game.dungeons; package emu.grasscutter.game.dungeons;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
...@@ -28,14 +28,20 @@ public class DungeonChallenge { ...@@ -28,14 +28,20 @@ public class DungeonChallenge {
private int challengeId; private int challengeId;
private boolean success; private boolean success;
private boolean progress; private boolean progress;
/**
* has more challenge
*/
private boolean stage;
private int score; private int score;
private int objective = 0; private int objective = 0;
private IntSet rewardedPlayers; private IntSet rewardedPlayers;
public DungeonChallenge(Scene scene, SceneGroup group) { public DungeonChallenge(Scene scene, SceneGroup group, int challengeId, int challengeIndex, int objective) {
this.scene = scene; this.scene = scene;
this.group = group; this.group = group;
this.challengeId = challengeId;
this.challengeIndex = challengeIndex;
this.objective = objective;
this.setRewardedPlayers(new IntOpenHashSet()); this.setRewardedPlayers(new IntOpenHashSet());
} }
...@@ -86,7 +92,15 @@ public class DungeonChallenge { ...@@ -86,7 +92,15 @@ public class DungeonChallenge {
public int getScore() { public int getScore() {
return score; return score;
} }
public boolean isStage() {
return stage;
}
public void setStage(boolean stage) {
this.stage = stage;
}
public int getTimeLimit() { public int getTimeLimit() {
return 600; return 600;
} }
...@@ -112,7 +126,7 @@ public class DungeonChallenge { ...@@ -112,7 +126,7 @@ public class DungeonChallenge {
if (this.isSuccess()) { if (this.isSuccess()) {
// Call success script event // Call success script event
this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null); this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null);
// Settle // Settle
settle(); settle();
} else { } else {
...@@ -122,8 +136,10 @@ public class DungeonChallenge { ...@@ -122,8 +136,10 @@ public class DungeonChallenge {
private void settle() { private void settle() {
getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene()));
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); if(!stage){
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0));
}
} }
public void onMonsterDie(EntityMonster entity) { public void onMonsterDie(EntityMonster entity) {
......
...@@ -3,10 +3,12 @@ package emu.grasscutter.game.dungeons; ...@@ -3,10 +3,12 @@ package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
...@@ -51,8 +53,9 @@ public class DungeonManager { ...@@ -51,8 +53,9 @@ public class DungeonManager {
int sceneId = data.getSceneId(); int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId); player.getScene().setPrevScene(sceneId);
if(player.getWorld().transferPlayerToScene(player, sceneId, data)){ if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver); player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
} }
player.getScene().setPrevScenePoint(pointId); player.getScene().setPrevScenePoint(pointId);
...@@ -78,19 +81,21 @@ public class DungeonManager { ...@@ -78,19 +81,21 @@ public class DungeonManager {
} }
public void exitDungeon(Player player) { public void exitDungeon(Player player) {
if (player.getScene().getSceneType() != SceneType.SCENE_DUNGEON) { Scene scene = player.getScene();
if (scene==null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return; return;
} }
// Get previous scene // Get previous scene
int prevScene = player.getScene().getPrevScene() > 0 ? player.getScene().getPrevScene() : 3; int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position // Get previous position
DungeonData dungeonData = player.getScene().getDungeonData(); DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION); Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) { if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, player.getScene().getPrevScenePoint()); ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) { if (entry != null) {
prevPos.set(entry.getPointData().getTranPos()); prevPos.set(entry.getPointData().getTranPos());
......
...@@ -9,15 +9,25 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { ...@@ -9,15 +9,25 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override @Override
public void onDungeonSettle(Scene scene) { public void onDungeonSettle(Scene scene) {
if(scene.getScriptManager().getVariables().containsKey("stage")
&& scene.getScriptManager().getVariables().get("stage") == 1){
return;
}
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
var towerManager = scene.getPlayers().get(0).getTowerManager(); var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone(); towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId())); scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(), towerManager.getCurrentFloorId(),
true, 3,
towerManager.canEnterScheduleFloor()
));
scene.broadcastPacket(new PacketDungeonSettleNotify(
scene.getChallenge(),
towerManager.hasNextFloor(),
towerManager.hasNextLevel(), towerManager.hasNextLevel(),
towerManager.getNextFloorId() towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()
)); ));
} }
......
...@@ -2,8 +2,8 @@ package emu.grasscutter.game.entity; ...@@ -2,8 +2,8 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.EquipType; import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
...@@ -12,22 +12,25 @@ import emu.grasscutter.game.props.EntityIdType; ...@@ -12,22 +12,25 @@ import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock; import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; 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.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; 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.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
...@@ -43,6 +46,7 @@ public class EntityAvatar extends GameEntity { ...@@ -43,6 +46,7 @@ public class EntityAvatar extends GameEntity {
public EntityAvatar(Scene scene, Avatar avatar) { public EntityAvatar(Scene scene, Avatar avatar) {
super(scene); super(scene);
this.avatar = avatar; this.avatar = avatar;
this.avatar.setCurrentEnergy();
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
GameItem weapon = this.getAvatar().getWeapon(); GameItem weapon = this.getAvatar().getWeapon();
...@@ -54,6 +58,7 @@ public class EntityAvatar extends GameEntity { ...@@ -54,6 +58,7 @@ public class EntityAvatar extends GameEntity {
public EntityAvatar(Avatar avatar) { public EntityAvatar(Avatar avatar) {
super(null); super(null);
this.avatar = avatar; this.avatar = avatar;
this.avatar.setCurrentEnergy();
} }
public Player getPlayer() { public Player getPlayer() {
...@@ -101,15 +106,69 @@ public class EntityAvatar extends GameEntity { ...@@ -101,15 +106,69 @@ public class EntityAvatar extends GameEntity {
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
this.killedType = PlayerDieType.PLAYER_DIE_KILL_BY_MONSTER; this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
this.killedBy = killerId; this.killedBy = killerId;
clearEnergy(PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER);
} }
public void onDeath(PlayerDieType dieType, int killerId) { public void onDeath(PlayerDieType dieType, int killerId) {
this.killedType = dieType; this.killedType = dieType;
this.killedBy = killerId; this.killedBy = killerId;
clearEnergy(PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER);
} }
@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_REASON_ABILITY, ChangeHpReason.CHANGE_HP_REASON_CHANGE_HP_ADD_ABILITY)
);
}
return healed;
}
public void clearEnergy(PropChangeReason reason) {
FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
this.avatar.setCurrentEnergy(curEnergyProp, 0);
this.getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, 0f, reason));
}
public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false);
}
public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) {
// Get current and maximum energy for this avatar.
FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
FightProperty maxEnergyProp = this.getAvatar().getSkillDepot().getElementType().getMaxEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
float maxEnergy = this.getFightProperty(maxEnergyProp);
// Get energy recharge.
float energyRecharge = this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY);
// Scale amount by energy recharge, if the amount is not flat.
if (!isFlat) {
amount *= energyRecharge;
}
// Determine the new energy value.
float newEnergy = Math.min(curEnergy + amount, maxEnergy);
// Set energy and notify.
if (newEnergy != curEnergy) {
this.avatar.setCurrentEnergy(curEnergyProp, newEnergy);
this.getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason));
}
}
public SceneAvatarInfo getSceneAvatarInfo() { public SceneAvatarInfo getSceneAvatarInfo() {
SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder() SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder()
.setUid(this.getPlayer().getUid()) .setUid(this.getPlayer().getUid())
...@@ -150,7 +209,7 @@ public class EntityAvatar extends GameEntity { ...@@ -150,7 +209,7 @@ public class EntityAvatar extends GameEntity {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_AVATAR) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR)
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder()) .setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority) .setEntityAuthorityInfo(authority)
...@@ -241,5 +300,5 @@ public class EntityAvatar extends GameEntity { ...@@ -241,5 +300,5 @@ public class EntityAvatar extends GameEntity {
// //
return abilityControlBlock.build(); return abilityControlBlock.build();
} }
} }
...@@ -35,6 +35,8 @@ public class EntityClientGadget extends EntityBaseGadget { ...@@ -35,6 +35,8 @@ public class EntityClientGadget extends EntityBaseGadget {
private int ownerEntityId; private int ownerEntityId;
private int targetEntityId; private int targetEntityId;
private boolean asyncLoad; private boolean asyncLoad;
private int originalOwnerEntityId;
public EntityClientGadget(Scene scene, Player player, EvtCreateGadgetNotify notify) { public EntityClientGadget(Scene scene, Player player, EvtCreateGadgetNotify notify) {
super(scene); super(scene);
...@@ -48,6 +50,14 @@ public class EntityClientGadget extends EntityBaseGadget { ...@@ -48,6 +50,14 @@ public class EntityClientGadget extends EntityBaseGadget {
this.ownerEntityId = notify.getPropOwnerEntityId(); this.ownerEntityId = notify.getPropOwnerEntityId();
this.targetEntityId = notify.getTargetEntityId(); this.targetEntityId = notify.getTargetEntityId();
this.asyncLoad = notify.getIsAsyncLoad(); this.asyncLoad = notify.getIsAsyncLoad();
GameEntity owner = scene.getEntityById(this.ownerEntityId);
if (owner instanceof EntityClientGadget ownerGadget) {
this.originalOwnerEntityId = ownerGadget.getOriginalOwnerEntityId();
}
else {
this.originalOwnerEntityId = this.ownerEntityId;
}
} }
@Override @Override
...@@ -79,6 +89,10 @@ public class EntityClientGadget extends EntityBaseGadget { ...@@ -79,6 +89,10 @@ public class EntityClientGadget extends EntityBaseGadget {
return this.asyncLoad; return this.asyncLoad;
} }
public int getOriginalOwnerEntityId() {
return this.originalOwnerEntityId;
}
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
...@@ -113,7 +127,7 @@ public class EntityClientGadget extends EntityBaseGadget { ...@@ -113,7 +127,7 @@ public class EntityClientGadget extends EntityBaseGadget {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_GADGET) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder()) .setEntityClientData(EntityClientData.newBuilder())
......
...@@ -4,7 +4,7 @@ import java.util.Arrays; ...@@ -4,7 +4,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.GadgetData; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
...@@ -122,7 +122,7 @@ public class EntityGadget extends EntityBaseGadget { ...@@ -122,7 +122,7 @@ public class EntityGadget extends EntityBaseGadget {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_GADGET) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder()) .setEntityClientData(EntityClientData.newBuilder())
......
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
...@@ -111,7 +111,7 @@ public class EntityItem extends EntityBaseGadget { ...@@ -111,7 +111,7 @@ public class EntityItem extends EntityBaseGadget {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_GADGET) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder()) .setEntityClientData(EntityClientData.newBuilder())
...@@ -127,7 +127,7 @@ public class EntityItem extends EntityBaseGadget { ...@@ -127,7 +127,7 @@ public class EntityItem extends EntityBaseGadget {
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId()) .setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto()) .setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_IN_AIR) .setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId()) .setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true); .setIsEnableInteract(true);
......
...@@ -2,9 +2,10 @@ package emu.grasscutter.game.entity; ...@@ -2,9 +2,10 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.def.MonsterCurveData; import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
...@@ -111,17 +112,41 @@ public class EntityMonster extends GameEntity { ...@@ -111,17 +112,41 @@ public class EntityMonster extends GameEntity {
this.poseId = poseId; this.poseId = poseId;
} }
@Override
public void damage(float amount, int killerId) {
// Get HP before damage.
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Apply damage.
super.damage(amount, killerId);
// Get HP after damage.
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Invoke energy drop logic.
for (Player player : this.getScene().getPlayers()) {
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
}
}
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
if (this.getSpawnEntry() != null) { if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
} }
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { // first set the challenge data
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null);
}
if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) {
getScene().getChallenge().onMonsterDie(this); getScene().getChallenge().onMonsterDie(this);
} }
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) {
if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){
getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this);
}
// prevent spawn monster after success
if(getScene().getChallenge() != null && getScene().getChallenge().inProgress()){
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null);
}
}
} }
public void recalcStats() { public void recalcStats() {
...@@ -186,7 +211,7 @@ public class EntityMonster extends GameEntity { ...@@ -186,7 +211,7 @@ public class EntityMonster extends GameEntity {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_MONSTER) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
.setMotionInfo(this.getMotionInfo()) .setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder()) .setEntityClientData(EntityClientData.newBuilder())
...@@ -215,7 +240,7 @@ public class EntityMonster extends GameEntity { ...@@ -215,7 +240,7 @@ public class EntityMonster extends GameEntity {
.setAuthorityPeerId(getWorld().getHostPeerId()) .setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(this.getPoseId()) .setPoseId(this.getPoseId())
.setBlockId(3001) .setBlockId(3001)
.setBornType(MonsterBornType.MONSTER_BORN_DEFAULT) .setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
.setSpecialNameId(40); .setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) { if (getMonsterData().getDescribeData() != null) {
......
...@@ -106,7 +106,7 @@ public class EntityVehicle extends EntityBaseGadget { ...@@ -106,7 +106,7 @@ public class EntityVehicle extends EntityBaseGadget {
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_GADGET) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo) .setGadget(gadgetInfo)
......
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import java.util.HashMap;
import java.util.Map;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
...@@ -9,8 +12,11 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; ...@@ -9,8 +12,11 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; 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 { public abstract class GameEntity {
protected int id; protected int id;
...@@ -25,9 +31,13 @@ public abstract class GameEntity { ...@@ -25,9 +31,13 @@ public abstract class GameEntity {
private int lastMoveSceneTimeMs; private int lastMoveSceneTimeMs;
private int lastMoveReliableSeq; private int lastMoveReliableSeq;
// Abilities
private Map<String, Float> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers;
public GameEntity(Scene scene) { public GameEntity(Scene scene) {
this.scene = scene; this.scene = scene;
this.moveState = MotionState.MOTION_NONE; this.moveState = MotionState.MOTION_STATE_NONE;
} }
public int getId() { public int getId() {
...@@ -54,6 +64,20 @@ public abstract class GameEntity { ...@@ -54,6 +64,20 @@ public abstract class GameEntity {
return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; 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 Int2FloatOpenHashMap getFightProperties();
public abstract Position getPosition(); public abstract Position getPosition();
...@@ -146,4 +170,53 @@ public abstract class GameEntity { ...@@ -146,4 +170,53 @@ public abstract class GameEntity {
public void setSpawnEntry(SpawnDataEntry spawnEntry) { public void setSpawnEntry(SpawnDataEntry spawnEntry) {
this.spawnEntry = 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, killerId);
}
}
} }
...@@ -2,14 +2,19 @@ package emu.grasscutter.game.expedition; ...@@ -2,14 +2,19 @@ package emu.grasscutter.game.expedition;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.io.FileReader; import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import static emu.grasscutter.Configuration.*;
public class ExpeditionManager { public class ExpeditionManager {
public GameServer getGameServer() { public GameServer getGameServer() {
return gameServer; return gameServer;
...@@ -28,7 +33,7 @@ public class ExpeditionManager { ...@@ -28,7 +33,7 @@ public class ExpeditionManager {
} }
public synchronized void load() { public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ExpeditionReward.json")) { try (Reader fileReader = new InputStreamReader(DataLoader.load("ExpeditionReward.json"))) {
getExpeditionRewardDataList().clear(); getExpeditionRewardDataList().clear();
List<ExpeditionRewardInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType()); List<ExpeditionRewardInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType());
if(banners.size() > 0) { if(banners.size() > 0) {
......
...@@ -104,7 +104,7 @@ public class FriendsList { ...@@ -104,7 +104,7 @@ public class FriendsList {
} }
// Handle // Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_ACCEPT) { // Request accepted if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true); myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true); theirFriendship.setIsFriend(true);
......
...@@ -95,13 +95,13 @@ public class Friendship { ...@@ -95,13 +95,13 @@ public class Friendship {
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId())) .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel()) .setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature()) .setSignature(getFriendProfile().getSignature())
.setOnlineState(getFriendProfile().isOnline() ? FriendOnlineState.FRIEND_ONLINE : FriendOnlineState.FREIEND_DISCONNECT) .setOnlineState(getFriendProfile().isOnline() ? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE : FriendOnlineState.FRIEND_ONLINE_STATE_FREIEND_DISCONNECT)
.setIsMpModeAvailable(true) .setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime()) .setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard()) .setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin()) .setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true) .setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PC) .setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build(); .build();
return proto; return proto;
......
package emu.grasscutter.game.gacha; package emu.grasscutter.game.gacha;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
import emu.grasscutter.utils.Utils;
import static emu.grasscutter.Configuration.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.common.ItemParamData;
public class GachaBanner { public class GachaBanner {
private int gachaType; private int gachaType;
...@@ -10,18 +15,35 @@ public class GachaBanner { ...@@ -10,18 +15,35 @@ public class GachaBanner {
private String prefabPath; private String prefabPath;
private String previewPrefabPath; private String previewPrefabPath;
private String titlePath; private String titlePath;
private int costItem; private int costItemId = 0;
private int costItemAmount = 1;
private int costItemId10 = 0;
private int costItemAmount10 = 10;
private int beginTime; private int beginTime;
private int endTime; private int endTime;
private int sortId; private int sortId;
private int[] rateUpItems1; private int[] rateUpItems4 = {};
private int[] rateUpItems2; private int[] rateUpItems5 = {};
private int baseYellowWeight = 60; // Max 10000 private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
private int basePurpleWeight = 510; // Max 10000 private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
private int eventChance = 50; // Chance to win a featured event item private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int softPity = 75; private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041};
private int hardPity = 90; private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
private boolean removeC6FromPool = false;
private boolean autoStripRateUpFromFallback = true;
private int[][] weights4 = {{1,510}, {8,510}, {10,10000}};
private int[][] weights5 = {{1,75}, {73,150}, {90,10000}};
private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}};
private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
private int eventChance4 = 50; // Chance to win a featured event item
private int eventChance5 = 50; // Chance to win a featured event item
private BannerType bannerType = BannerType.STANDARD; private BannerType bannerType = BannerType.STANDARD;
// Kinda wanna deprecate these but they're in people's configs
private int[] rateUpItems1 = {};
private int[] rateUpItems2 = {};
private int eventChance = -1;
private int costItem = 0;
public int getGachaType() { public int getGachaType() {
return gachaType; return gachaType;
...@@ -47,8 +69,15 @@ public class GachaBanner { ...@@ -47,8 +69,15 @@ public class GachaBanner {
return titlePath; return titlePath;
} }
public ItemParamData getCost(int numRolls) {
return switch (numRolls) {
case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10);
default -> new ItemParamData(getCostItem(), costItemAmount * numRolls);
};
}
public int getCostItem() { public int getCostItem() {
return costItem; return (costItem > 0) ? costItem : costItemId;
} }
public int getBeginTime() { public int getBeginTime() {
...@@ -63,90 +92,102 @@ public class GachaBanner { ...@@ -63,90 +92,102 @@ public class GachaBanner {
return sortId; return sortId;
} }
public int getBaseYellowWeight() { public int[] getRateUpItems4() {
return baseYellowWeight; return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4;
} }
public int[] getRateUpItems5() {
public int getBasePurpleWeight() { return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5;
return basePurpleWeight;
} }
public int[] getRateUpItems1() { public int[] getFallbackItems3() {return fallbackItems3;}
return rateUpItems1; public int[] getFallbackItems4Pool1() {return fallbackItems4Pool1;}
} public int[] getFallbackItems4Pool2() {return fallbackItems4Pool2;}
public int[] getFallbackItems5Pool1() {return fallbackItems5Pool1;}
public int[] getFallbackItems5Pool2() {return fallbackItems5Pool2;}
public int[] getRateUpItems2() { public boolean getRemoveC6FromPool() {return removeC6FromPool;}
return rateUpItems2; public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;}
}
public int getSoftPity() { public int getWeight(int rarity, int pity) {
return softPity - 1; return switch(rarity) {
case 4 -> Utils.lerp(pity, weights4);
default -> Utils.lerp(pity, weights5);
};
} }
public int getHardPity() { public int getPoolBalanceWeight(int rarity, int pity) {
return hardPity - 1; return switch(rarity) {
case 4 -> Utils.lerp(pity, poolBalanceWeights4);
default -> Utils.lerp(pity, poolBalanceWeights5);
};
} }
public int getEventChance() { public int getEventChance(int rarity) {
return eventChance; return switch(rarity) {
case 4 -> eventChance4;
default -> (eventChance > -1) ? eventChance : eventChance5;
};
} }
@Deprecated @Deprecated
public GachaInfo toProto() { public GachaInfo toProto() {
return toProto(""); return toProto("");
} }
public GachaInfo toProto(String sessionKey) { public GachaInfo toProto(String sessionKey) {
String record = "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
Grasscutter.getConfig().getDispatchOptions().Ip : + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
Grasscutter.getConfig().getDispatchOptions().PublicIp)
+ ":"
+ Integer.toString(Grasscutter.getConfig().getDispatchOptions().PublicPort == 0 ?
Grasscutter.getConfig().getDispatchOptions().Port :
Grasscutter.getConfig().getDispatchOptions().PublicPort)
+ "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;
// Grasscutter.getLogger().info("record = " + record); // Grasscutter.getLogger().info("record = " + record);
ItemParamData costItem1 = this.getCost(1);
ItemParamData costItem10 = this.getCost(10);
GachaInfo.Builder info = GachaInfo.newBuilder() GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType()) .setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId()) .setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime()) .setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime()) .setEndTime(this.getEndTime())
.setCostItemId(this.getCostItem()) .setCostItemId(costItem1.getId())
.setCostItemNum(1) .setCostItemNum(costItem1.getCount())
.setTenCostItemId(costItem10.getId())
.setTenCostItemNum(costItem10.getCount())
.setGachaPrefabPath(this.getPrefabPath()) .setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath()) .setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(record) .setGachaProbUrl(details)
.setGachaProbUrlOversea(record) .setGachaProbUrlOversea(details)
.setGachaRecordUrl(record) .setGachaRecordUrl(record)
.setGachaRecordUrlOversea(record) .setGachaRecordUrlOversea(record)
.setTenCostItemId(this.getCostItem())
.setTenCostItemNum(10)
.setLeftGachaTimes(Integer.MAX_VALUE) .setLeftGachaTimes(Integer.MAX_VALUE)
.setGachaTimesLimit(Integer.MAX_VALUE) .setGachaTimesLimit(Integer.MAX_VALUE)
.setGachaSortId(this.getSortId()); .setGachaSortId(this.getSortId());
if (this.getTitlePath() != null) { if (this.getTitlePath() != null) {
info.setGachaTitlePath(this.getTitlePath()); info.setTitleTextmap(this.getTitlePath());
} }
if (this.getRateUpItems1().length > 0) { if (this.getRateUpItems5().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1); GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems1()) { for (int id : getRateUpItems5()) {
upInfo.addItemIdList(id); upInfo.addItemIdList(id);
info.addMainNameId(id); info.addDisplayUp5ItemList(id);
} }
info.addGachaUpInfoList(upInfo); info.addGachaUpInfoList(upInfo);
} }
if (this.getRateUpItems2().length > 0) { if (this.getRateUpItems4().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2); GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems2()) { for (int id : getRateUpItems4()) {
upInfo.addItemIdList(id); upInfo.addItemIdList(id);
if (info.getSubNameIdCount() == 0) { if (info.getDisplayUp4ItemListCount() == 0) {
info.addSubNameId(id); info.addDisplayUp4ItemList(id);
} }
} }
......
...@@ -2,8 +2,11 @@ package emu.grasscutter.game.gacha; ...@@ -2,8 +2,11 @@ package emu.grasscutter.game.gacha;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.*; import java.nio.file.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
...@@ -12,12 +15,15 @@ import com.google.gson.reflect.TypeToken; ...@@ -12,12 +15,15 @@ import com.google.gson.reflect.TypeToken;
import com.sun.nio.file.SensitivityWatchEventModifier; import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.gacha.GachaBanner.BannerType; import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
...@@ -28,26 +34,25 @@ import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; ...@@ -28,26 +34,25 @@ import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp; import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import static emu.grasscutter.Configuration.*;
public class GachaManager { public class GachaManager {
private final GameServer server; private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners; private final Int2ObjectMap<GachaBanner> gachaBanners;
private GetGachaInfoRsp cachedProto; private GetGachaInfoRsp cachedProto;
WatchService watchService; WatchService watchService;
private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041};
private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
private int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
private int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
private static int starglitterId = 221; private static final int starglitterId = 221;
private static int stardustId = 222; private static final int stardustId = 222;
private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
public GachaManager(GameServer server) { public GachaManager(GameServer server) {
this.server = server; this.server = server;
...@@ -64,7 +69,7 @@ public class GachaManager { ...@@ -64,7 +69,7 @@ public class GachaManager {
return gachaBanners; return gachaBanners;
} }
public int randomRange(int min, int max) { public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min; return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
} }
...@@ -73,14 +78,16 @@ public class GachaManager { ...@@ -73,14 +78,16 @@ public class GachaManager {
} }
public synchronized void load() { public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) { try (Reader fileReader = new InputStreamReader(DataLoader.load("Banners.json"))) {
getGachaBanners().clear(); getGachaBanners().clear();
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType()); List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
if(banners.size() > 0) { if(banners.size() > 0) {
for (GachaBanner banner : banners) { for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getGachaType(), banner); getGachaBanners().put(banner.getScheduleId(), banner);
} }
Grasscutter.getLogger().info("Banners successfully loaded."); Grasscutter.getLogger().info("Banners successfully loaded.");
this.cachedProto = createProto(); this.cachedProto = createProto();
} else { } else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0."); Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
...@@ -90,116 +97,196 @@ public class GachaManager { ...@@ -90,116 +97,196 @@ public class GachaManager {
e.printStackTrace(); e.printStackTrace();
} }
} }
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.getAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class?
ItemData itemData = GameData.getItemDataMap().get(itemId);
if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)){
return -2; // Not an Avatar
}
Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000);
if (avatar == null) {
return -1; // Doesn't have
}
// Constellation
int constLevel = avatar.getCoreProudSkillLevel();
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100);
constLevel += (constItem == null)? 0 : constItem.getCount();
return constLevel;
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff);
int subTotal = 0;
for (int i=0; i<weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean pullFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) // Lost previous coinflip
|| (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
if (pullFeatured && (featured.length > 0)) {
itemId = getRandom(featured);
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
} else {
gachaInfo.addFailedFeaturedItemPulls(rarity, 1);
if (fallback1.length < 1) {
if (fallback2.length < 1) {
itemId = getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
} else {
itemId = getRandom(fallback2);
}
} else if (fallback2.length < 1) {
itemId = getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
itemId = switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
return itemId;
}
private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int gachaType, int times) { public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check // Sanity check
if (times != 10 && times != 1) { if (times != 10 && times != 1) {
return; return;
} }
if (player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp()); player.sendPacket(new PacketDoGachaRsp());
return; return;
} }
// Get banner // Get banner
GachaBanner banner = this.getGachaBanners().get(gachaType); GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) { if (banner == null) {
player.sendPacket(new PacketDoGachaRsp()); player.sendPacket(new PacketDoGachaRsp());
return; return;
} }
// Spend currency // Spend currency
if (banner.getCostItem() > 0) { ItemParamData cost = banner.getCost(times);
GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(banner.getCostItem()); if (cost.getCount() > 0 && !inventory.payItem(cost)) {
if (costItem == null || costItem.getCount() < times) { player.sendPacket(new PacketDoGachaRsp());
return; return;
}
player.getInventory().removeItem(costItem, times);
}
// Roll
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
IntList wonItems = new IntArrayList(times);
for (int i = 0; i < times; i++) {
int random = this.randomRange(1, 10000);
int itemId = 0;
int bonusYellowChance = gachaInfo.getPity5() >= banner.getSoftPity() ? 100 * (gachaInfo.getPity5() - banner.getSoftPity() - 1): 0;
int yellowChance = banner.getBaseYellowWeight() + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance;
int purpleChance = 10000 - (banner.getBasePurpleWeight() + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f)));
if (random <= yellowChance || gachaInfo.getPity5() >= banner.getHardPity()) {
if (banner.getRateUpItems1().length > 0) {
int eventChance = this.randomRange(1, 100);
if (eventChance <= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) {
itemId = getRandom(banner.getRateUpItems1());
gachaInfo.setFailedFeaturedItemPulls(0);
} else {
// Lost the 50/50... rip
gachaInfo.addFailedFeaturedItemPulls(1);
}
}
if (itemId == 0) {
int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2);
if (typeChance == 1) {
itemId = getRandom(this.yellowAvatars);
} else {
itemId = getRandom(this.yellowWeapons);
}
}
// Pity
gachaInfo.addPity4(1);
gachaInfo.setPity5(0);
} else if (random >= purpleChance || gachaInfo.getPity4() >= 9) {
if (banner.getRateUpItems2().length > 0) {
int eventChance = this.randomRange(1, 100);
if (eventChance >= 50) {
itemId = getRandom(banner.getRateUpItems2());
}
}
if (itemId == 0) {
int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2);
if (typeChance == 1) {
itemId = getRandom(this.purpleAvatars);
} else {
itemId = getRandom(this.purpleWeapons);
}
}
// Pity
gachaInfo.addPity5(1);
gachaInfo.setPity4(0);
} else {
itemId = getRandom(this.blueWeapons);
// Pity
gachaInfo.addPity4(1);
gachaInfo.addPity5(1);
}
// Add winning item
wonItems.add(itemId);
} }
// Add to character // Add to character
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>(); List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0; int stardust = 0, starglitter = 0;
if (banner.getRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int itemId : wonItems) { for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId); ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) { if (itemData == null) {
continue; continue; // Maybe we should bail out if an item fails instead of rolling the rest?
} }
// Write gacha record // Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), gachaType); GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord); DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item // Create gacha item
...@@ -208,63 +295,47 @@ public class GachaManager { ...@@ -208,63 +295,47 @@ public class GachaManager {
boolean isTransferItem = false; boolean isTransferItem = false;
// Const check // Const check
if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) { int constellation = checkPlayerAvatarConstellationLevel(player, itemId);
int avatarId = (itemData.getId() % 1000) + 10000000; switch (constellation) {
Avatar avatar = player.getAvatars().getAvatarById(avatarId); case -2: // Is weapon
if (avatar != null) { switch (itemData.getRankLevel()) {
int constLevel = avatar.getCoreProudSkillLevel(); case 5 -> addStarglitter = 10;
int constItemId = itemData.getId() + 100; case 4 -> addStarglitter = 2;
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); default -> addStardust = 15;
if (constItem != null) {
constLevel += constItem.getCount();
} }
break;
if (constLevel < 6) { case -1: // New character
// Not max const gachaItem.setIsGachaItemNew(true);
addStarglitter = 2; break;
// Add 1 const default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel()==5)? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.getRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel()==5)? 10 : 2;
int constItemId = itemId + 100;
GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
player.getInventory().addItem(constItemId, 1); inventory.addItem(constItemId, 1);
} else {
// Is max const
addStarglitter = 5;
} }
if (itemData.getRankLevel() == 5) {
addStarglitter *= 5;
}
isTransferItem = true; isTransferItem = true;
} else { break;
// New
gachaItem.setIsGachaItemNew(true);
}
} else {
// Is weapon
switch (itemData.getRankLevel()) {
case 5:
addStarglitter = 10;
break;
case 4:
addStarglitter = 2;
break;
case 3:
addStardust = 15;
break;
}
} }
// Create item // Create item
GameItem item = new GameItem(itemData); GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam()); gachaItem.setGachaItem(item.toItemParam());
player.getInventory().addItem(item); inventory.addItem(item);
stardust += addStardust; stardust += addStardust;
starglitter += addStarglitter; starglitter += addStarglitter;
if (addStardust > 0) { if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust)); gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
} if (addStarglitter > 0) { }
if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build(); ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) { if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam)); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
...@@ -277,9 +348,10 @@ public class GachaManager { ...@@ -277,9 +348,10 @@ public class GachaManager {
// Add stardust/starglitter // Add stardust/starglitter
if (stardust > 0) { if (stardust > 0) {
player.getInventory().addItem(stardustId, stardust); inventory.addItem(stardustId, stardust);
} if (starglitter > 0) { }
player.getInventory().addItem(starglitterId, starglitter); if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
} }
// Packets // Packets
...@@ -290,7 +362,7 @@ public class GachaManager { ...@@ -290,7 +362,7 @@ public class GachaManager {
if(this.watchService == null) { if(this.watchService == null) {
try { try {
this.watchService = FileSystems.getDefault().newWatchService(); this.watchService = FileSystems.getDefault().newWatchService();
Path path = new File(Grasscutter.getConfig().DATA_FOLDER).toPath(); Path path = new File(DATA()).toPath();
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH); path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload"); Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
...@@ -303,7 +375,7 @@ public class GachaManager { ...@@ -303,7 +375,7 @@ public class GachaManager {
@Subscribe @Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) { public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) { if(GAME_OPTIONS.watchGachaConfig) {
try { try {
WatchKey watchKey = watchService.take(); WatchKey watchKey = watchService.take();
...@@ -340,8 +412,13 @@ public class GachaManager { ...@@ -340,8 +412,13 @@ public class GachaManager {
private synchronized GetGachaInfoRsp createProto(String sessionKey) { private synchronized GetGachaInfoRsp createProto(String sessionKey) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345); GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) { for (GachaBanner banner : getGachaBanners().values()) {
proto.addGachaInfoList(banner.toProto(sessionKey)); if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD))
{
proto.addGachaInfoList(banner.toProto(sessionKey));
}
} }
return proto.build(); return proto.build();
......
...@@ -7,6 +7,11 @@ public class PlayerGachaBannerInfo { ...@@ -7,6 +7,11 @@ public class PlayerGachaBannerInfo {
private int pity5 = 0; private int pity5 = 0;
private int pity4 = 0; private int pity4 = 0;
private int failedFeaturedItemPulls = 0; private int failedFeaturedItemPulls = 0;
private int failedFeatured4ItemPulls = 0;
private int pity5Pool1 = 0;
private int pity5Pool2 = 0;
private int pity4Pool1 = 0;
private int pity4Pool2 = 0;
public int getPity5() { public int getPity5() {
return pity5; return pity5;
...@@ -32,15 +37,82 @@ public class PlayerGachaBannerInfo { ...@@ -32,15 +37,82 @@ public class PlayerGachaBannerInfo {
this.pity4 += amount; this.pity4 += amount;
} }
public int getFailedFeaturedItemPulls() { public int getFailedFeaturedItemPulls(int rarity) {
return failedFeaturedItemPulls; return switch (rarity) {
case 4 -> failedFeatured4ItemPulls;
default -> failedFeaturedItemPulls; // 5
};
} }
public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) { public void setFailedFeaturedItemPulls(int rarity, int amount) {
this.failedFeaturedItemPulls = failedEventCharacterPulls; switch (rarity) {
case 4 -> failedFeatured4ItemPulls = amount;
default -> failedFeaturedItemPulls = amount; // 5
};
} }
public void addFailedFeaturedItemPulls(int amount) { public void addFailedFeaturedItemPulls(int rarity, int amount) {
failedFeaturedItemPulls += amount; switch (rarity) {
case 4 -> failedFeatured4ItemPulls += amount;
default -> failedFeaturedItemPulls += amount; // 5
};
}
public int getPityPool(int rarity, int pool) {
return switch (rarity) {
case 4 -> switch (pool) {
case 1 -> pity4Pool1;
default -> pity4Pool2;
};
default -> switch (pool) {
case 1 -> pity5Pool1;
default -> pity5Pool2;
};
};
}
public void setPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
switch (pool) {
case 1 -> pity4Pool1 = amount;
default -> pity4Pool2 = amount;
};
break;
case 5:
default:
switch (pool) {
case 1 -> pity5Pool1 = amount;
default -> pity5Pool2 = amount;
};
break;
};
}
public void addPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
switch (pool) {
case 1 -> pity4Pool1 += amount;
default -> pity4Pool2 += amount;
};
break;
case 5:
default:
switch (pool) {
case 1 -> pity5Pool1 += amount;
default -> pity5Pool2 += amount;
};
break;
};
}
public void incPityAll() {
pity4++;
pity5++;
pity4Pool1++;
pity4Pool2++;
pity5Pool1++;
pity5Pool2++;
} }
} }
...@@ -15,9 +15,9 @@ import dev.morphia.annotations.Transient; ...@@ -15,9 +15,9 @@ import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.def.ReliquaryAffixData; import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryMainPropData; import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
......
...@@ -6,16 +6,15 @@ import java.util.LinkedList; ...@@ -6,16 +6,15 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.AvatarCostumeData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.excels.AvatarCostumeData;
import emu.grasscutter.data.def.AvatarFlycloakData; import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.excels.AvatarFlycloakData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
...@@ -28,6 +27,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ...@@ -28,6 +27,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import static emu.grasscutter.Configuration.*;
public class Inventory implements Iterable<GameItem> { public class Inventory implements Iterable<GameItem> {
private final Player player; private final Player player;
...@@ -39,10 +40,10 @@ public class Inventory implements Iterable<GameItem> { ...@@ -39,10 +40,10 @@ public class Inventory implements Iterable<GameItem> {
this.store = new Long2ObjectOpenHashMap<>(); this.store = new Long2ObjectOpenHashMap<>();
this.inventoryTypes = new Int2ObjectOpenHashMap<>(); this.inventoryTypes = new Int2ObjectOpenHashMap<>();
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon)); this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(INVENTORY_LIMITS.weapons));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic)); this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(INVENTORY_LIMITS.relics));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial)); this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(INVENTORY_LIMITS.materials));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture)); this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(INVENTORY_LIMITS.furniture));
} }
public Player getPlayer() { public Player getPlayer() {
...@@ -149,6 +150,14 @@ public class Inventory implements Iterable<GameItem> { ...@@ -149,6 +150,14 @@ public class Inventory implements Iterable<GameItem> {
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), null); addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), null);
} }
public void addItemParamDatas(Collection<ItemParamData> items) {
addItemParamDatas(items, null);
}
public void addItemParamDatas(Collection<ItemParamData> items, ActionReason reason) {
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), reason);
}
private synchronized GameItem putItem(GameItem item) { private synchronized GameItem putItem(GameItem item) {
// Dont add items that dont have a valid item definition. // Dont add items that dont have a valid item definition.
if (item.getItemData() == null) { if (item.getItemData() == null) {
...@@ -172,6 +181,9 @@ public class Inventory implements Iterable<GameItem> { ...@@ -172,6 +181,9 @@ public class Inventory implements Iterable<GameItem> {
// Handle // Handle
this.addVirtualItem(item.getItemId(), item.getCount()); this.addVirtualItem(item.getItemId(), item.getCount());
return item; return item;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_ADSORBATE) {
this.player.getEnergyManager().handlePickupElemBall(item);
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) { } else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) {
// Get avatar id // Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000; int avatarId = (item.getItemId() % 1000) + 10000000;
...@@ -228,6 +240,7 @@ public class Inventory implements Iterable<GameItem> { ...@@ -228,6 +240,7 @@ public class Inventory implements Iterable<GameItem> {
} }
private synchronized void putItem(GameItem item, InventoryTab tab) { private synchronized void putItem(GameItem item, InventoryTab tab) {
getPlayer().getCodex().checkAddedItem(item);
// Set owner and guid FIRST! // Set owner and guid FIRST!
item.setOwner(getPlayer()); item.setOwner(getPlayer());
// Put in item store // Put in item store
...@@ -239,26 +252,78 @@ public class Inventory implements Iterable<GameItem> { ...@@ -239,26 +252,78 @@ public class Inventory implements Iterable<GameItem> {
private void addVirtualItem(int itemId, int count) { private void addVirtualItem(int itemId, int count) {
switch (itemId) { switch (itemId) {
case 101: // Character exp case 101 -> // Character exp
getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
break; case 102 -> // Adventure exp
case 102: // Adventure exp getPlayer().addExpDirectly(count);
getPlayer().addExpDirectly(count); case 105 -> // Companionship exp
break; getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
case 105: // Companionship exp case 201 -> // Primogem
getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); getPlayer().setPrimogems(player.getPrimogems() + count);
break; case 202 -> // Mora
case 201: // Primogem getPlayer().setMora(player.getMora() + count);
getPlayer().setPrimogems(player.getPrimogems() + count); case 203 -> // Genesis Crystals
break; getPlayer().setCrystals(player.getCrystals() + count);
case 202: // Mora }
getPlayer().setMora(player.getMora() + count); }
break;
case 203: // Genesis Crystals private int getVirtualItemCount(int itemId) {
getPlayer().setCrystals(player.getCrystals() + count); switch (itemId) {
break; case 201: // Primogem
return player.getPrimogems();
case 202: // Mora
return player.getMora();
case 203: // Genesis Crystals
return player.getCrystals();
default:
GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S
return (item == null) ? 0 : item.getCount();
} }
} }
public boolean payItem(int id, int count) {
return payItem(new ItemParamData(id, count));
}
public boolean payItem(ItemParamData costItem) {
return payItems(new ItemParamData[] {costItem}, 1, null);
}
public boolean payItems(ItemParamData[] costItems) {
return payItems(costItems, 1, null);
}
public boolean payItems(ItemParamData[] costItems, int quantity) {
return payItems(costItems, quantity, null);
}
public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) {
// Make sure player has requisite items
for (ItemParamData cost : costItems) {
if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) {
return false;
}
}
// All costs are satisfied, now remove them all
for (ItemParamData cost : costItems) {
switch (cost.getId()) {
case 201 -> // Primogem
player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity));
case 202 -> // Mora
player.setMora(player.getMora() - (cost.getCount() * quantity));
case 203 -> // Genesis Crystals
player.setCrystals(player.getCrystals() - (cost.getCount() * quantity));
default ->
removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity);
}
}
if (reason != null) { // Do we need these?
// getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason));
}
// getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
return true;
}
public void removeItems(List<GameItem> items) { public void removeItems(List<GameItem> items) {
// TODO Bulk delete // TODO Bulk delete
......
package emu.grasscutter.game.managers; package emu.grasscutter.game.managers.ChatManager;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
...@@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketPrivateChatNotify; ...@@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
public class ChatManager { public class ChatManager implements ChatManagerHandler {
static final List<Character> PREFIXES = Arrays.asList('/', '!'); static final List<Character> PREFIXES = Arrays.asList('/', '!');
private final GameServer server; private final GameServer server;
......
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