Skip to content

Commit d18beee

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

File tree

9 files changed

+299
-8
lines changed

9 files changed

+299
-8
lines changed
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

+5
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+
}
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

+6-3
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

+1
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

+22
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

+28-5
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,27 +52,41 @@ 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 fish at this spot.`);
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;
5461
}
5562
return;
5663
}
64+
5765
// Check the players equipment and inventory for a tool
5866
let tool;
5967
switch (skill) {
6068
case Skill.MINING:
6169
tool = getBestPickaxe(player);
6270
break;
71+
case Skill.FISHING:
72+
// TODO (jameshallam93): different spots need different equipment
73+
tool = getFishingRod(player);
74+
break;
6375
case Skill.WOODCUTTING:
6476
tool = getBestAxe(player);
6577
break;
6678
}
79+
80+
// TODO (jameshallam93): 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;

0 commit comments

Comments
 (0)