Added families. (#511)

* Added families.

* Added joining of families.
This commit is contained in:
Ubaware
2019-08-19 10:16:10 -07:00
committed by Ronan Lana
parent f958624f6a
commit ec5a412e37
33 changed files with 1670 additions and 276 deletions

View File

@@ -1,8 +1,6 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -21,33 +19,210 @@
*/
package client;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.server.Server;
import tools.DatabaseConnection;
import tools.FilePrinter;
import tools.MaplePacketCreator;
import tools.Pair;
/**
* @author Ubaware
*/
public class MapleFamilyEntry {
private int familyId;
private int rank, reputation, totalReputation, todaysRep, totalJuniors, juniors, chrid;
private String familyName;
private final int characterID;
private volatile MapleFamily family;
private volatile MapleCharacter character;
public int getId() {
return familyId;
private volatile MapleFamilyEntry senior;
private final MapleFamilyEntry[] juniors = new MapleFamilyEntry[2];
private final int[] entitlements = new int[11];
private volatile int reputation, totalReputation;
private volatile int todaysRep, repsToSenior; //both are daily values
private volatile int totalJuniors, totalSeniors;
private volatile int generation;
private volatile boolean repChanged; //used to ignore saving unchanged rep values
// cached values for offline players
private String charName;
private int level;
private MapleJob job;
public MapleFamilyEntry(MapleFamily family, int characterID, String charName, int level, MapleJob job) {
this.family = family;
this.characterID = characterID;
this.charName = charName;
this.level = level;
this.job = job;
}
public void setFamilyId(int familyId) {
this.familyId = familyId;
public MapleCharacter getChr() {
return character;
}
public int getRank() {
return rank;
public void setCharacter(MapleCharacter newCharacter) {
if(newCharacter == null) cacheOffline(newCharacter);
else newCharacter.setFamilyEntry(this);
this.character = newCharacter;
}
public void setRank(int rank) {
this.rank = rank;
private void cacheOffline(MapleCharacter chr) {
if(chr != null) {
charName = chr.getName();
level = chr.getLevel();
job = chr.getJob();
}
}
public synchronized void join(MapleFamilyEntry senior) {
if(senior == null || getSenior() != null) return;
MapleFamily oldFamily = getFamily();
MapleFamily newFamily = senior.getFamily();
setSenior(senior, false);
addSeniorCount(newFamily.getTotalGenerations(), newFamily); //count will be overwritten by doFullCount()
newFamily.getLeader().doFullCount(); //easier than keeping track of numbers
oldFamily.setMessage(null, true);
newFamily.addEntryTree(this);
Server.getInstance().getWorld(oldFamily.getWorld()).removeFamily(oldFamily.getID());
//db
try(Connection con = DatabaseConnection.getConnection()) {
con.setAutoCommit(false);
boolean success = updateDBChangeFamily(con, getChrId(), newFamily.getID(), senior.getChrId());
for(MapleFamilyEntry junior : juniors) { // better to duplicate this than the SQL code
if(junior != null) {
success = junior.updateNewFamilyDB(con); // recursively updates juniors in db
if(!success) break;
}
}
if(!success) {
con.rollback();
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Could not absorb " + oldFamily.getName() + " family into " + newFamily.getName() + " family. (SQL ERROR)");
}
con.setAutoCommit(true);
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get connection to DB.");
e.printStackTrace();
}
}
public synchronized void fork() {
MapleFamily oldFamily = getFamily();
MapleFamilyEntry oldSenior = getSenior();
family = new MapleFamily(-1, oldFamily.getWorld());
Server.getInstance().getWorld(family.getWorld()).addFamily(family.getID(), family);
setSenior(null, false);
family.setLeader(this);
addSeniorCount(-getTotalSeniors(), family);
setTotalSeniors(0);
if(oldSenior != null) {
oldSenior.addJuniorCount(-getTotalJuniors());
oldSenior.removeJunior(this);
oldFamily.getLeader().doFullCount();
}
oldFamily.removeEntryBranch(this);
family.addEntryTree(this);
this.repsToSenior = 0;
this.repChanged = true;
family.setMessage("", true);
doFullCount(); //to make sure all counts are correct
// update db
try(Connection con = DatabaseConnection.getConnection()) {
con.setAutoCommit(false);
boolean success = updateDBChangeFamily(con, getChrId(), getFamily().getID(), 0);
for(MapleFamilyEntry junior : juniors) { // better to duplicate this than the SQL code
if(junior != null) {
success = junior.updateNewFamilyDB(con); // recursively updates juniors in db
if(!success) break;
}
}
if(!success) {
con.rollback();
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Could not fork family with new leader " + getName() + ". (Old senior : " + oldSenior.getName() + ", leader :" + oldFamily.getLeader().getName() + ")");
}
con.setAutoCommit(true);
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get connection to DB.");
e.printStackTrace();
}
}
private synchronized boolean updateNewFamilyDB(Connection con) {
if(!updateFamilyEntryDB(con, getChrId(), getFamily().getID())) return false;
if(!updateCharacterFamilyDB(con, getChrId(), getFamily().getID(), true)) return false;
for(MapleFamilyEntry junior : juniors) {
if(junior != null) {
if(!junior.updateNewFamilyDB(con)) return false;
}
}
return true;
}
private static boolean updateFamilyEntryDB(Connection con, int cid, int familyid) {
try(PreparedStatement ps = con.prepareStatement("UPDATE family_character SET familyid = ? WHERE cid = ?")) {
ps.setInt(1, familyid);
ps.setInt(2, cid);
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not update family id in 'family_character' for character id " + cid + ". (fork)");
e.printStackTrace();
return false;
}
return true;
}
private synchronized void addSeniorCount(int seniorCount, MapleFamily newFamily) { // traverses tree and subtracts seniors and updates family
if(newFamily != null) this.family = newFamily;
setTotalSeniors(getTotalSeniors() + seniorCount);
this.generation += seniorCount;
for(MapleFamilyEntry junior : juniors) {
if(junior != null) junior.addSeniorCount(seniorCount, newFamily);
}
}
private synchronized void addJuniorCount(int juniorCount) { // climbs tree and adds junior count
setTotalJuniors(getTotalJuniors() + juniorCount);
MapleFamilyEntry senior = getSenior();
if(senior != null) senior.addJuniorCount(juniorCount);
}
public MapleFamily getFamily() {
return family;
}
public int getChrId() {
return chrid;
return characterID;
}
public void setChrId(int chrid) {
this.chrid = chrid;
public String getName() {
MapleCharacter chr = character;
if(chr != null) return chr.getName();
else return charName;
}
public int getLevel() {
MapleCharacter chr = character;
if(chr != null) return chr.getLevel();
else return level;
}
public MapleJob getJob() {
MapleCharacter chr = character;
if(chr != null) return chr.getJob();
else return job;
}
public int getReputation() {
@@ -59,16 +234,182 @@ public class MapleFamilyEntry {
}
public void setReputation(int reputation) {
if(reputation != this.reputation) this.repChanged = true;
this.reputation = reputation;
}
public void setTodaysRep(int today) {
if(today != todaysRep) this.repChanged = true;
this.todaysRep = today;
}
public void gainReputation(int gain) {
public int getRepsToSenior() {
return repsToSenior;
}
public void setRepsToSenior(int reputation) {
if(reputation != this.repsToSenior) this.repChanged = true;
this.repsToSenior = reputation;
}
public void gainReputation(int gain, boolean countTowardsTotal) {
gainReputation(gain, countTowardsTotal, this);
}
private void gainReputation(int gain, boolean countTowardsTotal, MapleFamilyEntry from) {
if(gain != 0) repChanged = true;
this.reputation += gain;
this.totalReputation += gain;
this.todaysRep += gain;
if(gain > 0 && countTowardsTotal) {
this.totalReputation += gain;
}
MapleCharacter chr = getChr();
if(chr != null) chr.announce(MaplePacketCreator.sendGainRep(gain, from != null ? from.getName() : ""));
}
public void giveReputationToSenior(int gain, boolean includeSuperSenior) {
int actualGain = gain;
MapleFamilyEntry senior = getSenior();
if(senior != null && senior.getLevel() < getLevel() && gain > 0) actualGain /= 2; //don't halve negative values
if(senior != null) {
senior.gainReputation(actualGain, true, this);
if(actualGain > 0) {
this.repsToSenior += actualGain;
this.repChanged = true;
}
if(includeSuperSenior) {
senior = senior.getSenior();
if(senior != null) {
senior.gainReputation(actualGain, true, this);
}
}
}
}
public int getTotalReputation() {
return totalReputation;
}
public void setTotalReputation(int totalReputation) {
if(totalReputation != this.totalReputation) this.repChanged = true;
this.totalReputation = totalReputation;
}
public MapleFamilyEntry getSenior() {
return senior;
}
public synchronized boolean setSenior(MapleFamilyEntry senior, boolean save) {
if(this.senior == senior) return false;
MapleFamilyEntry oldSenior = this.senior;
this.senior = senior;
if(senior != null) {
if(senior.addJunior(this)) {
if(save) {
updateDBChangeFamily(getChrId(), senior.getFamily().getID(), senior.getChrId());
}
if(this.repsToSenior != 0) this.repChanged = true;
this.repsToSenior = 0;
this.addSeniorCount(1, null);
this.setTotalSeniors(senior.getTotalSeniors() + 1);
return true;
}
} else {
if(oldSenior != null) {
oldSenior.removeJunior(this);
}
}
return false;
}
private static boolean updateDBChangeFamily(int cid, int familyid, int seniorid) {
try(Connection con = DatabaseConnection.getConnection()) {
return updateDBChangeFamily(con, cid, familyid, seniorid);
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get connection to DB.");
e.printStackTrace();
return false;
}
}
private static boolean updateDBChangeFamily(Connection con, int cid, int familyid, int seniorid) {
try(PreparedStatement ps = con.prepareStatement("UPDATE family_character SET familyid = ?, seniorid = ?, reptosenior = 0 WHERE cid = ?")) {
ps.setInt(1, familyid);
ps.setInt(2, seniorid);
ps.setInt(3, cid);
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not update seniorid in 'family_character' for character id " + cid + ".");
e.printStackTrace();
return false;
}
return updateCharacterFamilyDB(con, cid, familyid, false);
}
private static boolean updateCharacterFamilyDB(Connection con, int charid, int familyid, boolean fork) {
try(PreparedStatement ps = con.prepareStatement("UPDATE characters SET familyid = ? WHERE id = ?")) {
ps.setInt(1, familyid);
ps.setInt(2, charid);
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not update familyid in 'characters' for character id " + charid + " when changing family. " + (fork ? "(fork)" : ""));
e.printStackTrace();
return false;
}
return true;
}
public List<MapleFamilyEntry> getJuniors() {
return Collections.unmodifiableList(Arrays.asList(juniors));
}
public MapleFamilyEntry getOtherJunior(MapleFamilyEntry junior) {
if(juniors[0] == junior) return juniors[1];
else if(juniors[1] == junior) return juniors[0];
return null;
}
public int getJuniorCount() { //close enough to be relatively consistent to multiple threads (and the result is not vital)
int juniorCount = 0;
if(juniors[0] != null) juniorCount++;
if(juniors[1] != null) juniorCount++;
return juniorCount;
}
public synchronized boolean addJunior(MapleFamilyEntry newJunior) {
for(int i = 0; i < juniors.length; i++) {
if(juniors[i] == null) { // successfully add new junior to family
juniors[i] = newJunior;
addJuniorCount(1);
getFamily().addEntry(newJunior);
return true;
}
}
return false;
}
public synchronized boolean isJunior(MapleFamilyEntry entry) { //require locking since result accuracy is vital
if(juniors[0] == entry) return true;
else if(juniors[1] == entry) return true;
return false;
}
public synchronized boolean removeJunior(MapleFamilyEntry junior) {
for(int i = 0; i < juniors.length; i++) {
if(juniors[i] == junior) {
juniors[i] = null;
return true;
}
}
return false;
}
public int getTotalSeniors() {
return totalSeniors;
}
public void setTotalSeniors(int totalSeniors) {
this.totalSeniors = totalSeniors;
}
public int getTotalJuniors() {
@@ -78,28 +419,134 @@ public class MapleFamilyEntry {
public void setTotalJuniors(int totalJuniors) {
this.totalJuniors = totalJuniors;
}
public int getJuniors() {
return juniors;
public void announceToSenior(byte[] packet, boolean includeSuperSenior) {
MapleFamilyEntry senior = getSenior();
if(senior != null) {
MapleCharacter seniorChr = senior.getChr();
if(seniorChr != null) seniorChr.announce(packet);
senior = senior.getSenior();
if(includeSuperSenior && senior != null) {
seniorChr = senior.getChr();
if(seniorChr != null) seniorChr.announce(packet);
}
}
}
public void updateSeniorFamilyInfo(boolean includeSuperSenior) {
MapleFamilyEntry senior = getSenior();
if(senior != null) {
MapleCharacter seniorChr = senior.getChr();
if(seniorChr != null) seniorChr.announce(MaplePacketCreator.getFamilyInfo(senior));
senior = senior.getSenior();
if(includeSuperSenior && senior != null) {
seniorChr = senior.getChr();
if(seniorChr != null) seniorChr.announce(MaplePacketCreator.getFamilyInfo(senior));
}
}
}
public void setJuniors(int juniors) {
this.juniors = juniors;
/**
* Traverses entire family tree to update senior/junior counts. Call on leader.
*/
public synchronized void doFullCount() {
Pair<Integer, Integer> counts = this.traverseAndUpdateCounts(0);
getFamily().setTotalGenerations(counts.getLeft() + 1);
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
private Pair<Integer, Integer> traverseAndUpdateCounts(int seniors) { // recursion probably limits family size, but it should handle a depth of a few thousand
setTotalSeniors(seniors);
this.generation = seniors;
int juniorCount = 0;
int highestGeneration = this.generation;
for(MapleFamilyEntry entry : juniors) {
if(entry != null) {
Pair<Integer, Integer> counts = entry.traverseAndUpdateCounts(seniors + 1);
juniorCount += counts.getRight(); //total juniors
if(counts.getLeft() > highestGeneration) highestGeneration = counts.getLeft();
}
}
setTotalJuniors(juniorCount);
return new Pair<>(highestGeneration, juniorCount); //creating new objects to return is a bit inefficient, but cleaner than packing into a long
}
public String getFamilyName() {
return familyName;
public boolean useEntitlement(MapleFamilyEntitlement entitlement) {
int id = entitlement.ordinal();
if(entitlements[id] >= 1) return false;
try(Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("INSERT INTO family_entitlement (entitlementid, charid, timestamp) VALUES (?, ?, ?)")) {
ps.setInt(1, id);
ps.setInt(2, getChrId());
ps.setLong(3, System.currentTimeMillis());
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not insert new row in 'family_entitlement' for character " + getName() + ".");
e.printStackTrace();
}
entitlements[id]++;
return true;
}
public boolean refundEntitlement(MapleFamilyEntitlement entitlement) {
int id = entitlement.ordinal();
try(Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("DELETE FROM family_entitlement WHERE entitlementid = ? AND charid = ?")) {
ps.setInt(1, id);
ps.setInt(2, getChrId());
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not refund family entitlement \"" + entitlement.getName() + "\" for character " + getName() + ".");
e.printStackTrace();
}
entitlements[id] = 0;
return true;
}
public int getTotalReputation() {
return totalReputation;
public boolean isEntitlementUsed(MapleFamilyEntitlement entitlement) {
return entitlements[entitlement.ordinal()] >= 1;
}
public void setTotalReputation(int totalReputation) {
this.totalReputation = totalReputation;
public int getEntitlementUsageCount(MapleFamilyEntitlement entitlement) {
return entitlements[entitlement.ordinal()];
}
public void setEntitlementUsed(int id) {
entitlements[id]++;
}
public void resetEntitlementUsages() {
for(MapleFamilyEntitlement entitlement : MapleFamilyEntitlement.values()) {
entitlements[entitlement.ordinal()] = 0;
}
}
public boolean saveReputation() {
if(!repChanged) return true;
try(Connection con = DatabaseConnection.getConnection()) {
return saveReputation(con);
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Could not get connection to DB.");
e.printStackTrace();
return false;
}
}
public boolean saveReputation(Connection con) {
if(!repChanged) return true;
try (PreparedStatement ps = con.prepareStatement("UPDATE family_character SET reputation = ?, todaysrep = ?, totalreputation = ?, reptosenior = ? WHERE cid = ?")) {
ps.setInt(1, getReputation());
ps.setInt(2, getTodaysRep());
ps.setInt(3, getTotalReputation());
ps.setInt(4, getRepsToSenior());
ps.setInt(5, getChrId());
ps.executeUpdate();
} catch(SQLException e) {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, e, "Failed to autosave rep to 'family_character' for charid " + getChrId());
e.printStackTrace();
return false;
}
return true;
}
public void savedSuccessfully() {
this.repChanged = false;
}
}