Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firemaking skill #165

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions game/plugins/src/main/kotlin/gg/rsmod/plugins/api/ext/PawnExt.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package gg.rsmod.plugins.api.ext

import gg.rsmod.game.model.Direction
import gg.rsmod.game.model.Hit
import gg.rsmod.game.model.attr.*
import gg.rsmod.game.model.entity.GameObject
import gg.rsmod.game.model.entity.Npc
import gg.rsmod.game.model.entity.Pawn
import gg.rsmod.game.model.entity.Player
import gg.rsmod.game.model.entity.*
import gg.rsmod.game.model.item.Item
import gg.rsmod.game.model.timer.FROZEN_TIMER
import gg.rsmod.game.model.timer.STUN_TIMER
Expand All @@ -22,10 +20,14 @@ fun Pawn.getInteractingItem(): Item = attr[INTERACTING_ITEM]!!.get()!!

fun Pawn.getInteractingItemId(): Int = attr[INTERACTING_ITEM_ID]!!

fun Pawn.getInteractingItemPair(): Pair<Item, Item> = Pair(getInteractingItem(), attr[OTHER_ITEM_ATTR]!!.get()!!)

fun Pawn.getInteractingItemSlot(): Int = attr[INTERACTING_ITEM_SLOT]!!

fun Pawn.getInteractingOption(): Int = attr[INTERACTING_OPT_ATTR]!!

fun Pawn.getInteractingGroundItem(): GroundItem = attr[INTERACTING_GROUNDITEM_ATTR]!!.get()!!

fun Pawn.getInteractingGameObj(): GameObject = attr[INTERACTING_OBJ_ATTR]!!.get()!!

fun Pawn.getInteractingNpc(): Npc = attr[INTERACTING_NPC_ATTR]!!.get()!!
Expand Down Expand Up @@ -100,4 +102,12 @@ fun Pawn.stun(cycles: Int) {
message("You have been stunned!")
}
}
}

fun Pawn.stepAway(){
for(direction in Direction.NESW){
if(!world.collision.isBlocked(tile, direction, false)){
walkTo(tile.step(direction))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package gg.rsmod.plugins.content.skills.firemaking

import gg.rsmod.game.model.entity.DynamicObject
import gg.rsmod.game.model.entity.GroundItem
import gg.rsmod.game.model.entity.Player
import gg.rsmod.game.model.item.Item
import gg.rsmod.game.model.queue.QueueTask
import gg.rsmod.plugins.api.Skills
import gg.rsmod.plugins.api.cfg.Items
import gg.rsmod.plugins.api.cfg.Objs
import gg.rsmod.plugins.api.ext.*

/**
* TODO: figure out what objects are used to represent the fire for each type of log
* TODO: figure out the formula RS uses to calculate the burn time of a single log
* TODO: figure out what formula RS uses for calculating the chance to successfully ignite a log
*
* This class is based upon the woodcutting skill by Tom.
*
* @author Stan van der Bend (https://www.rune-server.ee/members/StanDev/)
* @since 2019-06-23
* @version 1.0
*/
object Firemaking {

private const val ANIMATION = 733
private const val IGNITING_LOGS_SOUND = 2597
private const val BURNING_LOGS_SOUND = 2596
private const val DEFAULT_ASHES = Items.ASHES

const val DEFAULT_TINDERBOX = Items.TINDERBOX
const val DEFAULT_FIRE = Objs.FIRE_26185

data class Log(val type: LogType, val item: Int, val fire: Int)

/**
* Transforms the [item] into a [GroundItem] and then [lightLogOnGround].
*
* @param it the [QueueTask] instantiated by the firemaking plugin
* @param item the log [Item] being burned
* @param log the [LogType] of the log being burned
* @param fireId the id of the fire [DynamicObject] associated with the log
*/
suspend fun lightLog(it: QueueTask, item: Item, log: LogType, fireId: Int){

val player = it.player

if(!player.inventory.contains(item) || !canIgnite(player, log))
return

val groundItem = GroundItem(item, player.tile, player)

player.inventory.remove(item)
player.world.spawn(groundItem)

lightLogOnGround(it, groundItem, log, fireId)
}

/**
* Attempt to light a log on the ground to produce a fire.
*
* @param it the [QueueTask] instantiated by the firemaking plugin
* @param groundItem the log [GroundItem] being burned
* @param log the [LogType] of the log being burned
* @param fireId the id of the fire [DynamicObject] associated with the log
*/
suspend fun lightLogOnGround(it: QueueTask, groundItem: GroundItem, log: LogType, fireId: Int){

val player = it.player

if(!canIgnite(player, log))
return

player.filterableMessage("You attempt to light the logs.")

while (true) {

player.animate(ANIMATION)
it.wait(2)
player.playSound(IGNITING_LOGS_SOUND)
it.wait(1)

if (!canIgnite(player, log)) {
player.animate(-1)
break
}

if (attemptIgnition(player, log)) {

player.animate(-1)

val fire = DynamicObject(id = fireId, type = 10, rot = 0, tile = player.tile)
val world = player.world
world.queue {
world.remove(groundItem)
world.spawn(fire)
wait(log.burnTime.random())
world.remove(fire)
world.spawn(GroundItem(DEFAULT_ASHES, 1, fire.tile, player))
}


player.addXp(Skills.FIREMAKING, log.xp)
player.filterableMessage("You light a fire.")
player.playSound(BURNING_LOGS_SOUND)
player.stepAway()
it.wait(1)
player.faceTile(fire.tile)
break
}
it.wait(1)
}
}

/**
* Determine whether the [player] can ignite an [GroundItem] of type [log].
*
* @param player the [Player] lighting the [log]
* @param log the [LogType] of which to check the [player]'s stats for
* @return true if the [player] can ignite a [GroundItem] of type [log],
* false otherwise.
*/
private fun canIgnite(player: Player, log: LogType): Boolean {

if(player.getSkills().getMaxLevel(Skills.FIREMAKING) < log.level){
player.message("You need a Firemaking level of ${log.level} to burn this log.")
return false
}

if(!player.inventory.contains(DEFAULT_TINDERBOX)){
player.message("You need a tinderbox to light a fire.")
return false
}

if(player.world.getObject(player.tile, 10) != null){
player.message("You can't light a fire here.")
return false
}

return true
}

/**
* Roll a dice for successfully igniting a log.
*
* @param player the [Player] attempting to ignite a log
* @param log the [LogType] the type of log
* @return true if the [player] successfully ignited a log,
* false otherwise.
*/
private fun attemptIgnition(player: Player, log: LogType): Boolean{
val level = player.getSkills().getCurrentLevel(Skills.FIREMAKING)
val failureOdds = Math.random() * log.level
val successOdds = Math.random() * ((level + 1 - log.level) * (1 + log.level * 0.01))
return failureOdds < successOdds
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package gg.rsmod.plugins.content.skills.firemaking

/**
* Represents all type of logs that can be interacted with (e.g. burned) through the [Firemaking] skill.
*
* @author Stan van der Bend (https://www.rune-server.ee/members/StanDev/)
* @since 2019-06-23
* @version 1.0
*
* @param level the fire-making level required to burn this type of log
* @param xp the experience gained from burning one log of this type
* @param burnTime represents the range of possible durations for which a log of this type burns
* (this is currently the same as the respawnTime time for corresponding tree types)
*/
enum class LogType(val level: Int, val xp: Double, val burnTime: IntRange) {
NORMAL(level = 1, xp = 40.0, burnTime = 15..25),
ACHEY(level = 1, xp = 40.0, burnTime = 15..25),
OAK(level = 15, xp = 69.5, burnTime = 15..25),
WILLOW(level = 30, xp = 90.0, burnTime = 22..68),
TEAK(level = 35, xp = 105.0, burnTime = 22..68),
ARCTIC_PINE(level = 42, xp = 125.0, burnTime = 22..68),
MAPLE(level = 45, xp = 135.0, burnTime = 22..68),
MAHOGANY(level = 50, xp = 157.5, burnTime = 22..68),
YEW(level = 60, xp = 202.5, burnTime = 22..68),
MAGIC(level = 75, xp = 303.8, burnTime = 22..68),
REDWOOD(level = 90, xp = 350.0, burnTime = 50..100)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gg.rsmod.plugins.content.skills.firemaking

import gg.rsmod.plugins.content.skills.firemaking.Firemaking.DEFAULT_FIRE
import gg.rsmod.plugins.content.skills.firemaking.Firemaking.DEFAULT_TINDERBOX
import gg.rsmod.plugins.content.skills.firemaking.Firemaking.Log

private val logs = setOf(
Log(LogType.NORMAL, item = Items.LOGS, fire = DEFAULT_FIRE),
Log(LogType.ACHEY, item = Items.ACHEY_TREE_LOGS, fire = DEFAULT_FIRE),
Log(LogType.OAK, item = Items.OAK_LOGS, fire = DEFAULT_FIRE),
Log(LogType.WILLOW, item = Items.WILLOW_LOGS, fire = DEFAULT_FIRE),
Log(LogType.TEAK, item = Items.TEAK_LOGS, fire = DEFAULT_FIRE),
Log(LogType.MAPLE, item = Items.MAPLE_LOGS, fire = DEFAULT_FIRE),
Log(LogType.MAHOGANY, item = Items.MAHOGANY_LOGS, fire = DEFAULT_FIRE),
Log(LogType.YEW, item = Items.YEW_LOGS, fire = DEFAULT_FIRE),
Log(LogType.MAGIC, item = Items.MAGIC_LOGS, fire = DEFAULT_FIRE),
Log(LogType.REDWOOD, item = Items.REDWOOD_LOGS, fire = DEFAULT_FIRE)
)

logs.forEach { log ->

on_item_on_item(item1 = log.item, item2 = DEFAULT_TINDERBOX){
val logItem = player.getInteractingItemPair().toList().find { it.getName(player.world.definitions).contains("log", true) }?:return@on_item_on_item
player.queue {
Firemaking.lightLog(this, logItem, log.type, log.fire)
}
}

on_ground_item_option(item = log.item, option = "Light") {
val groundItem = player.getInteractingGroundItem()
player.queue {
Firemaking.lightLogOnGround(this, groundItem, log.type, log.fire)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package gg.rsmod.plugins.content.skills.woodcutting

import gg.rsmod.plugins.api.cfg.Items

/**
* @author Tom <[email protected]>
*/
enum class TreeType(val level: Int, val xp: Double, val log: Int, val depleteChance: Int, val respawnTime: IntRange) {
TREE(level = 1, xp = 25.0, log = 1511, depleteChance = 0, respawnTime = 15..25),
ACHEY(level = 1, xp = 25.0, log = 2862, depleteChance = 0, respawnTime = 15..25),
OAK(level = 15, xp = 37.5, log = 1521, depleteChance = 0, respawnTime = 15..25),
WILLOW(level = 30, xp = 67.5, log = 1519, depleteChance = 8, respawnTime = 22..68),
TEAK(level = 35, xp = 85.0, log = 6333, depleteChance = 8, respawnTime = 22..68),
MAPLE(level = 45, xp = 100.0, log = 1517, depleteChance = 8, respawnTime = 22..68),
HOLLOW(level = 45, xp = 82.0, log = 3239, depleteChance = 8, respawnTime = 22..68),
MAHOGANY(level = 50, xp = 125.0, log = 6332, depleteChance = 8, respawnTime = 22..68),
YEW(level = 60, xp = 175.0, log = 1515, depleteChance = 8, respawnTime = 22..68),
MAGIC(level = 75, xp = 250.0, log = 1513, depleteChance = 8, respawnTime = 22..68),
REDWOOD(level = 90, xp = 380.0, log = 19669, depleteChance = 11, respawnTime = 50..100),
TREE(level = 1, xp = 25.0, log = Items.LOGS, depleteChance = 0, respawnTime = 15..25),
ACHEY(level = 1, xp = 25.0, log = Items.ACHEY_TREE_LOGS, depleteChance = 0, respawnTime = 15..25),
OAK(level = 15, xp = 37.5, log = Items.OAK_LOGS, depleteChance = 0, respawnTime = 15..25),
WILLOW(level = 30, xp = 67.5, log = Items.WILLOW_LOGS, depleteChance = 8, respawnTime = 22..68),
TEAK(level = 35, xp = 85.0, log = Items.TEAK_LOGS, depleteChance = 8, respawnTime = 22..68),
MAPLE(level = 45, xp = 100.0, log = Items.MAPLE_LOGS, depleteChance = 8, respawnTime = 22..68),
HOLLOW(level = 45, xp = 82.0, log = Items.BARK, depleteChance = 8, respawnTime = 22..68),
MAHOGANY(level = 50, xp = 125.0, log = Items.MAHOGANY_LOGS, depleteChance = 8, respawnTime = 22..68),
YEW(level = 60, xp = 175.0, log = Items.YEW_LOGS, depleteChance = 8, respawnTime = 22..68),
MAGIC(level = 75, xp = 250.0, log = Items.MAGIC_LOGS, depleteChance = 8, respawnTime = 22..68),
REDWOOD(level = 90, xp = 380.0, log = Items.REDWOOD_LOGS, depleteChance = 11, respawnTime = 50..100),
}