package com.planet_ink.coffee_mud.Libraries; import com.planet_ink.coffee_mud.core.exceptions.BadEmailAddressException; import com.planet_ink.coffee_mud.core.interfaces.*; import com.planet_ink.coffee_mud.core.*; import com.planet_ink.coffee_mud.core.CMLib.Library; import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag; import com.planet_ink.coffee_mud.core.CMSecurity.DisFlag; import com.planet_ink.coffee_mud.core.collections.*; import com.planet_ink.coffee_mud.Libraries.interfaces.*; import com.planet_ink.coffee_mud.Libraries.interfaces.PlayerLibrary.ThinPlayer; import com.planet_ink.coffee_mud.Abilities.interfaces.*; import com.planet_ink.coffee_mud.Areas.interfaces.*; import com.planet_ink.coffee_mud.Behaviors.interfaces.*; import com.planet_ink.coffee_mud.CharClasses.interfaces.*; import com.planet_ink.coffee_mud.Commands.interfaces.*; import com.planet_ink.coffee_mud.Common.interfaces.*; import com.planet_ink.coffee_mud.Common.interfaces.PlayerAccount.AccountFlag; import com.planet_ink.coffee_mud.Common.interfaces.PlayerStats.PlayerFlag; import com.planet_ink.coffee_mud.Exits.interfaces.*; import com.planet_ink.coffee_mud.Items.interfaces.*; import com.planet_ink.coffee_mud.Locales.interfaces.*; import com.planet_ink.coffee_mud.MOBS.interfaces.*; import com.planet_ink.coffee_mud.Races.interfaces.*; import java.io.IOException; import java.util.*; /* Copyright 2008-2019 Bo Zimmerman Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ public class CMPlayers extends StdLibrary implements PlayerLibrary { @Override public String ID() { return "CMPlayers"; } protected SVector<MOB> playersList = new SVector<MOB>(); protected SVector<PlayerAccount> accountsList = new SVector<PlayerAccount>(); protected boolean allAccountsLoaded = false; protected CrossRefTreeMap<MOB,Room> playerLocations = new CrossRefTreeMap<MOB,Room>(Integer.MAX_VALUE,1); protected long[] autoPurgeDaysLevels = new long[1]; protected long[] prePurgeLevels = new long[1]; protected int autoPurgeHash = 0; protected PlayerLibrary[] playerLibList = new PlayerLibrary[0]; protected final static int PRIDE_TOP_SIZE = 10; protected final long[] topPrideExpiration = new long[TimeClock.TimePeriod.values().length]; @SuppressWarnings("unchecked") protected final List<Pair<String,Integer>>[][] topPlayers = new List[TimeClock.TimePeriod.values().length][AccountStats.PrideStat.values().length]; @SuppressWarnings("unchecked") protected final List<Pair<String,Integer>>[][] topAccounts = new List[TimeClock.TimePeriod.values().length][AccountStats.PrideStat.values().length]; protected final static List<Pair<String,Integer>> emptyPride = new ReadOnlyVector<Pair<String,Integer>>(1); @Override public int numPlayers() { return playersList.size(); } @Override public synchronized void addPlayer(final MOB newOne) { if(getPlayer(newOne.Name())!=null) return; if(playersList.contains(newOne)) return; PlayerAccount acct = null; if(newOne.playerStats()!=null) acct=newOne.playerStats().getAccount(); playersList.add(newOne); addAccount(acct); } @Override public synchronized void delPlayer(final MOB oneToDel) { if(oneToDel != null) { playersList.remove(oneToDel); playerLocations.removeFirst(oneToDel); Resources.removePersonalMap(oneToDel); } } @Override public Set<MOB> getPlayersHere(final Room room) { return playerLocations.getSecond(room); } @Override public void changePlayersLocation(final MOB mob, final Room room) { if(mob != null) { if(room == null) playerLocations.removeFirst(mob); else playerLocations.change(mob, room); } } @Override public ThinPlayer getThinPlayer(final String mobName) { final MOB M=this.getPlayer(mobName); if(M!=null) return getThinPlayer(M); return CMLib.database().getThinUser(mobName); } protected ThinPlayer getThinPlayer(final MOB mob) { return new ThinPlayer() { final MOB M=mob; @Override public String name() { return M.Name(); } @Override public String charClass() { return M.baseCharStats().getCurrentClass().ID(); } @Override public String race() { return M.baseCharStats().getMyRace().ID(); } @Override public int level() { return M.basePhyStats().level(); } @Override public int age() { return (M.playerStats()!=null)?(int)(M.getAgeMinutes()/60):0; } @Override public long last() { return (M.playerStats()!=null)?M.playerStats().getLastDateTime():0; } @Override public String email() { return (M.playerStats()!=null)?M.playerStats().getEmail():""; } @Override public String ip() { return (M.playerStats()!=null)?M.playerStats().getLastIP():""; } @Override public int exp() { return M.getExperience(); } @Override public int expLvl() { return M.getExpNeededLevel(); } @Override public String liege() { return M.getLiegeID(); } @Override public String worship() { return M.getWorshipCharID(); } }; } @Override public MOB getLoadPlayerByEmail(final String email) { for(final Enumeration<MOB> e=players();e.hasMoreElements();) { final MOB M=e.nextElement(); if((M!=null)&&(M.playerStats()!=null)&&(M.playerStats().getEmail().equalsIgnoreCase(email))) return M; } for(final Enumeration<ThinPlayer> e=thinPlayers("",null);e.hasMoreElements();) { final ThinPlayer P=e.nextElement(); if(P.email().equalsIgnoreCase(email)) return getLoadPlayer(P.name()); } return null; } @Override public void unloadOfflinePlayer(final MOB mob) { final PlayerStats pStats = mob.playerStats(); if(pStats != null) { pStats.getExtItems().delAllItems(true); if(getPlayer(mob.Name())==mob) { delPlayer(mob); mob.destroy(); } else // check other hosts { for(final PlayerLibrary pLib2 : getOtherPlayerLibAllHosts()) { if(pLib2.getPlayer(mob.Name())==mob) { delPlayer(mob); mob.destroy(); } } } } } @Override public boolean isSameAccount(final MOB player1, final MOB player2) { if((player1==null)||(player2==null)) return false; if((player1.playerStats()==null)||(player2.playerStats()==null)) return false; return player1.playerStats().getAccount()==player2.playerStats().getAccount(); } @Override public boolean isSameAccountIP(final MOB player1, final MOB player2) { if((player1==null)||(player2==null)) return false; if((player1.playerStats()==null)||(player2.playerStats()==null)) return false; if(player1.playerStats().getAccount()==player2.playerStats().getAccount()) return true; if((player1.session()==null)||(player2.session()==null)) return false; if(player1.session().getAddress().equals(player2.session().getAddress())) return true; return false; } @Override public PlayerAccount getLoadAccount(final String calledThis) { PlayerAccount A = getAccount(calledThis); if(A!=null) return A; if(allAccountsLoaded) return null; A=CMLib.database().DBReadAccount(calledThis); if(A!=null) addAccount(A); return A; } @Override public synchronized void addAccount(final PlayerAccount acct) { if(acct==null) return; if(accountsList.contains(acct)) return; for(final PlayerAccount A : accountsList) // dont consolodate this. { if(A.getAccountName().equals(acct.getAccountName())) return; } accountsList.add(acct); } @Override public PlayerAccount getLoadAccountByEmail(final String email) { if(!CMProps.isUsingAccountSystem()) return null; for(final Enumeration<PlayerAccount> e=accounts();e.hasMoreElements();) { final PlayerAccount P=e.nextElement(); if(P.getEmail().equalsIgnoreCase(email)) return P; } for(final Enumeration<PlayerAccount> e=accounts("",null);e.hasMoreElements();) { final PlayerAccount P=e.nextElement(); if(P.getEmail().equalsIgnoreCase(email)) return P; } return null; } @Override public PlayerAccount getAccount(String calledThis) { calledThis=CMStrings.capitalizeAndLower(calledThis); for(final PlayerAccount A : accountsList) { if(A.getAccountName().equals(calledThis)) return A; } for (final MOB M : playersList) { if((M.playerStats()!=null) &&(M.playerStats().getAccount()!=null) &&(M.playerStats().getAccount().getAccountName().equals(calledThis))) { addAccount(M.playerStats().getAccount()); return M.playerStats().getAccount(); } } return null; } protected PlayerLibrary[] getOtherPlayerLibAllHosts() { if(this.playerLibList.length>0) return this.playerLibList; final List<PlayerLibrary> list=new ArrayList<PlayerLibrary>(); list.add(this); final WorldMap map=CMLib.map(); for(final Enumeration<CMLibrary> pl=CMLib.libraries(CMLib.Library.PLAYERS); pl.hasMoreElements(); ) { final PlayerLibrary pLib2 = (PlayerLibrary)pl.nextElement(); if((pLib2 != null) &&(!list.contains(pLib2)) &&(map == CMLib.library(CMLib.getLibraryThreadID(Library.PLAYERS, pLib2), Library.MAP))) list.add(pLib2); } this.playerLibList = list.toArray(this.playerLibList); return this.playerLibList; } @Override public PlayerAccount getAccountAllHosts(final String calledThis) { for(final PlayerLibrary pLib2 : getOtherPlayerLibAllHosts()) { final PlayerAccount pA2=pLib2.getAccount(calledThis); if(pA2!=null) return pA2; } return null; } @Override public List<String> getPlayerLists() { return CMLib.database().getUserList(); } @Override public List<String> getPlayerListsAllHosts() { final List<String> finalList = new Vector<String>(); for(final PlayerLibrary pLib2 : getOtherPlayerLibAllHosts()) { finalList.addAll(pLib2.getPlayerLists()); } return finalList; } @Override public MOB getPlayer(String calledThis) { calledThis=CMStrings.capitalizeAndLower(calledThis); for (final MOB M : playersList) { if (M.Name().equals(calledThis)) return M; } return null; } @Override public MOB getPlayerAllHosts(final String calledThis) { for(final PlayerLibrary pLib2 : getOtherPlayerLibAllHosts()) { final MOB M=pLib2.getPlayer(calledThis); if(M!=null) return M; } return null; } @Override public MOB getLoadPlayer(final String last) { if(!CMProps.getBoolVar(CMProps.Bool.MUDSTARTED)) return null; MOB M=getPlayer(last); if(M!=null) return M; if(playerExists(last)) { M=CMLib.database().DBReadPlayer(CMStrings.capitalizeAndLower(last)); CMLib.database().DBReadFollowers(M,false); if(M.playerStats()!=null) M.playerStats().setLastUpdated(M.playerStats().getLastDateTime()); M.recoverPhyStats(); M.recoverCharStats(); final Race R=M.baseCharStats().getMyRace(); if(R.isGeneric()) CMLib.database().DBUpdateRaceCreationDate(R.ID()); Ability A=null; for(int a=0;a<M.numAbilities();a++) { A=M.fetchAbility(a); if(A!=null) A.autoInvocation(M, false); } } return M; } @Override public boolean accountExists(String name) { if(name==null) return false; name=CMStrings.capitalizeAndLower(name); return getLoadAccount(name)!=null; } @Override public boolean accountExistsAllHosts(final String name) { if(name==null) return false; for(final PlayerLibrary pLib2 : getOtherPlayerLibAllHosts()) { if(pLib2.accountExists(name)) return true; } return false; } @Override public boolean isLoadedPlayer(final MOB M) { return playersList.contains(M); } @Override public boolean isLoadedPlayer(final String mobName) { if(name==null) return false; name=CMStrings.capitalizeAndLower(name); for(final MOB M: playersList) { if(M.Name().equals(name)) return true; } return false; } @Override public boolean playerExists(String name) { if(name==null) return false; name=CMStrings.capitalizeAndLower(name); for(final MOB M: playersList) { if(M.Name().equals(name)) return true; } return CMLib.database().DBUserSearch(name)!=null; } @Override public boolean playerExistsAllHosts(String name) { if(name==null) return false; name=CMStrings.capitalizeAndLower(name); if(playerExists(name)) return true; for(final PlayerLibrary pLib : getOtherPlayerLibAllHosts()) { if(pLib != this) { for(final Enumeration<MOB> m=pLib.players();m.hasMoreElements();) { final MOB M=m.nextElement(); if(M.Name().equals(name)) return true; } final DatabaseEngine db = (DatabaseEngine)CMLib.library(CMLib.getLibraryThreadID(Library.PLAYERS, pLib), Library.DATABASE); if((db != CMLib.database()) && (db.DBUserSearch(name)!=null)) return true; } } return false; } @Override public Enumeration<MOB> players() { return playersList.elements(); } @Override public Enumeration<PlayerAccount> accounts() { return accountsList.elements(); } @Override public List<Pair<String,Integer>> getTopPridePlayers(final TimeClock.TimePeriod period, final AccountStats.PrideStat stat) { List<Pair<String,Integer>> top=topPlayers[period.ordinal()][stat.ordinal()]; if(top == null) top=emptyPride; return top; } @Override public List<Pair<String,Integer>> getTopPrideAccounts(final TimeClock.TimePeriod period, final AccountStats.PrideStat stat) { List<Pair<String,Integer>> top=topAccounts[period.ordinal()][stat.ordinal()]; if(top == null) top=emptyPride; return top; } private void removePrideStat(final List<Pair<String,Integer>> top, final String name, final int start) { for(int i=start;i<top.size();i++) { if(top.get(i).first.equals(name)) { top.remove(i); break; } } } @Override public int bumpPrideStat(final MOB mob, final AccountStats.PrideStat stat, final int amt) { if((amt != 0)&&(mob!=null)) { final PlayerStats pstats=mob.playerStats(); if(pstats != null) { if(!pstats.isSet(PlayerFlag.NOSTATS)) pstats.bumpPrideStat(stat, amt); if(!pstats.isSet(PlayerFlag.NOTOP)) adjustTopPrideStats(topPlayers,mob.Name(),stat,pstats); final PlayerAccount acct=pstats.getAccount(); if(acct != null) { if(!acct.isSet(AccountFlag.NOSTATS)) pstats.getAccount().bumpPrideStat(stat,amt); if(!acct.isSet(AccountFlag.NOTOP)) adjustTopPrideStats(topAccounts,pstats.getAccount().getAccountName(),stat,pstats.getAccount()); } return amt; } } return 0; } protected void adjustTopPrideStats(final List<Pair<String,Integer>>[][] topWhat, final String name, final AccountStats.PrideStat stat, final AccountStats astats) { for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values()) { final List<Pair<String,Integer>> top=topWhat[period.ordinal()][stat.ordinal()]; if(top == null) continue; synchronized(top) { final int pVal=astats.getPrideStat(period, stat); if(pVal <= 0) removePrideStat(top,name,0); else { boolean found=false; for(int i=0;i<top.size();i++) { if(top.get(i).first.equals(name)) { found=true; top.get(i).second=Integer.valueOf(pVal); break; } else if(pVal > top.get(i).second.intValue()) { top.add(i,new Pair<String,Integer>(name,Integer.valueOf(pVal))); removePrideStat(top,name,i+1); found=true; break; } } if((!found)&&(top.size()<PRIDE_TOP_SIZE)) top.add(new Pair<String,Integer>(name,Integer.valueOf(pVal))); } } } } @Override public void renamePlayer(final MOB mob, final String oldName) { final String newName = mob.Name(); CMLib.database().DBPlayerNameChange(oldName, newName); for(final Enumeration<MOB> p=CMLib.players().players();p.hasMoreElements();) { final MOB playerM=p.nextElement(); if(playerM.getWorshipCharID().equalsIgnoreCase(oldName)) playerM.setWorshipCharID(newName); if(playerM.getLiegeID().equalsIgnoreCase(oldName)) playerM.setLiegeID(newName); } for(int q=0;q<CMLib.quests().numQuests();q++) { final Quest Q=CMLib.quests().fetchQuest(q); if(Q.wasWinner(oldName)) { Q.declareWinner("-"+oldName); Q.declareWinner(newName); } } final PlayerStats pstats = mob.playerStats(); if(pstats!=null) { final PlayerAccount account = pstats.getAccount(); if(account != null) { account.delPlayer(oldName); account.addNewPlayer(mob); CMLib.database().DBUpdateAccount(account); account.setLastUpdated(System.currentTimeMillis()); } for(final Enumeration<Item> e=pstats.getExtItems().items(); e.hasMoreElements();) { final Item I=e.nextElement(); if(I instanceof PrivateProperty) { if(((PrivateProperty)I).getOwnerName().equalsIgnoreCase(oldName)) ((PrivateProperty)I).setOwnerName(newName); } } } for(final Enumeration<Room> r=CMLib.map().roomsFilled();r.hasMoreElements();) { Room R=r.nextElement(); if((R!=null)&&(R.roomID().length()>0)) { synchronized(("SYNC"+R.roomID()).intern()) { R=CMLib.map().getRoom(R); boolean changed=false; for(final Enumeration<Ability> a=R.effects();a.hasMoreElements();) { final Ability A=a.nextElement(); if(A==null) continue; if(A instanceof LandTitle) { if(((LandTitle)A).getOwnerName().equals(oldName)) { ((LandTitle)A).setOwnerName(newName); changed=true; } } else if(A.text().equals(oldName)) { changed=true; A.setMiscText(newName); } } if(changed) { CMLib.database().DBUpdateRoom(R); } } } } } @Override public void obliteratePlayer(MOB deadMOB, final boolean deleteAssets, final boolean quiet) { if(deadMOB==null) return; if(getPlayer(deadMOB.Name())!=null) { deadMOB=getPlayer(deadMOB.Name()); delPlayer(deadMOB); } for(final Session S : CMLib.sessions().allIterable()) { if((!S.isStopped())&&(S.mob()!=null)&&(S.mob().Name().equals(deadMOB.Name()))) deadMOB=S.mob(); } if(deadMOB.playerStats()!=null) { final PlayerAccount A=deadMOB.playerStats().getAccount(); if(A!=null) A.delPlayer(deadMOB); } final Room deadLoc=deadMOB.location(); final CMMsg msg=CMClass.getMsg(deadMOB,null,CMMsg.MSG_RETIRE,(quiet)?null:L("A horrible death cry is heard throughout the land.")); if(deleteAssets) { if(deadLoc!=null) deadLoc.send(deadMOB,msg); } final Session session = deadMOB.session(); if((session!=null)&&(!session.isStopped())) { session.logout(true); if(session!=null) session.stopSession(false,false,false); } if(deleteAssets) { try { for(final Enumeration<Room> r=CMLib.map().rooms();r.hasMoreElements();) { final Room R=r.nextElement(); if((R!=null)&&(R!=deadLoc)) { if(R.okMessage(deadMOB,msg)) R.sendOthers(deadMOB,msg); else { addPlayer(deadMOB); // your session is still gone, but at least you are not return; } } } } catch(final NoSuchElementException e) { } } final StringBuffer newNoPurge=new StringBuffer(""); final List<String> protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false)); boolean somethingDone=false; if((protectedOnes!=null)&&(protectedOnes.size()>0)) { for(int b=0;b<protectedOnes.size();b++) { final String B=protectedOnes.get(b); if(!B.equalsIgnoreCase(deadMOB.name())) newNoPurge.append(B+"\n"); else somethingDone=true; } if(somethingDone) Resources.updateFileResource("::protectedplayers.ini",newNoPurge); } final PlayerStats pStats = deadMOB.playerStats(); if(pStats != null) pStats.getExtItems().delAllItems(true); final List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.PLAYERPURGES, deadMOB); if(channels.size()>0) { String name=deadMOB.Name(); if((pStats != null) &&(pStats.getAccount()!=null)) name+=" ("+pStats.getAccount().getAccountName()+")"; final String msgStr = CMLib.lang().fullSessionTranslation("@x1 has just been deleted.",name); for(int i=0;i<channels.size();i++) { CMLib.commands().postChannel(channels.get(i),deadMOB.clans(),msgStr,true); } } CMLib.coffeeTables().bump(deadMOB,CoffeeTableRow.STAT_PURGES); CMLib.database().DBDeletePlayerOnly(deadMOB.Name()); deadMOB.delAllItems(false); for(int i=0;i<deadMOB.numItems();i++) { final Item I=deadMOB.getItem(i); if(I!=null) I.setContainer(null); } deadMOB.delAllItems(false); CMLib.database().DBUpdatePlayerItems(deadMOB); final int numFollowers=deadMOB.numFollowers(); for(int f=0;f<numFollowers;f++) { final MOB follower=deadMOB.fetchFollower(0); if(follower!=null) follower.setFollowing(null); } if(deleteAssets) { CMLib.database().DBUpdateFollowers(deadMOB); } deadMOB.delAllAbilities(); CMLib.database().DBUpdatePlayerAbilities(deadMOB); if(deleteAssets) { CMLib.database().DBDeletePlayerPrivateJournalEntries(deadMOB.Name()); CMLib.database().DBDeleteAllPlayerData(deadMOB.Name()); } final PlayerStats pstats = deadMOB.playerStats(); if(pstats!=null) { final PlayerAccount account = pstats.getAccount(); if(account != null) { account.delPlayer(deadMOB); CMLib.database().DBUpdateAccount(account); account.setLastUpdated(System.currentTimeMillis()); } } if(deleteAssets) { for(int q=0;q<CMLib.quests().numQuests();q++) { final Quest Q=CMLib.quests().fetchQuest(q); if(Q.wasWinner(deadMOB.Name())) Q.declareWinner("-"+deadMOB.Name()); } } Log.sysOut(deadMOB.name()+" has been deleted."); deadMOB.destroy(); } @Override public synchronized void obliterateAccountOnly(PlayerAccount deadAccount) { deadAccount = getLoadAccount(deadAccount.getAccountName()); if(deadAccount==null) return; accountsList.remove(deadAccount); final StringBuffer newNoPurge=new StringBuffer(""); final List<String> protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false)); boolean somethingDone=false; if((protectedOnes!=null)&&(protectedOnes.size()>0)) { for(int b=0;b<protectedOnes.size();b++) { final String B=protectedOnes.get(b); if(!B.equalsIgnoreCase(deadAccount.getAccountName())) newNoPurge.append(B+"\n"); else somethingDone=true; } if(somethingDone) Resources.updateFileResource("::protectedplayers.ini",newNoPurge); } CMLib.database().DBDeleteAccount(deadAccount); Log.sysOut(deadAccount.getAccountName()+" has been deleted."); } @Override public int savePlayers() { final boolean noCachePlayers=CMProps.getBoolVar(CMProps.Bool.PLAYERSNOCACHE); final int threadId = CMLib.getLibraryThreadID(Library.PLAYERS, this); final CMLib lib=CMLib.get(threadId); for(final MOB mob : playersList) { try { if(mob.amDestroyed()) continue; final PlayerStats pStats=mob.playerStats(); if((!mob.isMonster()) && (pStats.isSavable())) { lib._factions().updatePlayerFactions(mob,mob.location(), false); //setThreadStatus(serviceClient,"just saving "+mob.Name()); lib._database().DBUpdatePlayerMOBOnly(mob); if(mob.Name().length()==0) continue; //setThreadStatus(serviceClient,"saving "+mob.Name()+", "+mob.numItems()+" items"); lib._database().DBUpdatePlayerItems(mob); //setThreadStatus(serviceClient,"saving "+mob.Name()+", "+mob.numAbilities()+" abilities"); lib._database().DBUpdatePlayerAbilities(mob); //setThreadStatus(serviceClient,"saving "+mob.numFollowers()+" followers of "+mob.Name()); lib._database().DBUpdateFollowers(mob); final PlayerAccount account = pStats.getAccount(); pStats.setLastUpdated(System.currentTimeMillis()); if(account!=null) { //setThreadStatus(serviceClient,"saving account "+account.getAccountName()+" for "+mob.Name()); lib._database().DBUpdateAccount(account); account.setLastUpdated(System.currentTimeMillis()); } } else if((pStats!=null)&& (pStats.isSavable())) { if((pStats.getLastUpdated()==0) ||(pStats.getLastUpdated()<pStats.getLastDateTime()) ||(noCachePlayers && (!lib._flags().isInTheGame(mob, true)))) { if(noCachePlayers && (!lib._flags().isInTheGame(mob, true))) mob.delAllEffects(true); //setThreadStatus(serviceClient,"just saving "+mob.Name()); lib._database().DBUpdatePlayerMOBOnly(mob); if(mob.Name().length()==0) continue; //setThreadStatus(serviceClient,"just saving "+mob.Name()+", "+mob.numItems()+" items"); lib._database().DBUpdatePlayerItems(mob); //setThreadStatus(serviceClient,"just saving "+mob.Name()+", "+mob.numAbilities()+" abilities"); lib._database().DBUpdatePlayerAbilities(mob); pStats.setLastUpdated(System.currentTimeMillis()); } if(noCachePlayers && (!lib._flags().isInTheGame(mob, true))) { if(pStats != null) pStats.getExtItems().delAllItems(true); delPlayer(mob); mob.destroy(); } } } catch(final Throwable t) { Log.errOut(mob.Name(),t); } } return playersList.size(); } @Override public String getThinSortValue(final ThinPlayer player, final int code) { switch(code) { case 0: return player.name(); case 1: return player.charClass(); case 2: return player.race(); case 3: return Integer.toString(player.level()); case 4: return Integer.toString(player.age()); case 5: return Long.toString(player.last()); case 6: return player.email(); case 7: return player.ip(); } return player.name(); } public String getThinSortValue(final PlayerAccount account, final int code) { switch(code) { case 0: return account.getAccountName(); case 1: return Long.toString(account.getLastDateTime()); case 2: return account.getEmail(); case 3: return account.getLastIP(); case 4: return Integer.toString(account.numPlayers()); case 5: return Long.toString(account.getAccountExpiration()); } return account.getAccountName(); } @Override public int getCharThinSortCode(final String codeName, final boolean loose) { int x=CMParms.indexOf(CHAR_THIN_SORT_CODES,codeName); if(x<0) x=CMParms.indexOf(CHAR_THIN_SORT_CODES2,codeName); if(!loose) return x; if(x<0) { for(int s=0;s<CHAR_THIN_SORT_CODES.length;s++) { if(CHAR_THIN_SORT_CODES[s].startsWith(codeName)) x=s; } } if(x<0) { for(int s=0;s<CHAR_THIN_SORT_CODES2.length;s++) { if(CHAR_THIN_SORT_CODES2[s].startsWith(codeName)) x=s; } } return x; } public int getAccountThinSortCode(final String codeName, final boolean loose) { if((codeName == null)||(codeName.length()==0)) return -1; int x=CMParms.indexOf(ACCOUNT_THIN_SORT_CODES,codeName); if(!loose) return x; if(x<0) { for(int s=0;s<ACCOUNT_THIN_SORT_CODES.length;s++) { if(ACCOUNT_THIN_SORT_CODES[s].startsWith(codeName)) x=s; } } return x; } @Override @SuppressWarnings("unchecked") public Enumeration<ThinPlayer> thinPlayers(final String sort, final Map<String, Object> cache) { Vector<PlayerLibrary.ThinPlayer> V=(cache==null)?null:(Vector<PlayerLibrary.ThinPlayer>)cache.get("PLAYERLISTVECTOR"+sort); if(V==null) { V=new Vector<PlayerLibrary.ThinPlayer>(); V.addAll(CMLib.database().getExtendedUserList()); final int code=getCharThinSortCode(sort,false); if((sort.length()>0) &&(code>=0) &&(V.size()>1)) { final List<PlayerLibrary.ThinPlayer> unV=V; V=new Vector<PlayerLibrary.ThinPlayer>(); while(unV.size()>0) { ThinPlayer M=unV.get(0); String loweStr=getThinSortValue(M,code); ThinPlayer lowestM=M; for(int i=1;i<unV.size();i++) { M=unV.get(i); final String val=getThinSortValue(M,code); if((CMath.isNumber(val)&&CMath.isNumber(loweStr))) { if(CMath.s_long(val)<CMath.s_long(loweStr)) { loweStr=val; lowestM=M; } } else if(val.compareTo(loweStr)<0) { loweStr=val; lowestM=M; } } unV.remove(lowestM); V.add(lowestM); } } if(cache!=null) cache.put("PLAYERLISTVECTOR"+sort,V); } return V.elements(); } @Override @SuppressWarnings("unchecked") public Pair<Long,int[]>[] parsePrideStats(final String[] nextPeriods, final String[] prideStats) { final long now=System.currentTimeMillis(); final List<Pair<Long,int[]>> finalStats=new ArrayList<Pair<Long,int[]>>(TimeClock.TimePeriod.values().length); for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values()) { final Pair<Long,int[]> p=new Pair<Long,int[]>(Long.valueOf(0),new int[AccountStats.PrideStat.values().length]); if(period==TimeClock.TimePeriod.ALLTIME) p.first=Long.valueOf(Long.MAX_VALUE); else if((nextPeriods!=null)&&(nextPeriods.length>period.ordinal())&&(nextPeriods[period.ordinal()].length()>0)) p.first=Long.valueOf(CMath.s_long(nextPeriods[period.ordinal()])); if(now>p.first.longValue()) p.first=Long.valueOf(period.nextPeriod()); else if((prideStats.length>period.ordinal())&&(prideStats[period.ordinal()].length()>1)) { final String[] prides=prideStats[period.ordinal()].split(","); for(final AccountStats.PrideStat stat : AccountStats.PrideStat.values()) { if(prides.length > stat.ordinal()) { final String statVal=prides[stat.ordinal()]; if(statVal.length()>0) p.second[stat.ordinal()]=CMath.s_int(statVal); } } } finalStats.add(p); } return finalStats.toArray(new Pair[0]); } @Override public MOB findPlayerOnline(final String srchStr, final boolean exactOnly) { final MOB[] srch=new MOB[3]; for(final Enumeration<MOB> p=players();p.hasMoreElements();) { final MOB M=p.nextElement(); if((M!=null) &&(M.session()!=null) &&(CMLib.sessions().isSession(M.session()))) { if(M.Name().equalsIgnoreCase(srchStr)) return M; else if(M.name().equalsIgnoreCase(srchStr)) srch[0]=M; // keep looking for players if(!exactOnly) { if(CMLib.english().containsString(M.Name(),srchStr)) srch[1]=M; if(CMLib.english().containsString(M.name(),srchStr)) srch[2]=M; } } } for(int i=0;i<srch.length;i++) { if(srch[i]!=null) return srch[i]; } return null; } @Override @SuppressWarnings("unchecked") public Enumeration<PlayerAccount> accounts(final String sort, final Map<String, Object> cache) { Vector<PlayerAccount> V=(cache==null)?null:(Vector<PlayerAccount>)cache.get("ACCOUNTLISTVECTOR"+sort); if(V==null) { V=new Vector<PlayerAccount>(); if(!allAccountsLoaded) { if(CMProps.getBoolVar(CMProps.Bool.ACCOUNTSNOCACHE)) V.addAll(CMLib.database().DBListAccounts(null)); else { final List<PlayerAccount> rV=CMLib.database().DBListAccounts(null); for(final PlayerAccount A : rV) addAccount(A); allAccountsLoaded=true; V.addAll(accountsList); } } else V.addAll(accountsList); final int code=getAccountThinSortCode(sort,false); if(code<0) return V.elements(); else if(V.size()>1) { final Vector<PlayerAccount> unV=V; V=new Vector<PlayerAccount>(); while(unV.size()>0) { PlayerAccount A=unV.get(0); String loweStr=getThinSortValue(A,code); PlayerAccount lowestA=A; for(int i=1;i<unV.size();i++) { A=unV.get(i); final String val=getThinSortValue(A,code); if((CMath.isNumber(val)&&CMath.isNumber(loweStr))) { if(CMath.s_long(val)<CMath.s_long(loweStr)) { loweStr=val; lowestA=A; } } else if(val.compareTo(loweStr)<0) { loweStr=val; lowestA=A; } } unV.remove(lowestA); V.add(lowestA); } } if(cache!=null) cache.put("ACCOUNTLISTVECTOR"+sort,V); } return V.elements(); } private boolean isProtected(final List<String> protectedOnes, final String name) { boolean protectedOne=false; for(int p=0;p<protectedOnes.size();p++) { final String P=protectedOnes.get(p); if(P.equalsIgnoreCase(name)) { protectedOne=true; break; } } if(protectedOne) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" is protected from purging."); return true; } return false; } private boolean autoPurge() { if(CMSecurity.isDisabled(CMSecurity.DisFlag.AUTOPURGE)) return true; final String mask=CMProps.getVar(CMProps.Str.AUTOPURGE); if(mask.hashCode() != this.autoPurgeHash) { final int lastLevel=CMProps.getIntVar(CMProps.Int.LASTPLAYERLEVEL)+100; final long[][] presorted=CMLib.utensils().compileConditionalRange(CMParms.parseCommas(mask.trim(),true), 2, 0, lastLevel); autoPurgeDaysLevels=new long[lastLevel+1]; prePurgeLevels=new long[lastLevel+1]; for (int i = 0; i < autoPurgeDaysLevels.length; i++) autoPurgeDaysLevels[i] = 0; for (int i = 0; i < prePurgeLevels.length; i++) prePurgeLevels[i] = 0; for(int i=0;i<presorted.length;i++) { final long[] set=presorted[i]; if((set==null)||(set.length<2)) { Log.errOut("CMPlayers","Error in AUTOPURGE definition #"+(i+1)+" in coffeemud.ini file! Fix immediately!!"); continue; } final long val=set[0]; if(set[0]<=0) continue; final long prepurge=set[1]; long realVal=(val*TimeManager.MILI_DAY); long purgePoint=realVal-(prepurge*TimeManager.MILI_DAY); if(val <= 0) { realVal = 0; purgePoint = 0; } if(autoPurgeDaysLevels[i]==0) autoPurgeDaysLevels[i]=realVal; if(prePurgeLevels[i]==0) prePurgeLevels[i]=purgePoint; } this.autoPurgeHash=mask.hashCode(); } setThreadStatus(serviceClient,"autopurge process"); final List<PlayerLibrary.ThinPlayer> allUsers=CMLib.database().getExtendedUserList(); List<String> protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false)); if(protectedOnes==null) protectedOnes=new Vector<String>(); final List<String> warnedOnes=Resources.getFileLineVector(Resources.getFileResource("warnedplayers.ini",false)); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),"Autopurge process start. "+allUsers.size()+" users loaded, "+protectedOnes.size()+" protected, and "+warnedOnes.size()+" previously warned."); final StringBuilder warnStr=new StringBuilder(""); final Map<String,Long> warnMap=new TreeMap<String,Long>(); boolean warnChanged=false; if((warnedOnes!=null)&&(warnedOnes.size()>0)) { for(int b=0;b<warnedOnes.size();b++) { final String codedWarnStr=warnedOnes.get(b).trim(); if(codedWarnStr.trim().length()>0) { final int firstSpace=codedWarnStr.indexOf(' '); final String warnedName=codedWarnStr.substring(0, firstSpace).toUpperCase().trim(); final int lastSpace=codedWarnStr.lastIndexOf(' '); final long warningDateTime=CMath.s_long(codedWarnStr.substring(lastSpace+1).trim()); if((warningDateTime > 0) && (System.currentTimeMillis() < (warningDateTime + (10 * TimeManager.MILI_DAY)))) { warnStr.append(codedWarnStr+"\n"); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),"Warn loaded: "+warnedName+" last warned on "+CMLib.time().date2String(warningDateTime)); warnMap.put(warnedName, Long.valueOf(warningDateTime)); } else warnChanged=true; } } if(warnChanged) Resources.updateFileResource("::warnedplayers.ini",warnStr); warnChanged=false; } for(final ThinPlayer user : allUsers) { final String name=user.name(); final int level=user.level(); final long userLastLoginDateTime=user.last(); long purgeDateTime; long warnDateTime; if(level>=autoPurgeDaysLevels.length) { if(autoPurgeDaysLevels[autoPurgeDaysLevels.length-1]==0) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" last on "+CMLib.time().date2String(userLastLoginDateTime)+". Character is active."); continue; } purgeDateTime=userLastLoginDateTime + autoPurgeDaysLevels[autoPurgeDaysLevels.length-1]; warnDateTime=userLastLoginDateTime + prePurgeLevels[prePurgeLevels.length-1]; } else if(level>=0) { if(autoPurgeDaysLevels[level]==0) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" last on "+CMLib.time().date2String(userLastLoginDateTime)+". Character is active."); continue; } purgeDateTime=userLastLoginDateTime + autoPurgeDaysLevels[level]; warnDateTime=userLastLoginDateTime + prePurgeLevels[level]; } else { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" last on "+CMLib.time().date2String(userLastLoginDateTime)+" is level "+level+". Skipping."); continue; } if((System.currentTimeMillis()>purgeDateTime)||(System.currentTimeMillis()>warnDateTime)) { if(isProtected(protectedOnes, name)) continue; long foundWarningDateTime=-1; if(warnMap.containsKey(name.toUpperCase().trim())) foundWarningDateTime = warnMap.get(name.toUpperCase().trim()).longValue(); if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) { Log.debugOut(serviceClient.getName(), name+" last on "+CMLib.time().date2String(userLastLoginDateTime) +" will be warned on "+CMLib.time().date2String(warnDateTime) +" and purged on "+CMLib.time().date2String(purgeDateTime) +((foundWarningDateTime<0)?" never warned":(" last warned on "+CMLib.time().date2String(foundWarningDateTime)))); } if((foundWarningDateTime<0) &&(System.currentTimeMillis()>warnDateTime)) { final MOB M=getLoadPlayer(name); if((M!=null)&&(M.playerStats()!=null)) { warnStr.append(M.name()+" "+M.playerStats().getEmail()+" "+System.currentTimeMillis()+"\n"); warnMap.put(M.Name().toUpperCase().trim(),Long.valueOf(System.currentTimeMillis())); warnChanged=true; if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" is now warned."); warnPrePurge(M,purgeDateTime-System.currentTimeMillis()); } } else if((System.currentTimeMillis()>purgeDateTime) &&(foundWarningDateTime > 0) &&((System.currentTimeMillis()-foundWarningDateTime)>TimeManager.MILI_DAY)) { final MOB M=getLoadPlayer(name); if((M!=null)&&(!CMSecurity.isASysOp(M))&&(!CMSecurity.isAllowedAnywhere(M, CMSecurity.SecFlag.NOPURGE))) { obliteratePlayer(M,true, true); M.destroy(); Log.sysOut(serviceClient.getName(),"AutoPurged user "+name+" ("+M.basePhyStats().level()+"). Last logged in "+(CMLib.time().date2String(userLastLoginDateTime))+"."); } } } else if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),name+" last on "+CMLib.time().date2String(userLastLoginDateTime)+" will be warned on "+CMLib.time().date2String(warnDateTime)+" and purged on "+CMLib.time().date2String(purgeDateTime)); if(warnChanged) Resources.updateFileResource("::warnedplayers.ini",warnStr); } // accounts! if((!CMSecurity.isDisabled(CMSecurity.DisFlag.PURGEACCOUNTS))&&(CMProps.getIntVar(CMProps.Int.ACCOUNTPURGEDAYS)>0)) { for(final Enumeration<PlayerAccount> pe=CMLib.players().accounts("",null); pe.hasMoreElements();) { final PlayerAccount PA=pe.nextElement(); if((PA.numPlayers() > 0) ||(isProtected(protectedOnes, PA.getAccountName()))) continue; final long lastDateTimePurge = PA.getLastDateTime() + (TimeManager.MILI_DAY * CMProps.getIntVar(CMProps.Int.ACCOUNTPURGEDAYS)); final long lastUpdatedPurge = PA.getLastUpdated() + (TimeManager.MILI_DAY * CMProps.getIntVar(CMProps.Int.ACCOUNTPURGEDAYS)); final long accountExpPurge = PA.getAccountExpiration() + (TimeManager.MILI_DAY * CMProps.getIntVar(CMProps.Int.ACCOUNTPURGEDAYS)); long lastTime = lastDateTimePurge; if(lastUpdatedPurge > lastTime) lastTime=lastUpdatedPurge; if(accountExpPurge > lastTime) lastTime=accountExpPurge; if(System.currentTimeMillis()>lastTime) { Log.sysOut(serviceClient.getName(),"AutoPurged account "+PA.getAccountName()+"."); CMLib.players().obliterateAccountOnly(PA); } } } return true; } private void warnPrePurge(final MOB mob, long timeLeft) { // check for valid recipient if(mob==null) return; if((mob.playerStats()==null) ||(mob.playerStats().getEmail().length()==0)) // no email addy to forward TO { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),"Unable to warn "+mob.Name()+" due to lacking an email address."); return; } final boolean canReceiveRealEmail = (mob.playerStats()!=null) &&(mob.playerStats().getEmail().length()>0) &&(mob.isAttributeSet(MOB.Attrib.AUTOFORWARD)) &&((mob.playerStats().getAccount()==null) ||(!mob.playerStats().getAccount().isSet(AccountFlag.NOAUTOFORWARD))); if(!canReceiveRealEmail) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),mob.Name()+" Opting out of auto-forward."); } if(CMSecurity.isDisabled(DisFlag.SMTPCLIENT)) { if(CMSecurity.isDebugging(CMSecurity.DbgFlag.AUTOPURGE)) Log.debugOut(serviceClient.getName(),"Unable to warn "+mob.Name()+" due to smtp disable."); return; } // timeLeft is in millis final String from="AutoPurgeWarning"; final String to=mob.Name(); final String subj=CMProps.getVar(CMProps.Str.MUDNAME)+" Autopurge Warning: "+to; String textTimeLeft=""; if(timeLeft<0) timeLeft = 1000*60*60*24; if(timeLeft>(1000*60*60*24*2)) { final int days=(int)CMath.div((double)timeLeft,1000*60*60*24); textTimeLeft = days + " days"; } else { final int hours=(int)CMath.div((double)timeLeft,1000*60*60); textTimeLeft = hours + " hours"; } final String msg=L("Your character, @x1, is going to be autopurged by the system in @x2. ",to,textTimeLeft)+ L("If you would like to keep this character active, please re-login. This is an automated message, please do not reply."); CMLib.smtp().emailOrJournal(from, from, to, subj, CMLib.coffeeFilter().simpleOutFilter(msg)); } @Override public void resetAllPrideStats() { CMLib.threads().executeRunnable(new Runnable() { @Override public void run() { final List<Pair<String,Integer>>[][] pStats = CMLib.database().DBScanPridePlayerWinners(PRIDE_TOP_SIZE, (short)5); for(int x=0;x<pStats.length;x++) { for(int y=0;y<pStats[x].length;y++) topPlayers[x][y]=pStats[x][y]; } if(CMProps.isUsingAccountSystem()) { final List<Pair<String,Integer>>[][] aStats = CMLib.database().DBScanPrideAccountWinners(PRIDE_TOP_SIZE, (short)5); for(int x=0;x<aStats.length;x++) { for(int y=0;y<aStats[x].length;y++) topAccounts[x][y]=aStats[x][y]; } } for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values()) topPrideExpiration[period.ordinal()] = period.nextPeriod(); } }); } @Override public boolean activate() { if(serviceClient==null) { name="THPlayers"+Thread.currentThread().getThreadGroup().getName().charAt(0); serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK|Tickable.TICKID_LONGERMASK, MudHost.TIME_SAVETHREAD_SLEEP, 1); resetAllPrideStats(); } return true; } protected void saveLastMonthsTopsData() { final Command C=CMClass.getCommand("Top"); if(C!=null) { final Calendar calC=Calendar.getInstance(); final String dir="::/resources/sys_reports/"; final CMFile dirF=new CMFile(dir, null); if(!dirF.exists()) dirF.mkdir(); final String filename = "::/resources/sys_reports/"+name()+"_top_report_"+calC.get(Calendar.YEAR)+"-"+(calC.get(Calendar.MONTH)+1)+"-"+calC.get(Calendar.DAY_OF_MONTH)+".txt"; final CMFile F=new CMFile(filename, null); if(!F.exists()) { final MOB mob=CMLib.map().deity(); Object o; try { o = C.executeInternal(mob, 0, new Object[0]); if(o instanceof String) { final String str=CMStrings.removeColors((String)o); F.saveText(str); } } catch (final IOException e) { Log.errOut(e); } } } } @Override public boolean tick(final Tickable ticking, final int tickID) { if(!CMSecurity.isDisabled(CMSecurity.DisFlag.PLAYERTHREAD) &&(tickStatus == Tickable.STATUS_NOT)) { try { tickStatus=Tickable.STATUS_ALIVE; isDebugging=CMSecurity.isDebugging(DbgFlag.PLAYERTHREAD); if(checkDatabase() && CMProps.getBoolVar(CMProps.Bool.MUDSTARTED)) { setThreadStatus(serviceClient,"not saving players"); if((!CMSecurity.isDisabled(CMSecurity.DisFlag.SAVETHREAD)) &&(!CMSecurity.isDisabled(CMSecurity.DisFlag.PLAYERTHREAD))) { setThreadStatus(serviceClient,"checking player titles"); for(final MOB M : playersList) { if((M.playerStats()!=null) &&(M.playerStats().isSavable()) &&(CMLib.flags().isInTheGame(M,true))) { //boolean didSomething = CMLib.titles().evaluateAutoTitles(M); //didSomething = CMLib.achievements().evaluatePlayerAchievements(M);// || didSomething; CMLib.achievements().evaluateAccountAchievements(M);// || didSomething; //if(didSomething)&&(!CMLib.flags().isInTheGame(M,true))) // CMLib.database().DBUpdatePlayerMOBOnly(M); } } autoPurge(); setThreadStatus(serviceClient,"saving players"); if(!CMSecurity.isSaveFlag(CMSecurity.SaveFlag.NOPLAYERS)) savePlayers(); setThreadStatus(serviceClient,"not saving players"); } setThreadStatus(serviceClient,"expiring top metrics"); final long now=System.currentTimeMillis(); List<Pair<String,Integer>> top; for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values()) { if(period == TimeClock.TimePeriod.ALLTIME) continue; if(now > topPrideExpiration[period.ordinal()]) { if(period == TimeClock.TimePeriod.MONTH) saveLastMonthsTopsData(); topPrideExpiration[period.ordinal()] = period.nextPeriod(); for(final AccountStats.PrideStat stat : AccountStats.PrideStat.values()) { top=topAccounts[period.ordinal()][stat.ordinal()]; if(top!=null) { synchronized(top) { top.clear(); } } top=topPlayers[period.ordinal()][stat.ordinal()]; if(top!=null) { synchronized(top) { top.clear(); } } } } } setThreadStatus(serviceClient,"not doing anything"); } } finally { tickStatus=Tickable.STATUS_NOT; setThreadStatus(serviceClient,"sleeping"); } } return true; } @Override public boolean shutdown() { playersList.clear(); playerLocations.clear(); if(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK)) { CMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK); serviceClient=null; } return true; } @Override public void forceTick() { serviceClient.tickTicker(false); } }