diff --git a/game/plugin/skills/woodcutting/build.gradle b/game/plugin/skills/woodcutting/build.gradle new file mode 100644 index 00000000..0c154fb3 --- /dev/null +++ b/game/plugin/skills/woodcutting/build.gradle @@ -0,0 +1,8 @@ +plugin { + name = "woodcutting_skill" + packageName = "org.apollo.game.plugin.skills.woodcutting" + authors = [ + "tlf30" + ] + dependencies = ["api"] +} \ No newline at end of file diff --git a/game/plugin/skills/woodcutting/src/axe.kt b/game/plugin/skills/woodcutting/src/axe.kt new file mode 100644 index 00000000..d7d23e81 --- /dev/null +++ b/game/plugin/skills/woodcutting/src/axe.kt @@ -0,0 +1,23 @@ +package org.apollo.game.plugin.skills.woodcutting + +import org.apollo.game.model.Animation; + +//Animation IDs thanks to Deadly A G S at rune-server.ee +enum class Axe(val id: Int, val level: Int, val animation: Animation, val pulses: Int) { + RUNE(1359, 41, Animation(867), 3), + ADAMANT(1357, 31, Animation(869), 4), + MITHRIL(1355, 21, Animation(871), 5), + BLACK(1361, 11, Animation(873), 6), + STEEL(1353, 6, Animation(875), 6), + IRON(1349, 1, Animation(877), 7), + BRONZE(1351, 1, Animation(879), 8); + + companion object { + private val AXES = Axe.values() + fun getAxes(): Array { + return AXES + } + } +} + + diff --git a/game/plugin/skills/woodcutting/src/wood.kt b/game/plugin/skills/woodcutting/src/wood.kt new file mode 100644 index 00000000..71c910ad --- /dev/null +++ b/game/plugin/skills/woodcutting/src/wood.kt @@ -0,0 +1,72 @@ +package org.apollo.game.plugin.skills.woodcutting + +private val NORMAL_STUMP = 1342 +private val ACHEY_STUMP = 3371 +private val OAK_STUMP = 1342 +private val WILLOW_STUMP = 1342 +private val TEAK_STUMP = 1342 +private val MAPLE_STUMP = 1342 +private val MAHOGANY_STUMP = 1342 +private val YEW_STUMP = 1342 +private val MAGIC_STUMP = 1324 + +private val NORMAL_OBJECTS = hashSetOf( + 1276, 1277, 1278, 1279, 1280, 1282, 1283, 1284, 1285, 1285, 1286, 1289, 1290, 1291, 1315, + 1316, 1318, 1330, 1331, 1332, 1365, 1383, 1384, 2409, 3033, 3034, 3035, 3036, 3881, 3882, + 3883, 5902, 5903, 5904, 10041 +) + +private val ACHEY_OBJECTS = hashSetOf( + 2023 +) + +private val OAK_OBJECTS = hashSetOf( + 1281, 3037 +) + +private val WILLOW_OBJECTS = hashSetOf( + 5551, 5552, 5553 +) + +private val TEAK_OBJECTS = hashSetOf( + 9036 +) + + +private val MAPLE_OBJECTS = hashSetOf( + 1307, 4674 +) + +private val MAHOGANY_OBJECTS = hashSetOf( + 9034 +) + +private val YEW_OBJECTS = hashSetOf( + 1309 +) + +private val MAGIC_OBJECTS = hashSetOf( + 1292, 1306 +) + +/** + * values thanks to: http://oldschoolrunescape.wikia.com/wiki/Woodcutting + */ +enum class Tree(val id: Int, val objects: HashSet, val stump: Int, val level: Int, val exp: Double, val chance: Double) { + NORMAL(1511, NORMAL_OBJECTS, NORMAL_STUMP, 1, 25.0, 100.0), + ACHEY(2862, ACHEY_OBJECTS, ACHEY_STUMP, 1, 25.0, 100.0), + OAK(1521, OAK_OBJECTS, OAK_STUMP, 15, 37.5, 0.125), + WILLOW(1519, WILLOW_OBJECTS, WILLOW_STUMP, 30, 67.5, 0.125), + TEAK(6333, TEAK_OBJECTS, TEAK_STUMP, 35, 85.0, 0.125), + MAPLE(1517, MAPLE_OBJECTS, MAPLE_STUMP, 45, 100.0, 0.125), + MAHOGANY(6332, MAHOGANY_OBJECTS, MAHOGANY_STUMP, 50, 125.0, 0.125), + YEW(1515, YEW_OBJECTS, YEW_STUMP, 60, 175.0, 0.125), + MAGIC(1513, MAGIC_OBJECTS, MAGIC_STUMP, 75, 250.0, 0.125); + + companion object { + private val TREES = Tree.values().flatMap { tree -> tree.objects.map { Pair(it, tree) } }.toMap() + fun lookup(id: Int): Tree? = TREES[id] + } +} + + diff --git a/game/plugin/skills/woodcutting/src/woodcutting.plugin.kts b/game/plugin/skills/woodcutting/src/woodcutting.plugin.kts new file mode 100644 index 00000000..eb769d49 --- /dev/null +++ b/game/plugin/skills/woodcutting/src/woodcutting.plugin.kts @@ -0,0 +1,117 @@ +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.GameConstants +import org.apollo.game.action.ActionBlock +import org.apollo.game.action.AsyncDistancedAction +import org.apollo.game.message.impl.ObjectActionMessage +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.plugin.skills.woodcutting.* +import org.apollo.game.plugins.api.* +import java.util.* +import java.util.concurrent.TimeUnit + +class WoodcuttingTarget(val objectId: Int, val position: Position, val tree: Tree) { + + /** + * Get the tree object in the world + */ + fun getObject(world: World): Optional { + val region = world.regionRepository.fromPosition(position) + return region.findObject(position, objectId) + } + + /** + * Check if the tree was cut down + */ + fun isCutDown(mob: Player): Boolean { + return rand(100) <= tree.chance * 100 + } +} + +class WoodcuttingAction(val player: Player, val tool: Axe, val target: WoodcuttingTarget) : AsyncDistancedAction(DELAY, true, player, target.position, TREE_SIZE) { + + companion object { + private val DELAY = 0 + private val TREE_SIZE = 2 + private val MINIMUM_RESPAWN_TIME = 30L //In seconds + + /** + * Find the highest level axe the player has + */ + private fun axeFor(player: Player): Axe? { + return Axe.getAxes() + .filter { it.level <= player.skills.woodcutting.currentLevel } + .filter { player.equipment.contains(it.id) || player.inventory.contains(it.id) } + .sortedByDescending { it.level } + .firstOrNull() + } + + /** + * Starts a [WoodcuttingAction] for the specified [Player], terminating the [Message] that triggered it. + */ + fun start(message: ObjectActionMessage, player: Player, wood: Tree) { + val axe = axeFor(player) + if (axe != null) { + if (player.inventory.freeSlots() == 0) { + player.inventory.forceCapacityExceeded() + return + } + + val action = WoodcuttingAction(player, axe, WoodcuttingTarget(message.id, message.position, wood)) + player.startAction(action) + } else { + player.sendMessage("You do not have an axe for which you have the level to use.") + } + + message.terminate() + } + } + + override fun action(): ActionBlock = { + mob.turnTo(position) + + val level = mob.skills.woodcutting.currentLevel + if (level < target.tree.level) { + mob.sendMessage("You do not have the required level to cut down this tree.") + stop() + } + + while (isRunning) { + mob.sendMessage("You swing your axe at the tree.") + mob.playAnimation(tool.animation) + + wait(tool.pulses) + + //Check that the object exists in the world + val obj = target.getObject(mob.world) + if (!obj.isPresent) { + stop() + } + + if (mob.inventory.add(target.tree.id)) { + val logName = Definitions.item(target.tree.id)?.name?.toLowerCase(); + mob.sendMessage("You managed to cut some $logName.") + mob.skills.addExperience(Skill.WOODCUTTING, target.tree.exp) + } + + if (target.isCutDown(mob)) { + //respawn time: http://runescape.wikia.com/wiki/Trees + val respawn = TimeUnit.SECONDS.toMillis(MINIMUM_RESPAWN_TIME + rand(150)) / GameConstants.PULSE_DELAY + mob.world.expireObject(obj.get(), target.tree.stump, respawn.toInt()) + stop() + } + } + } +} + +on { ObjectActionMessage::class } + .where { option == 1 } + .then { + val tree = Tree.lookup(id) + if (tree != null) { + WoodcuttingAction.start(this, it, tree) + } + }