mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Add tests for api plugin
This commit is contained in:
@@ -5,27 +5,73 @@ import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.cache.def.ObjectDefinition
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* Provides plugins with access to item, npc, and object definitions
|
||||
*/
|
||||
object Definitions {
|
||||
fun item(id: Int): ItemDefinition? {
|
||||
|
||||
/**
|
||||
* Returns the [ItemDefinition] with the specified [id]. Callers of this function must perform bounds checking on
|
||||
* the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < ItemDefinition.count()`).
|
||||
*
|
||||
* @throws IndexOutOfBoundsException If the id is out of bounds.
|
||||
*/
|
||||
fun item(id: Int): ItemDefinition {
|
||||
return ItemDefinition.lookup(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [ItemDefinition] with the specified name, performing case-insensitive matching. If multiple items
|
||||
* share the same name, the item with the lowest id is returned.
|
||||
*
|
||||
* The name may be suffixed with an explicit item id (as a way to disambiguate in the above case), by ending the
|
||||
* name with `_id`, e.g. `monks_robe_42`. If an explicit id is attached, it must be bounds checked (in the same
|
||||
* manner as [item(id: Int)][item]).
|
||||
*/
|
||||
fun item(name: String): ItemDefinition? {
|
||||
return findEntity(ItemDefinition::getDefinitions, ItemDefinition::getName, name)
|
||||
}
|
||||
|
||||
fun obj(id: Int): ObjectDefinition? {
|
||||
/**
|
||||
* Returns the [ObjectDefinition] with the specified [id]. Callers of this function must perform bounds checking on
|
||||
* the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < ObjectDefinition.count()`).
|
||||
*
|
||||
* @throws IndexOutOfBoundsException If the id is out of bounds.
|
||||
*/
|
||||
fun obj(id: Int): ObjectDefinition {
|
||||
return ObjectDefinition.lookup(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [ObjectDefinition] with the specified name, performing case-insensitive matching. If multiple objects
|
||||
* share the same name, the object with the lowest id is returned.
|
||||
*
|
||||
* The name may be suffixed with an explicit object id (as a way to disambiguate in the above case), by ending the
|
||||
* name with `_id`, e.g. `man_2`. If an explicit id is attached, it must be bounds checked (in the same
|
||||
* manner as [object(id: Int)][object]).
|
||||
*/
|
||||
fun obj(name: String): ObjectDefinition? {
|
||||
return findEntity(ObjectDefinition::getDefinitions, ObjectDefinition::getName, name)
|
||||
}
|
||||
|
||||
fun npc(id: Int): NpcDefinition? {
|
||||
/**
|
||||
* Returns the [NpcDefinition] with the specified [id]. Callers of this function must perform bounds checking on
|
||||
* the [id] prior to invoking this method (i.e. verify that `id >= 0 && id < NpcDefinition.count()`).
|
||||
*
|
||||
* @throws IndexOutOfBoundsException If the id is out of bounds.
|
||||
*/
|
||||
fun npc(id: Int): NpcDefinition {
|
||||
return NpcDefinition.lookup(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [NpcDefinition] with the specified name, performing case-insensitive matching. If multiple npcs
|
||||
* share the same name, the npc with the lowest id is returned.
|
||||
*
|
||||
* The name may be suffixed with an explicit npc id (as a way to disambiguate in the above case), by ending the
|
||||
* name with `_id`, e.g. `man_2`. If an explicit id is attached, it must be bounds checked (in the same
|
||||
* manner as [npc(id: Int)][npc]).
|
||||
*/
|
||||
fun npc(name: String): NpcDefinition? {
|
||||
return findEntity(NpcDefinition::getDefinitions, NpcDefinition::getName, name)
|
||||
}
|
||||
|
||||
+16
-5
@@ -53,26 +53,37 @@ class SkillProxy(private val skills: SkillSet, private val skill: Int) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Boosts the current level of this skill by [amount], if possible (i.e. if `current + amount <= maximum + amount`).
|
||||
* Boosts the current level of this skill by [amount], if possible.
|
||||
*/
|
||||
fun boost(amount: Int) {
|
||||
val new = Math.min(current + amount, maximum + amount)
|
||||
require(amount >= 1) { "Can only boost skills by positive values." }
|
||||
|
||||
val new = if (current - maximum > amount) {
|
||||
current
|
||||
} else {
|
||||
Math.min(current + amount, maximum + amount)
|
||||
}
|
||||
|
||||
skills.setCurrentLevel(skill, new)
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains the current level of this skill by [amount], if possible (i.e. if `current - amount >= 0`).
|
||||
* Drains the current level of this skill by [amount], if possible.
|
||||
*/
|
||||
fun drain(amount: Int) {
|
||||
require(amount >= 1) { "Can only drain skills by positive values." }
|
||||
|
||||
val new = Math.max(current - amount, 0)
|
||||
skills.setCurrentLevel(skill, new)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the current level of this skill by [amount], if possible (i.e. if `current + amount < maximum`).
|
||||
* Restores the current level of this skill by [amount], if possible.
|
||||
*/
|
||||
fun restore(amount: Int) {
|
||||
val new = Math.max(current + amount, maximum)
|
||||
require(amount >= 1) { "Can only restore skills by positive values." }
|
||||
|
||||
val new = Math.min(current + amount, maximum)
|
||||
skills.setCurrentLevel(skill, new)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.apollo.game.plugin.api
|
||||
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.area.Region
|
||||
import org.apollo.game.model.entity.Entity
|
||||
import org.apollo.game.model.entity.EntityType
|
||||
import org.apollo.game.model.entity.EntityType.DYNAMIC_OBJECT
|
||||
import org.apollo.game.model.entity.EntityType.STATIC_OBJECT
|
||||
import org.apollo.game.model.entity.obj.DynamicGameObject
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
import org.apollo.game.scheduling.ScheduledTask
|
||||
|
||||
/**
|
||||
* Finds all of the [Entities][Entity] with the specified [EntityTypes][EntityType] at the specified [position], that
|
||||
* match the provided [predicate].
|
||||
*
|
||||
* ```
|
||||
* const val GOLD_COINS = 995
|
||||
* ...
|
||||
*
|
||||
* val allCoins: Sequence<GroundItem> = region.find(position, EntityType.GROUND_ITEM) { item -> item.id == GOLD_COINS }
|
||||
* ```
|
||||
*/
|
||||
fun <T : Entity> Region.find(position: Position, vararg types: EntityType, predicate: (T) -> Boolean): Sequence<T> {
|
||||
return getEntities<T>(position, *types).asSequence().filter(predicate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first [GameObject]s with the specified [id] at the specified [position].
|
||||
*
|
||||
* Note that the iteration order of entities in a [Region] is not defined - this function should not be used if there
|
||||
* may be more than [GameObject] with the specified [id] (see [Region.findObjects]).
|
||||
*/
|
||||
fun Region.findObject(position: Position, id: Int): GameObject? {
|
||||
return find<GameObject>(position, DYNAMIC_OBJECT, STATIC_OBJECT) { it.id == id }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds **all** [GameObject]s with the specified [id] at the specified [position].
|
||||
*/
|
||||
fun Region.findObjects(position: Position, id: Int): Sequence<GameObject> {
|
||||
return find(position, DYNAMIC_OBJECT, STATIC_OBJECT) { it.id == id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first [GameObject]s with the specified [id] at the specified [position].
|
||||
*
|
||||
* Note that the iteration order of entities in a [Region] is not defined - this function should not be used if there
|
||||
* may be more than [GameObject] with the specified [id] (see [World.findObjects]).
|
||||
*/
|
||||
fun World.findObject(position: Position, id: Int): GameObject? {
|
||||
return regionRepository.fromPosition(position).findObject(position, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds **all** [GameObject]s with the specified [id] at the specified [position].
|
||||
*/
|
||||
fun World.findObjects(position: Position, id: Int): Sequence<GameObject> {
|
||||
return regionRepository.fromPosition(position).findObjects(position, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified [GameObject] from the world, replacing it with [replacement] object for [delay] **pulses**.
|
||||
*/
|
||||
fun World.replaceObject(obj: GameObject, replacement: Int, delay: Int) {
|
||||
val replacementObj = DynamicGameObject.createPublic(this, replacement, obj.position, obj.type, obj.orientation)
|
||||
|
||||
schedule(ExpireObjectTask(this, obj, replacementObj, delay))
|
||||
}
|
||||
|
||||
/**
|
||||
* A [ScheduledTask] that temporarily replaces the [existing] [GameObject] with the [replacement] [GameObject] for the
|
||||
* specified [duration].
|
||||
*
|
||||
* @param existing The [GameObject] that already exists and should be replaced.
|
||||
* @param replacement The [GameObject] to replace the [existing] object with.
|
||||
* @param duration The time, in **pulses**, for the [replacement] object to exist in the game world.
|
||||
*/
|
||||
private class ExpireObjectTask(
|
||||
private val world: World,
|
||||
private val existing: GameObject,
|
||||
private val replacement: GameObject,
|
||||
private val duration: Int
|
||||
) : ScheduledTask(0, true) {
|
||||
|
||||
private var respawning: Boolean = false
|
||||
|
||||
override fun execute() {
|
||||
val region = world.regionRepository.fromPosition(existing.position)
|
||||
|
||||
if (!respawning) {
|
||||
world.spawn(replacement)
|
||||
respawning = true
|
||||
setDelay(duration)
|
||||
} else {
|
||||
region.removeEntity(replacement)
|
||||
world.spawn(existing)
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.apollo.game.plugin.api
|
||||
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.area.Region
|
||||
import org.apollo.game.model.entity.Entity
|
||||
import org.apollo.game.model.entity.EntityType
|
||||
import org.apollo.game.model.entity.EntityType.DYNAMIC_OBJECT
|
||||
import org.apollo.game.model.entity.EntityType.STATIC_OBJECT
|
||||
import org.apollo.game.model.entity.obj.DynamicGameObject
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
import org.apollo.game.scheduling.ScheduledTask
|
||||
|
||||
fun <T : Entity> Region.find(position: Position, predicate: (T) -> Boolean, vararg types: EntityType): Sequence<T> {
|
||||
return getEntities<T>(position, *types).asSequence().filter(predicate)
|
||||
}
|
||||
|
||||
fun Region.findObjects(position: Position, id: Int): Sequence<GameObject> {
|
||||
return find(position, { it.id == id }, DYNAMIC_OBJECT, STATIC_OBJECT)
|
||||
}
|
||||
|
||||
fun Region.findObject(position: Position, id: Int): GameObject? {
|
||||
return find<GameObject>(position, { it.id == id }, DYNAMIC_OBJECT, STATIC_OBJECT).firstOrNull()
|
||||
}
|
||||
|
||||
fun World.findObject(position: Position, objectId: Int): GameObject? {
|
||||
return regionRepository.fromPosition(position).findObject(position, objectId)
|
||||
}
|
||||
|
||||
fun World.findObjects(position: Position, id: Int): Sequence<GameObject> {
|
||||
return regionRepository.fromPosition(position).findObjects(position, id)
|
||||
}
|
||||
|
||||
fun World.expireObject(obj: GameObject, replacement: Int, respawnDelay: Int) {
|
||||
val replacementObj = DynamicGameObject.createPublic(this, replacement, obj.position, obj.type, obj.orientation)
|
||||
|
||||
schedule(ExpireObjectTask(this, obj, replacementObj, respawnDelay))
|
||||
}
|
||||
|
||||
|
||||
class ExpireObjectTask(
|
||||
private val world: World,
|
||||
private val existing: GameObject,
|
||||
private val replacement: GameObject,
|
||||
private val respawnDelay: Int
|
||||
) : ScheduledTask(0, true) {
|
||||
|
||||
private var respawning: Boolean = false
|
||||
|
||||
override fun execute() {
|
||||
val region = world.regionRepository.fromPosition(existing.position)
|
||||
|
||||
if (!respawning) {
|
||||
world.spawn(replacement)
|
||||
respawning = true
|
||||
setDelay(respawnDelay)
|
||||
} else {
|
||||
region.removeEntity(replacement)
|
||||
world.spawn(existing)
|
||||
stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.apollo.game.plugin.api
|
||||
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.cache.def.ObjectDefinition
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.params.ItemDefinitionSource
|
||||
import org.apollo.game.plugin.testing.junit.params.NpcDefinitionSource
|
||||
import org.apollo.game.plugin.testing.junit.params.ObjectDefinitionSource
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class DefinitionsTests {
|
||||
|
||||
@Test
|
||||
fun `can find an ItemDefinition directly using its id`() {
|
||||
val searched = Definitions.item(0)
|
||||
assertEquals(items.first().id, searched.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can find an ItemDefinition using its name`() {
|
||||
val searched = Definitions.item("item_two")
|
||||
assertEquals(items[2].id, searched?.id)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ItemDefinitionSource
|
||||
fun `can find ItemDefinitions directly using id suffixing`(item: ItemDefinition) {
|
||||
val searched = Definitions.item("${item.name}_${item.id}")
|
||||
assertEquals(item.id, searched?.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can find an NpcDefinition directly using its id`() {
|
||||
val searched = Definitions.npc(0)
|
||||
assertEquals(npcs.first().id, searched.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can find an NpcDefinition using its name`() {
|
||||
val searched = Definitions.npc("npc_two")
|
||||
assertEquals(items[2].id, searched?.id)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NpcDefinitionSource
|
||||
fun `can find NpcDefinitions directly using id suffixing`(npc: NpcDefinition) {
|
||||
val searched = Definitions.npc("${npc.name}_${npc.id}")
|
||||
assertEquals(npc.id, searched?.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can find an ObjectDefinition directly using its id`() {
|
||||
val searched = Definitions.obj(0)
|
||||
assertEquals(objs.first().id, searched.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can find an ObjectDefinition using its name`() {
|
||||
val searched = Definitions.obj("obj_two")
|
||||
assertEquals(items[2].id, searched?.id)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ObjectDefinitionSource
|
||||
fun `can find ObjectDefinitions directly using id suffixing`(obj: ObjectDefinition) {
|
||||
val searched = Definitions.obj("${obj.name}_${obj.id}")
|
||||
assertEquals(obj.id, searched?.id)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
@ItemDefinitions
|
||||
val items = listOf("item zero", "item one", "item two", "item duplicate name", "item duplicate name")
|
||||
.mapIndexed { id, name -> ItemDefinition(id).also { it.name = name } }
|
||||
|
||||
@NpcDefinitions
|
||||
val npcs = listOf("npc zero", "npc one", "npc two", "npc duplicate name", "npc duplicate name")
|
||||
.mapIndexed { id, name -> NpcDefinition(id).also { it.name = name } }
|
||||
|
||||
@ObjectDefinitions
|
||||
val objs = listOf("obj zero", "obj one", "obj two", "obj duplicate name", "obj duplicate name")
|
||||
.mapIndexed { id, name -> ObjectDefinition(id).also { it.name = name } }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.apollo.game.plugin.api
|
||||
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.model.entity.Skill
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.TestMock
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class PlayerTests {
|
||||
|
||||
@TestMock
|
||||
lateinit var player: Player
|
||||
|
||||
@BeforeEach
|
||||
fun setHitpointsLevel() {
|
||||
player.skillSet.setSkill(Skill.HITPOINTS, Skill(1_154.0, 10, 10))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can boost skill above maximum level`() {
|
||||
player.apply {
|
||||
hitpoints.boost(5)
|
||||
|
||||
assertEquals(15, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `boosts to the same skill do not accumulate`() {
|
||||
player.apply {
|
||||
hitpoints.boost(5)
|
||||
hitpoints.boost(4)
|
||||
|
||||
assertEquals(15, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `greater boosts can override earlier boosts`() {
|
||||
player.apply {
|
||||
hitpoints.boost(5)
|
||||
hitpoints.boost(7)
|
||||
|
||||
assertEquals(17, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can drain skills`() {
|
||||
player.apply {
|
||||
hitpoints.drain(5)
|
||||
|
||||
assertEquals(5, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `repeated drains on the same skill accumulate`() {
|
||||
player.apply {
|
||||
hitpoints.drain(4)
|
||||
hitpoints.drain(5)
|
||||
|
||||
assertEquals(1, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot drain skills below zero`() {
|
||||
player.apply {
|
||||
hitpoints.drain(99)
|
||||
|
||||
assertEquals(0, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can restore previously-drained skills`() {
|
||||
player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1)
|
||||
|
||||
player.apply {
|
||||
hitpoints.restore(5)
|
||||
|
||||
assertEquals(6, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `repeated restores on the same skill accumulate`() {
|
||||
player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1)
|
||||
|
||||
player.apply {
|
||||
hitpoints.restore(3)
|
||||
hitpoints.restore(4)
|
||||
|
||||
assertEquals(8, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot restore skills above their maximum level`() {
|
||||
player.skillSet.setCurrentLevel(Skill.HITPOINTS, 1)
|
||||
|
||||
player.apply {
|
||||
hitpoints.restore(99)
|
||||
|
||||
assertEquals(10, hitpoints.current)
|
||||
assertEquals(10, hitpoints.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.apollo.game.plugin.api
|
||||
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.plugin.api.Position.component1
|
||||
import org.apollo.game.plugin.api.Position.component2
|
||||
import org.apollo.game.plugin.api.Position.component3
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class PositionTests {
|
||||
|
||||
@Test
|
||||
fun `Positions are destructured in the correct order`() {
|
||||
val x = 10
|
||||
val y = 20
|
||||
val z = 1
|
||||
|
||||
val position = Position(x, y, z)
|
||||
val (x2, y2, z2) = position
|
||||
|
||||
assertEquals(x, x2) { "x coordinate mismatch in Position destructuring." }
|
||||
assertEquals(y, y2) { "y coordinate mismatch in Position destructuring." }
|
||||
assertEquals(z, z2) { "z coordinate mismatch in Position destructuring." }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user