Skip to content

Commit a874299

Browse files
authored
Merge branch 'develop' into fix-docker
2 parents 27c8283 + 6622b47 commit a874299

File tree

6 files changed

+242
-161
lines changed

6 files changed

+242
-161
lines changed

README.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,16 @@ The game server currently runs a build of RuneScape from October 30th-31st, 2006
1414

1515
## Setup
1616

17-
1. Download and install NodeJS **version 16 or higher**: https://nodejs.org/en/
18-
2. Clone the Github Repo: https://github.com/runejs/server
19-
3. Install dependencies by navigating to the project in your Terminal or command prompt and running the command npm install
20-
4. Copy the `config/server-config.example.json` and paste it into the same folder using the name `server-config.json`
21-
5. *Optional:* Go into the new `server-config.json` file and modify the RSA modulus and exponent with the ones matching your desired game client
22-
- You may also modify the server's port and host address from this configuration file
23-
6. Run the game server with `npm start`
24-
25-
The game server will spin up and be accessible via port 43594.
17+
### Prerequisites
2618

27-
### Setup using docker
19+
- [`docker`](https://docs.docker.com/get-docker/) and [`docker-compose`](https://docs.docker.com/compose/install/)
20+
- If on Windows, `docker-compose` comes with `docker`
2821

29-
1. Download and install Docker and Docker Compose: first https://docs.docker.com/get-docker/ then https://docs.docker.com/compose/install/
30-
2. Copy the `config/server-config.example.json` and paste it into the same folder using the name `server-config.json`
31-
3. Go into your new `server-config.json` file and modify your RSA modulus and exponent with the ones matching your game client
22+
1. Copy the `config/server-config.example.json` and paste it into the same folder using the name `server-config.json`
23+
2. Go into your new `server-config.json` file and modify your RSA modulus and exponent with the ones matching your game client
3224
- You may also modify the server's port and host address from this configuration file
33-
4. Build the docker image with `docker-compose build`
34-
5. Run the game server with `docker-compose up'
25+
3. Build the docker image with `docker-compose build`
26+
4. Run the game server with `docker-compose up'
3527

3628
The game server will spin up and be accessible via port 43594.
3729

@@ -40,6 +32,12 @@ The game server will spin up and be accessible via port 43594.
4032
The [RuneScape Java Client #435](https://github.com/runejs/refactored-client-435) must be used to log into a RuneJS game server.
4133

4234
## Additional Commands
35+
36+
Before running these commands, you must:
37+
38+
1. have [NodeJS **version 16 or higher**](https://nodejs.org/en/) installed on your machine
39+
2. run `npm install` from the root of this project
40+
4341
* `npm run game` Launches the game server by itself without building
4442
* `npm run game:dev` Builds and launches the game server by itself in watch mode
4543
* `npm run login` Launches the login server by itself without building

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import { findItem } from '@engine/config/config-handler';
1313
import { activeWorld } from '@engine/world';
1414
import { loopingEvent } from '@engine/plugins';
1515

16+
/**
17+
* Check if a player can harvest a given {@link IHarvestable}
18+
*
19+
* @returns a {@link HarvestTool} if the player can harvest the object, or undefined if they cannot.
20+
*/
1621
export function canInitiateHarvest(player: Player, target: IHarvestable, skill: Skill): undefined | HarvestTool {
1722
if (!target) {
1823
switch (skill) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { randomBetween } from '@engine/util';
2+
import { IHarvestable } from '@engine/world/config';
3+
4+
/**
5+
* Roll a random number between 0 and 255 and compare it to the percent needed to cut the tree.
6+
*
7+
* @param tree The tree to cut
8+
* @param toolLevel The level of the axe being used
9+
* @param woodcuttingLevel The player's woodcutting level
10+
*
11+
* @returns True if the tree was successfully cut, false otherwise
12+
*/
13+
export const canCut = (
14+
tree: IHarvestable,
15+
toolLevel: number,
16+
woodcuttingLevel: number
17+
): boolean => {
18+
const successChance = randomBetween(0, 255);
19+
20+
const percentNeeded =
21+
tree.baseChance + toolLevel + woodcuttingLevel;
22+
return successChance <= percentNeeded;
23+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
ObjectInteractionActionHook,
3+
} from '@engine/action';
4+
import { getTreeIds } from '@engine/world/config/harvestable-object';
5+
import { runWoodcuttingTask } from './woodcutting-task';
6+
7+
/**
8+
* Woodcutting plugin
9+
*
10+
* This uses the task system to schedule actions.
11+
*/
12+
export default {
13+
pluginId: 'rs:woodcutting',
14+
hooks: [
15+
/**
16+
* "Chop down" / "chop" object interaction hook.
17+
*/
18+
{
19+
type: 'object_interaction',
20+
options: [ 'chop down', 'chop' ],
21+
objectIds: getTreeIds(),
22+
handler: ({ player, object }) => {
23+
runWoodcuttingTask(player, object);
24+
}
25+
} as ObjectInteractionActionHook
26+
]
27+
};
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { Skill } from '@engine/world/actor/skills';
2+
import { canInitiateHarvest } from '@engine/world/skill-util/harvest-skill';
3+
import { getTreeFromHealthy, IHarvestable } from '@engine/world/config/harvestable-object';
4+
import { randomBetween } from '@engine/util/num';
5+
import { colorText } from '@engine/util/strings';
6+
import { colors } from '@engine/util/colors';
7+
import { rollBirdsNestType } from '@engine/world/skill-util/harvest-roll';
8+
import { soundIds } from '@engine/world/config/sound-ids';
9+
import { findItem, findObject } from '@engine/config/config-handler';
10+
import { activeWorld } from '@engine/world';
11+
import { canCut } from './chance';
12+
import { ActorLandscapeObjectInteractionTask } from '@engine/task/impl';
13+
import { Player } from '@engine/world/actor';
14+
import { LandscapeObject } from '@runejs/filestore';
15+
import { logger } from '@runejs/common';
16+
17+
class WoodcuttingTask extends ActorLandscapeObjectInteractionTask<Player> {
18+
/**
19+
* The tree being cut down.
20+
*/
21+
private treeInfo: IHarvestable;
22+
23+
/**
24+
* The number of ticks that `execute` has been called inside this task.
25+
*/
26+
private elapsedTicks = 0;
27+
28+
/**
29+
* Create a new woodcutting task.
30+
*
31+
* @param player The player that is attempting to cut down the tree.
32+
* @param landscapeObject The object that represents the tree.
33+
* @param sizeX The size of the tree in x axis.
34+
* @param sizeY The size of the tree in y axis.
35+
*/
36+
constructor(
37+
player: Player,
38+
landscapeObject: LandscapeObject,
39+
sizeX: number,
40+
sizeY: number
41+
) {
42+
super(
43+
player,
44+
landscapeObject,
45+
sizeX,
46+
sizeY
47+
);
48+
49+
if (!landscapeObject) {
50+
this.stop();
51+
return;
52+
}
53+
54+
this.treeInfo = getTreeFromHealthy(landscapeObject.objectId);
55+
if (!this.treeInfo) {
56+
this.stop();
57+
return;
58+
}
59+
}
60+
61+
/**
62+
* Execute the main woodcutting task loop. This method is called every game tick until the task is completed.
63+
*
64+
* As this task extends {@link ActorLandscapeObjectInteractionTask}, it's important that the
65+
* `super.execute` method is called at the start of this method.
66+
*
67+
* The base `execute` performs a number of checks that allow this task to function healthily.
68+
*/
69+
public execute(): void {
70+
super.execute();
71+
72+
if (!this.isActive || !this.landscapeObject) {
73+
return;
74+
}
75+
76+
// store the tick count before incrementing so we don't need to keep track of it in all the separate branches
77+
const taskIteration = this.elapsedTicks++;
78+
79+
const tool = canInitiateHarvest(this.actor, this.treeInfo, Skill.WOODCUTTING);
80+
81+
if (!tool) {
82+
this.stop();
83+
return;
84+
}
85+
86+
if(taskIteration === 0) {
87+
this.actor.sendMessage('You swing your axe at the tree.');
88+
this.actor.face(this.landscapeObjectPosition);
89+
this.actor.playAnimation(tool.animation);
90+
return;
91+
}
92+
93+
// play a random axe sound at the correct time
94+
if(taskIteration % 3 !== 0) {
95+
if(taskIteration % 1 === 0) {
96+
const randomSoundIdx = Math.floor(Math.random() * soundIds.axeSwing.length);
97+
this.actor.playSound(soundIds.axeSwing[randomSoundIdx], 7, 0);
98+
}
99+
return;
100+
}
101+
102+
// Get tool level, and set it to 2 if the tool is an iron hatchet or iron pickaxe axe
103+
// TODO why is this set to 2? Was ported from the old code
104+
let toolLevel = tool.level - 1;
105+
if(tool.itemId === 1349 || tool.itemId === 1267) {
106+
toolLevel = 2;
107+
}
108+
109+
// roll for success
110+
const succeeds = canCut(this.treeInfo, toolLevel, this.actor.skills.woodcutting.level);
111+
if(!succeeds) {
112+
return;
113+
}
114+
115+
const targetName: string = findItem(this.treeInfo.itemId).name.toLowerCase();
116+
117+
// if player doesn't have space in inventory, stop the task
118+
if(!this.actor.inventory.hasSpace()) {
119+
this.actor.sendMessage(`Your inventory is too full to hold any more ${targetName}.`, true);
120+
this.actor.playSound(soundIds.inventoryFull);
121+
this.stop();
122+
return;
123+
}
124+
125+
const itemToAdd = this.treeInfo.itemId;
126+
const roll = randomBetween(1, 256);
127+
// roll for bird nest chance
128+
if(roll === 1) {
129+
this.actor.sendMessage(colorText(`A bird's nest falls out of the tree.`, colors.red));
130+
activeWorld.globalInstance.spawnWorldItem(rollBirdsNestType(), this.actor.position,
131+
{ owner: this.actor || null, expires: 300 });
132+
} else { // Standard log chopper
133+
this.actor.sendMessage(`You manage to chop some ${targetName}.`);
134+
this.actor.giveItem(itemToAdd);
135+
}
136+
137+
this.actor.skills.woodcutting.addExp(this.treeInfo.experience);
138+
139+
// check if the tree should be broken
140+
if(randomBetween(0, 100) <= this.treeInfo.break) {
141+
this.actor.playSound(soundIds.oreDepeleted);
142+
this.actor.instance.replaceGameObject(this.treeInfo.objects.get(this.landscapeObject.objectId),
143+
this.landscapeObject, randomBetween(this.treeInfo.respawnLow, this.treeInfo.respawnHigh));
144+
this.stop();
145+
return;
146+
}
147+
148+
this.actor.playAnimation(tool.animation);
149+
150+
}
151+
152+
/**
153+
* This method is called when the task stops.
154+
*/
155+
public onStop(): void {
156+
super.onStop();
157+
158+
this.actor.stopAnimation();
159+
}
160+
}
161+
162+
export function runWoodcuttingTask(player: Player, landscapeObject: LandscapeObject): void {
163+
const objectConfig = findObject(landscapeObject.objectId);
164+
165+
if (!objectConfig) {
166+
logger.warn(`Player ${player.username} attempted to run a woodcutting task on an invalid object (id: ${landscapeObject.objectId})`);
167+
return;
168+
}
169+
170+
const sizeX = objectConfig.rendering.sizeX;
171+
const sizeY = objectConfig.rendering.sizeY;
172+
173+
player.enqueueTask(WoodcuttingTask, [ landscapeObject, sizeX, sizeY ]);
174+
}

0 commit comments

Comments
 (0)