mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-10-06 21:13:37 -04:00
1136 lines
35 KiB
Java
1136 lines
35 KiB
Java
/***********************************************************************************
|
|
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
|
|
* *
|
|
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
|
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
|
* Our goal is to create an emulator which will provide a server for players to *
|
|
* continue playing a game similar to the one they used to play. We are basing *
|
|
* it on the final publish of the game prior to end-game events. *
|
|
* *
|
|
* This file is part of Holocore. *
|
|
* *
|
|
* -------------------------------------------------------------------------------- *
|
|
* *
|
|
* Holocore is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Affero General Public License as *
|
|
* published by the Free Software Foundation, either version 3 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* Holocore is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Affero General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Affero General Public License *
|
|
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
***********************************************************************************/
|
|
package resources.objects;
|
|
|
|
import network.packets.Packet;
|
|
import network.packets.swg.zone.SceneCreateObjectByCrc;
|
|
import network.packets.swg.zone.SceneDestroyObject;
|
|
import network.packets.swg.zone.SceneEndBaselines;
|
|
import network.packets.swg.zone.UpdateContainmentMessage;
|
|
import network.packets.swg.zone.UpdateTransformMessage;
|
|
import network.packets.swg.zone.UpdateTransformWithParentMessage;
|
|
import network.packets.swg.zone.baselines.Baseline.BaselineType;
|
|
import network.packets.swg.zone.object_controller.DataTransform;
|
|
import network.packets.swg.zone.object_controller.DataTransformWithParent;
|
|
import resources.Location;
|
|
import resources.Terrain;
|
|
import resources.buildout.BuildoutArea;
|
|
import resources.client_info.visitors.ObjectData.ObjectDataAttribute;
|
|
import resources.common.CRC;
|
|
import resources.containers.ContainerPermissions;
|
|
import resources.containers.ContainerPermissionsFactory;
|
|
import resources.containers.ContainerResult;
|
|
import resources.containers.DefaultPermissions;
|
|
import resources.encodables.StringId;
|
|
import resources.network.BaselineBuilder;
|
|
import resources.network.BaselineObject;
|
|
import resources.network.NetBuffer;
|
|
import resources.network.NetBufferStream;
|
|
import resources.objects.building.BuildingObject;
|
|
import resources.objects.creature.CreatureObject;
|
|
import resources.persistable.Persistable;
|
|
import resources.persistable.SWGObjectFactory;
|
|
import resources.player.Player;
|
|
import resources.player.PlayerState;
|
|
import resources.server_info.Log;
|
|
import services.CoreManager;
|
|
import services.objects.ObjectCreator;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Hashtable;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
public abstract class SWGObject extends BaselineObject implements Comparable<SWGObject>, Persistable {
|
|
|
|
private final Location location;
|
|
private final long objectId;
|
|
private final HashMap <String, SWGObject> slots; // HashMap used for null value support
|
|
private final Set<SWGObject> containedObjects;
|
|
private final Map <String, String> attributes;
|
|
private final Map <ObjectDataAttribute, Object> dataAttributes;
|
|
private transient Set <SWGObject> objectsAware;
|
|
private transient Set <SWGObject> customAware;
|
|
private transient BuildoutArea buildoutArea;
|
|
private transient Player owner;
|
|
private List <List <String>> arrangement;
|
|
|
|
private ObjectClassification classification = ObjectClassification.GENERATED;
|
|
private GameObjectType gameObjectType = GameObjectType.GOT_NONE;
|
|
private ContainerPermissions containerPermissions;
|
|
private SWGObject parent = null;
|
|
private StringId stringId = new StringId("", "");
|
|
private StringId detailStringId = new StringId("", "");
|
|
private String template = "";
|
|
private int crc = 0;
|
|
private String objectName = "";
|
|
private int volume = 0;
|
|
private float complexity = 1;
|
|
private int containerType = 0;
|
|
private double loadRange = 0;
|
|
private int areaId = -1;
|
|
|
|
private int slotArrangement = -1;
|
|
|
|
public SWGObject() {
|
|
this(0, null);
|
|
}
|
|
|
|
public SWGObject(long objectId, BaselineType objectType) {
|
|
super(objectType);
|
|
this.objectId = objectId;
|
|
this.location = new Location();
|
|
this.objectsAware = new HashSet<>();
|
|
this.customAware = new HashSet<>();
|
|
this.slots = new HashMap<>();
|
|
this.containedObjects = new HashSet<>();
|
|
this.attributes = new LinkedHashMap<>();
|
|
this.dataAttributes = new Hashtable<>();
|
|
this.containerPermissions = new DefaultPermissions();
|
|
}
|
|
|
|
/**
|
|
* Adds the specified object to this object and places it in the appropriate slot if needed
|
|
* @param object Object to add to this container, which will either be put into the appropriate slot(s) or become a contained object
|
|
*/
|
|
public void addObject(SWGObject object) {
|
|
// If the arrangement is -1, then this object will be a contained object
|
|
int arrangementId = getArrangementId(object);
|
|
if (arrangementId == -1) {
|
|
synchronized (containedObjects) {
|
|
containedObjects.add(object);
|
|
}
|
|
} else {
|
|
// Not a child object, so time to check the slots!
|
|
|
|
// Check to make sure this object is able to go into a slot in the parent
|
|
List<String> requiredSlots = object.getArrangement().get(arrangementId - 4);
|
|
// Note that some objects don't have a descriptor, meaning it has no slots
|
|
|
|
// Add object to the slot
|
|
for (String requiredSlot : requiredSlots) {
|
|
setSlot(requiredSlot, object);
|
|
}
|
|
}
|
|
|
|
object.parent = this;
|
|
object.slotArrangement = arrangementId;
|
|
synchronized (object.objectsAware) {
|
|
object.objectsAware.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the specified object from this current object.
|
|
* @param object Object to remove
|
|
*/
|
|
public void removeObject(SWGObject object) {
|
|
synchronized (object.objectsAware) {
|
|
object.objectsAware.clear();
|
|
object.objectsAware.addAll(getObjectsAware());
|
|
object.objectsAware.remove(object);
|
|
}
|
|
// This object is a container object, so remove it from the container
|
|
if (object.getSlotArrangement() == -1) {
|
|
synchronized (containedObjects) {
|
|
containedObjects.remove(object);
|
|
}
|
|
} else {
|
|
for (String slot : (slotArrangement == -1 ?
|
|
object.getArrangement().get(0) : object.getArrangement().get(slotArrangement - 4))) {
|
|
setSlot(slot, null);
|
|
}
|
|
}
|
|
|
|
object.parent = null;
|
|
object.slotArrangement = -1;
|
|
}
|
|
|
|
/**
|
|
* Moves this object to the passed container if the requester has the MOVE permission for the container
|
|
* @param requester Object that is requesting to move the object, used for permission checking
|
|
* @param container Where this object should be moved to
|
|
* @return {@link ContainerResult}
|
|
*/
|
|
public ContainerResult moveToContainer(SWGObject requester, SWGObject container) {
|
|
// Check if the requester has MOVE permissions for the current container of the object
|
|
ContainerResult result = moveToContainerChecks(requester, container);
|
|
if (result != ContainerResult.SUCCESS)
|
|
return result;
|
|
|
|
// Get a pre-parent-removal list of the observers so we can send create/destroy/update messages
|
|
Set<SWGObject> oldObservers = getObservers();
|
|
Player prevOwner = (getParent() != null) ? getParent().getOwner() : null;
|
|
if (prevOwner != null)
|
|
oldObservers.add(prevOwner.getCreatureObject());
|
|
|
|
// Remove this object from the old parent if one exists
|
|
if (parent != null) {
|
|
parent.removeObject(this);
|
|
}
|
|
|
|
Player newOwner = null;
|
|
if (container != null) {
|
|
container.addObject(this);
|
|
newOwner = container.getOwner();
|
|
}
|
|
|
|
// Observer notification
|
|
Set<SWGObject> containerObservers = getObservers();
|
|
if (newOwner != null)
|
|
containerObservers.add(newOwner.getCreatureObject());
|
|
sendUpdatedContainment(oldObservers, containerObservers);
|
|
|
|
return ContainerResult.SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Attempts to move this object to the defined container without checking for permissions
|
|
* @param container
|
|
* @return {@link ContainerResult}
|
|
*/
|
|
public ContainerResult moveToContainer(SWGObject container) {
|
|
return moveToContainer(null, container);
|
|
}
|
|
|
|
private ContainerResult moveToContainerChecks(SWGObject requester, SWGObject container) {
|
|
if(!hasPermission(requester, ContainerPermissions.Permission.MOVE)) {
|
|
Log.w("SWGObject", "No permission 'MOVE' for requestor %s with object %s", requester, this);
|
|
return ContainerResult.NO_PERMISSION;
|
|
}
|
|
if (container == null)
|
|
return ContainerResult.SUCCESS;
|
|
|
|
// Check if the requester has MOVE permissions to the destination container
|
|
if (!container.hasPermission(requester, ContainerPermissions.Permission.MOVE)) {
|
|
Log.w("SWGObject", "No permission 'MOVE' for requestor %s with object %s", requester, this);
|
|
return ContainerResult.NO_PERMISSION;
|
|
}
|
|
|
|
// Check if object can fit into container or slots
|
|
int arrangementId = container.getArrangementId(this);
|
|
if (arrangementId == -1) {
|
|
// Item is going to go into the container, so check to see if it'll fit
|
|
if (container.getMaxContainerSize() <= container.getContainedObjects().size() && container.getMaxContainerSize() > 0) {
|
|
Log.w("SWGObject", "Unable to add object to container! Container Full. Max Size: %d", container.getMaxContainerSize());
|
|
return ContainerResult.CONTAINER_FULL;
|
|
}
|
|
} else {
|
|
// Item is going into slot(s)
|
|
for (String slotName : getArrangement().get(arrangementId - 4)) {
|
|
SWGObject equippedItem = container.getSlottedObject(slotName);
|
|
if (equippedItem != null) {
|
|
equippedItem.moveToContainer(requester, container.getSlottedObject("inventory"));
|
|
}
|
|
}
|
|
}
|
|
return ContainerResult.SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Checks if the passed object has all of the passed permissions
|
|
* @param object Requester to view this container
|
|
* @param permissions Permissions to check for
|
|
* @return
|
|
*/
|
|
public boolean hasPermission(SWGObject object, ContainerPermissions.Permission... permissions) {
|
|
if (object == null)
|
|
return true;
|
|
for (ContainerPermissions.Permission permission : permissions) {
|
|
switch(permission) {
|
|
case VIEW:
|
|
if (!containerPermissions.canView(object, this))
|
|
return false;
|
|
break;
|
|
case MOVE:
|
|
if (!containerPermissions.canMove(object, this))
|
|
return false;
|
|
break;
|
|
case REMOVE:
|
|
if (!containerPermissions.canRemove(object, this))
|
|
return false;
|
|
break;
|
|
case ADD:
|
|
if (!containerPermissions.canAdd(object, this))
|
|
return false;
|
|
break;
|
|
case ENTER:
|
|
if (!containerPermissions.canEnter(object, this))
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates a new permission group for this object with the given permissions for that group
|
|
* @param group Name of the permission group
|
|
* @param permissions Permissions for the group
|
|
*/
|
|
public void addPermissions(String group, ContainerPermissions.Permission... permissions) {
|
|
containerPermissions.addPermissions(group, permissions);
|
|
}
|
|
|
|
/**
|
|
* Removes the stated permissions from the group.
|
|
* @param group Name of the permission group
|
|
* @param permissions Permissions to remove
|
|
*/
|
|
public void removePermissions(String group, ContainerPermissions.Permission... permissions) {
|
|
containerPermissions.removePermissions(group, permissions);
|
|
}
|
|
|
|
/**
|
|
* Creates a new permission group specific to the permission requester that has the defined permissions. The name of the
|
|
* new group for this object will be the objectId of this object plus the objectId of the requester.
|
|
* <br>This is the same as calling addPermissions(String.valueOf(permissionRequester.getObjectId() + getObjectId()), permissions)
|
|
* with the added benefit of adding the group to the permissionRequester's joined container groups
|
|
* @param permissionRequester The object that should be given unique permissions to this object
|
|
* @param permissions Permissions that the permissionRequester will have for this object
|
|
*/
|
|
public void addPermissions(SWGObject permissionRequester, ContainerPermissions.Permission... permissions) {
|
|
addPermissions(String.valueOf(permissionRequester.getObjectId() + getObjectId()), permissions);
|
|
permissionRequester.joinPermissionGroup(String.valueOf(getObjectId() + permissionRequester.getObjectId()));
|
|
}
|
|
|
|
/**
|
|
* Removes all the unique permissions for the permissionRequester from this object.
|
|
* @param permissionRequester The object that should no longer have unique permissions to this object
|
|
*/
|
|
public void removePermissions(SWGObject permissionRequester) {
|
|
String group = String.valueOf(permissionRequester.getObjectId() + getObjectId());
|
|
removePermissions(group);
|
|
permissionRequester.containerPermissions.getJoinedGroups().remove(group);
|
|
}
|
|
|
|
/**
|
|
* Assigns this object to a permission group
|
|
* @param group
|
|
*/
|
|
public void joinPermissionGroup(String group) {
|
|
containerPermissions.getJoinedGroups().add(group);
|
|
}
|
|
|
|
public void setContainerPermissions(ContainerPermissions permissions) {
|
|
this.containerPermissions = permissions;
|
|
}
|
|
|
|
public void addAttribute(String attribute, String value) {
|
|
attributes.put(attribute, value);
|
|
}
|
|
|
|
/**
|
|
* Gets the object that occupies the specified slot
|
|
* @param slotName
|
|
* @return The {@link SWGObject} occupying the slot. Returns null if there is nothing in the slot or it doesn't exist.
|
|
*/
|
|
public SWGObject getSlottedObject(String slotName) {
|
|
synchronized (slots) {
|
|
return slots.get(slotName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a list of all the objects in the current container. This should only be used for viewing the objects
|
|
* in the current container.
|
|
* @return An unmodifiable {@link Collection} of {@link SWGObject}'s in the container
|
|
*/
|
|
public Collection<SWGObject> getContainedObjects() {
|
|
synchronized (containedObjects) {
|
|
return new ArrayList<>(containedObjects);
|
|
}
|
|
}
|
|
|
|
public boolean hasSlot(String slotName) {
|
|
synchronized (slots) {
|
|
return slots.containsKey(slotName);
|
|
}
|
|
}
|
|
|
|
public void setSlot(String name, SWGObject value) {
|
|
synchronized (slots) {
|
|
slots.put(name, value);
|
|
}
|
|
}
|
|
|
|
public Map<String, SWGObject> getSlots() {
|
|
synchronized (slots) {
|
|
return new HashMap<>(slots);
|
|
}
|
|
}
|
|
|
|
public void setOwner(Player player) {
|
|
if (owner != null)
|
|
owner.setCreatureObject(null);
|
|
this.owner = player;
|
|
if (player != null && this instanceof CreatureObject)
|
|
player.setCreatureObject((CreatureObject) this);
|
|
}
|
|
|
|
public void setLocation(Location l) {
|
|
if (l == null)
|
|
return;
|
|
location.mergeWith(l);
|
|
}
|
|
|
|
public void setLocation(double x, double y, double z) {
|
|
location.mergeLocation(x, y, z);
|
|
}
|
|
|
|
public void setStf(String stfFile, String stfKey) {
|
|
this.stringId = new StringId(stfFile, stfKey);
|
|
}
|
|
|
|
public void setStringId(String stringId) {
|
|
this.stringId = new StringId(stringId);
|
|
}
|
|
|
|
public void setDetailStf(String stfFile, String stfKey) {
|
|
this.detailStringId = new StringId(stfFile, stfKey);
|
|
}
|
|
|
|
public void setDetailStringId(String stf) {
|
|
this.detailStringId = new StringId(stf);
|
|
}
|
|
|
|
public void setTemplate(String template) {
|
|
this.template = template;
|
|
this.crc = CRC.getCrc(template);
|
|
}
|
|
|
|
public void setName(String name) {
|
|
this.objectName = name;
|
|
}
|
|
|
|
public void setVolume(int volume) {
|
|
this.volume = volume;
|
|
}
|
|
|
|
public void setComplexity(float complexity) {
|
|
this.complexity = complexity;
|
|
}
|
|
|
|
public void setBuildoutArea(BuildoutArea buildoutArea) {
|
|
this.buildoutArea = buildoutArea;
|
|
}
|
|
|
|
public void setBuildoutAreaId(int areaId) {
|
|
this.areaId = areaId;
|
|
}
|
|
|
|
public void setArrangement(List<List<String>> arrangement) {
|
|
this.arrangement = arrangement;
|
|
}
|
|
|
|
public Player getOwner() {
|
|
if (owner != null)
|
|
return owner;
|
|
|
|
if (getParent() != null)
|
|
return getParent().getOwner(); // Ziggy: Player owner is found recursively
|
|
|
|
return null;
|
|
}
|
|
|
|
public SWGObject getParent() {
|
|
return parent;
|
|
}
|
|
|
|
public SWGObject getSuperParent() {
|
|
SWGObject sParent = parent;
|
|
if (sParent == null)
|
|
return null;
|
|
while (sParent.getParent() != null)
|
|
sParent = sParent.getParent();
|
|
return sParent;
|
|
}
|
|
|
|
public StringId getStringId() {
|
|
return stringId;
|
|
}
|
|
|
|
public StringId getDetailStringId() {
|
|
return detailStringId;
|
|
}
|
|
|
|
public String getTemplate() {
|
|
return template;
|
|
}
|
|
|
|
public int getCrc() {
|
|
return crc;
|
|
}
|
|
|
|
public long getObjectId() {
|
|
return objectId;
|
|
}
|
|
|
|
public Location getLocation() {
|
|
return new Location(location);
|
|
}
|
|
|
|
public Location getWorldLocation() {
|
|
Location loc = new Location(location);
|
|
SWGObject parent = getParent();
|
|
while (parent != null) {
|
|
Location l = parent.location;
|
|
loc.translateLocation(l); // Have to access privately to avoid copies
|
|
parent = parent.getParent();
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
public double getX() {
|
|
return location.getX();
|
|
}
|
|
|
|
public double getY() {
|
|
return location.getY();
|
|
}
|
|
|
|
public double getZ() {
|
|
return location.getZ();
|
|
}
|
|
|
|
public Terrain getTerrain() {
|
|
return location.getTerrain();
|
|
}
|
|
|
|
public String getName() {
|
|
return objectName;
|
|
}
|
|
|
|
public int getVolume() {
|
|
return volume;
|
|
}
|
|
|
|
public float getComplexity() {
|
|
return complexity;
|
|
}
|
|
|
|
public BuildoutArea getBuildoutArea() {
|
|
if (buildoutArea == null && parent != null)
|
|
return parent.getBuildoutArea();
|
|
return buildoutArea;
|
|
}
|
|
|
|
public int getBuildoutAreaId() {
|
|
return areaId;
|
|
}
|
|
|
|
public Object getDataAttribute(ObjectDataAttribute key) {
|
|
return dataAttributes.get(key);
|
|
}
|
|
|
|
public void setDataAttribute(ObjectDataAttribute key, Object value) {
|
|
dataAttributes.put(key, value);
|
|
}
|
|
|
|
public List<List<String>> getArrangement() {
|
|
return arrangement;
|
|
}
|
|
|
|
public String getAttribute(String attribute) {
|
|
return attributes.get(attribute);
|
|
}
|
|
|
|
public Map<String, String> getAttributes() {
|
|
return attributes;
|
|
}
|
|
|
|
public int getContainerType() {
|
|
return containerType;
|
|
}
|
|
|
|
public void setContainerType(int containerType) {
|
|
this.containerType = containerType;
|
|
}
|
|
|
|
public int getSlotArrangement() {
|
|
return slotArrangement;
|
|
}
|
|
|
|
public void setSlotArrangement(int slotArrangement) {
|
|
this.slotArrangement = slotArrangement;
|
|
}
|
|
|
|
public int getMaxContainerSize() {
|
|
Object volume = dataAttributes.get(ObjectDataAttribute.CONTAINER_VOLUME_LIMIT);
|
|
if (volume == null) {
|
|
Log.w("SWGObject", "Volume is null!");
|
|
return 0;
|
|
}
|
|
return (Integer) volume;
|
|
}
|
|
|
|
public void setClassification(ObjectClassification classification) {
|
|
this.classification = classification;
|
|
}
|
|
|
|
public ObjectClassification getClassification() {
|
|
return classification;
|
|
}
|
|
|
|
public GameObjectType getGameObjectType() {
|
|
return gameObjectType;
|
|
}
|
|
|
|
public void setGameObjectType(GameObjectType gameObjectType) {
|
|
this.gameObjectType = gameObjectType;
|
|
}
|
|
|
|
public boolean isBuildout() {
|
|
return classification == ObjectClassification.BUILDOUT;
|
|
}
|
|
|
|
public boolean isSnapshot() {
|
|
return classification == ObjectClassification.SNAPSHOT;
|
|
}
|
|
|
|
public boolean isGenerated() {
|
|
return classification == ObjectClassification.GENERATED;
|
|
}
|
|
|
|
public double getLoadRange() {
|
|
return loadRange;
|
|
}
|
|
|
|
public void setLoadRange(double range) {
|
|
this.loadRange = range;
|
|
}
|
|
|
|
public ContainerPermissions getContainerPermissions() { return containerPermissions; }
|
|
|
|
/**
|
|
* Gets the arrangementId for the {@link SWGObject} for the current instance
|
|
* @param object
|
|
* @return Arrangement ID for the object
|
|
*/
|
|
public int getArrangementId(SWGObject object) {
|
|
synchronized (slots) {
|
|
if (slots.size() == 0 || object.getArrangement() == null)
|
|
return -1;
|
|
}
|
|
|
|
int arrangementId = 4;
|
|
int filledId = -1;
|
|
|
|
for (List<String> arrangementList : object.getArrangement()) {
|
|
boolean passesCompletely = true;
|
|
boolean isValid = true;
|
|
for (String slot : arrangementList) {
|
|
if (!hasSlot(slot)) {
|
|
isValid = false;
|
|
break;
|
|
} else if (getSlottedObject(slot) != null) {
|
|
passesCompletely = false;
|
|
}
|
|
}
|
|
if (isValid && passesCompletely)
|
|
return arrangementId;
|
|
else if (isValid)
|
|
filledId = arrangementId;
|
|
|
|
arrangementId++;
|
|
}
|
|
return (filledId != -1) ? filledId : -1;
|
|
}
|
|
|
|
private final void sendSceneCreateObject(Player target) {
|
|
if (target == null)
|
|
return;
|
|
SceneCreateObjectByCrc create = new SceneCreateObjectByCrc();
|
|
create.setObjectId(objectId);
|
|
create.setLocation(buildoutArea == null ? location : buildoutArea.adjustLocation(location));
|
|
create.setObjectCrc(crc);
|
|
target.sendPacket(create);
|
|
if (parent != null)
|
|
target.sendPacket(new UpdateContainmentMessage(objectId, parent.getObjectId(), slotArrangement));
|
|
}
|
|
|
|
private final void sendSceneDestroyObject(Player target) {
|
|
if (target == null)
|
|
return;
|
|
SceneDestroyObject destroy = new SceneDestroyObject();
|
|
destroy.setObjectId(objectId);
|
|
target.sendPacket(destroy);
|
|
}
|
|
|
|
public void createObject(Player target) {
|
|
createObject(target, false);
|
|
}
|
|
|
|
public void createObject(Player target, boolean ignoreSnapshotChecks) {
|
|
if (!hasPermission(target.getCreatureObject(), ContainerPermissions.Permission.VIEW)) {
|
|
// Log.i("SWGObject", target.getCreatureObject() + " doesn't have permission to view " + this + " -- skipping packet sending");
|
|
return;
|
|
}
|
|
|
|
if (!isSnapshot() || ignoreSnapshotChecks) {
|
|
sendSceneCreateObject(target);
|
|
sendBaselines(target);
|
|
}
|
|
createChildrenObjects(target, ignoreSnapshotChecks);
|
|
sendFinalBaselinePackets(target);
|
|
if (!isSnapshot() || ignoreSnapshotChecks)
|
|
target.sendPacket(new SceneEndBaselines(getObjectId()));
|
|
}
|
|
|
|
public void destroyObject(Player target) {
|
|
sendSceneDestroyObject(target);
|
|
}
|
|
|
|
public void clearAware() {
|
|
clearAware(true);
|
|
}
|
|
|
|
public void clearAware(boolean updateSelf) {
|
|
Set<SWGObject> objects = getObjectsAware();
|
|
for (SWGObject o : objects) {
|
|
o.awarenessOutOfRange(this, true);
|
|
awarenessOutOfRange(o, updateSelf);
|
|
}
|
|
}
|
|
|
|
public void resetAwareness() {
|
|
synchronized (objectsAware) {
|
|
objectsAware.clear();
|
|
}
|
|
}
|
|
|
|
public Set <SWGObject> getObjectsAware() {
|
|
Set<SWGObject> aware;
|
|
synchronized (objectsAware) {
|
|
aware = new HashSet<>(objectsAware);
|
|
}
|
|
synchronized (customAware) {
|
|
aware.addAll(customAware);
|
|
}
|
|
if (parent != null) {
|
|
aware.addAll(parent.getObjectsAware());
|
|
aware.add(getSuperParent());
|
|
}
|
|
return aware;
|
|
}
|
|
|
|
private boolean isAware(SWGObject obj) {
|
|
if (equals(obj))
|
|
return true;
|
|
synchronized (objectsAware) {
|
|
if (objectsAware.contains(obj))
|
|
return true;
|
|
}
|
|
if (isCustomAware(obj))
|
|
return true;
|
|
if (parent != null)
|
|
return parent.isAware(obj);
|
|
return false;
|
|
}
|
|
|
|
public void addCustomAware(SWGObject aware) {
|
|
internalAddCustomAware(aware, true);
|
|
aware.internalAddCustomAware(this, true);
|
|
}
|
|
|
|
public void removeCustomAware(SWGObject aware) {
|
|
internalRemoveCustomAware(aware, true);
|
|
aware.internalRemoveCustomAware(this, true);
|
|
}
|
|
|
|
private void internalAddCustomAware(SWGObject aware, boolean sendUpdates) {
|
|
boolean changed = false;
|
|
synchronized (customAware) {
|
|
changed = customAware.add(aware);
|
|
}
|
|
if (changed && sendUpdates && aware.getOwner() != null)
|
|
createObject(aware.getOwner());
|
|
}
|
|
|
|
private void internalRemoveCustomAware(SWGObject aware, boolean sendUpdates) {
|
|
boolean changed = false;
|
|
synchronized (customAware) {
|
|
changed = customAware.remove(aware);
|
|
}
|
|
if (changed && sendUpdates && aware.getOwner() != null)
|
|
destroyObject(aware.getOwner());
|
|
}
|
|
|
|
private boolean isCustomAware(SWGObject obj) {
|
|
synchronized (customAware) {
|
|
return customAware.contains(obj);
|
|
}
|
|
}
|
|
|
|
public void clearCustomAware(boolean sendUpdates) {
|
|
Set<SWGObject> copy;
|
|
synchronized (customAware) {
|
|
copy = new HashSet<>(customAware);
|
|
}
|
|
for (SWGObject aware : copy) {
|
|
internalRemoveCustomAware(aware, sendUpdates);
|
|
aware.internalRemoveCustomAware(this, sendUpdates);
|
|
}
|
|
}
|
|
|
|
public Set<SWGObject> getObservers() {
|
|
Player owner = getOwner();
|
|
SWGObject parent = getParent();
|
|
if (parent == null)
|
|
return getObservers(owner, this, true);
|
|
while (parent.getParent() != null)
|
|
parent = parent.getParent();
|
|
return parent.getObservers(owner, this, true);
|
|
}
|
|
|
|
private Set<SWGObject> getObservers(Player owner, SWGObject original, boolean initial) {
|
|
Set<SWGObject> nearby;
|
|
synchronized (containedObjects) {
|
|
nearby = new HashSet<>(containedObjects);
|
|
}
|
|
if (initial) {
|
|
nearby.addAll(getObjectsAware());
|
|
}
|
|
Set<SWGObject> observers = new HashSet<>();
|
|
for (SWGObject aware : nearby) {
|
|
if (!aware.hasPermission(original, ContainerPermissions.Permission.VIEW))
|
|
continue;
|
|
if (aware instanceof CreatureObject) {
|
|
Player awareOwner = aware.getOwner();
|
|
if (awareOwner == null || awareOwner.equals(owner))
|
|
continue;
|
|
if (awareOwner.getPlayerState() != PlayerState.ZONED_IN)
|
|
continue;
|
|
if (((CreatureObject) aware).isLoggedInPlayer())
|
|
observers.add(aware);
|
|
} else
|
|
observers.addAll(aware.getObservers(owner, original, false));
|
|
}
|
|
return observers;
|
|
}
|
|
|
|
public void sendObserversAndSelf(Packet ... packets) {
|
|
sendSelf(packets);
|
|
sendObservers(packets);
|
|
}
|
|
|
|
public void sendObservers(Packet ... packets) {
|
|
Set<SWGObject> observers = getObservers();
|
|
for (SWGObject observer : observers) {
|
|
observer.getOwner().sendPacket(packets);
|
|
}
|
|
}
|
|
|
|
public void sendSelf(Packet ... packets) {
|
|
Player owner = getOwner();
|
|
if (owner != null)
|
|
owner.sendPacket(packets);
|
|
}
|
|
|
|
protected void sendBaselines(Player target) {
|
|
target.sendPacket(createBaseline3(target));
|
|
target.sendPacket(createBaseline6(target));
|
|
|
|
if (getOwner() == target) {
|
|
target.sendPacket(createBaseline8(target));
|
|
target.sendPacket(createBaseline9(target));
|
|
}
|
|
}
|
|
|
|
private void sendUpdatedContainment(Set<SWGObject> oldObservers, Set<SWGObject> newObservers) {
|
|
long newId = (parent == null) ? 0 : parent.getObjectId();
|
|
for (SWGObject swgObject : oldObservers) {
|
|
if (newObservers.contains(swgObject))
|
|
swgObject.getOwner().sendPacket(new UpdateContainmentMessage(objectId, newId, slotArrangement));
|
|
else
|
|
destroyObject(swgObject.getOwner());
|
|
}
|
|
|
|
for (SWGObject swgObject : newObservers) {
|
|
if (!oldObservers.contains(swgObject))
|
|
createObject(swgObject.getOwner());
|
|
}
|
|
}
|
|
|
|
public void updateObjectAwareness(Set <SWGObject> withinRange) {
|
|
Set <SWGObject> outOfRange;
|
|
synchronized (objectsAware) {
|
|
outOfRange = new HashSet<>(objectsAware);
|
|
}
|
|
for (SWGObject o : outOfRange) {
|
|
if (!withinRange.contains(o)) {
|
|
awarenessOutOfRange(o, true);
|
|
o.awarenessOutOfRange(this, true);
|
|
}
|
|
}
|
|
for (SWGObject o : withinRange) {
|
|
if (!outOfRange.contains(o)) {
|
|
awarenessInRange(o, true);
|
|
o.awarenessInRange(this, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void awarenessOutOfRange(SWGObject o, boolean sendDestroy) {
|
|
boolean success = isAware(o);
|
|
synchronized (objectsAware) {
|
|
success = objectsAware.remove(o) && success;
|
|
}
|
|
if (success && sendDestroy) {
|
|
Player owner = getOwner();
|
|
if (owner != null)
|
|
o.destroyObject(owner);
|
|
}
|
|
}
|
|
|
|
protected void awarenessInRange(SWGObject o, boolean sendCreate) {
|
|
boolean success = !isAware(o);
|
|
synchronized (objectsAware) {
|
|
success = objectsAware.add(o) && success;
|
|
}
|
|
if (success && sendCreate) {
|
|
Player owner = getOwner();
|
|
if (owner != null)
|
|
o.createObject(owner);
|
|
}
|
|
}
|
|
|
|
public void sendDataTransforms(DataTransform dTransform) {
|
|
Location loc = dTransform.getLocation();
|
|
float speed = dTransform.getSpeed();
|
|
/* Even with these speed calculations, observer clients still have stuttering for movements, live only sent UTM's to observers or bouncing back the player
|
|
that is moving. The only work around to this seems to be to send a UTM to the client to force multiple DTM's to be sent back to the server for fluid movements.
|
|
if (x != loc.getX() && y != loc.getY() && z != loc.getZ())
|
|
speed = (float) loc.getSpeed(x, 0, z, MathUtils.calculateDeltaTime(lastMovementTimestamp, dTransform.getTimestamp()));*/
|
|
sendDataTransforms(loc, dTransform.getMovementAngle(), speed, dTransform.getLookAtYaw(), dTransform.isUseLookAtYaw(), dTransform.getUpdateCounter());
|
|
}
|
|
|
|
public void sendParentDataTransforms(DataTransformWithParent ptm) {
|
|
UpdateTransformWithParentMessage transform = new UpdateTransformWithParentMessage(ptm.getCellId(), getObjectId());
|
|
transform.setLocation(ptm.getLocation());
|
|
transform.setUpdateCounter(ptm.getCounter() + 1);
|
|
transform.setDirection(ptm.getMovementAngle());
|
|
transform.setSpeed((byte) ptm.getSpeed());
|
|
transform.setLookDirection((byte) (ptm.getLookAtYaw() * 16));
|
|
transform.setUseLookDirection(ptm.isUseLookAtYaw());
|
|
sendObserversAndSelf(transform);
|
|
}
|
|
|
|
public void sendDataTransforms(Location loc, byte direction, double speed, float lookAtYaw, boolean useLookAtYaw, int updates) {
|
|
UpdateTransformMessage transform = new UpdateTransformMessage();
|
|
transform.setObjectId(getObjectId()); // (short) (xPosition * 4 + 0.5)
|
|
transform.setX((short) (loc.getX() * 4));
|
|
transform.setY((short) (loc.getY() * 4));
|
|
transform.setZ((short) (loc.getZ() * 4));
|
|
transform.setUpdateCounter(updates + 1);
|
|
transform.setDirection(direction);
|
|
transform.setSpeed((byte) speed);
|
|
transform.setLookAtYaw((byte) (lookAtYaw * 16));
|
|
transform.setUseLookAtYaw(useLookAtYaw);
|
|
sendObserversAndSelf(transform);
|
|
}
|
|
|
|
protected void createChildrenObjects(Player target) {
|
|
createChildrenObjects(target, false);
|
|
}
|
|
|
|
protected void createChildrenObjects(Player target, boolean ignoreSnapshotChecks) {
|
|
synchronized (slots) {
|
|
if (slots.size() == 0 && containedObjects.size() == 0)
|
|
return;
|
|
}
|
|
|
|
List<SWGObject> sentObjects = new ArrayList<>();
|
|
|
|
// First create the objects in the slots
|
|
synchronized (slots) {
|
|
for (SWGObject slotObject : slots.values()) {
|
|
if (slotObject != null && !sentObjects.contains(slotObject)) {
|
|
slotObject.createObject(target, ignoreSnapshotChecks);
|
|
sentObjects.add(slotObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now create the contained objects
|
|
synchronized (containedObjects) {
|
|
for (SWGObject containedObject : containedObjects) {
|
|
if (containedObject != null && !sentObjects.contains(containedObject)) {
|
|
if (containedObject instanceof CreatureObject && ((CreatureObject) containedObject).isLoggedOutPlayer())
|
|
continue; // If it's a player, but that's logged out
|
|
containedObject.createObject(target, ignoreSnapshotChecks);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void sendFinalBaselinePackets(Player target) {
|
|
|
|
}
|
|
|
|
public boolean isInBuilding() {
|
|
SWGObject parent = getParent();
|
|
if (parent == null)
|
|
return false;
|
|
parent = parent.getParent();
|
|
return parent != null && parent instanceof BuildingObject;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("SWGObject[%d '%s' %s]", objectId, objectName, template.replace("object/", ""));
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(SWGObject obj) {
|
|
return Long.compare(objectId, obj.getObjectId());
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (!(o instanceof SWGObject))
|
|
return false;
|
|
return getObjectId() == ((SWGObject)o).getObjectId();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Long.valueOf(getObjectId()).hashCode();
|
|
}
|
|
|
|
protected void createBaseline3(Player target, BaselineBuilder bb) {
|
|
super.createBaseline3(target, bb);
|
|
bb.addFloat(complexity); // 0
|
|
bb.addObject(stringId); // 1
|
|
bb.addUnicode(objectName); // custom name -- 2
|
|
bb.addInt(volume); // 3
|
|
|
|
bb.incrementOperandCount(4);
|
|
}
|
|
|
|
protected void createBaseline6(Player target, BaselineBuilder bb) {
|
|
super.createBaseline6(target, bb);
|
|
bb.addInt(CoreManager.getGalaxyId()); // 0
|
|
bb.addObject(detailStringId); // 1
|
|
|
|
bb.incrementOperandCount(2);
|
|
}
|
|
|
|
protected void parseBaseline3(NetBuffer buffer) {
|
|
super.parseBaseline3(buffer);
|
|
complexity = buffer.getFloat();
|
|
stringId = buffer.getEncodable(StringId.class);
|
|
objectName = buffer.getUnicode();
|
|
volume = buffer.getInt();
|
|
}
|
|
|
|
protected void parseBaseline6(NetBuffer buffer) {
|
|
super.parseBaseline6(buffer);
|
|
buffer.getInt(); // Immutable ... can't change the galaxy id
|
|
detailStringId = buffer.getEncodable(StringId.class);
|
|
}
|
|
|
|
/* Baseline send permissions based on packet observations:
|
|
*
|
|
* Baseline1 sent if you have full permissions to the object.
|
|
* Baseline4 sent if you have full permissions to the object.
|
|
*
|
|
* Baseline8 sent if you have some permissions to the object.
|
|
* Baseline9 sent if you have some permissions to the object.
|
|
*
|
|
* Baseline3 always sent.
|
|
* Baseline6 always sent.
|
|
*
|
|
* Baseline7 sent on using the object.
|
|
*
|
|
* Only sent if they are defined (can still be empty if defined).
|
|
*/
|
|
|
|
@Override
|
|
public void save(NetBufferStream stream) {
|
|
stream.addByte(2);
|
|
location.save(stream);
|
|
ContainerPermissionsFactory.save(containerPermissions, stream);
|
|
stream.addBoolean(parent != null && parent.getClassification() != ObjectClassification.GENERATED);
|
|
if (parent != null && parent.getClassification() != ObjectClassification.GENERATED)
|
|
SWGObjectFactory.save(ObjectCreator.createObjectFromTemplate(parent.getObjectId(), parent.getTemplate()), stream);
|
|
synchronized (attributes) {
|
|
stream.addMap(attributes, (e) -> {
|
|
stream.addAscii(e.getKey());
|
|
stream.addAscii(e.getValue());
|
|
});
|
|
}
|
|
stream.addAscii(classification.name());
|
|
stream.addUnicode(objectName);
|
|
stream.addInt(volume);
|
|
stream.addFloat(complexity);
|
|
stream.addFloat((float) loadRange);
|
|
Set<SWGObject> contained;
|
|
synchronized (containedObjects) {
|
|
contained = new HashSet<>(containedObjects);
|
|
}
|
|
synchronized (slots) {
|
|
contained.addAll(slots.values());
|
|
contained.remove(null);
|
|
}
|
|
stream.addList(contained, (c) -> SWGObjectFactory.save(c, stream));
|
|
}
|
|
|
|
public void read(NetBufferStream stream) {
|
|
byte ver = stream.getByte();
|
|
location.read(stream);
|
|
if (ver <= 1)
|
|
containerPermissions.read(stream);
|
|
else
|
|
containerPermissions = ContainerPermissionsFactory.create(stream);
|
|
if (ver >= 1 && stream.getBoolean())
|
|
parent = SWGObjectFactory.create(stream);
|
|
stream.getList((i) -> attributes.put(stream.getAscii(), stream.getAscii()));
|
|
classification = ObjectClassification.valueOf(stream.getAscii());
|
|
objectName = stream.getUnicode();
|
|
volume = stream.getInt();
|
|
complexity = stream.getFloat();
|
|
loadRange = stream.getFloat();
|
|
Set<SWGObject> contained = new HashSet<>();
|
|
stream.getList((i) -> contained.add(SWGObjectFactory.create(stream)));
|
|
for (SWGObject obj : contained)
|
|
obj.moveToContainer(this);
|
|
}
|
|
|
|
public enum ObjectClassification {
|
|
GENERATED,
|
|
BUILDOUT,
|
|
SNAPSHOT
|
|
}
|
|
}
|