Event based and configurable player timeout system, visible through /f p. Also started storing the last player activity locally since the Bukkit API is broken. This will probably fix issues reported where players never get kicked.
This commit is contained in:
		@@ -1,10 +1,13 @@
 | 
				
			|||||||
package com.massivecraft.factions.cmd;
 | 
					package com.massivecraft.factions.cmd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.LinkedHashMap;
 | 
					import java.util.LinkedHashMap;
 | 
				
			||||||
 | 
					import java.util.Map.Entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.massivecraft.factions.Perm;
 | 
					import com.massivecraft.factions.Perm;
 | 
				
			||||||
import com.massivecraft.factions.cmd.arg.ARMPlayer;
 | 
					import com.massivecraft.factions.cmd.arg.ARMPlayer;
 | 
				
			||||||
 | 
					import com.massivecraft.factions.entity.MConf;
 | 
				
			||||||
import com.massivecraft.factions.entity.MPlayer;
 | 
					import com.massivecraft.factions.entity.MPlayer;
 | 
				
			||||||
 | 
					import com.massivecraft.factions.event.EventFactionsRemovePlayerMillis;
 | 
				
			||||||
import com.massivecraft.massivecore.Progressbar;
 | 
					import com.massivecraft.massivecore.Progressbar;
 | 
				
			||||||
import com.massivecraft.massivecore.cmd.req.ReqHasPerm;
 | 
					import com.massivecraft.massivecore.cmd.req.ReqHasPerm;
 | 
				
			||||||
import com.massivecraft.massivecore.util.TimeDiffUtil;
 | 
					import com.massivecraft.massivecore.util.TimeDiffUtil;
 | 
				
			||||||
@@ -52,17 +55,17 @@ public class CmdFactionsPlayer extends FactionsCommand
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		int progressbarWidth = (int) Math.round(mplayer.getPowerMax() / mplayer.getPowerMaxUniversal() * 100);
 | 
							int progressbarWidth = (int) Math.round(mplayer.getPowerMax() / mplayer.getPowerMaxUniversal() * 100);
 | 
				
			||||||
		msg("<k>Power: <v>%s", Progressbar.HEALTHBAR_CLASSIC.withQuota(progressbarQuota).withWidth(progressbarWidth).render());
 | 
							msg("<a>Power: <v>%s", Progressbar.HEALTHBAR_CLASSIC.withQuota(progressbarQuota).withWidth(progressbarWidth).render());
 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
		// INFO: Power (as digits)
 | 
							// INFO: Power (as digits)
 | 
				
			||||||
		msg("<k>Power: <v>%.2f / %.2f", mplayer.getPower(), mplayer.getPowerMax());
 | 
							msg("<a>Power: <v>%.2f / %.2f", mplayer.getPower(), mplayer.getPowerMax());
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// INFO: Power Boost
 | 
							// INFO: Power Boost
 | 
				
			||||||
		if (mplayer.hasPowerBoost())
 | 
							if (mplayer.hasPowerBoost())
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			double powerBoost = mplayer.getPowerBoost();
 | 
								double powerBoost = mplayer.getPowerBoost();
 | 
				
			||||||
			String powerBoostType = (powerBoost > 0 ? "bonus" : "penalty");
 | 
								String powerBoostType = (powerBoost > 0 ? "bonus" : "penalty");
 | 
				
			||||||
			msg("<k>Power Boost: <v>%f <i>(a manually granted %s)", powerBoost, powerBoostType);
 | 
								msg("<a>Power Boost: <v>%f <i>(a manually granted %s)", powerBoost, powerBoostType);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// INFO: Power per Hour
 | 
							// INFO: Power per Hour
 | 
				
			||||||
@@ -79,11 +82,33 @@ public class CmdFactionsPlayer extends FactionsCommand
 | 
				
			|||||||
			stringTillMax = Txt.parse(" <i>(%s <i>left till max)", unitcountsTillMaxFormated);
 | 
								stringTillMax = Txt.parse(" <i>(%s <i>left till max)", unitcountsTillMaxFormated);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		msg("<k>Power per Hour: <v>%.2f%s", mplayer.getPowerPerHour(), stringTillMax);
 | 
							msg("<a>Power per Hour: <v>%.2f%s", mplayer.getPowerPerHour(), stringTillMax);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// INFO: Power per Death
 | 
							// INFO: Power per Death
 | 
				
			||||||
		msg("<k>Power per Death: <v>%.2f", mplayer.getPowerPerDeath());
 | 
							msg("<a>Power per Death: <v>%.2f", mplayer.getPowerPerDeath());
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 | 
							// Display automatic kick / remove info if the system is in use
 | 
				
			||||||
 | 
							if (MConf.get().removePlayerMillisDefault <= 0) return;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							EventFactionsRemovePlayerMillis event = new EventFactionsRemovePlayerMillis(false, mplayer);
 | 
				
			||||||
 | 
							event.run();
 | 
				
			||||||
 | 
							msg("<i>Automatic removal after %s <i>of inactivity:", format(event.getMillis()));
 | 
				
			||||||
 | 
							for (Entry<String, Long> causeMillis : event.getCauseMillis().entrySet())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								String cause = causeMillis.getKey();
 | 
				
			||||||
 | 
								long millis = causeMillis.getValue();
 | 
				
			||||||
 | 
								msg("<a>%s<a>: <v>%s", cause, format(millis));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// TIME FORMAT
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public static String format(long millis)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							LinkedHashMap<TimeUnit, Long> unitcounts = TimeDiffUtil.unitcounts(millis, TimeUnit.getAllBut(TimeUnit.MILLISECOND, TimeUnit.WEEK, TimeUnit.MONTH));
 | 
				
			||||||
 | 
							return TimeDiffUtil.formatedVerboose(unitcounts);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1103,7 +1103,8 @@ public class Faction extends Entity<Faction> implements EconomyParticipator
 | 
				
			|||||||
			this.detach();
 | 
								this.detach();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
		{	// promote new faction leader
 | 
							{
 | 
				
			||||||
 | 
								// promote new faction leader
 | 
				
			||||||
			if (oldLeader != null)
 | 
								if (oldLeader != null)
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				oldLeader.setRole(Rel.MEMBER);
 | 
									oldLeader.setRole(Rel.MEMBER);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import com.massivecraft.factions.integration.dynmap.DynmapStyle;
 | 
				
			|||||||
import com.massivecraft.factions.listeners.FactionsListenerChat;
 | 
					import com.massivecraft.factions.listeners.FactionsListenerChat;
 | 
				
			||||||
import com.massivecraft.massivecore.store.Entity;
 | 
					import com.massivecraft.massivecore.store.Entity;
 | 
				
			||||||
import com.massivecraft.massivecore.util.MUtil;
 | 
					import com.massivecraft.massivecore.util.MUtil;
 | 
				
			||||||
 | 
					import com.massivecraft.massivecore.util.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class MConf extends Entity<MConf>
 | 
					public class MConf extends Entity<MConf>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -79,8 +80,21 @@ public class MConf extends Entity<MConf>
 | 
				
			|||||||
	// REMOVE DATA
 | 
						// REMOVE DATA
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public boolean removePlayerDataWhenBanned = true;
 | 
						public boolean removePlayerWhenBanned = true;
 | 
				
			||||||
	public double removePlayerDataAfterInactiveDays = 20.0;
 | 
						
 | 
				
			||||||
 | 
						// The Default
 | 
				
			||||||
 | 
						public long removePlayerMillisDefault = 10 * TimeUnit.MILLIS_PER_DAY;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Player Age Bonus
 | 
				
			||||||
 | 
						public Map<Long, Long> removePlayerMillisPlayerAgeToBonus = MUtil.map(
 | 
				
			||||||
 | 
							2 * TimeUnit.MILLIS_PER_WEEK, 10 * TimeUnit.MILLIS_PER_DAY  // +10 after 2 weeks
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Faction Age Bonus
 | 
				
			||||||
 | 
						public Map<Long, Long> removePlayerMillisFactionAgeToBonus = MUtil.map(
 | 
				
			||||||
 | 
							4 * TimeUnit.MILLIS_PER_WEEK, 10 * TimeUnit.MILLIS_PER_DAY, // +10 after 4 weeks
 | 
				
			||||||
 | 
							2 * TimeUnit.MILLIS_PER_WEEK,  5 * TimeUnit.MILLIS_PER_DAY  // +5 after 2 weeks
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
	// SPECIAL FACTION IDS
 | 
						// SPECIAL FACTION IDS
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import com.massivecraft.factions.Rel;
 | 
				
			|||||||
import com.massivecraft.factions.RelationParticipator;
 | 
					import com.massivecraft.factions.RelationParticipator;
 | 
				
			||||||
import com.massivecraft.factions.event.EventFactionsChunkChange;
 | 
					import com.massivecraft.factions.event.EventFactionsChunkChange;
 | 
				
			||||||
import com.massivecraft.factions.event.EventFactionsMembershipChange;
 | 
					import com.massivecraft.factions.event.EventFactionsMembershipChange;
 | 
				
			||||||
 | 
					import com.massivecraft.factions.event.EventFactionsRemovePlayerMillis;
 | 
				
			||||||
import com.massivecraft.factions.event.EventFactionsMembershipChange.MembershipChangeReason;
 | 
					import com.massivecraft.factions.event.EventFactionsMembershipChange.MembershipChangeReason;
 | 
				
			||||||
import com.massivecraft.factions.util.RelationUtil;
 | 
					import com.massivecraft.factions.util.RelationUtil;
 | 
				
			||||||
import com.massivecraft.massivecore.mixin.Mixin;
 | 
					import com.massivecraft.massivecore.mixin.Mixin;
 | 
				
			||||||
@@ -43,6 +44,7 @@ public class MPlayer extends SenderEntity<MPlayer> implements EconomyParticipato
 | 
				
			|||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public MPlayer load(MPlayer that)
 | 
						public MPlayer load(MPlayer that)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							this.setLastActivityMillis(that.lastActivityMillis);
 | 
				
			||||||
		this.setFactionId(that.factionId);
 | 
							this.setFactionId(that.factionId);
 | 
				
			||||||
		this.setRole(that.role);
 | 
							this.setRole(that.role);
 | 
				
			||||||
		this.setTitle(that.title);
 | 
							this.setTitle(that.title);
 | 
				
			||||||
@@ -57,11 +59,13 @@ public class MPlayer extends SenderEntity<MPlayer> implements EconomyParticipato
 | 
				
			|||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isDefault()
 | 
						public boolean isDefault()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							// Last activity millis is data we use for clearing out inactive players. So it does not in itself make the player data worth keeping.
 | 
				
			||||||
		if (this.hasFaction()) return false;
 | 
							if (this.hasFaction()) return false;
 | 
				
			||||||
		// Role means nothing without a faction.
 | 
							// Role means nothing without a faction.
 | 
				
			||||||
		// Title means nothing without a faction.
 | 
							// Title means nothing without a faction.
 | 
				
			||||||
 | 
							if (this.hasPowerBoost()) return false;
 | 
				
			||||||
		if (this.getPowerRounded() != (int) Math.round(MConf.get().defaultPlayerPower)) return false;
 | 
							if (this.getPowerRounded() != (int) Math.round(MConf.get().defaultPlayerPower)) return false;
 | 
				
			||||||
		if (this.isMapAutoUpdating()) return false;
 | 
							// if (this.isMapAutoUpdating()) return false; // Just having an auto updating map is not in itself reason enough for database storage.
 | 
				
			||||||
		if (this.isUsingAdminMode()) return false;
 | 
							if (this.isUsingAdminMode()) return false;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
@@ -99,6 +103,12 @@ public class MPlayer extends SenderEntity<MPlayer> implements EconomyParticipato
 | 
				
			|||||||
	// In this section of the source code we place the field declarations only.
 | 
						// 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.
 | 
						// Each field has it's own section further down since just the getter and setter logic takes up quite some place.
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						// The last known time of explicit player activity, such as login or logout.
 | 
				
			||||||
 | 
						// Null means "unknown" (as opposed to "never played on the server").
 | 
				
			||||||
 | 
						// The player might have been active very recently but we could lack that data.
 | 
				
			||||||
 | 
						// The reason being this data field was added in a version upgrade and has not been around for forever.
 | 
				
			||||||
 | 
						private Long lastActivityMillis = null;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	// This is a foreign key.
 | 
						// This is a foreign key.
 | 
				
			||||||
	// Each player belong to a faction.
 | 
						// Each player belong to a faction.
 | 
				
			||||||
	// Null means default.
 | 
						// Null means default.
 | 
				
			||||||
@@ -158,6 +168,46 @@ public class MPlayer extends SenderEntity<MPlayer> implements EconomyParticipato
 | 
				
			|||||||
		this.setAutoClaimFaction(null);
 | 
							this.setAutoClaimFaction(null);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// FIELD: lastActivityMillis
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Raw: Using only what we have stored
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public Long getLastActivityMillisRaw()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return this.lastActivityMillis;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void setLastActivityMillis(Long lastActivityMillis)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Clean input
 | 
				
			||||||
 | 
							Long target = lastActivityMillis;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Detect Nochange
 | 
				
			||||||
 | 
							if (MUtil.equals(this.lastActivityMillis, target)) return;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Apply
 | 
				
			||||||
 | 
							this.lastActivityMillis = target;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Mark as changed
 | 
				
			||||||
 | 
							this.changed();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void setLastActivityMillis()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							this.setLastActivityMillis(System.currentTimeMillis());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Finer: Using our raw data but also underlying system as fallback
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public Long getLastActivityMillis()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Long ret = this.getLastActivityMillisRaw();
 | 
				
			||||||
 | 
							if (ret != null) return ret;
 | 
				
			||||||
 | 
							return Mixin.getLastPlayed(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
	// FIELD: factionId
 | 
						// FIELD: factionId
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
@@ -625,6 +675,57 @@ public class MPlayer extends SenderEntity<MPlayer> implements EconomyParticipato
 | 
				
			|||||||
		return BoardColl.get().getFactionAt(ps).getRelationTo(this) == Rel.ENEMY;
 | 
							return BoardColl.get().getFactionAt(ps).getRelationTo(this) == Rel.ENEMY;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// INACTIVITY TIMEOUT
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public long getRemovePlayerMillis(boolean async)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							EventFactionsRemovePlayerMillis event = new EventFactionsRemovePlayerMillis(async, this);
 | 
				
			||||||
 | 
							event.run();
 | 
				
			||||||
 | 
							return event.getMillis();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public boolean considerRemovePlayerMillis(boolean async)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// This may or may not be required.
 | 
				
			||||||
 | 
							// Some users have been reporting a loop issue with the same player detaching over and over again.
 | 
				
			||||||
 | 
							// Maybe skipping ahead if the player is detached will solve the issue.
 | 
				
			||||||
 | 
							if (this.detached()) return false;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Get the last activity millis.
 | 
				
			||||||
 | 
							// Null means "unknown" in which case we does nothing.
 | 
				
			||||||
 | 
							Long lastActivityMillis = this.getLastActivityMillis();
 | 
				
			||||||
 | 
							if (lastActivityMillis == null) return false;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Consider
 | 
				
			||||||
 | 
							long toleranceMillis = this.getRemovePlayerMillis(async);
 | 
				
			||||||
 | 
							if (System.currentTimeMillis() - lastActivityMillis <= toleranceMillis) return false;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Inform
 | 
				
			||||||
 | 
							if (MConf.get().logFactionLeave || MConf.get().logFactionKick)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Factions.get().log("Player " + this.getName() + " was auto-removed due to inactivity.");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Promote a new leader if required.
 | 
				
			||||||
 | 
							if (this.getRole() == Rel.LEADER)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Faction faction = this.getFaction();
 | 
				
			||||||
 | 
								if (faction != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									this.getFaction().promoteNewLeader();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.leave();
 | 
				
			||||||
 | 
							this.detach();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
	// ACTIONS
 | 
						// ACTIONS
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
package com.massivecraft.factions.entity;
 | 
					package com.massivecraft.factions.entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.bukkit.Bukkit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.massivecraft.factions.Const;
 | 
					import com.massivecraft.factions.Const;
 | 
				
			||||||
import com.massivecraft.factions.Factions;
 | 
					import com.massivecraft.factions.Factions;
 | 
				
			||||||
import com.massivecraft.factions.Rel;
 | 
					 | 
				
			||||||
import com.massivecraft.massivecore.mixin.Mixin;
 | 
					 | 
				
			||||||
import com.massivecraft.massivecore.store.MStore;
 | 
					import com.massivecraft.massivecore.store.MStore;
 | 
				
			||||||
import com.massivecraft.massivecore.store.SenderColl;
 | 
					import com.massivecraft.massivecore.store.SenderColl;
 | 
				
			||||||
import com.massivecraft.massivecore.util.IdUtil;
 | 
					import com.massivecraft.massivecore.util.IdUtil;
 | 
				
			||||||
import com.massivecraft.massivecore.util.TimeUnit;
 | 
					 | 
				
			||||||
import com.massivecraft.massivecore.util.Txt;
 | 
					import com.massivecraft.massivecore.util.Txt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class MPlayerColl extends SenderColl<MPlayer>
 | 
					public class MPlayerColl extends SenderColl<MPlayer>
 | 
				
			||||||
@@ -41,87 +42,27 @@ public class MPlayerColl extends SenderColl<MPlayer>
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void removePlayerDataAfterInactiveDaysRoutine()
 | 
						public void considerRemovePlayerMillis()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		if (MConf.get().removePlayerDataAfterInactiveDays <= 0.0) return;
 | 
							// If the config option is 0 or below that means the server owner want it disabled.
 | 
				
			||||||
 | 
							if (MConf.get().removePlayerMillisDefault <= 0.0) return;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		long now = System.currentTimeMillis();
 | 
							// For each of the offline players...
 | 
				
			||||||
		double toleranceMillis = MConf.get().removePlayerDataAfterInactiveDays * TimeUnit.MILLIS_PER_DAY;
 | 
							// NOTE: If the player is currently online it's most definitely not inactive.
 | 
				
			||||||
 | 
							// NOTE: This check catches some important special cases like the @console "player".
 | 
				
			||||||
 | 
							final Collection<MPlayer> mplayersOffline = this.getAllOffline();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		for (MPlayer mplayer : this.getAll())
 | 
							Bukkit.getScheduler().runTaskAsynchronously(Factions.get(), new Runnable()
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			// This may or may not be required.
 | 
								@Override
 | 
				
			||||||
			// Some users have been reporting a loop issue with the same player detaching over and over again.
 | 
								public void run()
 | 
				
			||||||
			// Maybe skipping ahead if the player is detached will solve the issue.
 | 
					 | 
				
			||||||
			if (mplayer.detached()) continue;
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			if (mplayer.isOnline()) continue;
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			Long lastPlayed = Mixin.getLastPlayed(mplayer.getId());
 | 
					 | 
				
			||||||
			if (lastPlayed == null) continue;
 | 
					 | 
				
			||||||
			if (now - lastPlayed <= toleranceMillis) continue;
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			if (MConf.get().logFactionLeave || MConf.get().logFactionKick)
 | 
					 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				Factions.get().log("Player "+mplayer.getName()+" was auto-removed due to inactivity.");
 | 
									for (MPlayer mplayer : mplayersOffline)
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// if player is faction leader, sort out the faction since he's going away
 | 
					 | 
				
			||||||
			if (mplayer.getRole() == Rel.LEADER)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				Faction faction = mplayer.getFaction();
 | 
					 | 
				
			||||||
				if (faction != null)
 | 
					 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					mplayer.getFaction().promoteNewLeader();
 | 
										mplayer.considerRemovePlayerMillis(true);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
			mplayer.leave();
 | 
					 | 
				
			||||||
			mplayer.detach();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	/*
 | 
					 | 
				
			||||||
// This method is for the 1.8.X --> 2.0.0 migration
 | 
					 | 
				
			||||||
	public void migrate()
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		// Create file objects
 | 
					 | 
				
			||||||
		File oldFile = new File(Factions.get().getDataFolder(), "players.json");
 | 
					 | 
				
			||||||
		File newFile = new File(Factions.get().getDataFolder(), "players.json.migrated");
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// Already migrated?
 | 
					 | 
				
			||||||
		if ( ! oldFile.exists()) return;
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// Read the file content through GSON.
 | 
					 | 
				
			||||||
		Type type = new TypeToken<Map<String, MPlayer>>(){}.getType();
 | 
					 | 
				
			||||||
		Map<String, MPlayer> id2mplayer = Factions.get().gson.fromJson(DiscUtil.readCatch(oldFile), type);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// The Coll
 | 
					 | 
				
			||||||
		MPlayerColl coll = this.getForUniverse(MassiveCore.DEFAULT);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// Set the data
 | 
					 | 
				
			||||||
		for (Entry<String, MPlayer> entry : id2mplayer.entrySet())
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			String playerId = entry.getKey();
 | 
					 | 
				
			||||||
			MPlayer mplayer = entry.getValue();
 | 
					 | 
				
			||||||
			coll.attach(mplayer, playerId);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// Mark as migrated
 | 
					 | 
				
			||||||
		oldFile.renameTo(newFile);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	// -------------------------------------------- //
 | 
					 | 
				
			||||||
	// EXTRAS
 | 
					 | 
				
			||||||
	// -------------------------------------------- //
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public void clean()
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		for (MPlayerColl coll : this.getColls())
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			coll.clean();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					package com.massivecraft.factions.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.LinkedHashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Map.Entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.bukkit.event.HandlerList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.massivecraft.factions.entity.Faction;
 | 
				
			||||||
 | 
					import com.massivecraft.factions.entity.MConf;
 | 
				
			||||||
 | 
					import com.massivecraft.factions.entity.MPlayer;
 | 
				
			||||||
 | 
					import com.massivecraft.massivecore.event.EventMassiveCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class EventFactionsRemovePlayerMillis extends EventMassiveCore
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// REQUIRED EVENT CODE
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private static final HandlerList handlers = new HandlerList();
 | 
				
			||||||
 | 
						@Override public HandlerList getHandlers() { return handlers; }
 | 
				
			||||||
 | 
						public static HandlerList getHandlerList() { return handlers; }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// FIELD
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private final MPlayer mplayer;
 | 
				
			||||||
 | 
						public MPlayer getMPlayer() { return this.mplayer; }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private long millis;
 | 
				
			||||||
 | 
						public long getMillis() { return this.millis; }
 | 
				
			||||||
 | 
						public void setMillis(long millis) { this.millis = millis; }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private Map<String, Long> causeMillis = new LinkedHashMap<String, Long>();
 | 
				
			||||||
 | 
						public Map<String, Long> getCauseMillis() { return this.causeMillis; }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// CONSTRUCT
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public EventFactionsRemovePlayerMillis(boolean async, MPlayer mplayer)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							super(async);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							this.mplayer = mplayer;
 | 
				
			||||||
 | 
							this.millis = MConf.get().removePlayerMillisDefault;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Default
 | 
				
			||||||
 | 
							this.causeMillis.put("Default", MConf.get().removePlayerMillisDefault);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Player Age Bonus
 | 
				
			||||||
 | 
							this.applyPlayerAgeBonus();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Faction Age Bonus
 | 
				
			||||||
 | 
							this.applyFactionAgeBonus();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// UTIL
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void applyPlayerAgeBonus()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Skip if this bonus is totally disabled.
 | 
				
			||||||
 | 
							// We don't want it showing up with 0 for everyone.
 | 
				
			||||||
 | 
							if (MConf.get().removePlayerMillisPlayerAgeToBonus.isEmpty()) return;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Calculate First Played
 | 
				
			||||||
 | 
							Long firstPlayed = this.getMPlayer().getFirstPlayed();
 | 
				
			||||||
 | 
							Long age = 0L;
 | 
				
			||||||
 | 
							if (firstPlayed != null)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								age = System.currentTimeMillis() - firstPlayed;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Calculate the Bonus!
 | 
				
			||||||
 | 
							long bonus = 0;
 | 
				
			||||||
 | 
							for (Entry<Long, Long> entry : MConf.get().removePlayerMillisPlayerAgeToBonus.entrySet())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Long key = entry.getKey();
 | 
				
			||||||
 | 
								if (key == null) continue;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								Long value = entry.getValue();
 | 
				
			||||||
 | 
								if (value == null) continue;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								if (age >= key)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									bonus = value;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Apply
 | 
				
			||||||
 | 
							this.setMillis(this.getMillis() + bonus);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Inform
 | 
				
			||||||
 | 
							this.getCauseMillis().put("Player Age Bonus", bonus);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void applyFactionAgeBonus()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Skip if this bonus is totally disabled.
 | 
				
			||||||
 | 
							// We don't want it showing up with 0 for everyone.
 | 
				
			||||||
 | 
							if (MConf.get().removePlayerMillisFactionAgeToBonus.isEmpty()) return;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Calculate Faction Age
 | 
				
			||||||
 | 
							Faction faction = this.getMPlayer().getFaction();
 | 
				
			||||||
 | 
							long age = 0;
 | 
				
			||||||
 | 
							if ( ! faction.isNone())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								age = System.currentTimeMillis() - faction.getCreatedAtMillis();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Calculate the Bonus!
 | 
				
			||||||
 | 
							long bonus = 0;
 | 
				
			||||||
 | 
							for (Entry<Long, Long> entry : MConf.get().removePlayerMillisFactionAgeToBonus.entrySet())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Long key = entry.getKey();
 | 
				
			||||||
 | 
								if (key == null) continue;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								Long value = entry.getValue();
 | 
				
			||||||
 | 
								if (value == null) continue;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								if (age >= key)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									bonus = value;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Apply
 | 
				
			||||||
 | 
							this.setMillis(this.getMillis() + bonus);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Inform
 | 
				
			||||||
 | 
							this.getCauseMillis().put("Faction Age Bonus", bonus);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,6 +11,7 @@ import org.bukkit.Bukkit;
 | 
				
			|||||||
import org.bukkit.Location;
 | 
					import org.bukkit.Location;
 | 
				
			||||||
import org.bukkit.Material;
 | 
					import org.bukkit.Material;
 | 
				
			||||||
import org.bukkit.block.Block;
 | 
					import org.bukkit.block.Block;
 | 
				
			||||||
 | 
					import org.bukkit.command.CommandSender;
 | 
				
			||||||
import org.bukkit.entity.Enderman;
 | 
					import org.bukkit.entity.Enderman;
 | 
				
			||||||
import org.bukkit.entity.Entity;
 | 
					import org.bukkit.entity.Entity;
 | 
				
			||||||
import org.bukkit.entity.EntityType;
 | 
					import org.bukkit.entity.EntityType;
 | 
				
			||||||
@@ -71,6 +72,7 @@ import com.massivecraft.factions.event.EventFactionsPvpDisallowed;
 | 
				
			|||||||
import com.massivecraft.factions.event.EventFactionsPowerChange;
 | 
					import com.massivecraft.factions.event.EventFactionsPowerChange;
 | 
				
			||||||
import com.massivecraft.factions.event.EventFactionsPowerChange.PowerChangeReason;
 | 
					import com.massivecraft.factions.event.EventFactionsPowerChange.PowerChangeReason;
 | 
				
			||||||
import com.massivecraft.factions.util.VisualizeUtil;
 | 
					import com.massivecraft.factions.util.VisualizeUtil;
 | 
				
			||||||
 | 
					import com.massivecraft.massivecore.event.EventMassiveCorePlayerLeave;
 | 
				
			||||||
import com.massivecraft.massivecore.mixin.Mixin;
 | 
					import com.massivecraft.massivecore.mixin.Mixin;
 | 
				
			||||||
import com.massivecraft.massivecore.ps.PS;
 | 
					import com.massivecraft.massivecore.ps.PS;
 | 
				
			||||||
import com.massivecraft.massivecore.util.MUtil;
 | 
					import com.massivecraft.massivecore.util.MUtil;
 | 
				
			||||||
@@ -96,6 +98,49 @@ public class FactionsListenerMain implements Listener
 | 
				
			|||||||
		Bukkit.getPluginManager().registerEvents(this, Factions.get());
 | 
							Bukkit.getPluginManager().registerEvents(this, Factions.get());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
						// UPDATE LAST ACTIVITY
 | 
				
			||||||
 | 
						// -------------------------------------------- //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void updateLastActivity(CommandSender sender)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (sender == null) throw new RuntimeException("sender");
 | 
				
			||||||
 | 
							MPlayer mplayer = MPlayer.get(sender);
 | 
				
			||||||
 | 
							mplayer.setLastActivityMillis();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public static void updateLastActivitySoon(final CommandSender sender)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (sender == null) throw new RuntimeException("sender");
 | 
				
			||||||
 | 
							Bukkit.getScheduler().scheduleSyncDelayedTask(Factions.get(), new Runnable()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void run()
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									updateLastActivity(sender);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Can't be cancelled
 | 
				
			||||||
 | 
						@EventHandler(priority = EventPriority.LOWEST)
 | 
				
			||||||
 | 
						public void updateLastActivity(PlayerJoinEvent event)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// During the join event itself we want to be able to reach the old data.
 | 
				
			||||||
 | 
							// That is also the way the underlying fallback Mixin system does it and we do it that way for the sake of symmetry. 
 | 
				
			||||||
 | 
							// For that reason we wait till the next tick with updating the value.
 | 
				
			||||||
 | 
							updateLastActivitySoon(event.getPlayer());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Can't be cancelled
 | 
				
			||||||
 | 
						@EventHandler(priority = EventPriority.LOWEST)
 | 
				
			||||||
 | 
						public void updateLastActivity(EventMassiveCorePlayerLeave event)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Here we do however update immediately.
 | 
				
			||||||
 | 
							// The player data should be fully updated before leaving the server.
 | 
				
			||||||
 | 
							updateLastActivity(event.getPlayer());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
	// MOTD
 | 
						// MOTD
 | 
				
			||||||
	// -------------------------------------------- //
 | 
						// -------------------------------------------- //
 | 
				
			||||||
@@ -512,7 +557,7 @@ public class FactionsListenerMain implements Listener
 | 
				
			|||||||
		if (!player.isBanned()) return;
 | 
							if (!player.isBanned()) return;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// ... and we remove player data when banned ...
 | 
							// ... and we remove player data when banned ...
 | 
				
			||||||
		if (!MConf.get().removePlayerDataWhenBanned) return;
 | 
							if (!MConf.get().removePlayerWhenBanned) return;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// ... get rid of their stored info.
 | 
							// ... get rid of their stored info.
 | 
				
			||||||
		MPlayer mplayer = MPlayerColl.get().get(player, false);
 | 
							MPlayer mplayer = MPlayerColl.get().get(player, false);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ public class TaskPlayerDataRemove extends ModuloRepeatTask
 | 
				
			|||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void invoke(long now)
 | 
						public void invoke(long now)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		MPlayerColl.get().removePlayerDataAfterInactiveDaysRoutine();
 | 
							MPlayerColl.get().considerRemovePlayerMillis();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user