/*
* Copyright 2015 Demigods RPG
* Copyright 2015 Alexander Chauncey
* Copyright 2015 Alex Bennett
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.demigodsrpg.model;
import com.demigodsrpg.DGData;
import com.demigodsrpg.Demo;
import com.demigodsrpg.Setting;
import com.demigodsrpg.ability.AbilityCaster;
import com.demigodsrpg.ability.AbilityMetaData;
import com.demigodsrpg.ability.AbilityRegistry;
import com.demigodsrpg.aspect.Aspect;
import com.demigodsrpg.aspect.Aspects;
import com.demigodsrpg.aspect.Groups;
import com.demigodsrpg.battle.BattleMetaData;
import com.demigodsrpg.deity.Deity;
import com.demigodsrpg.deity.DeityType;
import com.demigodsrpg.family.Family;
import com.demigodsrpg.util.ZoneUtil;
import com.demigodsrpg.util.datasection.AbstractPersistentModel;
import com.demigodsrpg.util.datasection.DataSection;
import com.demigodsrpg.util.misc.RandomUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.map.hash.TIntDoubleHashMap;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class PlayerModel extends AbstractPersistentModel<String> implements AbilityCaster, Participant {
private final String mojangId;
private String lastKnownName;
// -- PARENTS -- //
private Optional<Deity> god;
private Optional<Deity> hero;
// -- CONTRACTS -- //
private List<Deity> contracts = new ArrayList<>();
private final List<String> aspects = new ArrayList<>(1);
private final List<String> shrineWarps = new ArrayList<>();
private Family family;
private final BiMap<String, String> binds = HashBiMap.create();
private final TIntDoubleHashMap experience;
private long lastLoginTime;
private double maxHealth;
private double favor;
private int level;
private boolean canPvp;
private boolean adminMode;
private int kills;
private int deaths;
private int teamKills;
@SuppressWarnings("deprecation")
public PlayerModel(Player player) {
mojangId = player.getUniqueId().toString();
lastKnownName = player.getName();
lastLoginTime = System.currentTimeMillis();
experience = new TIntDoubleHashMap(1);
// Debug data
if (Setting.DEBUG_DATA) {
handleDemo();
} else {
// Neutral faction
family = Family.NEUTRAL;
// Empty deities
god = Optional.empty();
hero = Optional.empty();
}
maxHealth = 20.0;
favor = 700.0;
level = 0;
canPvp = true;
adminMode = false;
kills = 0;
deaths = 0;
teamKills = 0;
}
@SuppressWarnings("unchecked")
public PlayerModel(String mojangId, DataSection conf) {
this.mojangId = mojangId;
lastKnownName = conf.getString("last_known_name");
lastLoginTime = conf.getLong("last_login_time");
aspects.addAll(conf.getStringList("aspects"));
if (conf.getStringList("shrine_warps") != null) {
shrineWarps.addAll(conf.getStringList("shrine_warps"));
}
god = Optional.ofNullable(DGData.DEITY_R.deityFromName(conf.getStringNullable("god")));
hero = Optional.ofNullable(DGData.DEITY_R.deityFromName(conf.getStringNullable("hero")));
for (Object contractName : conf.getList("contracts", new ArrayList<>())) {
Deity contract = DGData.DEITY_R.deityFromName(contractName.toString());
if (contract != null) {
contracts.add(contract);
}
}
family = DGData.FAMILY_R.familyFromName(conf.getStringNullable("faction"));
if (family == null) {
family = Family.NEUTRAL;
}
Map binds = conf.getSectionNullable("binds") != null ? (Map) conf.getSectionNullable("binds").getValues() : null;
if (binds != null) {
this.binds.putAll(binds);
}
maxHealth = conf.getDouble("max_health", 20.0);
favor = conf.getDouble("favor", 20.0);
experience = new TIntDoubleHashMap(1);
boolean expError = false;
for (Map.Entry<String, Object> entry : conf.getSectionNullable("devotion").getValues().entrySet()) {
try {
experience.put(Integer.valueOf(entry.getKey()), Double.valueOf(entry.getValue().toString()));
} catch (Exception ignored) {
expError = true;
}
}
if (expError) {
DGData.CONSOLE.warning("There was an error loading devotion data for " + lastKnownName + ".");
}
level = conf.getInt("level");
canPvp = conf.getBoolean("can_pvp", true);
adminMode = conf.getBoolean("admin_mode", false);
kills = conf.getInt("kills");
deaths = conf.getInt("deaths");
teamKills = conf.getInt("team_kills");
}
@Override
public Type getType() {
return Type.PERSISTENT;
}
@Override
public String getPersistentId() {
return mojangId;
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("last_known_name", lastKnownName);
map.put("last_login_time", lastLoginTime);
map.put("aspects", Lists.newArrayList(aspects));
map.put("shrine_warps", Lists.newArrayList(shrineWarps));
if (god.isPresent()) {
map.put("god", god.get().getName());
}
if (hero.isPresent()) {
map.put("hero", hero.get().getName());
}
map.put("contracts", contracts.stream().map(Deity::getName).collect(Collectors.toList()));
map.put("faction", family.getName());
map.put("binds", binds);
map.put("max_health", maxHealth);
map.put("favor", favor);
Map<Integer, Double> devotionMap = new HashMap<>();
TIntIterator iterator = experience.keySet().iterator();
while (iterator.hasNext()) {
int key = iterator.next();
try {
devotionMap.put(key, experience.get(key));
} catch (Exception ignored) {
}
}
map.put("devotion", devotionMap);
map.put("level", level);
map.put("can_pvp", canPvp);
map.put("admin_mode", adminMode);
map.put("kills", kills);
map.put("deaths", deaths);
map.put("team_kills", teamKills);
return map;
}
public String getMojangId() {
return mojangId;
}
public String getLastKnownName() {
return lastKnownName;
}
public void setLastKnownName(String lastKnownName) {
this.lastKnownName = lastKnownName;
DGData.PLAYER_R.register(this);
}
public long getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Long lastLoginTime) {
this.lastLoginTime = lastLoginTime;
DGData.PLAYER_R.register(this);
}
public List<String> getAspects() {
return aspects;
}
public void addAspect(Aspect aspect) {
aspects.add(aspect.name());
DGData.PLAYER_R.register(this);
}
public void removeAspect(Aspect aspect) {
aspects.remove(aspect.name());
DGData.PLAYER_R.register(this);
}
public List<String> getShrineWarps() {
return shrineWarps;
}
public void addShrineWarp(ShrineModel model) {
shrineWarps.add(model.getPersistentId());
DGData.PLAYER_R.register(this);
}
public void removeShrineWarp(ShrineModel model) {
shrineWarps.remove(model.getPersistentId());
DGData.PLAYER_R.register(this);
}
public void removeShrineWarp(String id) {
shrineWarps.remove(id);
DGData.PLAYER_R.register(this);
}
@Override
public Family getFamily() {
return family;
}
public void setFamily(Family family) {
this.family = family;
DGData.PLAYER_R.register(this);
}
public void setGod(Deity god) {
if (DeityType.GOD.equals(god.getDeityType())) {
this.god = Optional.ofNullable(god);
DGData.PLAYER_R.register(this);
} else {
throw new IllegalArgumentException("Cannot set a non-god deity as a god.");
}
}
public void setHero(Deity hero) {
if (DeityType.HERO.equals(hero.getDeityType())) {
this.hero = Optional.ofNullable(hero);
DGData.PLAYER_R.register(this);
} else {
throw new IllegalArgumentException("Cannot set a non-hero deity as a hero.");
}
}
public Optional<Deity> getGod() {
return god;
}
public Optional<Deity> getHero() {
return hero;
}
public List<Deity> getContracts() {
return contracts;
}
public void addContract(Deity deity) {
contracts.add(deity);
}
public void removeContract(Deity deity) {
contracts.remove(deity);
}
public boolean hasDeity(Deity deity) {
return god.isPresent() && god.get().equals(deity) || hero.isPresent() && hero.get().equals(deity) || contracts.contains(deity);
}
public double getMaxHealth() {
return maxHealth;
}
public void setMaxHealth(Double maxHealth) {
this.maxHealth = maxHealth;
DGData.PLAYER_R.register(this);
}
public double getFavor() {
return favor;
}
public void setFavor(double favor) {
this.favor = favor;
DGData.PLAYER_R.register(this);
}
public double getExperience(Aspect aspect) {
if (!experience.containsKey(aspect.getId())) {
return 0.0;
}
return experience.get(aspect.getId());
}
public double getExperience(String aspectName) {
int ordinal = Aspects.valueOf(aspectName).getId();
if (!experience.containsKey(ordinal)) {
return 0.0;
}
return experience.get(ordinal);
}
public Double getTotalExperience() {
double total = 0.0;
for (String aspect : aspects) {
total += getExperience(aspect);
}
return total;
}
public void setExperience(Aspect aspect, double experience, boolean announce) {
this.experience.put(aspect.getId(), experience);
calculateAscensions(announce);
DGData.PLAYER_R.register(this);
}
public void setExperience(String aspectName, double experience, boolean announce) {
int ordinal = Aspects.valueOf(aspectName).getId();
this.experience.put(ordinal, experience);
calculateAscensions(announce);
DGData.PLAYER_R.register(this);
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
DGData.PLAYER_R.register(this);
}
public Map<String, String> getBindsMap() {
return binds;
}
public AbilityMetaData getBound(Material material) {
if (binds.inverse().containsKey(material.name())) {
return AbilityRegistry.fromCommand(binds.inverse().get(material.name()));
}
return null;
}
public Material getBound(AbilityMetaData ability) {
return getBound(ability.getCommand());
}
public Material getBound(String abilityCommand) {
if (binds.containsKey(abilityCommand)) {
return Material.valueOf(binds.get(abilityCommand));
}
return null;
}
public void bind(AbilityMetaData ability, Material material) {
binds.put(ability.getCommand(), material.name());
DGData.PLAYER_R.register(this);
}
public void bind(String abilityCommand, Material material) {
binds.put(abilityCommand, material.name());
DGData.PLAYER_R.register(this);
}
public void unbind(AbilityMetaData ability) {
binds.remove(ability.getCommand());
DGData.PLAYER_R.register(this);
}
public void unbind(String abilityCommand) {
binds.remove(abilityCommand);
DGData.PLAYER_R.register(this);
}
public void unbind(Material material) {
binds.inverse().remove(material.name());
DGData.PLAYER_R.register(this);
}
public boolean getCanPvp() {
return canPvp;
}
public void setCanPvp(Boolean canPvp) {
this.canPvp = canPvp;
DGData.PLAYER_R.register(this);
}
public boolean getAdminMode() {
return adminMode;
}
public void setAdminMode(Boolean adminMode) {
this.adminMode = adminMode;
DGData.PLAYER_R.register(this);
}
public int getKills() {
return kills;
}
public void addKill() {
kills++;
DGData.PLAYER_R.register(this);
}
public int getDeaths() {
return deaths;
}
public void addDeath() {
deaths++;
DGData.PLAYER_R.register(this);
}
public int getTeamKills() {
return teamKills;
}
public void addTeamKill() {
teamKills++;
DGData.PLAYER_R.register(this);
}
public void resetTeamKills() {
teamKills = 0;
DGData.PLAYER_R.register(this);
}
public OfflinePlayer getOfflinePlayer() {
return Bukkit.getOfflinePlayer(UUID.fromString(mojangId));
}
public boolean getOnline() {
return getOfflinePlayer().isOnline();
}
@Override
public EntityType getEntityType() {
return EntityType.PLAYER;
}
@Override
public Location getLocation() {
if (getOnline()) {
return getOfflinePlayer().getPlayer().getLocation();
}
throw new UnsupportedOperationException("We don't support finding locations for players who aren't online.");
}
public boolean isDemigod() {
return hero != null && god != null;
}
public boolean hasAspect(Aspect aspect) {
return getAspects().contains(aspect.name());
}
public boolean hasPrereqs(Aspect aspect) {
int tier = aspect.getTier().ordinal();
if (tier == 0) {
return true;
}
Aspect.Group group = aspect.getGroup();
for (String hasName : getAspects()) {
Aspect has = Aspects.valueOf(hasName);
if (has.getGroup().equals(group) && has.getTier().ordinal() + 1 == tier) {
return true;
}
}
return false;
}
public List<Aspect.Group> getPotentialGroups() {
// Get the groups
List<Aspect.Group> groups = new ArrayList<>();
// Get the deities
List<Deity> deities = new ArrayList<>(contracts);
// Add hero and god tot he deity list
if (hero.isPresent()) {
deities.add(hero.get());
}
if (god.isPresent()) {
deities.add(god.get());
}
// For each deity, find the groups
for (Deity deity : deities) {
// Is the group already in the cache?
deity.getAspectGroups().stream().filter(group -> !groups.contains(group)).forEach(groups::add);
}
return groups;
}
public List<Aspect> getPotentialAspects(Aspect.Group group, boolean alwaysIncludeHero) {
List<Aspect> aspects = new ArrayList<>();
Optional<Aspect> heroAspect = Groups.heroAspectInGroup(group);
if (hero.isPresent() && hero.get().getAspectGroups().contains(group) && heroAspect.isPresent()) {
aspects.add(heroAspect.get());
}
List<Deity> gods = new ArrayList<>(contracts);
if (god.isPresent()) {
gods.add(god.get());
}
for (Deity deity : gods) {
if (deity.getAspectGroups().contains(group)) {
if (alwaysIncludeHero) {
List<Aspect> allAspects = Groups.aspectsInGroup(group);
if (heroAspect.isPresent()) {
allAspects.remove(heroAspect.get());
}
aspects.addAll(allAspects);
} else {
aspects.addAll(Groups.godAspectsInGroup(group));
}
break;
}
}
return aspects;
}
public List<Aspect> getPotentialAspects(boolean alwaysIncludeHero) {
List<Aspect> aspects = new ArrayList<>();
for (Aspect.Group group : getPotentialGroups()) {
aspects.addAll(getPotentialAspects(group, alwaysIncludeHero));
}
return aspects;
}
@SuppressWarnings("RedundantCast")
@Override
public boolean reward(BattleMetaData data) {
double experience = getTotalExperience();
teamKills += data.getTeamKills();
if (checkTeamKills()) {
double score = data.getHits() + data.getAssists() / 2;
score += data.getDenies();
score += data.getKills() * 2;
score -= data.getDeaths() * 1.5;
score *= (double) Setting.EXP_MULTIPLIER;
score /= aspects.size() + 1;
for (String aspect : aspects) {
setExperience(aspect, getExperience(aspect) + score, true);
}
}
DGData.PLAYER_R.register(this);
return experience > getTotalExperience();
}
public boolean checkTeamKills() {
if (!Family.EXCOMMUNICATED.equals(family)) {
if (Setting.MAX_TEAM_KILLS <= teamKills) {
// Reset them to excommunicated
setFamily(Family.EXCOMMUNICATED);
resetTeamKills();
// double former = getTotalExperience();
if (getOnline()) {
Player player = getOfflinePlayer().getPlayer();
player.sendMessage(ChatColor.RED + "Your former faction has just excommunicated you.");
player.sendMessage(ChatColor.RED + "You will no longer respawn at the faction spawn.");
// player.sendMessage(ChatColor.RED + "You have lost " +
// ChatColor.GOLD + DecimalFormat.getCurrencyInstance().format(former - getTotalExperience()) +
// ChatColor.RED + " experience.");
// player.sendMessage(ChatColor.YELLOW + "To join a faction, "); // TODO
}
return false;
}
}
return true;
}
public void giveHeroAspect(Deity hero, Aspect aspect) {
giveAspect(aspect);
setFamily(hero.getFamilies().get(0));
setMaxHealth(25.0);
setLevel(1);
setExperience(aspect, 20.0, true);
calculateAscensions(true);
}
public void giveAspect(Aspect aspect) {
aspects.add(aspect.name());
setExperience(aspect, 20.0, true);
}
public boolean canClaim(Aspect aspect) {
return costForNextAspect() <= level && !hasAspect(aspect) && hasPrereqs(aspect);
}
public boolean canContract(Deity deity) {
return Setting.NO_FACTION_CONTRACT_MODE || deity.getFamilies().contains(family);
}
public void calculateAscensions(boolean announce) {
Player player = getOfflinePlayer().getPlayer();
if (getLevel() >= Setting.ASCENSION_CAP) return;
boolean did = false;
while (getTotalExperience() >= (int) Math.ceil(500 * Math.pow(getLevel() + 1, 2.02)) && getLevel() < Setting.ASCENSION_CAP) {
did = true;
setMaxHealth(getMaxHealth() + 10.0);
player.setMaxHealth(getMaxHealth());
player.setHealthScale(20.0);
player.setHealthScaled(true);
player.setHealth(getMaxHealth());
setLevel(getLevel() + 1);
}
if (did && announce) {
player.sendMessage(ChatColor.AQUA + "Congratulations! Your Ascensions have increased to " + getLevel() + ".");
player.sendMessage(ChatColor.YELLOW + "Your maximum HP has increased to " + getMaxHealth() + ".");
}
DGData.PLAYER_R.register(this);
}
public int costForNextAspect() {
if (Setting.NO_COST_ASPECT_MODE) return 0;
switch (aspects.size()) {
case 1:
return 0;
case 2:
return 5;
case 3:
return 9;
case 4:
return 14;
case 5:
return 19;
case 6:
return 25;
case 7:
return 30;
case 8:
return 35;
case 9:
return 40;
case 10:
return 50;
case 11:
return 60;
case 12:
return 70;
case 13:
return 80;
}
return 120;
}
@SuppressWarnings("deprecation")
public void updateCanPvp() {
if (Bukkit.getPlayer(UUID.fromString(mojangId)) == null) return;
// Define variables
final Player player = Bukkit.getPlayer(UUID.fromString(mojangId));
final boolean inNoPvpZone = ZoneUtil.inNoPvpZone(player.getLocation());
if (DGData.BATTLE_R.isInBattle(this)) return;
if (!getCanPvp() && !inNoPvpZone) {
setCanPvp(true);
player.sendMessage(ChatColor.GRAY + "You can now enter in a battle.");
} else if (!inNoPvpZone) {
setCanPvp(true);
DGData.SERVER_R.remove(player.getName(), "pvp_cooldown");
} else if (getCanPvp() && !DGData.SERVER_R.contains(player.getName(), "pvp_cooldown")) {
int delay = 10;
DGData.SERVER_R.put(player.getName(), "pvp_cooldown", true, delay, TimeUnit.SECONDS);
final PlayerModel THIS = this;
Bukkit.getScheduler().scheduleSyncDelayedTask(DGData.PLUGIN, new BukkitRunnable() {
@Override
public void run() {
if (ZoneUtil.inNoPvpZone(player.getLocation())) {
if (DGData.BATTLE_R.isInBattle(THIS)) return;
setCanPvp(false);
player.sendMessage(ChatColor.GRAY + "You are now safe from other players.");
}
}
}, (delay * 20));
}
}
@Deprecated
public void cleanse() {
// Neutral faction
family = Family.NEUTRAL;
// Empty deities
god = Optional.empty();
hero = Optional.empty();
// Reset stats
maxHealth = 20.0;
favor = 700.0;
level = 0;
experience.clear();
// Clear binds and aspects
binds.clear();
aspects.clear();
// Check for demo
if (Setting.DEBUG_DATA) {
handleDemo();
}
// Save
DGData.PLAYER_R.register(this);
}
private void handleDemo() {
// Debug deities
god = Optional.of(Demo.D.LOREM);
// Debug aspects
int roll = RandomUtil.generateIntRange(0, 2);
if (roll == 0) {
hero = Optional.of(Demo.D.IPSUM);
family = Demo.D.IPSUM.getFamilies().get(0);
addAspect(Aspects.BLOODLUST_ASPECT_HERO);
setExperience(Aspects.BLOODLUST_ASPECT_HERO, 1000, false);
} else if (roll == 1) {
hero = Optional.of(Demo.D.DOLOR);
family = Demo.D.DOLOR.getFamilies().get(0);
addAspect(Aspects.MAGNETISM_ASPECT_HERO);
setExperience(Aspects.MAGNETISM_ASPECT_HERO, 5000, false);
} else {
god = Optional.of(Demo.D.SIT);
hero = Optional.of(Demo.D.AMET);
family = Demo.D.AMET.getFamilies().get(0);
addAspect(Aspects.WATER_ASPECT_HERO);
setExperience(Aspects.WATER_ASPECT_HERO, 1000, false);
}
}
}