Skip to content

Commit

Permalink
Merge pull request #393 from Jameskmonger/mining-task
Browse files Browse the repository at this point in the history
refactor: ♻️ convert mining to new task system
  • Loading branch information
SchauweM authored Mar 9, 2023
2 parents b15847a + f815ff0 commit 3e733e5
Show file tree
Hide file tree
Showing 7 changed files with 1,487 additions and 1,312 deletions.
2,438 changes: 1,289 additions & 1,149 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
"homepage": "https://github.com/runejs/server#readme",
"dependencies": {
"@runejs/common": "^2.0.1",
"@runejs/store": "^1.0.0-beta.1",
"@runejs/login-server": "^2.0.0",
"@runejs/store": "^1.0.0-beta.1",
"@runejs/update-server": "^1.3.0",
"bigi": "^1.4.2",
"js-yaml": "^3.14.1",
Expand Down
161 changes: 3 additions & 158 deletions src/engine/world/skill-util/harvest-skill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import { Player } from '@engine/world/actor/player/player';
import { IHarvestable } from '@engine/world/config/harvestable-object';
import { soundIds } from '@engine/world/config/sound-ids';
import { Skill } from '@engine/world/actor/skills';
import { getBestAxe, getBestPickaxe, HarvestTool } from '@engine/world/config/harvest-tool';
import { randomBetween } from '@engine/util/num';
import { ObjectInteractionAction } from '@engine/action';
import { colors } from '@engine/util/colors';
import { checkForGemBoost } from '@engine/world/skill-util/glory-boost';
import { colorText } from '@engine/util/strings';
import { rollBirdsNestType, rollGemRockResult, rollGemType } from '@engine/world/skill-util/harvest-roll';
import { getBestAxe, HarvestTool } from '@engine/world/config/harvest-tool';
import { findItem } from '@engine/config/config-handler';
import { activeWorld } from '@engine/world';
import { loopingEvent } from '@engine/plugins';
Expand All @@ -20,29 +14,18 @@ import { logger } from '@runejs/common';
* @returns a {@link HarvestTool} if the player can harvest the object, or undefined if they cannot.
*/
export function canInitiateHarvest(player: Player, target: IHarvestable, skill: Skill): undefined | HarvestTool {
if (!target) {
switch (skill) {
case Skill.MINING:
player.sendMessage('There is current no ore available in this rock.');
break;
default:
player.sendMessage(colorText('HARVEST SKILL ERROR, PLEASE CONTACT DEVELOPERS', colors.red));
break;


}
player.playSound(soundIds.oreEmpty, 7, 0);
return;
}

const item = findItem(target.itemId);

if (!item) {
logger.error(`Could not find item with id ${target.itemId} for harvestable object.`);
player.sendMessage('Sorry, there was an error. Please contact a developer.');
return;
}

let targetName = item.name.toLowerCase();

switch (skill) {
case Skill.MINING:
targetName = targetName.replace(' ore', '');
Expand All @@ -53,9 +36,6 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
// Check player level against the required level
if (!player.skills.hasLevel(skill, target.level)) {
switch (skill) {
case Skill.MINING:
player.sendMessage(`You need a Mining level of ${target.level} to mine this rock.`, true);
break;
case Skill.WOODCUTTING:
player.sendMessage(`You need a Woodcutting level of ${target.level} to chop down this tree.`, true);
break;
Expand All @@ -65,18 +45,12 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
// Check the players equipment and inventory for a tool
let tool;
switch (skill) {
case Skill.MINING:
tool = getBestPickaxe(player);
break;
case Skill.WOODCUTTING:
tool = getBestAxe(player);
break;
}
if (tool == null) {
switch (skill) {
case Skill.MINING:
player.sendMessage('You do not have a pickaxe for which you have the level to use.');
break;
case Skill.WOODCUTTING:
player.sendMessage('You do not have an axe for which you have the level to use.');
break;
Expand All @@ -93,132 +67,3 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:


}

export function handleHarvesting(details: ObjectInteractionAction, tool: HarvestTool, target: IHarvestable, skill: Skill): void {
let itemToAdd = target.itemId;
if (itemToAdd === 1436 && details.player.skills.hasLevel(Skill.MINING, 30)) {
itemToAdd = 7936;
}
if (details.object.objectId === 2111 && details.player.skills.hasLevel(Skill.MINING, 30)) {
itemToAdd = rollGemRockResult().itemId;
}
const item = findItem(target.itemId);
if (!item) {
logger.error(`Could not find item with id ${target.itemId} for harvestable object.`);
return;
}

let targetName = item.name.toLowerCase();

switch (skill) {
case Skill.MINING:
targetName = targetName.replace(' ore', '');
break;
}

switch (skill) {
case Skill.MINING:
details.player.sendMessage('You swing your pick at the rock.');
break;
case Skill.WOODCUTTING:
details.player.sendMessage('You swing your axe at the tree.');
break;
}
details.player.face(details.position);
details.player.playAnimation(tool.animation);

// Create a looping action to handle the tick related actions in harvesting
const loop = loopingEvent({ player: details.player });
let elapsedTicks = 0;

loop.event.subscribe(() => {

// Check if the amount of ticks passed equal the tools pulses
if (elapsedTicks % 3 === 0 && elapsedTicks != 0) {
const successChance = randomBetween(0, 255);
let toolLevel = tool.level - 1;
if (tool.itemId === 1349 || tool.itemId === 1267) {
toolLevel = 2;
}
const percentNeeded = target.baseChance + toolLevel + details.player.skills.values[skill].level;
if (successChance <= percentNeeded) {
if (details.player.inventory.hasSpace()) {
let randomLoot = false;
let roll = 0;
switch (skill) {
case Skill.MINING:
roll = randomBetween(1, checkForGemBoost(details.player));
if (roll === 1) {
randomLoot = true;
details.player.sendMessage(colorText('You found a rare gem.', colors.red));
details.player.giveItem(rollGemType());
}
break;
case Skill.WOODCUTTING:
roll = randomBetween(1, 256);
if (roll === 1) {
randomLoot = true;
details.player.sendMessage(colorText('A bird\'s nest falls out of the tree.', colors.red));
activeWorld.globalInstance.spawnWorldItem(rollBirdsNestType(), details.player.position,
{ owner: details.player, expires: 300 });
}
break;
}
if (!randomLoot) {
switch (skill) {
case Skill.MINING:
details.player.sendMessage(`You manage to mine some ${targetName}.`);
break;
case Skill.WOODCUTTING:
details.player.sendMessage(`You manage to chop some ${targetName}.`);
break;
}

details.player.giveItem(itemToAdd);

}
details.player.skills.addExp(skill, target.experience);
if (randomBetween(0, 100) <= target.break) {
details.player.playSound(soundIds.oreDepeleted);

const replacementObject = target.objects.get(details.object.objectId);
const respawnTime = randomBetween(target.respawnLow, target.respawnHigh);

if (replacementObject) {
details.player.instance.replaceGameObject(replacementObject, details.object, respawnTime);
}

details.player.playAnimation(null);

loop.cancel();
return;
}
} else {
details.player.sendMessage(`Your inventory is too full to hold any more ${targetName}.`, true);
details.player.playSound(soundIds.inventoryFull);
loop.cancel();
return;
}
}

} else {
if (elapsedTicks % 1 == 0 && elapsedTicks != 0) {
switch (skill) {
case Skill.MINING:
details.player.playSound(soundIds.pickaxeSwing, 7, 0);
break;
case Skill.WOODCUTTING:
details.player.playSound(soundIds.axeSwing[Math.floor(Math.random() * soundIds.axeSwing.length)], 7, 0);
break;
}
}
}
if (elapsedTicks % 3 == 0 && elapsedTicks != 0) {
details.player.playAnimation(tool.animation);
}
elapsedTicks++;
}, () => {
}, () => {
details.player.playAnimation(null);
});
}
23 changes: 23 additions & 0 deletions src/plugins/skills/mining/chance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { randomBetween } from '@engine/util';
import { IHarvestable } from '@engine/world/config';

/**
* Roll a random number between 0 and 255 and compare it to the percent needed to mine the ore.
*
* @param ore The ore to mine
* @param toolLevel The level of the pickaxe being used
* @param miningLevel The player's mining level
*
* @returns True if the tree was successfully cut, false otherwise
*/
export const canMine = (
ore: IHarvestable,
toolLevel: number,
miningLevel: number
): boolean => {
const successChance = randomBetween(0, 255);

const percentNeeded =
ore.baseChance + toolLevel + miningLevel;
return successChance <= percentNeeded;
};
144 changes: 144 additions & 0 deletions src/plugins/skills/mining/mining-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { LandscapeObject } from '@runejs/filestore';
import { findItem } from '@engine/config';
import { ActorLandscapeObjectInteractionTask } from '@engine/task/impl';
import { colors, colorText, randomBetween } from '@engine/util';
import { Player, Skill } from '@engine/world/actor';
import { HarvestTool, IHarvestable, soundIds } from '@engine/world/config';
import { checkForGemBoost } from '@engine/world/skill-util/glory-boost';
import { rollGemType } from '@engine/world/skill-util/harvest-roll';
import { canMine } from './chance';

/**
* A task that handles mining. It is a subclass of ActorLandscapeObjectInteractionTask, which means that it will
* walk to the object, and then execute the task when it is in range.
*
* The mining task will repeat until the player's inventory is full, or the rock is depleted, or the task is otherwise
* stopped.
*
* @author jameskmonger
*/
export class MiningTask extends ActorLandscapeObjectInteractionTask<Player> {
/**
* The number of ticks that have elapsed since this task was started.
*
* We use this to determine when to mine the next ore, or play the next animation.
*/
private elapsedTicks = 0;

/**
* The name of the item that we are mining.
*/
private targetItemName: string;

constructor(player: Player, landscapeObject: LandscapeObject, private readonly ore: IHarvestable, private readonly tool: HarvestTool) {
super(player, landscapeObject);

const item = findItem(ore.itemId);

if (!item) {
throw new Error(`Could not find item with ID ${ore.itemId}`);
}

this.targetItemName = item.name.toLowerCase().replace(' ore', '')
}

public execute(): void {
const taskIteration = this.elapsedTicks++;

// This will be null if the player is not in range of the object.
if (!this.landscapeObject) {
return;
}

if (!this.hasLevel()) {
this.actor.sendMessage(`You need a Mining level of ${this.ore.level} to mine this rock.`, true);
return;
}

if (!this.hasMaterials()) {
this.actor.sendMessage('You do not have a pickaxe for which you have the level to use.');
return;
}

// Check if the players inventory is full, and notify them if its full.
if (!this.actor.inventory.hasSpace()) {
this.actor.sendMessage(`Your inventory is too full to hold any more ${this.targetItemName}.`, true);
this.actor.playSound(soundIds.inventoryFull);
return;
}

// mining in original plugin took 3 ticks to mine a rock, so we'll do the same for now
if (taskIteration % 3 !== 0) {
return;
}

this.actor.playSound(soundIds.pickaxeSwing, 7, 0);
this.actor.playAnimation(this.tool.animation);

// Get tool level, and set it to 2 if the tool is an iron hatchet or iron pickaxe
// TODO why is this set to 2? Was ported from the old code
let toolLevel = this.tool.level - 1;
if(this.tool.itemId === 1349 || this.tool.itemId === 1267) {
toolLevel = 2;
}

// roll for success
const succeeds = canMine(this.ore, toolLevel, this.actor.skills.mining.level);
if(!succeeds) {
return;
}

const findsRareGem = randomBetween(1, checkForGemBoost(this.actor)) === 1;
if (findsRareGem) {
this.actor.sendMessage(colorText('You found a rare gem.', colors.red));
this.actor.giveItem(rollGemType());
} else {
this.actor.sendMessage(`You manage to mine some ${this.targetItemName}.`);
this.actor.giveItem(this.ore.itemId);

// TODO (Jameskmonger) handle Gem rocks and Pure essence rocks
// if (itemToAdd === 1436 && details.player.skills.hasLevel(Skill.MINING, 30)) {
// itemToAdd = 7936;
// }
// if (details.object.objectId === 2111 && details.player.skills.hasLevel(Skill.MINING, 30)) {
// itemToAdd = rollGemRockResult().itemId;
// }
}

this.actor.skills.addExp(Skill.MINING, this.ore.experience);

// check if the rock is depleted
if (randomBetween(0, 100) <= this.ore.break) {
this.actor.playSound(soundIds.oreDepeleted);
this.actor.playAnimation(null);

const replacementObject = this.ore.objects.get(this.landscapeObject.objectId);

if (replacementObject) {
const respawnTime = randomBetween(this.ore.respawnLow, this.ore.respawnHigh);
this.actor.instance.replaceGameObject(replacementObject, this.landscapeObject, respawnTime);
}

this.stop();
return;
}
}

/**
* Checks if the player has the pickaxe they started with.
*
* @returns true if the player has the pickaxe, false otherwise
*/
private hasMaterials() {
return this.actor.inventory.has(this.tool.itemId);
}

/**
* Check that the player still has the level to mine the ore.
*
* @returns true if the player has the level, false otherwise
*/
private hasLevel() {
return this.actor.skills.hasLevel(Skill.MINING, this.ore.level);
}
}
Loading

0 comments on commit 3e733e5

Please sign in to comment.