Skip to content

Commit 22f2929

Browse files
feat: fishing skill
Co-authored-by: James Monger <jameskmonger@hotmail.co.uk>
1 parent 493098f commit 22f2929

File tree

9 files changed

+299
-8
lines changed

9 files changed

+299
-8
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"npc": "rs:fishing_spot_bait_a",
4+
"spawn_x": 3239,
5+
"spawn_y": 3241,
6+
"spawn_level": 0,
7+
"movement_radius": 0,
8+
"face": "WEST"
9+
},
10+
{
11+
"npc": "rs:fishing_spot_bait_a",
12+
"spawn_x": 3239,
13+
"spawn_y": 3244,
14+
"spawn_level": 0,
15+
"movement_radius": 0,
16+
"face": "WEST"
17+
}
18+
]

data/config/npcs/fishing.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rs:fishing_spot_bait_a": {
3+
"game_id": 233
4+
}
5+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { LandscapeObject } from '@runejs/filestore';
2+
import { activeWorld, Position } from '@engine/world';
3+
import { Actor } from '@engine/world/actor';
4+
import { ActorWalkToTask } from './actor-walk-to-task';
5+
6+
/**
7+
* A task for an {@link Actor} to interact with another {@link Actor}.
8+
*
9+
* This task extends {@link ActorWalkToTask} and will walk the actor to the object.
10+
* Once the actor is within range of the target Actor, the task will expose the `otherActor` property
11+
*
12+
* @author jameshallam
13+
*/
14+
export abstract class ActorActorInteractionTask<TActor extends Actor = Actor, TOtherActor extends Actor = Actor> extends ActorWalkToTask<TActor, Position> {
15+
/*
16+
* TODO (jameskmonger) consider exposing this, currently people must always access it through `otherActor`
17+
* or through their own constructor
18+
*/
19+
private _targetActor: TOtherActor;
20+
21+
/**
22+
* Gets the {@link TOtherActor} that this task is interacting with.
23+
*
24+
* @returns The target actor, if is still present, and if the actor is at the destination.
25+
* Otherwise, `null`.
26+
*
27+
* TODO (jameskmonger) unit test this
28+
*/
29+
protected get otherActor(): TOtherActor | null {
30+
if (!this.atDestination) {
31+
return null;
32+
}
33+
34+
if (!this._targetActor) {
35+
return null;
36+
}
37+
38+
return this._targetActor;
39+
}
40+
41+
/**
42+
* Get the position of this task's target npc
43+
*
44+
* @returns The position of this task's target npc, or `null` if the npc is not present
45+
*/
46+
protected get otherActorPosition(): Position {
47+
if (!this._targetActor) {
48+
return null;
49+
}
50+
return this._targetActor.position
51+
}
52+
53+
/**
54+
* @param actor The actor executing this task.
55+
* @param targetActor The `TOtherActor` to interact with.
56+
* @param sizeX The size of the target TOtherActor in the X direction.
57+
* @param sizeY The size of the target TOtherActor in the Y direction.
58+
*/
59+
constructor (
60+
actor: TActor,
61+
targetActor: TOtherActor,
62+
sizeX: number = 1,
63+
sizeY: number = 1
64+
) {
65+
super(
66+
actor,
67+
// TODO (jameskmonger) this doesn't currently account for a moving NPC target
68+
targetActor.position,
69+
Math.max(sizeX, sizeY)
70+
);
71+
72+
if (!targetActor) {
73+
this.stop();
74+
return;
75+
}
76+
77+
this._targetActor = targetActor;
78+
}
79+
80+
/**
81+
* Checks for the continued presence of the target {@link Actor}, and stops the task if it is no longer present.
82+
*
83+
* TODO (jameskmonger) unit test this
84+
*/
85+
public execute() {
86+
super.execute();
87+
88+
if (!this.isActive || !this.atDestination) {
89+
return;
90+
}
91+
92+
// stop the task if the actor no longer exists
93+
if (!this._targetActor) {
94+
this.stop();
95+
return;
96+
}
97+
98+
// TODO: check npc still exists
99+
}
100+
}

src/engine/task/impl/actor-landscape-object-interaction-task.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { ActorWalkToTask } from './actor-walk-to-task';
1212
* @author jameskmonger
1313
*/
1414
export abstract class ActorLandscapeObjectInteractionTask<TActor extends Actor = Actor> extends ActorWalkToTask<TActor, LandscapeObject> {
15+
/*
16+
* TODO (jameskmonger) consider exposing this, currently people must always access it through `otherActor`
17+
* or through their own constructor
18+
*/
1519
private _landscapeObject: LandscapeObject;
1620
private _objectPosition: Position;
1721

@@ -24,9 +28,6 @@ export abstract class ActorLandscapeObjectInteractionTask<TActor extends Actor =
2428
* TODO (jameskmonger) unit test this
2529
*/
2630
protected get landscapeObject(): LandscapeObject | null {
27-
// TODO (jameskmonger) consider if we want to do these checks rather than delegating to the child task
28-
// as currently the subclass has to store it in a subclass property if it wants to use it
29-
// without these checks
3031
if (!this.atDestination) {
3132
return null;
3233
}
@@ -91,11 +92,13 @@ export abstract class ActorLandscapeObjectInteractionTask<TActor extends Actor =
9192
return;
9293
}
9394

95+
// stop the task if the object no longer exists
9496
if (!this._landscapeObject) {
9597
this.stop();
9698
return;
9799
}
98100

101+
// find the object in the world and validate that it still exists
99102
const { object: worldObject } = activeWorld.findObjectAtLocation(this.actor, this._landscapeObject.objectId, this._objectPosition);
100103

101104
if (!worldObject) {

src/engine/task/impl/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { ActorTask } from './actor-task'
22
export { ActorWalkToTask } from './actor-walk-to-task'
33
export { ActorWorldItemInteractionTask } from './actor-world-item-interaction-task'
44
export { ActorLandscapeObjectInteractionTask } from './actor-landscape-object-interaction-task'
5+
export { ActorActorInteractionTask } from './actor-actor-interaction-task'

src/engine/world/config/harvest-tool.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ const Axes: HarvestTool[] = [
4848
{ itemId: 6739, level: 61, animation: 2846 }
4949
];
5050

51+
const FishingRods: HarvestTool[] = [
52+
{ itemId: 309, level: 1, animation: 1363 }
53+
];
54+
55+
/**
56+
* Checks the players inventory and equipment for fishing rod
57+
* @param player
58+
* @return the highest level pickage the player can use, or null if theres none found
59+
*/
60+
export function getFishingRod(player: Player): HarvestTool | null {
61+
for (let i = FishingRods.length - 1; i >= 0; i--) {
62+
if (player.skills.hasLevel(Skill.FISHING, FishingRods[i].level)) {
63+
if (player.hasItemOnPerson(FishingRods[i].itemId)) {
64+
return FishingRods[i];
65+
}
66+
}
67+
}
68+
return null;
69+
}
70+
71+
5172
/**
5273
* Checks the players inventory and equipment for pickaxe
5374
* @param player
@@ -63,6 +84,7 @@ export function getBestPickaxe(player: Player): HarvestTool | null {
6384
}
6485
return null;
6586
}
87+
6688
/**
6789
* Checks the players inventory and equipment for axe
6890
* @param player

src/engine/world/skill-util/harvest-skill.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Player } from '@engine/world/actor/player/player';
22
import { IHarvestable } from '@engine/world/config/harvestable-object';
33
import { soundIds } from '@engine/world/config/sound-ids';
44
import { Skill } from '@engine/world/actor/skills';
5-
import { getBestAxe, getBestPickaxe, HarvestTool } from '@engine/world/config/harvest-tool';
5+
import { getBestAxe, getBestPickaxe, HarvestTool, getFishingRod } from '@engine/world/config/harvest-tool';
66
import { randomBetween } from '@engine/util/num';
77
import { ObjectInteractionAction } from '@engine/action';
88
import { colors } from '@engine/util/colors';
@@ -18,19 +18,20 @@ import { loopingEvent } from '@engine/plugins';
1818
*
1919
* @returns a {@link HarvestTool} if the player can harvest the object, or undefined if they cannot.
2020
*/
21-
export function canInitiateHarvest(player: Player, target: IHarvestable, skill: Skill): undefined | HarvestTool {
21+
export function canInitiateHarvest(player: Player, target: Pick<IHarvestable, 'itemId' | 'level'>, skill: Skill): undefined | HarvestTool {
2222
if (!target) {
2323
switch (skill) {
2424
case Skill.MINING:
2525
player.sendMessage('There is current no ore available in this rock.');
26+
player.playSound(soundIds.oreEmpty, 7, 0);
27+
break;
28+
case Skill.FISHING:
29+
player.sendMessage('There are no fish in that spot');
2630
break;
2731
default:
2832
player.sendMessage(colorText('HARVEST SKILL ERROR, PLEASE CONTACT DEVELOPERS', colors.red));
2933
break;
30-
31-
3234
}
33-
player.playSound(soundIds.oreEmpty, 7, 0);
3435
return;
3536
}
3637

@@ -39,6 +40,9 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
3940
case Skill.MINING:
4041
targetName = targetName.replace(' ore', '');
4142
break;
43+
case Skill.FISHING:
44+
targetName = 'fish';
45+
break;
4246
}
4347

4448

@@ -48,6 +52,9 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
4852
case Skill.MINING:
4953
player.sendMessage(`You need a Mining level of ${target.level} to mine this rock.`, true);
5054
break;
55+
case Skill.FISHING:
56+
player.sendMessage(`You need a Fishing level of ${target.level} to mine this rock`);
57+
break;
5158
case Skill.WOODCUTTING:
5259
player.sendMessage(`You need a Woodcutting level of ${target.level} to chop down this tree.`, true);
5360
break;
@@ -60,15 +67,26 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
6067
case Skill.MINING:
6168
tool = getBestPickaxe(player);
6269
break;
70+
case Skill.FISHING:
71+
// TODO: different spots need different equipment
72+
tool = getFishingRod(player);
73+
break;
6374
case Skill.WOODCUTTING:
6475
tool = getBestAxe(player);
6576
break;
6677
}
78+
79+
// let secondaryTool;
80+
// TODO: some activities need more than one tool, e.g. bait
81+
6782
if (tool == null) {
6883
switch (skill) {
6984
case Skill.MINING:
7085
player.sendMessage('You do not have a pickaxe for which you have the level to use.');
7186
break;
87+
case Skill.FISHING:
88+
player.sendMessage('You do not have a fishing rod for which you have the level to use.');
89+
break;
7290
case Skill.WOODCUTTING:
7391
player.sendMessage('You do not have an axe for which you have the level to use.');
7492
break;
@@ -88,9 +106,11 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill:
88106

89107
export function handleHarvesting(details: ObjectInteractionAction, tool: HarvestTool, target: IHarvestable, skill: Skill): void {
90108
let itemToAdd = target.itemId;
109+
// This is rune essence to pure essence
91110
if (itemToAdd === 1436 && details.player.skills.hasLevel(Skill.MINING, 30)) {
92111
itemToAdd = 7936;
93112
}
113+
// This is to deal with gem rocks
94114
if (details.object.objectId === 2111 && details.player.skills.hasLevel(Skill.MINING, 30)) {
95115
itemToAdd = rollGemRockResult().itemId;
96116
}
@@ -106,6 +126,9 @@ export function handleHarvesting(details: ObjectInteractionAction, tool: Harvest
106126
case Skill.MINING:
107127
details.player.sendMessage('You swing your pick at the rock.');
108128
break;
129+
case Skill.FISHING:
130+
details.player.sendMessage('You cast your line out.');
131+
break;
109132
case Skill.WOODCUTTING:
110133
details.player.sendMessage('You swing your axe at the tree.');
111134
break;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Skill } from '@engine/world/actor/skills';
2+
import { Player, Npc } from '@engine/world/actor';
3+
import { soundIds } from '@engine/world/config/sound-ids';
4+
import { findItem, findNpc, findObject } from '@engine/config/config-handler';
5+
import { activeWorld } from '@engine/world';
6+
import { ActorActorInteractionTask } from '@engine/task/impl';
7+
import { LandscapeObject } from '@runejs/filestore';
8+
import { logger } from '@runejs/common';
9+
import { IHarvestable } from '@engine/world/config';
10+
import { canInitiateHarvest } from '@engine/world/skill-util/harvest-skill';
11+
import { randomBetween } from '@engine/util';
12+
13+
14+
class FishingTask extends ActorActorInteractionTask<Player, Npc>{
15+
16+
private elapsedTicks = 0;
17+
18+
constructor(
19+
player: Player,
20+
fishingSpot: Npc,
21+
) {
22+
super(
23+
player,
24+
fishingSpot,
25+
1,
26+
1
27+
);
28+
29+
30+
if (!fishingSpot) {
31+
this.stop();
32+
return;
33+
}
34+
35+
}
36+
public execute(): void {
37+
super.execute()
38+
39+
if (!this.isActive || !this.otherActor) {
40+
return;
41+
}
42+
43+
// store the tick count before incrementing so we don't need to keep track of it in all the separate branches
44+
const taskIteration = this.elapsedTicks++;
45+
46+
// TODO wire this up into fishing spot config
47+
const fishingSpotInfo: Pick<IHarvestable, 'itemId' | 'level'> = {
48+
itemId: 335,
49+
50+
// TODO change this too
51+
level: 1
52+
}
53+
54+
const tool = canInitiateHarvest(this.actor, fishingSpotInfo, Skill.FISHING);
55+
56+
if (!tool) {
57+
this.stop();
58+
return;
59+
}
60+
if(taskIteration === 0) {
61+
this.actor.sendMessage('You swing your axe at the tree.');
62+
this.actor.face(this.otherActor);
63+
this.actor.playAnimation(tool.animation);
64+
return;
65+
}
66+
const roll = randomBetween(1, 256)
67+
if(roll > 200){
68+
this.actor.giveItem(335)
69+
}
70+
71+
this.actor.sendMessage('Doing a fish with a lovely long ' + findItem(tool.itemId).name)
72+
this.actor.playAnimation(tool.animation)
73+
}
74+
public onStop(): void {
75+
super.onStop();
76+
77+
this.actor.sendMessage('=====Stopped fishing=======')
78+
}
79+
}
80+
81+
82+
export function runFishingTask(player: Player, npc: Npc): void {
83+
// const npcConfig = findNpc(npc.id);
84+
85+
// if (!npcConfig) {
86+
// logger.warn(`Player ${player.username} attempted to run a fishing task on an invalid object (id: ${npc.id})`);
87+
// return;
88+
// }
89+
90+
const sizeX = 1
91+
const sizeY = 1
92+
player.enqueueTask(FishingTask, [ npc, sizeX, sizeY ]);
93+
}

0 commit comments

Comments
 (0)