Moved Faction entity to useage of MStore as well.

This commit is contained in:
Olof Larsson 2013-04-12 09:47:43 +02:00
parent 76f3f044ca
commit 3036b0a157
24 changed files with 265 additions and 904 deletions

View File

@ -164,7 +164,8 @@ public class Board extends Entity<Board, String> implements BoardInterface
for (Entry<PS, TerritoryAccess> entry : this.map.entrySet())
{
TerritoryAccess territoryAccess = entry.getValue();
if (FactionColl.i.exists(territoryAccess.getHostFactionId())) continue;
if (FactionColl.get().containsId(territoryAccess.getHostFactionId())) continue;
PS ps = entry.getKey();
this.removeAt(ps);

View File

@ -17,14 +17,17 @@ public class Const
public static final String COLLECTION_BASENAME_PLAYER = COLLECTION_BASENAME_+"player";
public static final String COLLECTION_BASENAME_FACTION = COLLECTION_BASENAME_+"faction";
// ASCII Map
// Defautlt faction ids
public static final String FACTIONID_NONE = "0";
public static final String FACTIONID_SAFEZONE = "-1";
public static final String FACTIONID_WARZONE = "-2";
// ASCII Map
public static final int MAP_HEIGHT = 8;
public static final int MAP_WIDTH = 39;
public static final char[] MAP_KEY_CHARS = "\\/#?$%=&^ABCDEFGHJKLMNOPQRSTUVWXYZ1234567890abcdeghjmnopqrsuvwxyz".toCharArray();
// Enumerations
public static final Set<Material> MATERIALS_EDIT_ON_INTERACT = MUtil.set(
Material.DIODE_BLOCK_OFF,
Material.DIODE_BLOCK_ON,

View File

@ -66,7 +66,7 @@ public class FPlayer extends SenderEntity<FPlayer> implements EconomyParticipato
// FIELD: factionId
private String factionId;
public Faction getFaction() { if(this.factionId == null) {return null;} return FactionColl.i.get(this.factionId); }
public Faction getFaction() { if(this.factionId == null) {return null;} return FactionColl.get().get(this.factionId); }
public String getFactionId() { return this.factionId; }
public boolean hasFaction() { return this.factionId != null && ! factionId.equals("0"); }
public void setFaction(Faction faction)
@ -149,7 +149,7 @@ public class FPlayer extends SenderEntity<FPlayer> implements EconomyParticipato
this.loginPvpDisabled = (ConfServer.noPVPDamageToOthersForXSecondsAfterLogin > 0) ? true : false;
this.powerBoost = 0.0;
if ( ! ConfServer.newPlayerStartingFactionID.equals("0") && FactionColl.i.exists(ConfServer.newPlayerStartingFactionID))
if ( ! ConfServer.newPlayerStartingFactionID.equals("0") && FactionColl.get().containsId(ConfServer.newPlayerStartingFactionID))
{
this.factionId = ConfServer.newPlayerStartingFactionID;
}
@ -157,7 +157,7 @@ public class FPlayer extends SenderEntity<FPlayer> implements EconomyParticipato
public final void resetFactionData(boolean doSpoutUpdate)
{
if (this.factionId != null && FactionColl.i.exists(this.factionId)) // Avoid infinite loop! TODO: I think that this is needed is a sign we need to refactor.
if (this.factionId != null && FactionColl.get().containsId(this.factionId)) // Avoid infinite loop! TODO: I think that this is needed is a sign we need to refactor.
{
Faction currentFaction = this.getFaction();
if (currentFaction != null)

View File

@ -24,7 +24,7 @@ public class FPlayerColl extends SenderColl<FPlayer>
}
// -------------------------------------------- //
// OVERRIDE
// OVERRIDE: COLL
// -------------------------------------------- //
// TODO: Init and migration routine!
@ -70,11 +70,10 @@ public class FPlayerColl extends SenderColl<FPlayer>
{
for (FPlayer fplayer : this.getAll())
{
if ( ! FactionColl.i.exists(fplayer.getFactionId()))
{
Factions.get().log("Reset faction data (invalid faction) for player "+fplayer.getName());
fplayer.resetFactionData(false);
}
if (FactionColl.get().containsId(fplayer.getFactionId())) continue;
Factions.get().log("Reset faction data (invalid faction) for player "+fplayer.getName());
fplayer.resetFactionData(false);
}
}

View File

@ -11,28 +11,28 @@ import com.massivecraft.factions.iface.RelationParticipator;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.integration.SpoutFeatures;
import com.massivecraft.factions.util.*;
import com.massivecraft.factions.zcore.persist.Entity;
import com.massivecraft.mcore.ps.PS;
import com.massivecraft.mcore.store.Entity;
import com.massivecraft.mcore.util.Txt;
import com.massivecraft.mcore.xlib.gson.annotations.SerializedName;
public class Faction extends Entity implements EconomyParticipator
public class Faction extends Entity<Faction, String> implements EconomyParticipator
{
// -------------------------------------------- //
// META
// -------------------------------------------- //
/*public static Faction get(Object oid)
public static Faction get(Object oid)
{
return FactionColl.get().get(oid);
}*/
}
// -------------------------------------------- //
// OVERRIDE: ENTITY
// -------------------------------------------- //
/*@Override
@Override
public Faction load(Faction that)
{
this.relationWish = that.relationWish;
@ -47,26 +47,6 @@ public class Faction extends Entity implements EconomyParticipator
this.permOverrides = that.permOverrides;
return this;
}*/
// -------------------------------------------- //
// Persistance and entity management
// -------------------------------------------- //
@Override
public void postDetach()
{
if (Econ.shouldBeUsed())
{
Econ.setBalance(getAccountId(), 0);
}
// Clean the board
// TODO: Use events for this instead
BoardColl.get().clean();
// Clean the fplayers
FPlayerColl.get().clean();
}
// -------------------------------------------- //
@ -411,7 +391,7 @@ public class Faction extends Entity implements EconomyParticipator
{
ret.put(rel, new ArrayList<String>());
}
for (Faction faction : FactionColl.i.get())
for (Faction faction : FactionColl.get().getAll())
{
Rel relation = faction.getRelationTo(this);
if (onlyNonNeutral && relation == Rel.NEUTRAL) continue;

View File

@ -3,29 +3,129 @@ package com.massivecraft.factions;
import java.io.File;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.Map.Entry;
import org.bukkit.ChatColor;
import com.massivecraft.mcore.store.Coll;
import com.massivecraft.mcore.store.MStore;
import com.massivecraft.mcore.util.DiscUtil;
import com.massivecraft.mcore.util.Txt;
import com.massivecraft.mcore.xlib.gson.reflect.TypeToken;
import com.massivecraft.factions.integration.Econ;
import com.massivecraft.factions.util.MiscUtil;
import com.massivecraft.factions.zcore.persist.EntityCollection;
public class FactionColl extends EntityCollection<Faction>
public class FactionColl extends Coll<Faction, String>
{
// -------------------------------------------- //
// INSTANCE & CONSTRUCT
// -------------------------------------------- //
public static FactionColl i = new FactionColl();
// public static FactionColl get() { return i; }
// TODO: Crap, does not work because of the instance method colliding.
private static FactionColl i = new FactionColl();
public static FactionColl get() { return i; }
private FactionColl()
{
super(MStore.getDb(ConfServer.dburi), Factions.get(), "ai", Const.COLLECTION_BASENAME_FACTION, Faction.class, String.class, false);
}
// -------------------------------------------- //
// OVERRIDE: COLL
// -------------------------------------------- //
@Override
public void init()
{
super.init();
this.migrate();
this.createDefaultFactions();
// TODO: Refactor and fix with the member-index.
// populate all faction player lists
// or as you also could describe it: Reindex of Members
for (Faction faction : this.getAll())
{
faction.refreshFPlayers();
}
}
// TODO: REMEMBER: Deciding on the next AI value is part of the migration routine.
public void migrate()
{
// Create file objects
File oldFile = new File(Factions.get().getDataFolder(), "factions.json");
File newFile = new File(Factions.get().getDataFolder(), "factions.json.migrated");
// Already migrated?
if ( ! oldFile.exists()) return;
// Read the file content through GSON.
Type type = new TypeToken<Map<String, Faction>>(){}.getType();
Map<String, Faction> id2faction = Factions.get().gson.fromJson(DiscUtil.readCatch(oldFile), type);
// Set the data
for (Entry<String, Faction> entry : id2faction.entrySet())
{
String factionId = entry.getKey();
Faction faction = entry.getValue();
FactionColl.get().create(factionId).load(faction);
}
// Mark as migrated
oldFile.renameTo(newFile);
}
@Override
public Faction detachId(Object oid)
{
String accountId = this.get(oid).getAccountId();
Faction ret = super.detachId(oid);
if (Econ.shouldBeUsed())
{
Econ.setBalance(accountId, 0);
}
// Clean the board
// TODO: Use events for this instead?
BoardColl.get().clean();
// Clean the fplayers
FPlayerColl.get().clean();
return ret;
}
// -------------------------------------------- //
// GET
// -------------------------------------------- //
// TODO: I hope this one is not crucial anymore.
// If it turns out to be I will just have to recreate the feature in the proper place.
/*
@Override
public Faction get(String id)
{
if ( ! this.exists(id))
{
Factions.get().log(Level.WARNING, "Non existing factionId "+id+" requested! Issuing cleaning!");
BoardColl.get().clean();
FPlayerColl.get().clean();
}
return super.get(id);
}
*/
public Faction getNone()
{
return this.get(Const.FACTIONID_NONE);
}
/*
private FactionColl()
{
super
@ -44,165 +144,12 @@ public class FactionColl extends EntityCollection<Faction>
return new TypeToken<Map<String, Faction>>(){}.getType();
}
@Override
public boolean loadFromDisc()
{
if ( ! super.loadFromDisc()) return false;
// -------------------------------------------- //
// Create Default Special Factions
// -------------------------------------------- //
if ( ! this.exists("0"))
{
Faction faction = this.create("0");
faction.setTag(ChatColor.DARK_GREEN+"Wilderness");
faction.setDescription("");
setFlagsForWilderness(faction);
}
if ( ! this.exists("-1"))
{
Faction faction = this.create("-1");
faction.setTag("SafeZone");
faction.setDescription("Free from PVP and monsters");
setFlagsForSafeZone(faction);
}
if ( ! this.exists("-2"))
{
Faction faction = this.create("-2");
faction.setTag("WarZone");
faction.setDescription("Not the safest place to be");
setFlagsForWarZone(faction);
}
// -------------------------------------------- //
// Fix From Old Formats
// -------------------------------------------- //
Faction wild = this.get("0");
Faction safeZone = this.get("-1");
Faction warZone = this.get("-2");
// Remove troublesome " " from old pre-1.6.0 names
if (safeZone != null && safeZone.getTag().contains(" "))
safeZone.setTag("SafeZone");
if (warZone != null && warZone.getTag().contains(" "))
warZone.setTag("WarZone");
// Set Flags if they are not set already.
if (wild != null && ! wild.getFlag(FFlag.PERMANENT))
setFlagsForWilderness(wild);
if (safeZone != null && ! safeZone.getFlag(FFlag.PERMANENT))
setFlagsForSafeZone(safeZone);
if (warZone != null && ! warZone.getFlag(FFlag.PERMANENT))
setFlagsForWarZone(warZone);
// populate all faction player lists
for (Faction faction : i.get())
{
faction.refreshFPlayers();
}
return true;
}
*/
// -------------------------------------------- //
// FLAG SETTERS
// -------------------------------------------- //
public static void setFlagsForWilderness(Faction faction)
{
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, false);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, true);
faction.setFlag(FFlag.PVP, true);
faction.setFlag(FFlag.FRIENDLYFIRE, false);
faction.setFlag(FFlag.MONSTERS, true);
faction.setFlag(FFlag.EXPLOSIONS, true);
faction.setFlag(FFlag.FIRESPREAD, true);
//faction.setFlag(FFlag.LIGHTNING, true);
faction.setFlag(FFlag.ENDERGRIEF, true);
faction.setPermittedRelations(FPerm.BUILD, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
}
public static void setFlagsForSafeZone(Faction faction)
{
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, true);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, false);
faction.setFlag(FFlag.PVP, false);
faction.setFlag(FFlag.FRIENDLYFIRE, false);
faction.setFlag(FFlag.MONSTERS, false);
faction.setFlag(FFlag.EXPLOSIONS, false);
faction.setFlag(FFlag.FIRESPREAD, false);
//faction.setFlag(FFlag.LIGHTNING, false);
faction.setFlag(FFlag.ENDERGRIEF, false);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.TERRITORY, Rel.LEADER, Rel.OFFICER, Rel.MEMBER);
}
public static void setFlagsForWarZone(Faction faction)
{
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, true);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, true);
faction.setFlag(FFlag.PVP, true);
faction.setFlag(FFlag.FRIENDLYFIRE, true);
faction.setFlag(FFlag.MONSTERS, true);
faction.setFlag(FFlag.EXPLOSIONS, true);
faction.setFlag(FFlag.FIRESPREAD, true);
//faction.setFlag(FFlag.LIGHTNING, true);
faction.setFlag(FFlag.ENDERGRIEF, true);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.TERRITORY, Rel.LEADER, Rel.OFFICER, Rel.MEMBER);
}
// -------------------------------------------- //
// GET
// -------------------------------------------- //
@Override
public Faction get(String id)
{
if ( ! this.exists(id))
{
Factions.get().log(Level.WARNING, "Non existing factionId "+id+" requested! Issuing cleaning!");
BoardColl.get().clean();
FPlayerColl.get().clean();
}
return super.get(id);
}
public Faction getNone()
{
return this.get("0");
}
// -------------------------------------------- //
// Faction tag
// FACTION TAG
// -------------------------------------------- //
public static ArrayList<String> validateTag(String str)
@ -233,7 +180,7 @@ public class FactionColl extends EntityCollection<Faction>
public Faction getByTag(String str)
{
String compStr = MiscUtil.getComparisonString(str);
for (Faction faction : this.get())
for (Faction faction : this.getAll())
{
if (faction.getComparisonTag().equals(compStr))
{
@ -248,7 +195,7 @@ public class FactionColl extends EntityCollection<Faction>
Map<String, Faction> tag2faction = new HashMap<String, Faction>();
// TODO: Slow index building
for (Faction faction : this.get())
for (Faction faction : this.getAll())
{
tag2faction.put(ChatColor.stripColor(faction.getTag()), faction);
}
@ -268,7 +215,7 @@ public class FactionColl extends EntityCollection<Faction>
if ( ! Econ.shouldBeUsed()) return;
Factions.get().log("Running econLandRewardRoutine...");
for (Faction faction : this.get())
for (Faction faction : this.getAll())
{
int landCount = faction.getLandRounded();
if (!faction.getFlag(FFlag.PEACEFUL) && landCount > 0)
@ -283,5 +230,103 @@ public class FactionColl extends EntityCollection<Faction>
}
}
}
// -------------------------------------------- //
// CREATE DEFAULT FACTIONS
// -------------------------------------------- //
public void createDefaultFactions()
{
this.createNoneFaction();
this.createSafeZoneFaction();
this.createWarZoneFaction();
}
public void createNoneFaction()
{
if (this.containsId(Const.FACTIONID_NONE)) return;
Faction faction = this.create(Const.FACTIONID_NONE);
faction.setTag(ChatColor.DARK_GREEN+"Wilderness");
faction.setDescription("");
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, false);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, true);
faction.setFlag(FFlag.PVP, true);
faction.setFlag(FFlag.FRIENDLYFIRE, false);
faction.setFlag(FFlag.MONSTERS, true);
faction.setFlag(FFlag.EXPLOSIONS, true);
faction.setFlag(FFlag.FIRESPREAD, true);
//faction.setFlag(FFlag.LIGHTNING, true);
faction.setFlag(FFlag.ENDERGRIEF, true);
faction.setPermittedRelations(FPerm.BUILD, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
}
public void createSafeZoneFaction()
{
if (this.containsId(Const.FACTIONID_SAFEZONE)) return;
Faction faction = this.create(Const.FACTIONID_SAFEZONE);
faction.setTag("SafeZone");
faction.setDescription("Free from PVP and monsters");
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, true);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, false);
faction.setFlag(FFlag.PVP, false);
faction.setFlag(FFlag.FRIENDLYFIRE, false);
faction.setFlag(FFlag.MONSTERS, false);
faction.setFlag(FFlag.EXPLOSIONS, false);
faction.setFlag(FFlag.FIRESPREAD, false);
//faction.setFlag(FFlag.LIGHTNING, false);
faction.setFlag(FFlag.ENDERGRIEF, false);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.TERRITORY, Rel.LEADER, Rel.OFFICER, Rel.MEMBER);
}
public void createWarZoneFaction()
{
if (this.containsId(Const.FACTIONID_WARZONE)) return;
Faction faction = this.create(Const.FACTIONID_WARZONE);
faction.setTag("WarZone");
faction.setDescription("Not the safest place to be");
faction.setOpen(false);
faction.setFlag(FFlag.PERMANENT, true);
faction.setFlag(FFlag.PEACEFUL, true);
faction.setFlag(FFlag.INFPOWER, true);
faction.setFlag(FFlag.POWERLOSS, true);
faction.setFlag(FFlag.PVP, true);
faction.setFlag(FFlag.FRIENDLYFIRE, true);
faction.setFlag(FFlag.MONSTERS, true);
faction.setFlag(FFlag.EXPLOSIONS, true);
faction.setFlag(FFlag.FIRESPREAD, true);
//faction.setFlag(FFlag.LIGHTNING, true);
faction.setFlag(FFlag.ENDERGRIEF, true);
faction.setPermittedRelations(FPerm.DOOR, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.CONTAINER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.BUTTON, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.LEVER, Rel.LEADER, Rel.OFFICER, Rel.MEMBER, Rel.RECRUIT, Rel.ALLY, Rel.TRUCE, Rel.NEUTRAL, Rel.ENEMY);
faction.setPermittedRelations(FPerm.TERRITORY, Rel.LEADER, Rel.OFFICER, Rel.MEMBER);
}
}

View File

@ -80,7 +80,7 @@ public class Factions extends MPlugin
// Load Conf from disk
FPlayerColl.get().init();
FactionColl.i.loadFromDisc();
FactionColl.get().init();
BoardColl.get().init();
// Add Base Commands

View File

@ -13,7 +13,7 @@ public class TerritoryAccess
private String hostFactionId;
public String getHostFactionId() { return this.hostFactionId; }
public Faction getHostFaction() { return FactionColl.i.get(this.hostFactionId); }
public Faction getHostFaction() { return FactionColl.get().get(this.hostFactionId); }
public void setHostFactionId(String hostFactionId) { this.hostFactionId = hostFactionId; }
private boolean hostFactionAllowed = true;
@ -132,7 +132,7 @@ public class TerritoryAccess
{
if (list.length() > 0)
list.append(", ");
list.append(FactionColl.i.get(factionID).getTag());
list.append(FactionColl.get().get(factionID).getTag());
}
return list.toString();
}

View File

@ -44,7 +44,7 @@ public class CmdFactionsCreate extends FCommand
return;
}
if (FactionColl.i.isTagTaken(tag))
if (FactionColl.get().isTagTaken(tag))
{
msg("<b>That tag is already in use.");
return;
@ -61,14 +61,16 @@ public class CmdFactionsCreate extends FCommand
if ( ! canAffordCommand(ConfServer.econCostCreate, "to create a new faction")) return;
// trigger the faction creation event (cancellable)
FactionCreateEvent createEvent = new FactionCreateEvent(me, tag);
String factionId = FactionColl.get().getIdStrategy().generate(FactionColl.get());
FactionCreateEvent createEvent = new FactionCreateEvent(me, tag, factionId);
Bukkit.getServer().getPluginManager().callEvent(createEvent);
if(createEvent.isCancelled()) return;
// then make 'em pay (if applicable)
if ( ! payForCommand(ConfServer.econCostCreate, "to create a new faction", "for creating a new faction")) return;
Faction faction = FactionColl.i.create();
Faction faction = FactionColl.get().create(factionId);
// TODO: Why would this even happen??? Auto increment clash??
if (faction == null)

View File

@ -37,9 +37,9 @@ public class CmdFactionsList extends FCommand
// if economy is enabled, they're not on the bypass list, and this command has a cost set, make 'em pay
if ( ! payForCommand(ConfServer.econCostList, "to list the factions", "for listing the factions")) return;
ArrayList<Faction> factionList = new ArrayList<Faction>(FactionColl.i.get());
ArrayList<Faction> factionList = new ArrayList<Faction>(FactionColl.get().getAll());
factionList.remove(FactionColl.i.getNone());
factionList.remove(FactionColl.get().getNone());
// TODO: Add flag SECRET To factions instead.
//factionList.remove(Factions.i.getSafeZone());
//factionList.remove(Factions.i.getWarZone());
@ -91,7 +91,7 @@ public class CmdFactionsList extends FCommand
sendMessage(Txt.getPage(lines, this.argAsInt(0, 1), "Faction List"));
*/
factionList.add(0, FactionColl.i.getNone());
factionList.add(0, FactionColl.get().getNone());
final int pageheight = 9;
int pagenumber = this.argAsInt(0, 1);
@ -111,7 +111,7 @@ public class CmdFactionsList extends FCommand
{
if (faction.isNone())
{
lines.add(Txt.parse("<i>Factionless<i> %d online", FactionColl.i.getNone().getFPlayersWhereOnline(true).size()));
lines.add(Txt.parse("<i>Factionless<i> %d online", FactionColl.get().getNone().getFPlayersWhereOnline(true).size()));
continue;
}
lines.add(Txt.parse("%s<i> %d/%d online, %d/%d/%d",

View File

@ -35,7 +35,7 @@ public class CmdFactionsOpen extends FCommand
// Inform
myFaction.msg("%s<i> changed the faction to <h>%s<i>.", fme.describeTo(myFaction, true), open);
for (Faction faction : FactionColl.i.get())
for (Faction faction : FactionColl.get().getAll())
{
if (faction == myFaction)
{

View File

@ -36,7 +36,7 @@ public class CmdFactionsTag extends FCommand
String tag = this.argAsString(0);
// TODO does not first test cover selfcase?
if (FactionColl.i.isTagTaken(tag) && ! MiscUtil.getComparisonString(tag).equals(myFaction.getComparisonTag()))
if (FactionColl.get().isTagTaken(tag) && ! MiscUtil.getComparisonString(tag).equals(myFaction.getComparisonTag()))
{
msg("<b>That tag is already taken");
return;
@ -66,7 +66,7 @@ public class CmdFactionsTag extends FCommand
// Inform
myFaction.msg("%s<i> changed your faction tag to %s", fme.describeTo(myFaction, true), myFaction.getTag(myFaction));
for (Faction faction : FactionColl.i.get())
for (Faction faction : FactionColl.get().getAll())
{
if (faction == myFaction)
{

View File

@ -229,13 +229,13 @@ public abstract class FCommand extends MCommand<Factions>
// First we try an exact match
if (faction == null)
{
faction = FactionColl.i.getByTag(name);
faction = FactionColl.get().getByTag(name);
}
// Next we match faction tags
if (faction == null)
{
faction = FactionColl.i.getBestTagMatch(name);
faction = FactionColl.get().getBestTagMatch(name);
}
// Next we match player names

View File

@ -1,13 +1,10 @@
package com.massivecraft.factions.event;
import org.bukkit.entity.Player;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.FactionColl;
public class FactionCreateEvent extends Event implements Cancellable
{
// -------------------------------------------- //
@ -22,36 +19,30 @@ public class FactionCreateEvent extends Event implements Cancellable
// FIELDS
// -------------------------------------------- //
private boolean cancelled;
private boolean cancelled = false;
@Override public boolean isCancelled() { return this.cancelled; }
@Override public void setCancelled(boolean cancelled) { this.cancelled = cancelled; }
// TODO: Could the fields be reorganized to achieve symmetry?
private final CommandSender sender;
public CommandSender getSender() { return this.sender; }
// TODO: How do we know what universe? Should we perhaps actually create the faction?
private String factionTag;
public String getFactionTag() { return this.factionTag; }
private Player sender;
public FPlayer getFPlayer()
{
return FPlayer.get(this.sender);
}
public String getFactionId()
{
return FactionColl.i.getNextId();
}
private String factionId;
public String getFactionId() { return this.factionId; }
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FactionCreateEvent(Player sender, String tag)
public FactionCreateEvent(CommandSender sender, String factionTag, String factionId)
{
this.cancelled = false;
this.factionTag = tag;
this.sender = sender;
this.factionTag = factionTag;
this.factionId = factionId;
}
}

View File

@ -34,7 +34,7 @@ public class FactionDisbandEvent extends Event implements Cancellable
public Faction getFaction()
{
return FactionColl.i.get(id);
return FactionColl.get().get(id);
}
public FPlayer getFPlayer()

View File

@ -16,7 +16,7 @@ public class EconLandRewardTask implements Runnable
@Override
public void run()
{
FactionColl.i.econLandRewardRoutine();
FactionColl.get().econLandRewardRoutine();
// TODO: This technique is TPS dependent and wrong.
// Instead of restarting a TPS dependent task the task should poll every once in a while for the system millis.

View File

@ -6,8 +6,6 @@ import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import com.massivecraft.factions.zcore.persist.EM;
import com.massivecraft.factions.zcore.persist.SaveTask;
import com.massivecraft.mcore.util.Txt;
import com.massivecraft.mcore.xlib.gson.Gson;
import com.massivecraft.mcore.xlib.gson.GsonBuilder;
@ -17,14 +15,6 @@ public abstract class MPlugin extends JavaPlugin
{
// Persist related
public Gson gson;
private Integer saveTask = null;
private boolean autoSave = true;
protected boolean loadSuccessful = false;
public boolean getAutoSave() {return this.autoSave;}
public void setAutoSave(boolean val) {this.autoSave = val;}
// Listeners
public MPluginSecretPlayerListener mPluginSecretPlayerListener;
// -------------------------------------------- //
// ENABLE
@ -39,18 +29,7 @@ public abstract class MPlugin extends JavaPlugin
this.getDataFolder().mkdirs();
this.gson = this.getGsonBuilder().create();
// Create and register listeners
this.mPluginSecretPlayerListener = new MPluginSecretPlayerListener(this);
// Register recurring tasks
long saveTicks = 20 * 60 * 30; // Approximately every 30 min
if (saveTask == null)
{
saveTask = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, new SaveTask(this), saveTicks, saveTicks);
}
loadSuccessful = true;
return true;
}
@ -61,14 +40,6 @@ public abstract class MPlugin extends JavaPlugin
public void onDisable()
{
if (saveTask != null)
{
this.getServer().getScheduler().cancelTask(saveTask);
saveTask = null;
}
// only save data if plugin actually loaded successfully
if (loadSuccessful)
EM.saveAllToDisc();
log("Disabled");
}
@ -93,56 +64,6 @@ public abstract class MPlugin extends JavaPlugin
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.VOLATILE);
}
// -------------------------------------------- //
// LANG AND TAGS
// -------------------------------------------- //
/*
// These are not supposed to be used directly.
// They are loaded and used through the TextUtil instance for the plugin.
public Map<String, String> rawTags = new LinkedHashMap<String, String>();
public void addRawTags()
{
this.rawTags.put("l", "<green>"); // logo
this.rawTags.put("a", "<gold>"); // art
this.rawTags.put("n", "<silver>"); // notice
this.rawTags.put("i", "<yellow>"); // info
this.rawTags.put("g", "<lime>"); // good
this.rawTags.put("b", "<rose>"); // bad
this.rawTags.put("h", "<pink>"); // highligh
this.rawTags.put("c", "<aqua>"); // command
this.rawTags.put("p", "<teal>"); // parameter
}
public void initTXT()
{
this.addRawTags();
Type type = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> tagsFromFile = this.persist.load(type, "tags");
if (tagsFromFile != null) this.rawTags.putAll(tagsFromFile);
this.persist.save(this.rawTags, "tags");
for (Entry<String, String> rawTag : this.rawTags.entrySet())
{
this.txt.tags.put(rawTag.getKey(), TextUtil.parseColor(rawTag.getValue()));
}
}*/
// -------------------------------------------- //
// HOOKS
// -------------------------------------------- //
public void preAutoSave()
{
}
public void postAutoSave()
{
}
// -------------------------------------------- //
// LOGGING
// -------------------------------------------- //

View File

@ -1,34 +0,0 @@
package com.massivecraft.factions.zcore;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import com.massivecraft.factions.zcore.persist.EM;
import com.massivecraft.factions.zcore.persist.Entity;
import com.massivecraft.factions.zcore.persist.EntityCollection;
import com.massivecraft.factions.zcore.persist.PlayerEntityCollection;
public class MPluginSecretPlayerListener implements Listener
{
public MPlugin p;
public MPluginSecretPlayerListener(MPlugin p)
{
this.p = p;
Bukkit.getPluginManager().registerEvents(this, this.p);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerPreLogin(PlayerLoginEvent event)
{
for (EntityCollection<? extends Entity> ecoll : EM.class2Entities.values())
{
if (ecoll instanceof PlayerEntityCollection)
{
ecoll.get(event.getPlayer().getName());
}
}
}
}

View File

@ -1,74 +0,0 @@
package com.massivecraft.factions.zcore.persist;
import java.util.*;
import com.massivecraft.factions.zcore.persist.Entity;
import com.massivecraft.factions.zcore.persist.EntityCollection;
public class EM
{
public static Map<Class<? extends Entity>, EntityCollection<? extends Entity>> class2Entities = new LinkedHashMap<Class<? extends Entity>, EntityCollection<? extends Entity>>();
@SuppressWarnings("unchecked")
public static <T extends Entity> EntityCollection<T> getEntitiesCollectionForEntityClass(Class<T> entityClass)
{
return (EntityCollection<T>) class2Entities.get(entityClass);
}
public static void setEntitiesCollectionForEntityClass(Class<? extends Entity> entityClass, EntityCollection<? extends Entity> entities)
{
class2Entities.put(entityClass, entities);
}
// -------------------------------------------- //
// ATTACH AND DETACH
// -------------------------------------------- //
@SuppressWarnings("unchecked")
public static <T extends Entity> void attach(T entity)
{
EntityCollection<T> ec = (EntityCollection<T>) getEntitiesCollectionForEntityClass(entity.getClass());
ec.attach(entity);
}
@SuppressWarnings("unchecked")
public static <T extends Entity> void detach(T entity)
{
EntityCollection<T> ec = (EntityCollection<T>) getEntitiesCollectionForEntityClass(entity.getClass());
ec.detach(entity);
}
@SuppressWarnings("unchecked")
public static <T extends Entity> boolean attached(T entity)
{
EntityCollection<T> ec = (EntityCollection<T>) getEntitiesCollectionForEntityClass(entity.getClass());
return ec.attached(entity);
}
@SuppressWarnings("unchecked")
public static <T extends Entity> boolean detached(T entity)
{
EntityCollection<T> ec = (EntityCollection<T>) getEntitiesCollectionForEntityClass(entity.getClass());
return ec.detached(entity);
}
// -------------------------------------------- //
// DISC
// -------------------------------------------- //
public static void saveAllToDisc()
{
for (EntityCollection<? extends Entity> ec : class2Entities.values())
{
ec.saveToDisc();
}
}
public static void loadAllFromDisc()
{
for (EntityCollection<? extends Entity> ec : class2Entities.values())
{
ec.loadFromDisc();
}
}
}

View File

@ -1,65 +0,0 @@
package com.massivecraft.factions.zcore.persist;
public abstract class Entity
{
public Entity()
{
}
protected transient String id = null;
public String getId()
{
return id;
}
protected void setId(String id)
{
this.id = id;
}
public boolean shouldBeSaved()
{
return true;
}
// -------------------------------------------- //
// ATTACH AND DETACH
// -------------------------------------------- //
public void attach()
{
EM.attach(this);
}
public void detach()
{
EM.detach(this);
}
public boolean attached()
{
return EM.attached(this);
}
public boolean detached()
{
return EM.detached(this);
}
// -------------------------------------------- //
// EVENTS
// -------------------------------------------- //
public void preDetach()
{
}
public void postDetach()
{
}
}

View File

@ -1,292 +0,0 @@
package com.massivecraft.factions.zcore.persist;
import java.io.File;
import java.lang.reflect.Type;
import java.util.*;
import java.util.logging.Level;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import com.massivecraft.mcore.util.DiscUtil;
import com.massivecraft.mcore.util.Txt;
import com.massivecraft.mcore.xlib.gson.Gson;
import com.massivecraft.mcore.xlib.gson.JsonSyntaxException;
public abstract class EntityCollection<E extends Entity>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
// These must be instantiated in order to allow for different configuration (orders, comparators etc)
private Collection<E> entities;
protected Map<String, E> id2entity;
// If the entities are creative they will create a new instance if a non existent id was requested
private boolean creative;
public boolean isCreative() { return creative; }
public void setCreative(boolean creative) { this.creative = creative; }
// This is the auto increment for the primary key "id"
private int nextId;
// This ugly crap is necessary due to java type erasure
private Class<E> entityClass;
public abstract Type getMapType(); // This is special stuff for GSON.
// Info on how to persist
private Gson gson;
public Gson getGson() { return gson; }
public void setGson(Gson gson) { this.gson = gson; }
private File file;
public File getFile() { return file; }
public void setFile(File file) { this.file = file; }
// -------------------------------------------- //
// CONSTRUCTORS
// -------------------------------------------- //
public EntityCollection(Class<E> entityClass, Collection<E> entities, Map<String, E> id2entity, File file, Gson gson, boolean creative)
{
this.entityClass = entityClass;
this.entities = entities;
this.id2entity = id2entity;
this.file = file;
this.gson = gson;
this.creative = creative;
this.nextId = 1;
EM.setEntitiesCollectionForEntityClass(this.entityClass, this);
}
public EntityCollection(Class<E> entityClass, Collection<E> entities, Map<String, E> id2entity, File file, Gson gson)
{
this(entityClass, entities, id2entity, file, gson, false);
}
// -------------------------------------------- //
// GET
// -------------------------------------------- //
public Collection<E> get()
{
return entities;
}
public Map<String, E> getMap()
{
return this.id2entity;
}
public E get(String id)
{
if (this.creative) return this.getCreative(id);
return id2entity.get(id);
}
public E getCreative(String id)
{
E e = id2entity.get(id);
if (e != null) return e;
return this.create(id);
}
public boolean exists(String id)
{
if (id == null) return false;
return id2entity.get(id) != null;
}
public E getBestIdMatch(String pattern)
{
String id = Txt.getBestCIStart(this.id2entity.keySet(), pattern);
if (id == null) return null;
return this.id2entity.get(id);
}
// -------------------------------------------- //
// CREATE
// -------------------------------------------- //
public synchronized E create()
{
return this.create(this.getNextId());
}
public synchronized E create(String id)
{
if ( ! this.isIdFree(id)) return null;
E e = null;
try
{
e = this.entityClass.newInstance();
} catch (Exception ignored) {
ignored.printStackTrace();
}
e.setId(id);
this.entities.add(e);
this.id2entity.put(e.getId(), e);
this.updateNextIdForId(id);
return e;
}
// -------------------------------------------- //
// ATTACH AND DETACH
// -------------------------------------------- //
public void attach(E entity)
{
if (entity.getId() != null) return;
entity.setId(this.getNextId());
this.entities.add(entity);
this.id2entity.put(entity.getId(), entity);
}
public void detach(E entity)
{
entity.preDetach();
this.entities.remove(entity);
this.id2entity.remove(entity.getId());
entity.postDetach();
}
public void detach(String id)
{
E entity = this.id2entity.get(id);
if (entity == null) return;
this.detach(entity);
}
public boolean attached(E entity)
{
return this.entities.contains(entity);
}
public boolean detached(E entity)
{
return ! this.attached(entity);
}
// -------------------------------------------- //
// DISC
// -------------------------------------------- //
public boolean saveToDisc()
{
Map<String, E> entitiesThatShouldBeSaved = new HashMap<String, E>();
for (E entity : this.entities)
{
if (entity.shouldBeSaved())
{
entitiesThatShouldBeSaved.put(entity.getId(), entity);
}
}
return this.saveCore(entitiesThatShouldBeSaved);
}
private boolean saveCore(Map<String, E> entities)
{
return DiscUtil.writeCatch(this.file, this.gson.toJson(entities));
}
public boolean loadFromDisc()
{
Map<String, E> id2entity = this.loadCore();
if (id2entity == null) return false;
this.entities.clear();
this.entities.addAll(id2entity.values());
this.id2entity.clear();
this.id2entity.putAll(id2entity);
this.fillIds();
return true;
}
private Map<String, E> loadCore()
{
if ( ! this.file.exists())
{
return new HashMap<String, E>();
}
String content = DiscUtil.readCatch(this.file);
if (content == null)
{
return null;
}
Type type = this.getMapType();
try
{
return this.gson.fromJson(content, type);
}
catch(JsonSyntaxException ex)
{
Bukkit.getLogger().log(Level.WARNING, "JSON error encountered loading \"" + file + "\": " + ex.getLocalizedMessage());
// backup bad file, so user can attempt to recover something from it
File backup = new File(file.getPath()+"_bad");
if (backup.exists()) backup.delete();
Bukkit.getLogger().log(Level.WARNING, "Backing up copy of bad file to: "+backup);
file.renameTo(backup);
return null;
}
}
// -------------------------------------------- //
// ID MANAGEMENT
// -------------------------------------------- //
public String getNextId()
{
while ( ! isIdFree(this.nextId) )
{
this.nextId += 1;
}
return Integer.toString(this.nextId);
}
public boolean isIdFree(String id)
{
return ! this.id2entity.containsKey(id);
}
public boolean isIdFree(int id)
{
return this.isIdFree(Integer.toString(id));
}
protected synchronized void fillIds()
{
this.nextId = 1;
for(Entry<String, E> entry : this.id2entity.entrySet())
{
String id = entry.getKey();
E entity = entry.getValue();
entity.id = id;
this.updateNextIdForId(id);
}
}
protected synchronized void updateNextIdForId(int id)
{
if (this.nextId < id)
{
this.nextId = id + 1;
}
}
protected void updateNextIdForId(String id)
{
try
{
int idAsInt = Integer.parseInt(id);
this.updateNextIdForId(idAsInt);
}
catch (Exception ignored) { }
}
}

View File

@ -1,51 +0,0 @@
package com.massivecraft.factions.zcore.persist;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class PlayerEntity extends Entity
{
public Player getPlayer()
{
return Bukkit.getPlayerExact(this.getId());
}
public boolean isOnline()
{
return this.getPlayer() != null;
}
// make sure target player should be able to detect that this player is online
public boolean isOnlineAndVisibleTo(Player player)
{
Player target = this.getPlayer();
return target != null && player.canSee(target);
}
public boolean isOffline()
{
return ! isOnline();
}
// -------------------------------------------- //
// Message Sending Helpers
// -------------------------------------------- //
public void sendMessage(String msg)
{
Player player = this.getPlayer();
if (player == null) return;
player.sendMessage(msg);
}
public void sendMessage(List<String> msgs)
{
for(String msg : msgs)
{
this.sendMessage(msg);
}
}
}

View File

@ -1,45 +0,0 @@
package com.massivecraft.factions.zcore.persist;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.massivecraft.mcore.xlib.gson.Gson;
/**
* The PlayerEntityCollection is an EntityCollection with the extra features
* a player skin usually requires.
*
* This entity collection is not only creative. It even creates the instance for the player
* when the player logs in to the server.
*
* This way we can be sure that PlayerEntityCollection.get() will contain
* all entities in PlayerEntityCollection.getOnline()
*/
public abstract class PlayerEntityCollection<E extends Entity> extends EntityCollection<E>
{
public PlayerEntityCollection(Class<E> entityClass, Collection<E> entities, Map<String, E> id2entity, File file, Gson gson)
{
super(entityClass, entities, id2entity, file, gson, true);
}
public E get(Player player)
{
return this.get(player.getName());
}
public Set<E> getOnline()
{
Set<E> entities = new HashSet<E>();
for (Player player : Bukkit.getServer().getOnlinePlayers())
{
entities.add(this.get(player));
}
return entities;
}
}

View File

@ -1,20 +0,0 @@
package com.massivecraft.factions.zcore.persist;
import com.massivecraft.factions.zcore.MPlugin;
public class SaveTask implements Runnable
{
MPlugin p;
public SaveTask(MPlugin p)
{
this.p = p;
}
public void run()
{
if ( ! p.getAutoSave()) return;
p.preAutoSave();
EM.saveAllToDisc();
p.postAutoSave();
}
}