Collision System More Friendly For Addon Developers (#645)

* Implemented new collision detection system

* Update Collision for Addon developers

* Update Collision Comments
This commit is contained in:
Nathan Braun 2016-11-02 23:09:47 -07:00 committed by Christopher Martin
parent b96d41e554
commit e6850e8139
6 changed files with 216 additions and 75 deletions

View file

@ -36,6 +36,7 @@ public class ProjectKorra extends JavaPlugin {
public static Logger log;
public static PKLogHandler handler;
public static CollisionManager collisionManager;
public static CollisionInitializer collisionInitializer;
public static long time_step = 1;
public Updater updater;
@ -61,9 +62,10 @@ public class ProjectKorra extends JavaPlugin {
new Commands(this);
new MultiAbilityManager();
new ComboManager();
CoreAbility.registerAbilities();
collisionManager = new CollisionManager();
new CollisionInitializer(collisionManager).initializeCollisions();
collisionInitializer = new CollisionInitializer(collisionManager);
CoreAbility.registerAbilities();
collisionInitializer.initializeDefaultCollisions(); // must be called after abilities have been registered
collisionManager.startCollisionDetection();
Preset.loadExternalPresets();
@ -121,4 +123,20 @@ public class ProjectKorra extends JavaPlugin {
handler.close();
}
public static CollisionManager getCollisionManager() {
return collisionManager;
}
public static void setCollisionManager(CollisionManager collisionManager) {
ProjectKorra.collisionManager = collisionManager;
}
public static CollisionInitializer getCollisionInitializer() {
return collisionInitializer;
}
public static void setCollisionInitializer(CollisionInitializer collisionInitializer) {
ProjectKorra.collisionInitializer = collisionInitializer;
}
}

View file

@ -60,7 +60,8 @@ import sun.reflect.ReflectionFactory;
*/
public abstract class CoreAbility implements Ability {
private static final Map<Class<? extends CoreAbility>, Map<UUID, Map<Integer, CoreAbility>>> INSTANCES = new ConcurrentHashMap<>();
private static final Set<CoreAbility> INSTANCES = Collections.newSetFromMap(new ConcurrentHashMap<CoreAbility, Boolean>());
private static final Map<Class<? extends CoreAbility>, Map<UUID, Map<Integer, CoreAbility>>> INSTANCES_BY_PLAYER = new ConcurrentHashMap<>();
private static final Map<Class<? extends CoreAbility>, Set<CoreAbility>> INSTANCES_BY_CLASS = new ConcurrentHashMap<>();
private static final Map<String, CoreAbility> ABILITIES_BY_NAME = new ConcurrentSkipListMap<>(); // preserves ordering
private static final Map<Class<? extends CoreAbility>, CoreAbility> ABILITIES_BY_CLASS = new ConcurrentHashMap<>();
@ -145,18 +146,19 @@ public abstract class CoreAbility implements Ability {
Class<? extends CoreAbility> clazz = getClass();
UUID uuid = player.getUniqueId();
if (!INSTANCES.containsKey(clazz)) {
INSTANCES.put(clazz, new ConcurrentHashMap<UUID, Map<Integer, CoreAbility>>());
if (!INSTANCES_BY_PLAYER.containsKey(clazz)) {
INSTANCES_BY_PLAYER.put(clazz, new ConcurrentHashMap<UUID, Map<Integer, CoreAbility>>());
}
if (!INSTANCES.get(clazz).containsKey(uuid)) {
INSTANCES.get(clazz).put(uuid, new ConcurrentHashMap<Integer, CoreAbility>());
if (!INSTANCES_BY_PLAYER.get(clazz).containsKey(uuid)) {
INSTANCES_BY_PLAYER.get(clazz).put(uuid, new ConcurrentHashMap<Integer, CoreAbility>());
}
if (!INSTANCES_BY_CLASS.containsKey(clazz)) {
INSTANCES_BY_CLASS.put(clazz, Collections.newSetFromMap(new ConcurrentHashMap<CoreAbility, Boolean>()));
}
INSTANCES.get(clazz).get(uuid).put(this.id, this);
INSTANCES_BY_PLAYER.get(clazz).get(uuid).put(this.id, this);
INSTANCES_BY_CLASS.get(clazz).add(this);
INSTANCES.add(this);
}
/**
@ -177,7 +179,7 @@ public abstract class CoreAbility implements Ability {
Bukkit.getServer().getPluginManager().callEvent(new AbilityEndEvent(this));
removed = true;
Map<UUID, Map<Integer, CoreAbility>> classMap = INSTANCES.get(getClass());
Map<UUID, Map<Integer, CoreAbility>> classMap = INSTANCES_BY_PLAYER.get(getClass());
if (classMap != null) {
Map<Integer, CoreAbility> playerMap = classMap.get(player.getUniqueId());
if (playerMap != null) {
@ -188,13 +190,14 @@ public abstract class CoreAbility implements Ability {
}
if (classMap.size() == 0) {
INSTANCES.remove(getClass());
INSTANCES_BY_PLAYER.remove(getClass());
}
}
if (INSTANCES_BY_CLASS.containsKey(getClass())) {
INSTANCES_BY_CLASS.get(getClass()).remove(this);
}
INSTANCES.remove(this);
}
/**
@ -311,10 +314,18 @@ public abstract class CoreAbility implements Ability {
*/
@SuppressWarnings("unchecked")
public static <T extends CoreAbility> Collection<T> getAbilities(Player player, Class<T> clazz) {
if (player == null || clazz == null || INSTANCES.get(clazz) == null || INSTANCES.get(clazz).get(player.getUniqueId()) == null) {
if (player == null || clazz == null || INSTANCES_BY_PLAYER.get(clazz) == null || INSTANCES_BY_PLAYER.get(clazz).get(player.getUniqueId()) == null) {
return Collections.emptySet();
}
return (Collection<T>) INSTANCES.get(clazz).get(player.getUniqueId()).values();
return (Collection<T>) INSTANCES_BY_PLAYER.get(clazz).get(player.getUniqueId()).values();
}
/**
* @return a Collection of all of the CoreAbilities that are currently
* alive. Do not modify this Collection.
*/
public static Collection<CoreAbility> getAbilitiesByInstances() {
return INSTANCES;
}
/**
@ -360,7 +371,7 @@ public abstract class CoreAbility implements Ability {
public static Set<Player> getPlayers(Class<? extends CoreAbility> clazz) {
HashSet<Player> players = new HashSet<>();
if (clazz != null) {
Map<UUID, Map<Integer, CoreAbility>> uuidMap = INSTANCES.get(clazz);
Map<UUID, Map<Integer, CoreAbility>> uuidMap = INSTANCES_BY_PLAYER.get(clazz);
if (uuidMap != null) {
for (UUID uuid : uuidMap.keySet()) {
Player uuidPlayer = Bukkit.getPlayer(uuid);
@ -682,7 +693,7 @@ public abstract class CoreAbility implements Ability {
int playerCounter = 0;
HashMap<String, Integer> classCounter = new HashMap<>();
for (Map<UUID, Map<Integer, CoreAbility>> map1 : INSTANCES.values()) {
for (Map<UUID, Map<Integer, CoreAbility>> map1 : INSTANCES_BY_PLAYER.values()) {
playerCounter++;
for (Map<Integer, CoreAbility> map2 : map1.values()) {
for (CoreAbility coreAbil : map2.values()) {

View file

@ -8,6 +8,10 @@ import com.projectkorra.projectkorra.ability.CoreAbility;
* A Collision is used to represent the collision between two CoreAbility
* objects.
*
* To register Collisions Addon developers should use:<br>
* ProjectKorra.getCollisionInitializer().addCollision(myCoreAbility)
* ProjectKorra.getCollisionInitializer().addSmallAbility(myCoreAbility)
*
* @see CollisionManager
*/
public class Collision {

View file

@ -1,9 +1,10 @@
package com.projectkorra.projectkorra.ability.util;
import java.util.ArrayList;
import com.projectkorra.projectkorra.ability.CoreAbility;
import com.projectkorra.projectkorra.airbending.AirBlast;
import com.projectkorra.projectkorra.airbending.AirBubble;
import com.projectkorra.projectkorra.airbending.AirCombo;
import com.projectkorra.projectkorra.airbending.AirCombo.AirStream;
import com.projectkorra.projectkorra.airbending.AirCombo.AirSweep;
import com.projectkorra.projectkorra.airbending.AirFlight;
@ -28,7 +29,6 @@ import com.projectkorra.projectkorra.firebending.BlazeArc;
import com.projectkorra.projectkorra.firebending.Combustion;
import com.projectkorra.projectkorra.firebending.FireBlast;
import com.projectkorra.projectkorra.firebending.FireBlastCharged;
import com.projectkorra.projectkorra.firebending.FireCombo;
import com.projectkorra.projectkorra.firebending.FireCombo.FireKick;
import com.projectkorra.projectkorra.firebending.FireCombo.FireSpin;
import com.projectkorra.projectkorra.firebending.FireCombo.FireWheel;
@ -47,7 +47,6 @@ import com.projectkorra.projectkorra.waterbending.SurgeWave;
import com.projectkorra.projectkorra.waterbending.Torrent;
import com.projectkorra.projectkorra.waterbending.TorrentWave;
import com.projectkorra.projectkorra.waterbending.WaterBubble;
import com.projectkorra.projectkorra.waterbending.WaterCombo;
import com.projectkorra.projectkorra.waterbending.WaterCombo.IceBullet;
import com.projectkorra.projectkorra.waterbending.WaterCombo.IceWave;
import com.projectkorra.projectkorra.waterbending.WaterManipulation;
@ -63,17 +62,24 @@ import com.projectkorra.projectkorra.waterbending.WaterSpoutWave;
*/
public class CollisionInitializer {
private CollisionManager cm;
private CollisionManager collisionManager;
private ArrayList<CoreAbility> smallAbilities;
private ArrayList<CoreAbility> largeAbilities;
private ArrayList<CoreAbility> comboAbilities;
private ArrayList<CoreAbility> removeSpoutAbilities;
public CollisionInitializer(CollisionManager cm) {
this.cm = cm;
public CollisionInitializer(CollisionManager collisionManager) {
this.collisionManager = collisionManager;
this.smallAbilities = new ArrayList<>();
this.comboAbilities = new ArrayList<>();
this.largeAbilities = new ArrayList<>();
this.removeSpoutAbilities = new ArrayList<>();
}
@SuppressWarnings("unused")
public void initializeCollisions() {
public void initializeDefaultCollisions() {
CoreAbility airBlast = CoreAbility.getAbility(AirBlast.class);
CoreAbility airBubble = CoreAbility.getAbility(AirBubble.class);
CoreAbility airCombo = CoreAbility.getAbility(AirCombo.class);
CoreAbility airFlight = CoreAbility.getAbility(AirFlight.class);
CoreAbility airScooter = CoreAbility.getAbility(AirScooter.class);
CoreAbility airShield = CoreAbility.getAbility(AirShield.class);
@ -100,7 +106,6 @@ public class CollisionInitializer {
CoreAbility combustion = CoreAbility.getAbility(Combustion.class);
CoreAbility fireBlast = CoreAbility.getAbility(FireBlast.class);
CoreAbility fireBlastCharged = CoreAbility.getAbility(FireBlastCharged.class);
CoreAbility fireCombo = CoreAbility.getAbility(FireCombo.class);
CoreAbility fireJet = CoreAbility.getAbility(FireJet.class);
CoreAbility fireKick = CoreAbility.getAbility(FireKick.class);
CoreAbility fireSpin = CoreAbility.getAbility(FireSpin.class);
@ -122,55 +127,147 @@ public class CollisionInitializer {
CoreAbility torrent = CoreAbility.getAbility(Torrent.class);
CoreAbility torrentWave = CoreAbility.getAbility(TorrentWave.class);
CoreAbility waterBubble = CoreAbility.getAbility(WaterBubble.class);
CoreAbility waterCombo = CoreAbility.getAbility(WaterCombo.class);
CoreAbility waterManipulation = CoreAbility.getAbility(WaterManipulation.class);
CoreAbility waterSpout = CoreAbility.getAbility(WaterSpout.class);
CoreAbility waterSpoutWave = CoreAbility.getAbility(WaterSpoutWave.class);
CoreAbility[] smallDamageAbils = { airSwipe, earthBlast, waterManipulation, fireBlast, combustion, blazeArc };
CoreAbility[] abilitiesThatRemoveSmall = { earthSmash, airShield, airCombo, fireCombo, waterCombo, fireBlastCharged };
CoreAbility[] abilsThatRemoveSpouts = { airSwipe, earthBlast, waterManipulation, fireBlast, fireBlastCharged, earthSmash, fireCombo, airCombo, waterCombo };
CoreAbility[] damageComboAbils = { fireKick, fireSpin, fireWheel, airSweep, iceBullet };
CoreAbility[] smallAbils = { airSwipe, earthBlast, waterManipulation, fireBlast, combustion, blazeArc };
CoreAbility[] largeAbils = { earthSmash, airShield, fireBlastCharged, fireKick, fireSpin, fireWheel, airSweep, iceBullet };
CoreAbility[] comboAbils = { fireKick, fireSpin, fireWheel, airSweep, iceBullet };
CoreAbility[] removeSpoutAbils = { airSwipe, earthBlast, waterManipulation, fireBlast, fireBlastCharged, earthSmash, fireKick, fireSpin, fireWheel, airSweep, iceBullet };
// All small damaging abilities block each other
for (int i = 0; i < smallDamageAbils.length; i++) {
for (int j = i; j < smallDamageAbils.length; j++) {
cm.add(new Collision(smallDamageAbils[i], smallDamageAbils[j], true, true));
for (CoreAbility smallAbil : smallAbils) {
addSmallAbility(smallAbil);
}
for (CoreAbility largeAbil : largeAbils) {
addLargeAbility(largeAbil);
}
for (CoreAbility comboAbil : comboAbils) {
addComboAbility(comboAbil);
}
for (CoreAbility removeSpoutAbil : removeSpoutAbils) {
addRemoveSpoutAbility(removeSpoutAbil);
}
// All combos block each other
for (int i = 0; i < damageComboAbils.length; i++) {
for (int j = i; j < damageComboAbils.length; j++) {
cm.add(new Collision(damageComboAbils[i], damageComboAbils[j], true, true));
}
collisionManager.addCollision(new Collision(airShield, airBlast, false, true));
collisionManager.addCollision(new Collision(airShield, airSuction, false, true));
collisionManager.addCollision(new Collision(airShield, airStream, false, true));
for (CoreAbility comboAbil : comboAbils) {
collisionManager.addCollision(new Collision(airShield, comboAbil, false, true));
}
// These abilities remove all small damaging abilities
for (CoreAbility abilThatRemoves : abilitiesThatRemoveSmall) {
for (CoreAbility smallDamageAbil : smallDamageAbils) {
cm.add(new Collision(abilThatRemoves, smallDamageAbil, false, true));
}
collisionManager.addCollision(new Collision(fireShield, fireBlast, false, true));
collisionManager.addCollision(new Collision(fireShield, fireBlastCharged, false, true));
collisionManager.addCollision(new Collision(fireShield, waterManipulation, false, true));
collisionManager.addCollision(new Collision(fireShield, earthBlast, false, true));
collisionManager.addCollision(new Collision(fireShield, airSweep, false, true));
}
for (CoreAbility spoutDestroyAbil : abilsThatRemoveSpouts) {
cm.add(new Collision(spoutDestroyAbil, airSpout, false, true));
cm.add(new Collision(spoutDestroyAbil, waterSpout, false, true));
cm.add(new Collision(spoutDestroyAbil, sandSpout, false, true));
/**
* An ability that collides with other small abilities. (EarthBlast,
* FireBlast). Two colliding small abilities will remove each other. A small
* ability is removed when it collides with a large ability.
*
* @param smallAbility the small CoreAbility
*/
public void addSmallAbility(CoreAbility smallAbility) {
if (smallAbility == null) {
return;
}
smallAbilities.add(smallAbility);
for (CoreAbility otherSmallAbility : smallAbilities) {
collisionManager.addCollision(new Collision(smallAbility, otherSmallAbility, true, true));
}
cm.add(new Collision(airShield, airBlast, false, true));
cm.add(new Collision(airShield, airSuction, false, true));
cm.add(new Collision(airShield, airStream, false, true));
for (CoreAbility comboAbil : damageComboAbils) {
cm.add(new Collision(airShield, comboAbil, false, true));
}
cm.add(new Collision(fireShield, fireBlast, false, true));
cm.add(new Collision(fireShield, fireBlastCharged, false, true));
cm.add(new Collision(fireShield, waterManipulation, false, true));
cm.add(new Collision(fireShield, earthBlast, false, true));
cm.add(new Collision(fireShield, airSweep, false, true));
for (CoreAbility largeAbility : largeAbilities) {
collisionManager.addCollision(new Collision(largeAbility, smallAbility, false, true));
}
}
/**
* A large ability that removes small abilities (EarthSmash, Charged
* FireBlast). The large ability remains while the small ability is
* destroyed.
*
* @param largeAbility the large CoreAbility
*/
public void addLargeAbility(CoreAbility largeAbility) {
if (largeAbility == null) {
return;
}
largeAbilities.add(largeAbility);
for (CoreAbility smallAbility : smallAbilities) {
collisionManager.addCollision(new Collision(largeAbility, smallAbility, false, true));
}
}
/**
* A combo ability that collides with all other combo abilities. Both
* colliding combo abilities are destroyed.
*
* @param comboAbility the combo CoreAbility
*/
public void addComboAbility(CoreAbility comboAbility) {
if (comboAbility == null) {
return;
}
comboAbilities.add(comboAbility);
for (CoreAbility otherComboAbility : smallAbilities) {
collisionManager.addCollision(new Collision(comboAbility, otherComboAbility, true, true));
}
}
/**
* An ability that destroys WaterSpout, AirSpout, and SandSpout upon
* contact. The spout is destroyed and this ability remains.
*
* @param ability the ability that removes spouts
*/
public void addRemoveSpoutAbility(CoreAbility ability) {
if (ability == null) {
return;
}
removeSpoutAbilities.add(ability);
collisionManager.addCollision(new Collision(ability, CoreAbility.getAbility(AirSpout.class), false, true));
collisionManager.addCollision(new Collision(ability, CoreAbility.getAbility(WaterSpout.class), false, true));
collisionManager.addCollision(new Collision(ability, CoreAbility.getAbility(SandSpout.class), false, true));
}
public CollisionManager getCollisionManager() {
return collisionManager;
}
public ArrayList<CoreAbility> getSmallAbilities() {
return smallAbilities;
}
public void setSmallAbilities(ArrayList<CoreAbility> smallAbilities) {
this.smallAbilities = smallAbilities;
}
public ArrayList<CoreAbility> getLargeAbilities() {
return largeAbilities;
}
public void setLargeAbilities(ArrayList<CoreAbility> largeAbilities) {
this.largeAbilities = largeAbilities;
}
public ArrayList<CoreAbility> getComboAbilities() {
return comboAbilities;
}
public void setComboAbilities(ArrayList<CoreAbility> comboAbilities) {
this.comboAbilities = comboAbilities;
}
public ArrayList<CoreAbility> getRemoveSpoutAbilities() {
return removeSpoutAbilities;
}
public void setRemoveSpoutAbilities(ArrayList<CoreAbility> removeSpoutAbilities) {
this.removeSpoutAbilities = removeSpoutAbilities;
}
}

View file

@ -16,8 +16,12 @@ import com.projectkorra.projectkorra.event.AbilityCollisionEvent;
/**
* A CollisionManager is used to monitor possible collisions between all
* CoreAbilities. Use {@link #add(Collision)} to begin monitoring for collision
* between two abilities, as shown in {@link CollisionInitializer}.
* CoreAbilities. Use {@link #addCollision(Collision)} to begin monitoring for
* collision between two abilities, as shown in {@link CollisionInitializer}.
* <p>
* Addon developers should use:<br>
* ProjectKorra.getCollisionInitializer().addCollision(myCoreAbility)
* ProjectKorra.getCollisionInitializer().addSmallAbility(myCoreAbility)
* <p>
* For a CoreAbility to collide properly, the {@link CoreAbility#isCollidable()}
* , {@link CoreAbility#getCollisionRadius()},
@ -67,10 +71,13 @@ public class CollisionManager {
this.detectionDelay = 1;
this.minAbilityTickAlive = 3;
this.certainNoCollisionDistance = 100;
collisions = new ArrayList<>();
this.collisions = new ArrayList<>();
}
private void detectCollisions() {
if (CoreAbility.getAbilitiesByInstances().size() <= 1) {
return;
}
HashMap<CoreAbility, List<Location>> locationsCache = new HashMap<>();
for (Collision collision : collisions) {
@ -161,12 +168,16 @@ public class CollisionManager {
}
/**
* Adds a new Collision to the CollisionManager so that two abilities can be
* checked for collisions.
* Adds a "fake" Collision to the CollisionManager so that two abilities can
* be checked for collisions. This Collision only needs to define the
* abilityFirst, abilitySecond, removeFirst, and removeSecond.
*
* @param collision a Collision containing two CoreAbility classes
*/
public void add(Collision collision) {
public void addCollision(Collision collision) {
if (collision == null || collision.getAbilityFirst() == null || collision.getAbilitySecond() == null) {
return;
}
collisions.add(collision);
}