Skip to content

Commit

Permalink
feat: add helper functions to get the indices of high resolution npcs
Browse files Browse the repository at this point in the history
This allows servers which relied on high resolution indices for
mechanics such as aggression/hunt to continue using their old methods.
  • Loading branch information
Z-Kris committed Sep 14, 2024
1 parent 8a2baba commit 0cd17c7
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,35 @@ public class NpcInfo internal constructor(
this.viewDistance = MAX_SMALL_PACKET_DISTANCE
}

/**
* Gets the high resolution indices in a new arraylist of integers.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @return the newly created arraylist of indices
*/
public fun getHighResolutionIndices(): ArrayList<Int> {
val collection = ArrayList<Int>(highResolutionNpcIndexCount)
for (i in 0..<highResolutionNpcIndexCount) {
val index = highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Appends the high resolution indices to the provided [collection]. This can be used to determine which NPCs the
* player is currently seeing in the client. Servers often rely on this metric to determine things
* such as aggression/hunt.
* @param collection the mutable collection of integer indices to append the indices into.
* @return the provided [collection] to chaining.
*/
public fun <T> appendHighResolutionIndices(collection: T): T where T : MutableCollection<Int> {
for (i in 0..<highResolutionNpcIndexCount) {
val index = highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Turns this npc info structure into a respective npc info packet, depending
* on the current known view distance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,100 @@ public class NpcInfo internal constructor(
}
}

/**
* Gets the world details implementation of the specified [worldId], or null if it doesn't exist.
* This function will throw an exception if it is called post-deallocation for the root world,
* as it will then be deallocated.
*/
private fun getDetailsOrNull(worldId: Int): NpcInfoWorldDetails? {
val details =
if (worldId == ROOT_WORLD) {
details[WORLD_ENTITY_CAPACITY]
} else {
if (worldId !in 0..<WORLD_ENTITY_CAPACITY) {
return null
}
details[worldId]
}
return details
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices
*/
public fun getHighResolutionIndices(worldId: Int): ArrayList<Int> {
val details = getDetails(worldId)
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers, or null
* if the provided world does not exist.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices, or null if the world does not exist.
*/
public fun getHighResolutionIndicesOrNull(worldId: Int): ArrayList<Int>? {
val details = getDetailsOrNull(worldId) ?: return null
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Appends the high resolution indices of the given [worldId] to the provided
* [collection]. This can be used to determine which NPCs the player is currently
* seeing in the client. Servers often rely on this metric to determine things
* such as aggression/hunt.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @param collection the mutable collection of integer indices to append the indices into.
* @param throwExceptionIfNoWorld whether to throw an exception if the world does not exist.
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD],
* as long as [throwExceptionIfNoWorld] is true.
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated. This will only be thrown if [throwExceptionIfNoWorld]
* is true.
* @return the provided [collection] to chaining.
*/
@JvmOverloads
public fun <T> appendHighResolutionIndices(
worldId: Int,
collection: T,
throwExceptionIfNoWorld: Boolean = true,
): T where T : MutableCollection<Int> {
val details =
if (throwExceptionIfNoWorld) {
getDetails(worldId)
} else {
getDetailsOrNull(worldId) ?: return collection
}
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Returns the backing byte buffer holding all the computed information.
* @throws IllegalStateException if the buffer is null, meaning it has no yet been
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,100 @@ public class NpcInfo internal constructor(
}
}

/**
* Gets the world details implementation of the specified [worldId], or null if it doesn't exist.
* This function will throw an exception if it is called post-deallocation for the root world,
* as it will then be deallocated.
*/
private fun getDetailsOrNull(worldId: Int): NpcInfoWorldDetails? {
val details =
if (worldId == ROOT_WORLD) {
details[WORLD_ENTITY_CAPACITY]
} else {
if (worldId !in 0..<WORLD_ENTITY_CAPACITY) {
return null
}
details[worldId]
}
return details
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices
*/
public fun getHighResolutionIndices(worldId: Int): ArrayList<Int> {
val details = getDetails(worldId)
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers, or null
* if the provided world does not exist.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices, or null if the world does not exist.
*/
public fun getHighResolutionIndicesOrNull(worldId: Int): ArrayList<Int>? {
val details = getDetailsOrNull(worldId) ?: return null
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Appends the high resolution indices of the given [worldId] to the provided
* [collection]. This can be used to determine which NPCs the player is currently
* seeing in the client. Servers often rely on this metric to determine things
* such as aggression/hunt.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @param collection the mutable collection of integer indices to append the indices into.
* @param throwExceptionIfNoWorld whether to throw an exception if the world does not exist.
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD],
* as long as [throwExceptionIfNoWorld] is true.
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated. This will only be thrown if [throwExceptionIfNoWorld]
* is true.
* @return the provided [collection] to chaining.
*/
@JvmOverloads
public fun <T> appendHighResolutionIndices(
worldId: Int,
collection: T,
throwExceptionIfNoWorld: Boolean = true,
): T where T : MutableCollection<Int> {
val details =
if (throwExceptionIfNoWorld) {
getDetails(worldId)
} else {
getDetailsOrNull(worldId) ?: return collection
}
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Returns the backing byte buffer holding all the computed information.
* @throws IllegalStateException if the buffer is null, meaning it has no yet been
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,100 @@ public class NpcInfo internal constructor(
}
}

/**
* Gets the world details implementation of the specified [worldId], or null if it doesn't exist.
* This function will throw an exception if it is called post-deallocation for the root world,
* as it will then be deallocated.
*/
private fun getDetailsOrNull(worldId: Int): NpcInfoWorldDetails? {
val details =
if (worldId == ROOT_WORLD) {
details[WORLD_ENTITY_CAPACITY]
} else {
if (worldId !in 0..<WORLD_ENTITY_CAPACITY) {
return null
}
details[worldId]
}
return details
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices
*/
public fun getHighResolutionIndices(worldId: Int): ArrayList<Int> {
val details = getDetails(worldId)
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Gets the high resolution indices of the given [worldId] in a new arraylist of integers, or null
* if the provided world does not exist.
* The list is initialized to an initial capacity equal to the high resolution npc index count.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD].
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated.
* @return the newly created arraylist of indices, or null if the world does not exist.
*/
public fun getHighResolutionIndicesOrNull(worldId: Int): ArrayList<Int>? {
val details = getDetailsOrNull(worldId) ?: return null
val collection = ArrayList<Int>(details.highResolutionNpcIndexCount)
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Appends the high resolution indices of the given [worldId] to the provided
* [collection]. This can be used to determine which NPCs the player is currently
* seeing in the client. Servers often rely on this metric to determine things
* such as aggression/hunt.
* @param worldId the worldId to collect the indices from. For root world, use [ROOT_WORLD].
* @param collection the mutable collection of integer indices to append the indices into.
* @param throwExceptionIfNoWorld whether to throw an exception if the world does not exist.
* @throws IllegalArgumentException if the world id is not in range of 0..<2048, or [ROOT_WORLD],
* as long as [throwExceptionIfNoWorld] is true.
* @throws IllegalStateException if the provided world has not been allocated. It is up to the
* caller to ensure the world they're accessible is available. Root world will always be available
* as long as the given info object is allocated. This will only be thrown if [throwExceptionIfNoWorld]
* is true.
* @return the provided [collection] to chaining.
*/
@JvmOverloads
public fun <T> appendHighResolutionIndices(
worldId: Int,
collection: T,
throwExceptionIfNoWorld: Boolean = true,
): T where T : MutableCollection<Int> {
val details =
if (throwExceptionIfNoWorld) {
getDetails(worldId)
} else {
getDetailsOrNull(worldId) ?: return collection
}
for (i in 0..<details.highResolutionNpcIndexCount) {
val index = details.highResolutionNpcIndices[i].toInt()
collection.add(index)
}
return collection
}

/**
* Returns the backing byte buffer holding all the computed information.
* @throws IllegalStateException if the buffer is null, meaning it has no yet been
Expand Down
Loading

0 comments on commit 0cd17c7

Please sign in to comment.