mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-16 14:24:57 +01:00
Implement gacha banners (not newbie)
This commit is contained in:
@@ -50,6 +50,9 @@ public class GameData {
|
|||||||
@Getter private static DataTable<CharGemInstanceDef> CharGemInstanceDataTable = new DataTable<>();
|
@Getter private static DataTable<CharGemInstanceDef> CharGemInstanceDataTable = new DataTable<>();
|
||||||
@Getter private static DataTable<WeekBossLevelDef> WeekBossLevelDataTable = new DataTable<>();
|
@Getter private static DataTable<WeekBossLevelDef> WeekBossLevelDataTable = new DataTable<>();
|
||||||
|
|
||||||
|
@Getter private static DataTable<GachaDef> GachaDataTable = new DataTable<>();
|
||||||
|
@Getter private static DataTable<GachaStorageDef> GachaStorageDataTable = new DataTable<>();
|
||||||
|
|
||||||
@Getter private static DataTable<WorldClassDef> WorldClassDataTable = new DataTable<>();
|
@Getter private static DataTable<WorldClassDef> WorldClassDataTable = new DataTable<>();
|
||||||
@Getter private static DataTable<GuideGroupDef> GuideGroupDataTable = new DataTable<>();
|
@Getter private static DataTable<GuideGroupDef> GuideGroupDataTable = new DataTable<>();
|
||||||
@Getter private static DataTable<HandbookDef> HandbookDataTable = new DataTable<>();
|
@Getter private static DataTable<HandbookDef> HandbookDataTable = new DataTable<>();
|
||||||
|
|||||||
113
src/main/java/emu/nebula/data/resources/GachaDef.java
Normal file
113
src/main/java/emu/nebula/data/resources/GachaDef.java
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package emu.nebula.data.resources;
|
||||||
|
|
||||||
|
import emu.nebula.data.BaseDef;
|
||||||
|
import emu.nebula.data.GameData;
|
||||||
|
import emu.nebula.data.ResourceType;
|
||||||
|
import emu.nebula.util.WeightedList;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ResourceType(name = "Gacha.json")
|
||||||
|
public class GachaDef extends BaseDef {
|
||||||
|
private int Id;
|
||||||
|
private int StorageId;
|
||||||
|
private int GachaType;
|
||||||
|
|
||||||
|
private int GuaranteeTimes;
|
||||||
|
private int GuaranteeTid;
|
||||||
|
private int GuaranteeQty;
|
||||||
|
|
||||||
|
// Packages
|
||||||
|
private int ATypePkg;
|
||||||
|
private int BTypePkg;
|
||||||
|
private int CTypePkg;
|
||||||
|
|
||||||
|
private int ATypeUpPkg;
|
||||||
|
private int BTypeUpPkg;
|
||||||
|
private int CTypeUpPkg;
|
||||||
|
|
||||||
|
private int BGuaranteePkg;
|
||||||
|
|
||||||
|
private transient WeightedList<GachaPackage> packageA;
|
||||||
|
private transient WeightedList<GachaPackage> packageB;
|
||||||
|
private transient WeightedList<GachaPackage> packageC;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GachaStorageDef getStorageData() {
|
||||||
|
return GameData.getGachaStorageDataTable().get(this.getStorageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
// Get storage
|
||||||
|
var storage = this.getStorageData();
|
||||||
|
|
||||||
|
// Package A
|
||||||
|
this.packageA = new WeightedList<GachaPackage>();
|
||||||
|
|
||||||
|
if (this.ATypePkg > 0) {
|
||||||
|
packageA.add(
|
||||||
|
10000 - storage.getATypeUpProb(),
|
||||||
|
new GachaPackage(GachaPackageType.A, this.ATypePkg)
|
||||||
|
);
|
||||||
|
} if (this.ATypeUpPkg > 0) {
|
||||||
|
packageA.add(
|
||||||
|
storage.getATypeUpProb(),
|
||||||
|
new GachaPackage(GachaPackageType.A_UP, this.ATypeUpPkg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package B
|
||||||
|
this.packageB = new WeightedList<GachaPackage>();
|
||||||
|
|
||||||
|
if (this.BTypePkg > 0) {
|
||||||
|
packageB.add(
|
||||||
|
10000 - storage.getBTypeUpProb(),
|
||||||
|
new GachaPackage(GachaPackageType.B, this.BTypePkg)
|
||||||
|
);
|
||||||
|
} else if (this.BGuaranteePkg > 0) {
|
||||||
|
packageB.add(
|
||||||
|
10000 - storage.getBTypeUpProb(),
|
||||||
|
new GachaPackage(GachaPackageType.B, this.BGuaranteePkg)
|
||||||
|
);
|
||||||
|
} if (this.BTypeUpPkg > 0) {
|
||||||
|
packageB.add(
|
||||||
|
storage.getBTypeUpProb(),
|
||||||
|
new GachaPackage(GachaPackageType.B_UP, this.BTypeUpPkg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package C
|
||||||
|
this.packageC = new WeightedList<GachaPackage>();
|
||||||
|
|
||||||
|
if (this.CTypePkg > 0) {
|
||||||
|
packageC.add(
|
||||||
|
10000,
|
||||||
|
new GachaPackage(GachaPackageType.C, this.CTypePkg)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class GachaPackage {
|
||||||
|
private GachaPackageType type;
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public GachaPackage(GachaPackageType type, int id) {
|
||||||
|
this.type = type;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GachaPackageType {
|
||||||
|
A,
|
||||||
|
A_UP,
|
||||||
|
B,
|
||||||
|
B_UP,
|
||||||
|
C;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main/java/emu/nebula/data/resources/GachaPkgDef.java
Normal file
35
src/main/java/emu/nebula/data/resources/GachaPkgDef.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package emu.nebula.data.resources;
|
||||||
|
|
||||||
|
import emu.nebula.data.BaseDef;
|
||||||
|
import emu.nebula.data.ResourceType;
|
||||||
|
import emu.nebula.data.ResourceType.LoadPriority;
|
||||||
|
import emu.nebula.util.WeightedList;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ResourceType(name = "GachaPkg.json", loadPriority = LoadPriority.LOW)
|
||||||
|
public class GachaPkgDef extends BaseDef {
|
||||||
|
private int PkgId;
|
||||||
|
private int GoodsId;
|
||||||
|
private int Weight;
|
||||||
|
|
||||||
|
private static final Int2ObjectMap<WeightedList<Integer>> packages = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
@Override @Deprecated
|
||||||
|
public int getId() {
|
||||||
|
return PkgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeightedList<Integer> getPackageById(int packageId) {
|
||||||
|
return packages.get(packageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
// Add to package
|
||||||
|
var list = packages.computeIfAbsent(this.getPkgId(), i -> new WeightedList<Integer>());
|
||||||
|
list.add(this.getWeight(), this.getGoodsId());
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/main/java/emu/nebula/data/resources/GachaStorageDef.java
Normal file
26
src/main/java/emu/nebula/data/resources/GachaStorageDef.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package emu.nebula.data.resources;
|
||||||
|
|
||||||
|
import emu.nebula.data.BaseDef;
|
||||||
|
import emu.nebula.data.ResourceType;
|
||||||
|
import emu.nebula.data.ResourceType.LoadPriority;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ResourceType(name = "GachaStorage.json", loadPriority = LoadPriority.HIGH)
|
||||||
|
public class GachaStorageDef extends BaseDef {
|
||||||
|
private int Id;
|
||||||
|
|
||||||
|
private int DefaultId;
|
||||||
|
private int DefaultQty;
|
||||||
|
private int CostId;
|
||||||
|
private int CostQty;
|
||||||
|
|
||||||
|
private int ATypeUpProb;
|
||||||
|
private int BTypeUpProb;
|
||||||
|
private int BTypeGuaranteeProb;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import java.util.Timer;
|
|||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import emu.nebula.game.gacha.GachaModule;
|
||||||
import emu.nebula.game.player.PlayerModule;
|
import emu.nebula.game.player.PlayerModule;
|
||||||
import emu.nebula.net.GameSession;
|
import emu.nebula.net.GameSession;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||||
@@ -16,13 +17,16 @@ public class GameContext {
|
|||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
private final PlayerModule playerModule;
|
private final PlayerModule playerModule;
|
||||||
|
private final GachaModule gachaModule;
|
||||||
|
|
||||||
// Cleanup thread
|
// Cleanup thread
|
||||||
private final Timer cleanupTimer;
|
private final Timer cleanupTimer;
|
||||||
|
|
||||||
public GameContext() {
|
public GameContext() {
|
||||||
this.sessions = new Object2ObjectOpenHashMap<>();
|
this.sessions = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
this.playerModule = new PlayerModule(this);
|
this.playerModule = new PlayerModule(this);
|
||||||
|
this.gachaModule = new GachaModule(this);
|
||||||
|
|
||||||
this.cleanupTimer = new Timer();
|
this.cleanupTimer = new Timer();
|
||||||
this.cleanupTimer.scheduleAtFixedRate(new CleanupTask(this), 0, TimeUnit.SECONDS.toMillis(60));
|
this.cleanupTimer.scheduleAtFixedRate(new CleanupTask(this), 0, TimeUnit.SECONDS.toMillis(60));
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class Character implements GameDatabaseObject {
|
|||||||
int exp = 0;
|
int exp = 0;
|
||||||
|
|
||||||
// Check if item is an exp item
|
// Check if item is an exp item
|
||||||
for (var entry : params.getEntrySet()) {
|
for (var entry : params.entries()) {
|
||||||
var data = GameData.getCharItemExpDataTable().get(entry.getIntKey());
|
var data = GameData.getCharItemExpDataTable().get(entry.getIntKey());
|
||||||
if (data == null) return null;
|
if (data == null) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public class GameDisc implements GameDatabaseObject {
|
|||||||
int exp = 0;
|
int exp = 0;
|
||||||
|
|
||||||
// Check if item is an exp item
|
// Check if item is an exp item
|
||||||
for (var entry : params.getEntrySet()) {
|
for (var entry : params.entries()) {
|
||||||
var data = GameData.getDiscItemExpDataTable().get(entry.getIntKey());
|
var data = GameData.getDiscItemExpDataTable().get(entry.getIntKey());
|
||||||
if (data == null) return null;
|
if (data == null) return null;
|
||||||
|
|
||||||
|
|||||||
99
src/main/java/emu/nebula/game/gacha/GachaBannerInfo.java
Normal file
99
src/main/java/emu/nebula/game/gacha/GachaBannerInfo.java
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package emu.nebula.game.gacha;
|
||||||
|
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
|
import dev.morphia.annotations.Entity;
|
||||||
|
import dev.morphia.annotations.Id;
|
||||||
|
import emu.nebula.data.resources.GachaDef;
|
||||||
|
import emu.nebula.data.resources.GachaDef.GachaPackage;
|
||||||
|
import emu.nebula.data.resources.GachaPkgDef;
|
||||||
|
import emu.nebula.database.GameDatabaseObject;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.util.Utils;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity(value = "banner_info", useDiscriminator = false)
|
||||||
|
public class GachaBannerInfo implements GameDatabaseObject {
|
||||||
|
@Id
|
||||||
|
private ObjectId id;
|
||||||
|
|
||||||
|
private int bannerId;
|
||||||
|
private int playerUid;
|
||||||
|
|
||||||
|
private int total;
|
||||||
|
private int missTimesA;
|
||||||
|
private int missTimesUpA;
|
||||||
|
private int missTimesB;
|
||||||
|
private boolean usedGuarantee;
|
||||||
|
|
||||||
|
@Deprecated //Morphia only
|
||||||
|
public GachaBannerInfo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public GachaBannerInfo(Player player, GachaDef data) {
|
||||||
|
this.playerUid = player.getUid();
|
||||||
|
this.bannerId = data.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int doPull(GachaDef data) {
|
||||||
|
// Pull chances
|
||||||
|
int chanceA = 20; // 2%
|
||||||
|
int chanceB = 100; // 8%
|
||||||
|
|
||||||
|
// 4 star pity
|
||||||
|
if (this.missTimesB >= 9) {
|
||||||
|
chanceB = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 star pity
|
||||||
|
if (this.missTimesA >= 159) {
|
||||||
|
chanceA = 1000;
|
||||||
|
chanceB = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add miss times
|
||||||
|
this.missTimesB++;
|
||||||
|
this.missTimesA++;
|
||||||
|
//this.missTimesUpA++;
|
||||||
|
|
||||||
|
// Get random
|
||||||
|
int random = Utils.randomRange(1, 1000);
|
||||||
|
GachaPackage gp = null;
|
||||||
|
|
||||||
|
if (random <= chanceA) {
|
||||||
|
// Reset pity
|
||||||
|
this.missTimesA = 0;
|
||||||
|
|
||||||
|
// Get A package
|
||||||
|
gp = data.getPackageA().next();
|
||||||
|
} else if (random <= chanceB) {
|
||||||
|
// Add miss times
|
||||||
|
this.missTimesB = 0;
|
||||||
|
|
||||||
|
// Get B package
|
||||||
|
gp = data.getPackageB().next();
|
||||||
|
} else {
|
||||||
|
// Get C package
|
||||||
|
gp = data.getPackageC().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (gp == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get package
|
||||||
|
var pkg = GachaPkgDef.getPackageById(gp.getId());
|
||||||
|
if (pkg == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add total pulls
|
||||||
|
this.total++;
|
||||||
|
|
||||||
|
// Get random id
|
||||||
|
return pkg.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/java/emu/nebula/game/gacha/GachaManager.java
Normal file
46
src/main/java/emu/nebula/game/gacha/GachaManager.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package emu.nebula.game.gacha;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.data.resources.GachaDef;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.game.player.PlayerManager;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
|
||||||
|
public class GachaManager extends PlayerManager {
|
||||||
|
private final Int2ObjectMap<GachaBannerInfo> bannerInfos;
|
||||||
|
private boolean loaded;
|
||||||
|
|
||||||
|
public GachaManager(Player player) {
|
||||||
|
super(player);
|
||||||
|
this.bannerInfos = new Int2ObjectOpenHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Collection<GachaBannerInfo> getBannerInfos() {
|
||||||
|
return this.bannerInfos.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GachaBannerInfo getBannerInfo(GachaDef gachaData) {
|
||||||
|
if (!this.loaded) {
|
||||||
|
this.loadFromDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.bannerInfos.computeIfAbsent(
|
||||||
|
gachaData.getId(),
|
||||||
|
i -> new GachaBannerInfo(this.getPlayer(), gachaData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromDatabase() {
|
||||||
|
var db = Nebula.getGameDatabase();
|
||||||
|
|
||||||
|
db.getObjects(GachaBannerInfo.class, "playerUid", getPlayerUid()).forEach(bannerInfo -> {
|
||||||
|
this.bannerInfos.put(bannerInfo.getBannerId(), bannerInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/main/java/emu/nebula/game/gacha/GachaModule.java
Normal file
166
src/main/java/emu/nebula/game/gacha/GachaModule.java
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package emu.nebula.game.gacha;
|
||||||
|
|
||||||
|
import emu.nebula.data.GameData;
|
||||||
|
import emu.nebula.game.GameContext;
|
||||||
|
import emu.nebula.game.GameContextModule;
|
||||||
|
import emu.nebula.game.inventory.ItemAcquireMap;
|
||||||
|
import emu.nebula.game.inventory.ItemParamMap;
|
||||||
|
import emu.nebula.game.inventory.ItemType;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.game.player.PlayerChangeInfo;
|
||||||
|
import emu.nebula.proto.Public.Transform;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
|
|
||||||
|
public class GachaModule extends GameContextModule {
|
||||||
|
|
||||||
|
public GachaModule(GameContext context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GachaResult spin(Player player, int bannerId, int mode) {
|
||||||
|
// Get pull count
|
||||||
|
int amount = mode == 2 ? 10 : 1;
|
||||||
|
|
||||||
|
// Get banner data
|
||||||
|
var banner = GameData.getGachaDataTable().get(bannerId);
|
||||||
|
if (banner == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bannerStorage = banner.getStorageData();
|
||||||
|
if (bannerStorage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create change info
|
||||||
|
var change = new PlayerChangeInfo();
|
||||||
|
|
||||||
|
// Check if we have the materials to gacha TODO
|
||||||
|
int costQty = player.getInventory().getItemCount(bannerStorage.getDefaultId());
|
||||||
|
int costReq = bannerStorage.getDefaultQty() * amount;
|
||||||
|
|
||||||
|
if (costReq > costQty) {
|
||||||
|
// Not enough materials, check if we can convert
|
||||||
|
int convertQty = player.getInventory().getResourceCount(bannerStorage.getCostId());
|
||||||
|
int convertReq = bannerStorage.getCostQty() * (costReq - costQty);
|
||||||
|
|
||||||
|
// Check if we can buy pulls
|
||||||
|
if (convertReq > convertQty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to pull currency
|
||||||
|
player.getInventory().removeItem(bannerStorage.getCostId(), convertReq, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume pull currency
|
||||||
|
player.getInventory().removeItem(bannerStorage.getDefaultId(), Math.min(costReq, costQty), change);
|
||||||
|
|
||||||
|
// Get gacha banner info
|
||||||
|
var info = player.getGachaManager().getBannerInfo(banner);
|
||||||
|
|
||||||
|
// Do gacha
|
||||||
|
var results = new IntArrayList();
|
||||||
|
|
||||||
|
for (int i = 0; i < amount; i++) {
|
||||||
|
int id = info.doPull(banner);
|
||||||
|
if (id <= 0) continue;
|
||||||
|
|
||||||
|
results.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup variables
|
||||||
|
var acquireItems = new ItemAcquireMap(player, results);
|
||||||
|
var transformItemsSrc = new ItemParamMap();
|
||||||
|
var transformItemsDst = new ItemParamMap();
|
||||||
|
var bonusItems = new ItemParamMap();
|
||||||
|
|
||||||
|
// Add for player
|
||||||
|
for (var entry : acquireItems.getItems().int2ObjectEntrySet()) {
|
||||||
|
// Get ids and aquire params
|
||||||
|
int id = entry.getIntKey();
|
||||||
|
var acquire = entry.getValue();
|
||||||
|
|
||||||
|
// Add to player
|
||||||
|
if (acquire.getType() == ItemType.Char) {
|
||||||
|
// Get add amount
|
||||||
|
int count = acquire.getCount();
|
||||||
|
|
||||||
|
// Add char to player
|
||||||
|
if (acquire.getBegin() == 0) {
|
||||||
|
player.getInventory().addItem(id, 1, change);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Talent material
|
||||||
|
if (count > 0) {
|
||||||
|
var characterData = GameData.getCharacterDataTable().get(id);
|
||||||
|
if (characterData == null) continue;
|
||||||
|
|
||||||
|
transformItemsSrc.add(id, count);
|
||||||
|
transformItemsDst.add(characterData.getFragmentsId(), characterData.getTransformQty() * count);
|
||||||
|
transformItemsDst.add(24, 40 * count); // Expert permits
|
||||||
|
}
|
||||||
|
} else if (acquire.getType() == ItemType.Disc) {
|
||||||
|
// Get add amount
|
||||||
|
int begin = acquire.getBegin();
|
||||||
|
int count = acquire.getCount();
|
||||||
|
|
||||||
|
// Add disc to player
|
||||||
|
if (begin == 0) {
|
||||||
|
player.getInventory().addItem(id, 1, change);
|
||||||
|
count--;
|
||||||
|
begin++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Talent material
|
||||||
|
int maxTransformCount = Math.max(6 - begin, 0);
|
||||||
|
int transformCount = Math.min(count, maxTransformCount);
|
||||||
|
int extraCount = count - maxTransformCount;
|
||||||
|
|
||||||
|
// Transform
|
||||||
|
if (transformCount > 0) {
|
||||||
|
var discData = GameData.getDiscDataTable().get(id);
|
||||||
|
if (discData == null) continue;
|
||||||
|
|
||||||
|
// Star material
|
||||||
|
transformItemsSrc.add(id, transformCount);
|
||||||
|
transformItemsDst.add(discData.getTransformItemId(), transformCount);
|
||||||
|
} else if (extraCount > 0) {
|
||||||
|
// Permit
|
||||||
|
transformItemsSrc.add(id, extraCount);
|
||||||
|
transformItemsDst.add(23, 100 * extraCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Travel permits
|
||||||
|
bonusItems.add(23, 100 * acquire.getCount());
|
||||||
|
} else {
|
||||||
|
// Should never happen
|
||||||
|
bonusItems.add(id, acquire.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add gold discs
|
||||||
|
bonusItems.add(602, 30 * acquire.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add transform items to extra items
|
||||||
|
bonusItems.add(transformItemsDst); // Add transform items
|
||||||
|
|
||||||
|
// Add extra items
|
||||||
|
player.getInventory().addItems(bonusItems, change);
|
||||||
|
|
||||||
|
// Add acquire/transform protos
|
||||||
|
change.add(acquireItems.toProto());
|
||||||
|
|
||||||
|
var transform = Transform.newInstance();
|
||||||
|
transformItemsSrc.toItemTemplateStream().forEach(transform::addSrc);
|
||||||
|
transformItemsDst.toItemTemplateStream().forEach(transform::addDst);
|
||||||
|
change.add(transform);
|
||||||
|
|
||||||
|
// Save banner info to database
|
||||||
|
info.save();
|
||||||
|
|
||||||
|
// Complete
|
||||||
|
return new GachaResult(info, change, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/emu/nebula/game/gacha/GachaResult.java
Normal file
19
src/main/java/emu/nebula/game/gacha/GachaResult.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package emu.nebula.game.gacha;
|
||||||
|
|
||||||
|
import emu.nebula.game.player.PlayerChangeInfo;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class GachaResult {
|
||||||
|
private GachaBannerInfo info;
|
||||||
|
private PlayerChangeInfo change;
|
||||||
|
private IntList cards;
|
||||||
|
|
||||||
|
public GachaResult(GachaBannerInfo info, PlayerChangeInfo change, IntList cards) {
|
||||||
|
this.info = info;
|
||||||
|
this.change = change;
|
||||||
|
this.cards = cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -207,7 +207,7 @@ public class Inventory extends PlayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add items
|
// Add items
|
||||||
for (var param : params.getEntrySet()) {
|
for (var param : params.entries()) {
|
||||||
this.addItem(param.getIntKey(), param.getIntValue(), changes);
|
this.addItem(param.getIntKey(), param.getIntValue(), changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ public class Inventory extends PlayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove items
|
// Remove items
|
||||||
for (var param : params.getEntrySet()) {
|
for (var param : params.entries()) {
|
||||||
this.removeItem(param.getIntKey(), param.getIntValue(), changes);
|
this.removeItem(param.getIntKey(), param.getIntValue(), changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ public class Inventory extends PlayerManager {
|
|||||||
public synchronized boolean verifyItems(ItemParamMap params) {
|
public synchronized boolean verifyItems(ItemParamMap params) {
|
||||||
boolean hasItems = true;
|
boolean hasItems = true;
|
||||||
|
|
||||||
for (var param : params.getEntrySet()) {
|
for (var param : params.entries()) {
|
||||||
hasItems = this.verifyItem(param.getIntKey(), param.getIntValue());
|
hasItems = this.verifyItem(param.getIntKey(), param.getIntValue());
|
||||||
|
|
||||||
if (!hasItems) {
|
if (!hasItems) {
|
||||||
|
|||||||
92
src/main/java/emu/nebula/game/inventory/ItemAcquireMap.java
Normal file
92
src/main/java/emu/nebula/game/inventory/ItemAcquireMap.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package emu.nebula.game.inventory;
|
||||||
|
|
||||||
|
import emu.nebula.data.GameData;
|
||||||
|
import emu.nebula.game.player.Player;
|
||||||
|
import emu.nebula.proto.Public.AcqInfo;
|
||||||
|
import emu.nebula.proto.Public.Acquire;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ItemAcquireMap {
|
||||||
|
private final Int2ObjectMap<ItemAcquireParam> items;
|
||||||
|
|
||||||
|
public ItemAcquireMap(Player player, IntList list) {
|
||||||
|
this.items = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
for (int id : list) {
|
||||||
|
// Get item data
|
||||||
|
var data = GameData.getItemDataTable().get(id);
|
||||||
|
if (data == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to acquire map
|
||||||
|
if (!this.getItems().containsKey(id)) {
|
||||||
|
// Get starting count
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
// Check item type
|
||||||
|
if (data.getItemType() == ItemType.Char) {
|
||||||
|
var character = player.getCharacters().getCharacterById(id);
|
||||||
|
|
||||||
|
if (character != null) {
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
} else if (data.getItemType() == ItemType.Disc) {
|
||||||
|
var disc = player.getCharacters().getDiscById(id);
|
||||||
|
|
||||||
|
if (disc != null) {
|
||||||
|
count = 1;
|
||||||
|
count += disc.getStar();
|
||||||
|
count += player.getInventory().getItemCount(disc.getData().getTransformItemId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var acquireInfo = new ItemAcquireParam(data.getItemType(), count);
|
||||||
|
acquireInfo.add();
|
||||||
|
|
||||||
|
this.getItems().put(id, acquireInfo);
|
||||||
|
} else {
|
||||||
|
this.getItems().get(id).add();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proto
|
||||||
|
|
||||||
|
public Acquire toProto() {
|
||||||
|
var proto = Acquire.newInstance();
|
||||||
|
|
||||||
|
for (var entry : this.items.int2ObjectEntrySet()) {
|
||||||
|
var a = AcqInfo.newInstance()
|
||||||
|
.setTid(entry.getIntKey())
|
||||||
|
.setBegin(entry.getValue().getBegin())
|
||||||
|
.setCount(entry.getValue().getCount());
|
||||||
|
|
||||||
|
proto.addList(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class ItemAcquireParam {
|
||||||
|
private ItemType type;
|
||||||
|
private int begin;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public ItemAcquireParam(ItemType type, int i) {
|
||||||
|
this.type = type;
|
||||||
|
this.begin = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add() {
|
||||||
|
this.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,13 @@ import java.util.stream.Stream;
|
|||||||
import emu.nebula.proto.Public.Item;
|
import emu.nebula.proto.Public.Item;
|
||||||
import emu.nebula.proto.Public.ItemInfo;
|
import emu.nebula.proto.Public.ItemInfo;
|
||||||
import emu.nebula.proto.Public.ItemTpl;
|
import emu.nebula.proto.Public.ItemTpl;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
|
||||||
|
|
||||||
import us.hebi.quickbuf.RepeatedMessage;
|
import us.hebi.quickbuf.RepeatedMessage;
|
||||||
|
|
||||||
public class ItemParamMap extends Int2IntOpenHashMap {
|
public class ItemParamMap extends Int2IntLinkedOpenHashMap {
|
||||||
private static final long serialVersionUID = -4186524272780523459L;
|
private static final long serialVersionUID = -4186524272780523459L;
|
||||||
|
|
||||||
public FastEntrySet entries() {
|
|
||||||
return this.int2IntEntrySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override @Deprecated
|
@Override @Deprecated
|
||||||
public int addTo(int itemId, int count) {
|
public int addTo(int itemId, int count) {
|
||||||
@@ -55,9 +53,7 @@ public class ItemParamMap extends Int2IntOpenHashMap {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
public FastEntrySet entries() {
|
||||||
|
|
||||||
public FastEntrySet getEntrySet() {
|
|
||||||
return this.int2IntEntrySet();
|
return this.int2IntEntrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +70,13 @@ public class ItemParamMap extends Int2IntOpenHashMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Stream<ItemTpl> toItemTemplateStream() {
|
public Stream<ItemTpl> toItemTemplateStream() {
|
||||||
return getEntrySet()
|
return entries()
|
||||||
.stream()
|
.stream()
|
||||||
.map(e -> ItemTpl.newInstance().setTid(e.getIntKey()).setQty(e.getIntValue()));
|
.map(e -> ItemTpl.newInstance().setTid(e.getIntKey()).setQty(e.getIntValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<Item> toItemProtoStream() {
|
public Stream<Item> toItemProtoStream() {
|
||||||
return getEntrySet()
|
return entries()
|
||||||
.stream()
|
.stream()
|
||||||
.map(e -> Item.newInstance().setTid(e.getIntKey()).setQty(e.getIntValue()));
|
.map(e -> Item.newInstance().setTid(e.getIntKey()).setQty(e.getIntValue()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import emu.nebula.database.GameDatabaseObject;
|
|||||||
import emu.nebula.game.account.Account;
|
import emu.nebula.game.account.Account;
|
||||||
import emu.nebula.game.character.CharacterStorage;
|
import emu.nebula.game.character.CharacterStorage;
|
||||||
import emu.nebula.game.formation.FormationManager;
|
import emu.nebula.game.formation.FormationManager;
|
||||||
|
import emu.nebula.game.gacha.GachaManager;
|
||||||
import emu.nebula.game.instance.InstanceManager;
|
import emu.nebula.game.instance.InstanceManager;
|
||||||
import emu.nebula.game.inventory.Inventory;
|
import emu.nebula.game.inventory.Inventory;
|
||||||
import emu.nebula.game.mail.Mailbox;
|
import emu.nebula.game.mail.Mailbox;
|
||||||
@@ -66,6 +67,7 @@ public class Player implements GameDatabaseObject {
|
|||||||
// Managers
|
// Managers
|
||||||
private final transient CharacterStorage characters;
|
private final transient CharacterStorage characters;
|
||||||
private final transient Inventory inventory;
|
private final transient Inventory inventory;
|
||||||
|
private transient GachaManager gachaManager;
|
||||||
|
|
||||||
// Referenced data
|
// Referenced data
|
||||||
private transient FormationManager formations;
|
private transient FormationManager formations;
|
||||||
@@ -79,6 +81,7 @@ public class Player implements GameDatabaseObject {
|
|||||||
this.sessions = new HashSet<>();
|
this.sessions = new HashSet<>();
|
||||||
this.characters = new CharacterStorage(this);
|
this.characters = new CharacterStorage(this);
|
||||||
this.inventory = new Inventory(this);
|
this.inventory = new Inventory(this);
|
||||||
|
this.gachaManager = new GachaManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player(Account account, String name, boolean gender) {
|
public Player(Account account, String name, boolean gender) {
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ public class NetMsgIdUtils {
|
|||||||
return msgIdMap.getOrDefault(msgId, "UNKNOWN");
|
return msgIdMap.getOrDefault(msgId, "UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static void dumpPacketIds() {
|
public static void dumpPacketIds() {
|
||||||
try (FileWriter writer = new FileWriter("./MsgIds_" + GameConstants.VERSION + ".json")) {
|
try (FileWriter writer = new FileWriter("./MsgIds_" + GameConstants.VERSION + ".json")) {
|
||||||
// Create sorted tree map
|
// Create sorted tree map
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package emu.nebula.server.handlers;
|
|||||||
|
|
||||||
import emu.nebula.net.NetHandler;
|
import emu.nebula.net.NetHandler;
|
||||||
import emu.nebula.net.NetMsgId;
|
import emu.nebula.net.NetMsgId;
|
||||||
|
import emu.nebula.proto.GachaInformation.GachaInfo;
|
||||||
import emu.nebula.proto.GachaInformation.GachaInformationResp;
|
import emu.nebula.proto.GachaInformation.GachaInformationResp;
|
||||||
import emu.nebula.net.HandlerId;
|
import emu.nebula.net.HandlerId;
|
||||||
import emu.nebula.net.GameSession;
|
import emu.nebula.net.GameSession;
|
||||||
@@ -11,10 +12,23 @@ public class HandlerGachaInformationReq extends NetHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
||||||
|
// Build response
|
||||||
var rsp = GachaInformationResp.newInstance();
|
var rsp = GachaInformationResp.newInstance();
|
||||||
|
|
||||||
// TODO
|
for (var bannerInfo : session.getPlayer().getGachaManager().getBannerInfos()) {
|
||||||
|
var info = GachaInfo.newInstance()
|
||||||
|
.setId(bannerInfo.getBannerId())
|
||||||
|
.setGachaTotalTimes(bannerInfo.getTotal())
|
||||||
|
.setTotalTimes(bannerInfo.getTotal())
|
||||||
|
.setAupMissTimes(bannerInfo.getMissTimesA())
|
||||||
|
.setAMissTimes(bannerInfo.getMissTimesA())
|
||||||
|
.setReveFirstTenReward(true)
|
||||||
|
.setRecvGuaranteeReward(bannerInfo.isUsedGuarantee());
|
||||||
|
|
||||||
|
rsp.addInformation(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode and send
|
||||||
return session.encodeMsg(NetMsgId.gacha_information_succeed_ack, rsp);
|
return session.encodeMsg(NetMsgId.gacha_information_succeed_ack, rsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package emu.nebula.server.handlers;
|
|||||||
|
|
||||||
import emu.nebula.net.NetHandler;
|
import emu.nebula.net.NetHandler;
|
||||||
import emu.nebula.net.NetMsgId;
|
import emu.nebula.net.NetMsgId;
|
||||||
import emu.nebula.proto.GachaInformation.GachaInformationResp;
|
import emu.nebula.proto.GachaNewbieInfoOuterClass.GachaNewbieInfo;
|
||||||
|
import emu.nebula.proto.GachaNewbieInfoOuterClass.GachaNewbieInfoResp;
|
||||||
import emu.nebula.net.HandlerId;
|
import emu.nebula.net.HandlerId;
|
||||||
import emu.nebula.net.GameSession;
|
import emu.nebula.net.GameSession;
|
||||||
|
|
||||||
@@ -11,7 +12,12 @@ public class HandlerGachaNewbieInfoReq extends NetHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
||||||
var rsp = GachaInformationResp.newInstance();
|
var rsp = GachaNewbieInfoResp.newInstance();
|
||||||
|
var info = GachaNewbieInfo.newInstance()
|
||||||
|
.setId(5)
|
||||||
|
.setReceive(true);
|
||||||
|
|
||||||
|
rsp.addList(info);
|
||||||
|
|
||||||
return session.encodeMsg(NetMsgId.gacha_newbie_info_succeed_ack, rsp);
|
return session.encodeMsg(NetMsgId.gacha_newbie_info_succeed_ack, rsp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,50 +2,51 @@ package emu.nebula.server.handlers;
|
|||||||
|
|
||||||
import emu.nebula.net.NetHandler;
|
import emu.nebula.net.NetHandler;
|
||||||
import emu.nebula.net.NetMsgId;
|
import emu.nebula.net.NetMsgId;
|
||||||
|
import emu.nebula.net.HandlerId;
|
||||||
|
import emu.nebula.Nebula;
|
||||||
|
import emu.nebula.net.GameSession;
|
||||||
import emu.nebula.proto.GachaSpin.GachaCard;
|
import emu.nebula.proto.GachaSpin.GachaCard;
|
||||||
import emu.nebula.proto.GachaSpin.GachaSpinReq;
|
import emu.nebula.proto.GachaSpin.GachaSpinReq;
|
||||||
import emu.nebula.proto.GachaSpin.GachaSpinResp;
|
import emu.nebula.proto.GachaSpin.GachaSpinResp;
|
||||||
import emu.nebula.proto.Public.ItemTpl;
|
import emu.nebula.proto.Public.ItemTpl;
|
||||||
import emu.nebula.util.Utils;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import emu.nebula.net.HandlerId;
|
|
||||||
import emu.nebula.Nebula;
|
|
||||||
import emu.nebula.data.GameData;
|
|
||||||
import emu.nebula.net.GameSession;
|
|
||||||
|
|
||||||
@HandlerId(NetMsgId.gacha_spin_req)
|
@HandlerId(NetMsgId.gacha_spin_req)
|
||||||
public class HandlerGachaSpinReq extends NetHandler {
|
public class HandlerGachaSpinReq extends NetHandler {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
public byte[] handle(GameSession session, byte[] message) throws Exception {
|
||||||
|
// Parse request
|
||||||
var req = GachaSpinReq.parseFrom(message);
|
var req = GachaSpinReq.parseFrom(message);
|
||||||
|
|
||||||
// Temp
|
// Do gacha
|
||||||
var list = new IntArrayList();
|
var result = Nebula.getGameContext().getGachaModule().spin(
|
||||||
|
session.getPlayer(),
|
||||||
|
req.getId(),
|
||||||
|
req.getMode()
|
||||||
|
);
|
||||||
|
|
||||||
for (var def : GameData.getCharacterDataTable()) {
|
if (result == null) {
|
||||||
if (def.getGrade() == 1 && def.isAvailable()) {
|
return session.encodeMsg(NetMsgId.gacha_spin_failed_ack);
|
||||||
list.add(def.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build response
|
// Build response
|
||||||
var rsp = GachaSpinResp.newInstance()
|
var rsp = GachaSpinResp.newInstance()
|
||||||
.setTime(Nebula.getCurrentTime());
|
.setTime(Nebula.getCurrentTime())
|
||||||
|
.setAMissTimes(result.getInfo().getMissTimesA())
|
||||||
|
.setAupMissTimes(result.getInfo().getMissTimesA())
|
||||||
|
.setTotalTimes(result.getInfo().getTotal())
|
||||||
|
.setGachaTotalTimes(result.getInfo().getTotal())
|
||||||
|
.setAupGuaranteeTimes(result.getInfo().isUsedGuarantee() ? 0 : 1)
|
||||||
|
.setChange(result.getChange().toProto());
|
||||||
|
|
||||||
rsp.getMutableChange();
|
for (int id : result.getCards()) {
|
||||||
rsp.getMutableNextPackage();
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
int id = Utils.randomElement(list);
|
|
||||||
|
|
||||||
var card = GachaCard.newInstance()
|
var card = GachaCard.newInstance()
|
||||||
.setCard(ItemTpl.newInstance().setTid(id).setQty(1));
|
.setCard(ItemTpl.newInstance().setTid(id).setQty(1));
|
||||||
|
|
||||||
rsp.addCards(card);
|
rsp.addCards(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode and send response
|
||||||
return session.encodeMsg(NetMsgId.gacha_spin_succeed_ack, rsp);
|
return session.encodeMsg(NetMsgId.gacha_spin_succeed_ack, rsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
src/main/java/emu/nebula/util/WeightedList.java
Normal file
35
src/main/java/emu/nebula/util/WeightedList.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package emu.nebula.util;
|
||||||
|
|
||||||
|
import java.util.NavigableMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class WeightedList<E> {
|
||||||
|
private final NavigableMap<Double, E> map = new TreeMap<>();
|
||||||
|
private double total = 0;
|
||||||
|
|
||||||
|
public WeightedList() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeightedList<E> add(double weight, E result) {
|
||||||
|
if (weight <= 0) return this;
|
||||||
|
total += weight;
|
||||||
|
map.put(total, result);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E next() {
|
||||||
|
double value = ThreadLocalRandom.current().nextDouble() * total;
|
||||||
|
return map.higherEntry(value).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.map.clear();
|
||||||
|
this.total = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user