Skip to content

Commit

Permalink
Island levels (#178)
Browse files Browse the repository at this point in the history
* Stores level data on a per island basis

* Migrate after BentoBox worlds have loaded.

* Added new Admin set initial level handicap command

* Bug fixing

* Fix test

* Removed code smell
  • Loading branch information
tastybento committed Jul 26, 2020
1 parent 507cefd commit dab0e84
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 94 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.3.4</build.version>
<build.version>2.4.0</build.version>
</properties>

<!-- Profiles will allow to automatically change build version. -->
Expand Down
18 changes: 7 additions & 11 deletions src/main/java/world/bentobox/level/Level.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.commands.AdminLevelCommand;
import world.bentobox.level.commands.AdminLevelStatusCommand;
import world.bentobox.level.commands.AdminSetInitialLevelCommand;
import world.bentobox.level.commands.AdminTopCommand;
import world.bentobox.level.commands.IslandLevelCommand;
import world.bentobox.level.commands.IslandTopCommand;
Expand All @@ -30,7 +31,6 @@
import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandActivitiesListeners;
import world.bentobox.level.listeners.JoinLeaveListener;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.requests.LevelRequestHandler;
import world.bentobox.level.requests.TopTenRequestHandler;

Expand Down Expand Up @@ -99,6 +99,9 @@ public void onEnable() {

@EventHandler
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Perform upgrade check
manager.migrate();
// Load TopTens
manager.loadTopTens();
/*
* DEBUG code to generate fake islands and then try to level them all.
Expand All @@ -119,8 +122,8 @@ public void onBentoBoxReady(BentoBoxReadyEvent e) {
getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> {
this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r ->
log("Result for island calc " + r.getLevel() + " at " + is.getCenter()));
this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r ->
log("Result for island calc " + r.getLevel() + " at " + is.getCenter()));
});
}, 60L);*/
Expand Down Expand Up @@ -187,6 +190,7 @@ private void registerCommands(GameModeAddon gm) {
new AdminLevelCommand(this, adminCommand);
new AdminTopCommand(this, adminCommand);
new AdminLevelStatusCommand(this, adminCommand);
new AdminSetInitialLevelCommand(this, adminCommand);
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
Expand Down Expand Up @@ -319,12 +323,4 @@ public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID
if (island != null) getManager().calculateLevel(playerUUID, island);
}

/**
* Load a player from the cache or database
* @param targetPlayer - UUID of target player
* @return LevelsData object or null if not found
*/
public LevelsData getLevelsData(UUID targetPlayer) {
return getManager().getLevelsData(targetPlayer);
}
}
160 changes: 98 additions & 62 deletions src/main/java/world/bentobox/level/LevelsManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
Expand All @@ -21,6 +22,8 @@
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import com.google.common.collect.Maps;

import world.bentobox.bentobox.api.events.addon.AddonBaseEvent;
import world.bentobox.bentobox.api.events.addon.AddonEvent;
import world.bentobox.bentobox.api.panels.PanelItem;
Expand All @@ -33,6 +36,7 @@
import world.bentobox.level.calculators.Results;
import world.bentobox.level.events.IslandLevelCalculatedEvent;
import world.bentobox.level.events.IslandPreLevelEvent;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
import world.bentobox.level.panels.DetailsGUITab;
Expand All @@ -55,9 +59,9 @@ public class LevelsManager {


// Database handler for level data
private final Database<LevelsData> handler;
private final Database<IslandLevels> handler;
// A cache of island levels.
private final Map<UUID, LevelsData> levelsCache;
private final Map<String, IslandLevels> levelsCache;

private final Database<TopTenData> topTenHandler;
// Top ten lists
Expand All @@ -72,7 +76,7 @@ public LevelsManager(Level addon) {
// Get the BentoBox database
// Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database
handler = new Database<>(addon, LevelsData.class);
handler = new Database<>(addon, IslandLevels.class);
// Top Ten handler
topTenHandler = new Database<>(addon, TopTenData.class);
// Initialize the cache
Expand All @@ -83,6 +87,39 @@ public LevelsManager(Level addon) {
background = new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name(" ").build();
}

public void migrate() {
Database<LevelsData> oldDb = new Database<>(addon, LevelsData.class);
oldDb.loadObjects().forEach(ld -> {
try {
UUID owner = UUID.fromString(ld.getUniqueId());
// Step through each world
ld.getLevels().keySet().stream()
// World
.map(Bukkit::getWorld).filter(Objects::nonNull)
// Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull)
.forEach(i -> {
// Make new database entry
World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId());
il.setInitialLevel(ld.getInitialLevel(w));
il.setLevel(ld.getLevel(w));
il.setMdCount(ld.getMdCount(w));
il.setPointsToNextLevel(ld.getPointsToNextLevel(w));
il.setUwCount(ld.getUwCount(w));
// Save it
handler.saveObjectAsync(il);
});
// Now delete the old database entry
oldDb.deleteID(ld.getUniqueId());
} catch (Exception e) {
addon.logError("Could not migrate level data database! " + e.getMessage());
e.printStackTrace();
return;
}
});
}

/**
* Add a score to the top players list
* @param world - world
Expand Down Expand Up @@ -286,9 +323,7 @@ private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, Wor
* @return initial level of island
*/
public long getInitialLevel(Island island) {
@Nullable
LevelsData ld = getLevelsData(island.getOwner());
return ld == null ? 0 : ld.getInitialLevel(island.getWorld());
return getLevelsData(island).getInitialLevel();
}

/**
Expand All @@ -299,11 +334,9 @@ public long getInitialLevel(Island island) {
*/
public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
// Get the island owner
UUID owner = addon.getIslands().getOwner(world, targetPlayer);
if (owner == null) return 0L;
LevelsData ld = getLevelsData(owner);
return ld == null ? 0L : ld.getLevel(world);
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getLevel();
}

/**
Expand All @@ -317,23 +350,30 @@ public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPl
}

/**
* Load a level data for the island owner from the cache or database. Only island owners are stored.
* @param islandOwner - UUID of island owner
* @return LevelsData object or null if not found
* Load a level data for the island from the cache or database.
* @param island - UUID of island
* @return IslandLevels object
*/
@Nullable
public LevelsData getLevelsData(@NonNull UUID islandOwner) {
@NonNull
public IslandLevels getLevelsData(@NonNull Island island) {
String id = island.getUniqueId();
if (levelsCache.containsKey(id)) {
return levelsCache.get(id);
}
// Get from database if not in cache
if (!levelsCache.containsKey(islandOwner) && handler.objectExists(islandOwner.toString())) {
LevelsData ld = handler.loadObject(islandOwner.toString());
if (handler.objectExists(id)) {
IslandLevels ld = handler.loadObject(id);
if (ld != null) {
levelsCache.put(islandOwner, ld);
levelsCache.put(id, ld);
} else {
handler.deleteID(islandOwner.toString());
handler.deleteID(id);
levelsCache.put(id, new IslandLevels(id));
}
} else {
levelsCache.put(id, new IslandLevels(id));
}
// Return cached value or null
return levelsCache.get(islandOwner);
// Return cached value
return levelsCache.get(id);
}

/**
Expand All @@ -344,10 +384,8 @@ public LevelsData getLevelsData(@NonNull UUID islandOwner) {
*/
public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return "";
UUID owner = addon.getIslands().getOwner(world, targetPlayer);
if (owner == null) return "";
LevelsData ld = getLevelsData(owner);
return ld == null ? "" : String.valueOf(ld.getPointsToNextLevel(world));
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
}

/**
Expand Down Expand Up @@ -394,9 +432,6 @@ void loadTopTens() {
// Update based on user data
// Remove any non island owners
tt.getTopTen().keySet().removeIf(u -> !addon.getIslands().isOwner(world, u));
for (UUID uuid : tt.getTopTen().keySet()) {
tt.getTopTen().compute(uuid, (k,v) -> v = updateLevel(k, world));
}
} else {
addon.logError("TopTen world '" + tt.getUniqueId() + "' is not known on server. You might want to delete this table. Skipping...");
}
Expand All @@ -409,14 +444,6 @@ void loadTopTens() {
* @param uuid - the player's uuid
*/
public void removeEntry(World world, UUID uuid) {
// Load the user if they haven't yet done anything to put them in the cache
this.getLevelsData(uuid);
// Remove them
if (levelsCache.containsKey(uuid)) {
levelsCache.get(uuid).remove(world);
// Save
handler.saveObjectAsync(levelsCache.get(uuid));
}
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
topTenHandler.saveObjectAsync(topTenLists.get(world));
Expand All @@ -441,14 +468,14 @@ public void saveTopTen(World world) {
}

/**
* Set an initial island level for player
* @param island - the island to set. Must have a non-null world and owner
* Set an initial island level
* @param island - the island to set. Must have a non-null world
* @param lv - initial island level
*/
public void setInitialIslandLevel(@NonNull Island island, long lv) {
if (island.getOwner() == null || island.getWorld() == null) return;
levelsCache.computeIfAbsent(island.getOwner(), LevelsData::new).setInitialLevel(island.getWorld(), lv);
handler.saveObjectAsync(levelsCache.get(island.getOwner()));
if (island.getWorld() == null) return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
}

/**
Expand All @@ -458,10 +485,18 @@ public void setInitialIslandLevel(@NonNull Island island, long lv) {
* @param lv - level
*/
public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
levelsCache.computeIfAbsent(targetPlayer, LevelsData::new).setLevel(world, lv);
handler.saveObjectAsync(levelsCache.get(targetPlayer));
// Update TopTen
addToTopTen(world, targetPlayer, levelsCache.get(targetPlayer).getLevel(world));
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
if (island != null) {
String id = island.getUniqueId();
IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new);
// Remove the initial level
il.setLevel(lv - il.getInitialLevel());
handler.saveObjectAsync(levelsCache.get(id));
// Update TopTen
addToTopTen(world, targetPlayer, levelsCache.get(id).getLevel());
}

}

/**
Expand All @@ -471,26 +506,27 @@ public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, lon
* @param r - results of the calculation
*/
private void setIslandResults(World world, @NonNull UUID owner, Results r) {
LevelsData ld = levelsCache.computeIfAbsent(owner, LevelsData::new);
ld.setLevel(world, r.getLevel());
ld.setUwCount(world, r.getUwCount());
ld.setMdCount(world, r.getMdCount());
ld.setPointsToNextLevel(world, r.getPointsToNextLevel());
levelsCache.put(owner, ld);
// Get the island
Island island = addon.getIslands().getIsland(world, owner);
if (island == null) return;
IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new);
ld.setLevel(r.getLevel() - ld.getInitialLevel());
ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem)));
ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem)));
ld.setPointsToNextLevel(r.getPointsToNextLevel());
levelsCache.put(island.getUniqueId(), ld);
handler.saveObjectAsync(ld);
// Update TopTen
addToTopTen(world, owner, ld.getLevel(world));
addToTopTen(world, owner, ld.getLevel());
}

private Long updateLevel(UUID uuid, World world) {
if (handler.objectExists(uuid.toString())) {
@Nullable
LevelsData ld = handler.loadObject(uuid.toString());
if (ld != null) {
return ld.getLevel(world);
}
}
return 0L;
/**
* Removes island from cache when it is deleted
* @param uniqueId - id of island
*/
public void deleteIsland(String uniqueId) {
levelsCache.remove(uniqueId);
handler.deleteID(uniqueId);
}

}

0 comments on commit dab0e84

Please sign in to comment.