Files
Server/src/engine/gameManager/GroupManager.java
T
MagicBotandClaude Opus 5.5 6b4e9815e0 Switch Item.template and MobLoot to CObjectTemplate
Item.template is now the cache template from CObjectTemplate.lookup
instead of the legacy DB ItemTemplate, so item.template.cacheTemplate.X
becomes item.template.X. MobLoot, LootManager, ItemManager.isTwoHanded/
isShield and CombatManager.getSwingAnimation take a CObjectTemplate.

No value changes: every getter already read the cache template through
the bridge, and the lookup resolves the same 7586 ids as
static_item_templates with the same item_type (offline parity p17.py,
0 differences).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:55:28 -04:00

387 lines
12 KiB
Java

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.gameManager;
import engine.mbEnums;
import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.UpdateGoldMsg;
import engine.net.client.msg.group.GroupUpdateMsg;
import engine.objects.*;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public enum GroupManager {
GROUPMANAGER;
// used for quick lookup of groups by the ID of the group sent in the msg
private static final ConcurrentHashMap<Integer, Group> groupsByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
// an index for playercharacters to group membership
private static final ConcurrentHashMap<AbstractCharacter, Group> groupsByAC = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
private static int groupCount = 0;
/*
* Class Implementation
*/
public static void removeFromGroups(AbstractCharacter ac) {
Group gr = null;
synchronized (GroupManager.groupsByAC) {
gr = GroupManager.groupsByAC.remove(ac);
}
}
public static void LeaveGroup(ClientConnection origin) {
PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
LeaveGroup(source);
}
public static void LeaveGroup(PlayerCharacter source) {
if (source == null)
return;
Group group = GroupManager.groupsByAC.get(source);
if (group == null) // source is not in a group
return;
// Cleanup group window for player quiting
GroupUpdateMsg groupUpdateMsg = new GroupUpdateMsg();
groupUpdateMsg.setGroup(group);
groupUpdateMsg.setPlayer(source);
groupUpdateMsg.setMessageType(3);
Set<PlayerCharacter> groupMembers = group.getMembers();
for (PlayerCharacter groupMember : groupMembers) {
if (groupMember == null)
continue;
groupUpdateMsg = new GroupUpdateMsg();
groupUpdateMsg.setGroup(group);
groupUpdateMsg.setPlayer(source);
groupUpdateMsg.setMessageType(3);
groupUpdateMsg.setPlayer(groupMember);
Dispatch dispatch = Dispatch.borrow(source, groupUpdateMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
// Remove from group
int size = group.removeGroupMember(source);
// remove from the group -> ac mapping list
GroupManager.groupsByAC.remove(source);
if (size == 0) {
GroupManager.deleteGroup(group);
return; // group empty so cleanup group and we're done
}
// set new group lead if needed
if (group.getGroupLead() == source) {
PlayerCharacter newLead = group.getMembers().iterator().next();
group.setGroupLead(newLead.getObjectUUID());
groupUpdateMsg = new GroupUpdateMsg();
groupUpdateMsg.setGroup(group);
groupUpdateMsg.setPlayer(newLead);
groupUpdateMsg.addPlayer(source);
groupUpdateMsg.setMessageType(2);
group.sendUpdate(groupUpdateMsg);
// Disable Formation
newLead.setFollow(false);
groupUpdateMsg = new GroupUpdateMsg();
groupUpdateMsg.setGroup(group);
groupUpdateMsg.setPlayer(newLead);
groupUpdateMsg.setMessageType(8);
group.sendUpdate(groupUpdateMsg);
}
//send message to group
PlayerCharacter pc = group.getGroupLead();
//Fixed
String text = source.getFirstName() + " has left the group.";
ChatManager.chatGroupInfo(pc, text);
// cleanup other group members screens
groupUpdateMsg = new GroupUpdateMsg();
groupUpdateMsg.setGroup(group);
groupUpdateMsg.setPlayer(source);
groupUpdateMsg.setMessageType(3);
group.sendUpdate(groupUpdateMsg);
}
//This updates health/stamina/mana and loc of all players in group
public static void RefreshWholeGroupList(PlayerCharacter source, ClientConnection origin, Group gexp) {
if (source == null || origin == null)
return;
Group group = GroupManager.groupsByAC.get(source);
if (group == null)
return;
if (gexp.getObjectUUID() != group.getObjectUUID())
return;
Set<PlayerCharacter> groupMembers = group.getMembers();
if (groupMembers.size() < 2)
return;
// Send all group members health/mana/stamina/loc.
for (PlayerCharacter groupMember : groupMembers) {
if (groupMember == null)
continue;
GroupUpdateMsg gum = new GroupUpdateMsg(5, 1, groupMembers, group);
gum.setPlayerUUID(groupMember.getObjectUUID());
Dispatch dispatch = Dispatch.borrow(groupMember, gum);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
}
public static void RefreshMyGroupList(PlayerCharacter source, ClientConnection origin) {
if (source == null || origin == null)
return;
Group group = GroupManager.groupsByAC.get(source);
if (group == null)
return;
Set<PlayerCharacter> members = group.getMembers();
// Send all group members to player added
for (PlayerCharacter groupMember : members) {
if (groupMember == null)
continue;
GroupUpdateMsg gum = new GroupUpdateMsg();
gum.setGroup(group);
gum.setMessageType(1);
gum.setPlayer(groupMember);
Dispatch dispatch = Dispatch.borrow(groupMember, gum);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
}
public static void RefreshMyGroupListSinglePlayer(PlayerCharacter source, ClientConnection origin, PlayerCharacter playerToRefresh) {
// send msg type 1 to the source player on this connection to update the group
// list stats for the player that has just been loaded
if (source == null || origin == null || playerToRefresh == null)
return;
Group group = GroupManager.groupsByAC.get(source);
if (group == null)
return;
// only send if the 2 players are in the same group
if (group != GroupManager.groupsByAC.get(playerToRefresh))
return;
GroupUpdateMsg gum = new GroupUpdateMsg();
gum.setGroup(group);
gum.setMessageType(1);
gum.setPlayer(playerToRefresh);
Dispatch dispatch = Dispatch.borrow(source, gum);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
public static void RefreshOthersGroupList(PlayerCharacter source) {
// refresh my stats on everyone elses group list
if (source == null)
return;
Group group = GroupManager.groupsByAC.get(source);
if (group == null)
return;
//construct message
GroupUpdateMsg gim = new GroupUpdateMsg();
gim.setGroup(group);
gim.setMessageType(1);
gim.setPlayer(source);
group.sendUpdate(gim);
}
public static int incrGroupCount() {
GroupManager.groupCount++;
return GroupManager.groupCount;
}
public static boolean deleteGroup(Group g) {
// remove all players from the mapping
Set<PlayerCharacter> members = g.getMembers();
for (PlayerCharacter pc : members) {
if (pc != null) {
GroupManager.removeFromGroups(pc);
}
}
// remove the group ID from the list
GroupManager.groupsByID.remove(g.getObjectUUID());
g.clearMembers();
g.removeUpdateGroupJob();
return true;
}
public static Group addNewGroup(Group group) {
PlayerCharacter pc = group.getGroupLead();
GroupManager.addGroup(group);
if (pc != null) {
GroupManager.addPlayerGroupMapping(pc, group);
return group;
}
return null;
}
private static Group addGroup(Group group) {
if (GroupManager.groupsByID.containsKey(group.getObjectUUID())) {
return null;
}
GroupManager.groupsByID.put(group.getObjectUUID(), group);
return group;
}
public static Group getGroup(int groupID) {
return GroupManager.groupsByID.get(groupID);
}
public static Group getGroup(PlayerCharacter pc) {
return GroupManager.groupsByAC.get(pc);
}
public static void addPlayerGroupMapping(PlayerCharacter pc, Group grp) {
GroupManager.groupsByAC.put(pc, grp);
}
public static boolean goldSplit(PlayerCharacter pc, Item item, ClientConnection origin, AbstractWorldObject tar) {
if (item == null || pc == null || tar == null) {
Logger.error("null something");
return false;
}
if (!item.template.getItemType().equals(mbEnums.ItemType.GOLD)) //only split goldItem
return false;
Group group = getGroup(pc);
if (group == null || !group.getSplitGold()) //make sure player is grouped and split is on
return false;
ArrayList<PlayerCharacter> playersSplit = new ArrayList<>();
//get group members
for (PlayerCharacter groupMember : group.getMembers()) {
if (pc.getLoc().distanceSquared2D(groupMember.getLoc()) > MBServerStatics.CHARACTER_LOAD_RANGE * MBServerStatics.CHARACTER_LOAD_RANGE)
continue;
if (!groupMember.isAlive())
continue;
playersSplit.add(groupMember);
}
//make sure more than one group member in loot range
int size = playersSplit.size();
if (size < 2)
return false;
int total = item.getNumOfItems();
int amount = total / size;
int dif = total - (size * amount);
if (AbstractWorldObject.IsAbstractCharacter(tar)) {
} else if (tar.getObjectType().equals(mbEnums.GameObjectType.Corpse)) {
Corpse corpse = (Corpse) tar;
corpse.getInventory().remove(item);
} else {
Logger.error("target not corpse or character");
return false;
}
if (item.getObjectType() == mbEnums.GameObjectType.MobLoot) {
if (tar.getObjectType() == mbEnums.GameObjectType.Mob) {
((Mob) tar).charItemManager.delete(item);
} else
item.setNumOfItems(0);
} else
item.setNumOfItems(0);
for (PlayerCharacter splitPlayer : playersSplit) {
int amt = (group.isGroupLead(splitPlayer)) ? (amount + dif) : amount;
if (amt > 0)
splitPlayer.charItemManager.addGoldToInventory(amt, false);
}
for (PlayerCharacter splitPlayer : playersSplit) {
UpdateGoldMsg ugm = new UpdateGoldMsg(splitPlayer);
ugm.configure();
Dispatch dispatch = Dispatch.borrow(splitPlayer, ugm);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
UpdateGoldMsg updateTargetGold = new UpdateGoldMsg(tar);
updateTargetGold.configure();
DispatchManager.dispatchMsgToInterestArea(tar, updateTargetGold, mbEnums.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
// //TODO send group split message
String text = "Group Split: " + amount;
ChatManager.chatGroupInfo(pc, text);
return true;
}
}