package com.massivecraft.factions.entity; import java.util.*; import java.util.Map.Entry; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import com.massivecraft.factions.EconomyParticipator; import com.massivecraft.factions.FactionEqualsPredictate; import com.massivecraft.factions.Factions; import com.massivecraft.factions.Lang; import com.massivecraft.factions.Rel; import com.massivecraft.factions.RelationParticipator; import com.massivecraft.factions.util.*; import com.massivecraft.massivecore.mixin.Mixin; import com.massivecraft.massivecore.money.Money; import com.massivecraft.massivecore.ps.PS; import com.massivecraft.massivecore.store.Entity; import com.massivecraft.massivecore.util.IdUtil; import com.massivecraft.massivecore.util.MUtil; import com.massivecraft.massivecore.util.Txt; public class Faction extends Entity implements EconomyParticipator { // -------------------------------------------- // // META // -------------------------------------------- // public static Faction get(Object oid) { return FactionColl.get().get(oid); } // -------------------------------------------- // // OVERRIDE: ENTITY // -------------------------------------------- // @Override public Faction load(Faction that) { this.setName(that.name); this.setDescription(that.description); this.setMotd(that.motd); this.setCreatedAtMillis(that.createdAtMillis); this.setHome(that.home); this.setPowerBoost(that.powerBoost); this.setInvitedPlayerIds(that.invitedPlayerIds); this.setRelationWishes(that.relationWishes); this.setFlagIds(that.flags); this.setPermIds(that.perms); return this; } @Override public void preDetach(String id) { // The database must be fully inited. // We may move factions around during upgrades. if (!Factions.get().isDatabaseInitialized()) return; // Zero balance Money.set(this, null, 0); // Clean the board BoardColl.get().clean(); // Clean the mplayers MPlayerColl.get().clean(); } // -------------------------------------------- // // FIELDS: RAW // -------------------------------------------- // // In this section of the source code we place the field declarations only. // Each field has it's own section further down since just the getter and setter logic takes up quite some place. // The actual faction id looks something like "54947df8-0e9e-4471-a2f9-9af509fb5889" and that is not too easy to remember for humans. // Thus we make use of a name. Since the id is used in all foreign key situations changing the name is fine. // Null should never happen. The name must not be null. private String name = null; // Factions can optionally set a description for themselves. // This description can for example be seen in territorial alerts. // Null means the faction has no description. private String description = null; // Factions can optionally set a message of the day. // This message will be shown when logging on to the server. // Null means the faction has no motd private String motd = null; // We store the creation date for the faction. // It can be displayed on info pages etc. private long createdAtMillis = System.currentTimeMillis(); // Factions can optionally set a home location. // If they do their members can teleport there using /f home // Null means the faction has no home. private PS home = null; // Factions usually do not have a powerboost. It defaults to 0. // The powerBoost is a custom increase/decrease to default and maximum power. // Null means the faction has powerBoost (0). private Double powerBoost = null; // Can anyone join the Faction? // If the faction is open they can. // If the faction is closed an invite is required. // Null means default. // private Boolean open = null; // This is the ids of the invited players. // They are actually "senderIds" since you can invite "@console" to your faction. // Null means no one is invited private Set invitedPlayerIds = null; // The keys in this map are factionIds. // Null means no special relation whishes. private Map relationWishes = null; // The flag overrides are modifications to the default values. // Null means default. private Map flags = null; // The perm overrides are modifications to the default values. // Null means default. private Map> perms = null; // -------------------------------------------- // // FIELD: id // -------------------------------------------- // // FINER public boolean isNone() { return this.getId().equals(Factions.ID_NONE); } public boolean isNormal() { return ! this.isNone(); } // -------------------------------------------- // // FIELD: name // -------------------------------------------- // // RAW public String getName() { String ret = this.name; if (MConf.get().factionNameForceUpperCase) { ret = ret.toUpperCase(); } return ret; } public void setName(String name) { // Clean input String target = name; // Detect Nochange if (MUtil.equals(this.name, target)) return; // Apply this.name = target; // Mark as changed this.changed(); } // FINER public String getComparisonName() { return MiscUtil.getComparisonString(this.getName()); } public String getName(String prefix) { return prefix + this.getName(); } public String getName(RelationParticipator observer) { if (observer == null) return getName(); return this.getName(this.getColorTo(observer).toString()); } // -------------------------------------------- // // FIELD: description // -------------------------------------------- // // RAW public boolean hasDescription() { return this.description != null; } public String getDescription() { if (this.hasDescription()) return this.description; return Lang.FACTION_NODESCRIPTION; } public void setDescription(String description) { // Clean input String target = description; if (target != null) { target = target.trim(); // This code should be kept for a while to clean out the previous default text that was actually stored in the database. if (target.length() == 0 || target.equals("Default faction description :(")) { target = null; } } // Detect Nochange if (MUtil.equals(this.description, target)) return; // Apply this.description = target; // Mark as changed this.changed(); } // -------------------------------------------- // // FIELD: motd // -------------------------------------------- // // RAW public boolean hasMotd() { return this.motd != null; } public String getMotd() { if (this.hasMotd()) return Txt.parse(this.motd); return Lang.FACTION_NOMOTD; } public void setMotd(String description) { // Clean input String target = description; if (target != null) { target = target.trim(); if (target.length() == 0) { target = null; } } // Detect Nochange if (MUtil.equals(this.motd, target)) return; // Apply this.motd = target; // Mark as changed this.changed(); } // FINER public List getMotdMessages() { final String title = Txt.titleize(this.getName() + " - Message of the Day"); final String motd = "" + this.getMotd(); final List messages = Txt.parse(MUtil.list(title, motd)); return messages; } // -------------------------------------------- // // FIELD: createdAtMillis // -------------------------------------------- // public long getCreatedAtMillis() { return this.createdAtMillis; } public void setCreatedAtMillis(long createdAtMillis) { // Clean input long target = createdAtMillis; // Detect Nochange if (MUtil.equals(this.createdAtMillis, createdAtMillis)) return; // Apply this.createdAtMillis = target; // Mark as changed this.changed(); } // -------------------------------------------- // // FIELD: home // -------------------------------------------- // public PS getHome() { this.verifyHomeIsValid(); return this.home; } public void verifyHomeIsValid() { if (this.isValidHome(this.home)) return; this.home = null; msg("Your faction home has been un-set since it is no longer in your territory."); } public boolean isValidHome(PS ps) { if (ps == null) return true; if (!MConf.get().homesMustBeInClaimedTerritory) return true; if (BoardColl.get().getFactionAt(ps) == this) return true; return false; } public boolean hasHome() { return this.getHome() != null; } public void setHome(PS home) { // Clean input PS target = home; // Detect Nochange if (MUtil.equals(this.home, target)) return; // Apply this.home = target; // Mark as changed this.changed(); } // -------------------------------------------- // // FIELD: powerBoost // -------------------------------------------- // // RAW public double getPowerBoost() { Double ret = this.powerBoost; if (ret == null) ret = 0D; return ret; } public void setPowerBoost(Double powerBoost) { // Clean input Double target = powerBoost; if (target == null || target == 0) target = null; // Detect Nochange if (MUtil.equals(this.powerBoost, target)) return; // Apply this.powerBoost = target; // Mark as changed this.changed(); } // -------------------------------------------- // // FIELD: open // -------------------------------------------- // /* public boolean isDefaultOpen() { return MConf.get().defaultFactionOpen; } public boolean isOpen() { Boolean ret = this.open; if (ret == null) ret = this.isDefaultOpen(); return ret; } public void setOpen(Boolean open) { // Clean input Boolean target = open; // Detect Nochange if (MUtil.equals(this.open, target)) return; // Apply this.open = target; // Mark as changed this.changed(); }*/ // -------------------------------------------- // // FIELD: invitedPlayerIds // -------------------------------------------- // // RAW public TreeSet getInvitedPlayerIds() { TreeSet ret = new TreeSet(String.CASE_INSENSITIVE_ORDER); if (this.invitedPlayerIds != null) ret.addAll(this.invitedPlayerIds); return ret; } public void setInvitedPlayerIds(Collection invitedPlayerIds) { // Clean input TreeSet target; if (invitedPlayerIds == null || invitedPlayerIds.isEmpty()) { target = null; } else { target = new TreeSet(String.CASE_INSENSITIVE_ORDER); for (String invitedPlayerId : invitedPlayerIds) { target.add(invitedPlayerId.toLowerCase()); } } // Detect Nochange if (MUtil.equals(this.invitedPlayerIds, target)) return; // Apply this.invitedPlayerIds = target; // Mark as changed this.changed(); } // FINER public boolean isInvited(String playerId) { return this.getInvitedPlayerIds().contains(playerId); } public boolean isInvited(MPlayer mplayer) { return this.isInvited(mplayer.getId()); } public boolean setInvited(String playerId, boolean invited) { TreeSet invitedPlayerIds = this.getInvitedPlayerIds(); boolean ret; if (invited) { ret = invitedPlayerIds.add(playerId.toLowerCase()); } else { ret = invitedPlayerIds.remove(playerId.toLowerCase()); } this.setInvitedPlayerIds(invitedPlayerIds); return ret; } public void setInvited(MPlayer mplayer, boolean invited) { this.setInvited(mplayer.getId(), invited); } // -------------------------------------------- // // FIELD: relationWish // -------------------------------------------- // // RAW public Map getRelationWishes() { Map ret = new LinkedHashMap(); if (this.relationWishes != null) ret.putAll(this.relationWishes); return ret; } public void setRelationWishes(Map relationWishes) { // Clean input Map target; if (relationWishes == null || relationWishes.isEmpty()) { target = null; } else { target = new LinkedHashMap(relationWishes); } // Detect Nochange if (MUtil.equals(this.relationWishes, target)) return; // Apply this.relationWishes = target; // Mark as changed this.changed(); } // FINER public Rel getRelationWish(String factionId) { Rel ret = this.getRelationWishes().get(factionId); if (ret == null) ret = Rel.NEUTRAL; return ret; } public Rel getRelationWish(Faction faction) { return this.getRelationWish(faction.getId()); } public void setRelationWish(String factionId, Rel rel) { Map relationWishes = this.getRelationWishes(); if (rel == null || rel == Rel.NEUTRAL) { relationWishes.remove(factionId); } else { relationWishes.put(factionId, rel); } this.setRelationWishes(relationWishes); } public void setRelationWish(Faction faction, Rel rel) { this.setRelationWish(faction.getId(), rel); } // TODO: What is this and where is it used? public Map> getFactionNamesPerRelation(RelationParticipator rp) { return getFactionNamesPerRelation(rp, false); } // onlyNonNeutral option provides substantial performance boost on large servers for listing only non-neutral factions public Map> getFactionNamesPerRelation(RelationParticipator rp, boolean onlyNonNeutral) { Map> ret = new HashMap>(); for (Rel rel : Rel.values()) { ret.put(rel, new ArrayList()); } for (Faction faction : FactionColl.get().getAll()) { Rel relation = faction.getRelationTo(this); if (onlyNonNeutral && relation == Rel.NEUTRAL) continue; ret.get(relation).add(faction.getName(rp)); } return ret; } // -------------------------------------------- // // FIELD: flagOverrides // -------------------------------------------- // // RAW public Map getFlags() { // We start with default values ... Map ret = new LinkedHashMap(); for (MFlag mflag : MFlag.getAll()) { ret.put(mflag, mflag.isStandard()); } // ... and if anything is explicitly set we use that info ... if (this.flags != null) { Iterator> iter = this.flags.entrySet().iterator(); while (iter.hasNext()) { // ... for each entry ... Entry entry = iter.next(); // ... extract id and remove null values ... String id = entry.getKey(); if (id == null) { iter.remove(); continue; } // ... resolve object and skip unknowns ... MFlag mflag = MFlag.get(id); if (mflag == null) continue; ret.put(mflag, entry.getValue()); } } return ret; } public void setFlagIds(Map flags) { // Clean input Map target = null; if (flags != null) { // We start out with what was suggested target = new LinkedHashMap(flags); // However if the context is fully live we try to throw some default values away. if (this.attached() && Factions.get().isDatabaseInitialized()) { Iterator> iter = target.entrySet().iterator(); while (iter.hasNext()) { // For each entry ... Entry entry = iter.next(); // ... extract id and remove null values ... String id = entry.getKey(); if (id == null) { iter.remove(); continue; } // ... remove if known and standard ... MFlag mflag = MFlag.get(id); if (mflag != null && mflag.isStandard() == entry.getValue()) { iter.remove(); } } if (target.isEmpty()) target = null; } } // Detect Nochange if (MUtil.equals(this.flags, target)) return; // Apply this.flags = target; // Mark as changed this.changed(); } public void setFlags(Map flags) { Map flagIds = new LinkedHashMap(); for (Entry entry : flags.entrySet()) { flagIds.put(entry.getKey().getId(), entry.getValue()); } setFlagIds(flagIds); } // FINER public boolean getFlag(MFlag flag) { return this.getFlags().get(flag); } public void setFlag(MFlag flag, boolean value) { Map flags = this.getFlags(); flags.put(flag, value); this.setFlags(flags); } // -------------------------------------------- // // FIELD: permOverrides // -------------------------------------------- // // RAW public Map> getPerms() { // We start with default values ... Map> ret = new LinkedHashMap>(); for (MPerm mperm : MPerm.getAll()) { ret.put(mperm, new LinkedHashSet(mperm.getStandard())); } // ... and if anything is explicitly set we use that info ... if (this.perms != null) { Iterator>> iter = this.perms.entrySet().iterator(); while (iter.hasNext()) { // ... for each entry ... Entry> entry = iter.next(); // ... extract id and remove null values ... String id = entry.getKey(); if (id == null) { iter.remove(); continue; } // ... resolve object and skip unknowns ... MPerm mperm = MPerm.get(id); if (mperm == null) continue; ret.put(mperm, new LinkedHashSet(entry.getValue())); } } return ret; } public void setPermIds(Map> perms) { // Clean input Map> target = null; if (perms != null) { // We start out with what was suggested target = new LinkedHashMap>(); for (Entry> entry : perms.entrySet()) { target.put(entry.getKey(), new LinkedHashSet(entry.getValue())); } // However if the context is fully live we try to throw some default values away. if (this.attached() && Factions.get().isDatabaseInitialized()) { Iterator>> iter = target.entrySet().iterator(); while (iter.hasNext()) { // For each entry ... Entry> entry = iter.next(); // ... extract id and remove null values ... String id = entry.getKey(); if (id == null) { iter.remove(); continue; } // ... remove if known and standard ... MPerm mperm = MPerm.get(id); if (mperm != null && mperm.getStandard().equals(entry.getValue())) { iter.remove(); } } if (target.isEmpty()) target = null; } } // Detect Nochange if (MUtil.equals(this.perms, target)) return; // Apply this.perms = target; // Mark as changed this.changed(); } public void setPerms(Map> perms) { Map> permIds = new LinkedHashMap>(); for (Entry> entry : perms.entrySet()) { permIds.put(entry.getKey().getId(), entry.getValue()); } setPermIds(permIds); } // FINER public Set getPermittedRelations(MPerm perm) { return this.getPerms().get(perm); } public void setPermittedRelations(MPerm perm, Set rels) { Map> perms = this.getPerms(); perms.put(perm, rels); this.setPerms(perms); } public void setPermittedRelations(MPerm perm, Rel... rels) { Set temp = new HashSet(); temp.addAll(Arrays.asList(rels)); this.setPermittedRelations(perm, temp); } public void setRelationPermitted(MPerm perm, Rel rel, boolean permitted) { Map> perms = this.getPerms(); Set rels = perms.get(perm); if (permitted) { rels.add(rel); } else { rels.remove(rel); } this.setPerms(perms); } // -------------------------------------------- // // OVERRIDE: RelationParticipator // -------------------------------------------- // @Override public String describeTo(RelationParticipator observer, boolean ucfirst) { return RelationUtil.describeThatToMe(this, observer, ucfirst); } @Override public String describeTo(RelationParticipator observer) { return RelationUtil.describeThatToMe(this, observer); } @Override public Rel getRelationTo(RelationParticipator observer) { return RelationUtil.getRelationOfThatToMe(this, observer); } @Override public Rel getRelationTo(RelationParticipator observer, boolean ignorePeaceful) { return RelationUtil.getRelationOfThatToMe(this, observer, ignorePeaceful); } @Override public ChatColor getColorTo(RelationParticipator observer) { return RelationUtil.getColorOfThatToMe(this, observer); } // -------------------------------------------- // // POWER // -------------------------------------------- // // TODO: Implement a has enough feature. public double getPower() { if (this.getFlag(MFlag.getInfpower())) return 999999; double ret = 0; for (MPlayer mplayer : this.getMPlayers()) { ret += mplayer.getPower(); } double factionPowerMax = MConf.get().factionPowerMax; if (factionPowerMax > 0 && ret > factionPowerMax) { ret = factionPowerMax; } ret += this.getPowerBoost(); return ret; } public double getPowerMax() { if (this.getFlag(MFlag.getInfpower())) return 999999; double ret = 0; for (MPlayer mplayer : this.getMPlayers()) { ret += mplayer.getPowerMax(); } double factionPowerMax = MConf.get().factionPowerMax; if (factionPowerMax > 0 && ret > factionPowerMax) { ret = factionPowerMax; } ret += this.getPowerBoost(); return ret; } public int getPowerRounded() { return (int) Math.round(this.getPower()); } public int getPowerMaxRounded() { return (int) Math.round(this.getPowerMax()); } public int getLandCount() { return BoardColl.get().getCount(this); } public int getLandCountInWorld(String worldName) { return Board.get(worldName).getCount(this); } public boolean hasLandInflation() { return this.getLandCount() > this.getPowerRounded(); } // -------------------------------------------- // // FOREIGN KEY: MPLAYER // -------------------------------------------- // protected transient List mplayers = new ArrayList(); public void reindexMPlayers() { this.mplayers.clear(); String factionId = this.getId(); if (factionId == null) return; for (MPlayer mplayer : MPlayerColl.get().getAll()) { if (!MUtil.equals(factionId, mplayer.getFactionId())) continue; this.mplayers.add(mplayer); } } // TODO: Even though this check method removeds the invalid entries it's not a true solution. // TODO: Find the bug causing non-attached MPlayers to be present in the index. private void checkMPlayerIndex() { Iterator iter = this.mplayers.iterator(); while (iter.hasNext()) { MPlayer mplayer = iter.next(); if (!mplayer.attached()) { String msg = Txt.parse("WARN: Faction %s aka %s had unattached mplayer in index:", this.getName(), this.getId()); Factions.get().log(msg); Factions.get().log(Factions.get().gson.toJson(mplayer)); iter.remove(); } } } public List getMPlayers() { this.checkMPlayerIndex(); return new ArrayList(this.mplayers); } public List getMPlayersWhereOnline(boolean online) { List ret = this.getMPlayers(); Iterator iter = ret.iterator(); while (iter.hasNext()) { MPlayer mplayer = iter.next(); if (mplayer.isOnline() != online) { iter.remove(); } } return ret; } public List getMPlayersWhereRole(Rel role) { List ret = this.getMPlayers(); Iterator iter = ret.iterator(); while (iter.hasNext()) { MPlayer mplayer = iter.next(); if (mplayer.getRole() != role) { iter.remove(); } } return ret; } public MPlayer getLeader() { List ret = this.getMPlayers(); Iterator iter = ret.iterator(); while (iter.hasNext()) { MPlayer mplayer = iter.next(); if (mplayer.getRole() == Rel.LEADER) { return mplayer; } } return null; } public List getOnlineCommandSenders() { List ret = new ArrayList(); for (CommandSender player : IdUtil.getOnlineSenders()) { MPlayer mplayer = MPlayer.get(player); if (mplayer.getFaction() != this) continue; ret.add(player); } return ret; } public List getOnlinePlayers() { List ret = new ArrayList(); for (Player player : Bukkit.getOnlinePlayers()) { MPlayer mplayer = MPlayer.get(player); if (mplayer.getFaction() != this) continue; ret.add(player); } return ret; } // used when current leader is about to be removed from the faction; promotes new leader, or disbands faction if no other members left public void promoteNewLeader() { if ( ! this.isNormal()) return; if (this.getFlag(MFlag.getPermanent()) && MConf.get().permanentFactionsDisableLeaderPromotion) return; MPlayer oldLeader = this.getLeader(); // get list of officers, or list of normal members if there are no officers List replacements = this.getMPlayersWhereRole(Rel.OFFICER); if (replacements == null || replacements.isEmpty()) { replacements = this.getMPlayersWhereRole(Rel.MEMBER); } if (replacements == null || replacements.isEmpty()) { // faction leader is the only member; one-man faction if (this.getFlag(MFlag.getPermanent())) { if (oldLeader != null) { // TODO: Where is the logic in this? Why MEMBER? Why not LEADER again? And why not OFFICER or RECRUIT? oldLeader.setRole(Rel.MEMBER); } return; } // no members left and faction isn't permanent, so disband it if (MConf.get().logFactionDisband) { Factions.get().log("The faction "+this.getName()+" ("+this.getId()+") has been disbanded since it has no members left."); } for (MPlayer mplayer : MPlayerColl.get().getAllOnline()) { mplayer.msg("The faction %s was disbanded.", this.getName(mplayer)); } this.detach(); } else { // promote new faction leader if (oldLeader != null) { oldLeader.setRole(Rel.MEMBER); } replacements.get(0).setRole(Rel.LEADER); this.msg("Faction leader %s has been removed. %s has been promoted as the new faction leader.", oldLeader == null ? "" : oldLeader.getName(), replacements.get(0).getName()); Factions.get().log("Faction "+this.getName()+" ("+this.getId()+") leader was removed. Replacement leader: "+replacements.get(0).getName()); } } // -------------------------------------------- // // FACTION ONLINE STATE // -------------------------------------------- // public boolean isAllMPlayersOffline() { return this.getMPlayersWhereOnline(true).size() == 0; } public boolean isAnyMPlayersOnline() { return !this.isAllMPlayersOffline(); } public boolean isFactionConsideredOffline() { return this.isAllMPlayersOffline(); } public boolean isFactionConsideredOnline() { return !this.isFactionConsideredOffline(); } public boolean isExplosionsAllowed() { boolean explosions = this.getFlag(MFlag.getExplosions()); boolean offlineexplosions = this.getFlag(MFlag.getOfflineexplosions()); boolean online = this.isFactionConsideredOnline(); return (online && explosions) || (!online && offlineexplosions); } // -------------------------------------------- // // MESSAGES // -------------------------------------------- // // These methods are simply proxied in from the Mixin. // CONVENIENCE SEND MESSAGE public boolean sendMessage(String message) { return Mixin.messagePredictate(new FactionEqualsPredictate(this), message); } public boolean sendMessage(String... messages) { return Mixin.messagePredictate(new FactionEqualsPredictate(this), messages); } public boolean sendMessage(Collection messages) { return Mixin.messagePredictate(new FactionEqualsPredictate(this), messages); } // CONVENIENCE MSG public boolean msg(String msg) { return Mixin.msgPredictate(new FactionEqualsPredictate(this), msg); } public boolean msg(String msg, Object... args) { return Mixin.msgPredictate(new FactionEqualsPredictate(this), msg, args); } public boolean msg(Collection msgs) { return Mixin.msgPredictate(new FactionEqualsPredictate(this), msgs); } }