Skip to content

Commit 60c951a

Browse files
author
src
authored
Merge branch 'develop' into development
2 parents 2586832 + e98bf92 commit 60c951a

20 files changed

+1239
-325
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"start:infra": "concurrently \"npm run start:update\" \"npm run start:login\"",
1313
"start:standalone": "concurrently \"npm run start:infra\" \"npm run start:game\"",
1414
"lint": "eslint -c .eslintrc.js --ext .ts src",
15+
"lint-fix": "eslint -c .eslintrc.js --ext .ts src --fix",
1516
"fake-players": "concurrently \"npm run build:watch\" \"node --max-old-space-size=4096 dist/index.js -fakePlayers\"",
1617
"fake-players-tick": "concurrently \"npm run build:watch\" \"node --max-old-space-size=4096 dist/index.js -fakePlayers -tickTime\"",
1718
"build:watch": "babel ./src --out-dir dist --extensions \".ts,.tsx,.js\" --source-maps --watch",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Player, playerOptions } from '../../world/actor/player/player';
2+
import { world } from '../../game-server';
3+
import { World } from '../../world';
4+
import { logger } from '@runejs/core';
5+
import { PacketData } from '../inbound-packets';
6+
7+
8+
const magicAttackPacket = (player: Player, packet: PacketData) => {
9+
const { buffer } = packet;
10+
const npcWorldIndex = buffer.get('short', 'u'); // unsigned short BE
11+
const widgetId = buffer.get('short', 'u', 'le'); // unsigned short LE
12+
const widgetChildId = buffer.get(); // unsigned short LE
13+
14+
const npc = world.npcList[npcWorldIndex];
15+
16+
player.actionPipeline.call('magic_on_npc', npc, player, widgetId, widgetChildId);
17+
};
18+
19+
export default [{
20+
opcode: 253,
21+
size: 6,
22+
handler: magicAttackPacket
23+
}];

src/game-engine/world/action/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export type ActionType =
3636
| 'move_item'
3737
| 'spawned_item_interaction'
3838

39+
| 'magic_on_item'
40+
| 'magic_on_player'
41+
| 'magic_on_npc'
42+
3943
| 'player_init'
4044
| 'player_command'
4145
| 'player_interaction'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Player } from '@engine/world/actor/player/player';
2+
import { ActionHook, getActionHooks } from '@engine/world/action/hooks';
3+
import { advancedNumberHookFilter, questHookFilter } from '@engine/world/action/hooks/hook-filters';
4+
import { ActionPipe, RunnableHooks } from '@engine/world/action/index';
5+
import { Npc } from '../actor/npc/npc';
6+
7+
8+
/**
9+
* Defines a button action hook.
10+
*/
11+
export interface MagicOnNPCActionHook extends ActionHook<MagicOnNPCAction, magiconnpcActionHandler> {
12+
// The npc world id that was clicked on after choosing the spell
13+
npcworldId?: number;
14+
// The IDs of the UI widgets that the buttons are on.
15+
widgetIds?: number[];
16+
// The child ID or list of child IDs of the button(s) within the UI widget.
17+
buttonIds?: number | number[];
18+
// Whether or not this item action should cancel other running or queued actions.
19+
cancelActions?: boolean;
20+
}
21+
22+
23+
/**
24+
* The button action hook handler function to be called when the hook's conditions are met.
25+
*/
26+
export type magiconnpcActionHandler = (buttonAction: MagicOnNPCAction) => void | Promise<void>;
27+
28+
29+
/**
30+
* Details about a button action being performed.
31+
*/
32+
export interface MagicOnNPCAction {
33+
// The npc world id that was clicked on after choosing the spell
34+
npc: Npc;
35+
// The player performing the action.
36+
player: Player;
37+
// The ID of the UI widget that the button is on.
38+
widgetId: number;
39+
// The child ID of the button within the UI widget.
40+
buttonId: number;
41+
}
42+
43+
44+
/**
45+
* The pipe that the game engine hands button actions off to.
46+
* @param npc
47+
* @param player
48+
* @param widgetId
49+
* @param buttonId
50+
*/
51+
const magicOnNpcActionPipe = (npc:Npc, player: Player, widgetId: number, buttonId: number): RunnableHooks<MagicOnNPCAction> => {
52+
//console.info(`pew pew you use magic on ${npc.name}!`);
53+
54+
// Find all object action plugins that reference this location object
55+
const matchingHooks = getActionHooks<MagicOnNPCActionHook>('magic_on_npc');
56+
57+
58+
return {
59+
hooks: matchingHooks,
60+
actionPosition: player.position,
61+
action: {
62+
npc,
63+
player,
64+
widgetId,
65+
buttonId
66+
}
67+
}
68+
69+
70+
};
71+
72+
73+
/**
74+
* Button action pipe definition.
75+
*/
76+
export default [ 'magic_on_npc', magicOnNpcActionPipe ] as ActionPipe;

src/game-engine/world/actor/actor.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { WalkingQueue } from './walking-queue';
22
import { ItemContainer } from '../items/item-container';
33
import { Animation, DamageType, Graphic, UpdateFlags } from './update-flags';
44
import { Npc } from './npc/npc';
5-
import { Skills } from '@engine/world/actor/skills';
5+
import { Skill, Skills } from '@engine/world/actor/skills';
66
import { Item } from '@engine/world/items/item';
77
import { Position } from '@engine/world/position';
88
import { DirectionData, directionFromIndex } from '@engine/world/direction';
@@ -14,6 +14,11 @@ import { WorldInstance } from '@engine/world/instances';
1414
import { Player } from '@engine/world/actor/player/player';
1515
import { ActionCancelType, ActionPipeline } from '@engine/world/action';
1616
import { LandscapeObject } from '@runejs/filestore';
17+
import { Behavior } from './behaviors/behavior';
18+
import { soundIds } from '../config/sound-ids';
19+
import { animationIds } from '../config/animation-ids';
20+
import { findNpc } from '../../config';
21+
import { itemIds } from '../config/item-ids';
1722

1823

1924
/**
@@ -36,6 +41,25 @@ export abstract class Actor {
3641

3742
public pathfinding: Pathfinding = new Pathfinding(this);
3843
public lastMovementPosition: Position;
44+
// #region Behaviors and Combat flags/checks
45+
public inCombat: boolean = false;
46+
public meleeDistance: number = 1;
47+
public Behaviors: Behavior[] = [];
48+
public isDead: boolean = false;
49+
public combatTargets: Actor[] = [];
50+
public hitPoints = this.skills.hitpoints.level * 4;
51+
public maxHitPoints = this.skills.hitpoints.level * 4;
52+
public get highestCombatSkill(): Skill {
53+
const attack = this.skills.getLevel('attack');
54+
const magic = this.skills.getLevel('magic');
55+
const ranged = this.skills.getLevel('ranged');
56+
57+
if (ranged > magic && ranged > ranged) return ranged;
58+
else if (magic > attack && magic > ranged) return magic;
59+
else return attack;
60+
}
61+
62+
// #endregion
3963

4064
protected randomMovementInterval;
4165

@@ -59,20 +83,36 @@ export abstract class Actor {
5983
this._busy = false;
6084
}
6185

62-
public damage(amount: number, damageType: DamageType = DamageType.DAMAGE): 'alive' | 'dead' {
63-
let remainingHitpoints: number = this.skills.hitpoints.level - amount;
64-
const maximumHitpoints: number = this.skills.hitpoints.levelForExp;
65-
if(remainingHitpoints < 0) {
66-
remainingHitpoints = 0;
67-
}
68-
69-
this.skills.setHitpoints(remainingHitpoints);
86+
public damage(amount: number, damageType: DamageType = DamageType.DAMAGE) {
87+
const armorReduction = 0;
88+
const spellDamageReduction = 0;
89+
const poisonReistance = 0;
90+
amount -= armorReduction;
91+
this.hitPoints -= amount;
92+
this.skills.setHitpoints(this.hitPoints);
7093
this.updateFlags.addDamage(amount, amount === 0 ? DamageType.NO_DAMAGE : damageType,
71-
remainingHitpoints, maximumHitpoints);
72-
73-
return remainingHitpoints === 0 ? 'dead' : 'alive';
94+
this.hitPoints, this.maxHitPoints);
95+
//this actor should respond when hit
96+
world.playLocationSound(this.position, soundIds.npc.human.noArmorHitPlayer,5)
97+
this.playAnimation(this.getBlockAnimation());
7498
}
7599

100+
101+
102+
//public damage(amount: number, damageType: DamageType = DamageType.DAMAGE): 'alive' | 'dead' {
103+
// let remainingHitpoints: number = this.skills.hitpoints.level - amount;
104+
// const maximumHitpoints: number = this.skills.hitpoints.levelForExp;
105+
// if(remainingHitpoints < 0) {
106+
// remainingHitpoints = 0;
107+
// }
108+
109+
// this.skills.setHitpoints(remainingHitpoints);
110+
// this.updateFlags.addDamage(amount, amount === 0 ? DamageType.NO_DAMAGE : damageType,
111+
// remainingHitpoints, maximumHitpoints);
112+
113+
// return remainingHitpoints === 0 ? 'dead' : 'alive';
114+
//}
115+
76116
/**
77117
* Waits for the actor to reach the specified position before resolving it's promise.
78118
* The promise will be rejected if the actor's walking queue changes or their movement is otherwise canceled.
@@ -549,4 +589,5 @@ export abstract class Actor {
549589
};
550590
}
551591

592+
552593
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Actor } from '../actor';
2+
import { regionChangeActionFactory } from '@engine/world/action/region-change.action';
3+
import { Subject } from 'rxjs';
4+
import { logger } from '@runejs/core';
5+
import { Behavior, BehaviorType } from './behavior';
6+
import { Timestamp } from 'rxjs/dist/types/internal/types';
7+
import { timestamp } from 'rxjs/dist/types/operators';
8+
import { Npc } from '../npc/npc';
9+
import { Player } from '../player/player';
10+
11+
export class AutoAttackBehavior extends Behavior {
12+
13+
Type = BehaviorType.Combat;
14+
Name = 'auto-attack-combat';
15+
16+
private _combatPulse;
17+
private _CoolDown: number = 3;
18+
private _lastAttack = new Date();
19+
private _player: Player;
20+
21+
//this should be called when combat starts
22+
public async init(me: Actor, them: Actor): Promise<void> {
23+
this.Me = me;
24+
this.Them = them;
25+
this._player = (me as Player);
26+
console.log('all set to auto attack!');
27+
(this.Them as Npc).npcEvents.on('death', (npc) => this._player.onNpcKill);
28+
await super.init(me, them);
29+
}
30+
31+
public async tick() {
32+
if (this.Me == null) return;
33+
34+
return new Promise<void>(resolve => {
35+
//only use boolean checks in behaviors never calculated values if you can help it (performance)
36+
if (this.Me.inCombat) {
37+
if (this.Them.isDead) {
38+
this.Me.combatTargets.pop();
39+
resolve();
40+
return;
41+
}
42+
if (this.Distance > 25) {
43+
this.Me.inCombat = false;
44+
console.log('target too far away - ending combat');
45+
resolve();
46+
}
47+
48+
//If we are not in range move there
49+
if (this.Distance > this.Me.meleeDistance) this.Me.moveTo(this.Them);
50+
//Are you in range to attack?
51+
if (this.Distance <= this.Me.meleeDistance && this.coolDownElapsed) {
52+
this.doAttack();
53+
this.resetCoolDown(this._CoolDown);
54+
}
55+
56+
if (!this.Me.isDead) super.tick();
57+
resolve();
58+
}
59+
60+
61+
});
62+
}
63+
public async doAttack(): Promise<void> {
64+
return new Promise<void>(resolve => {
65+
//do attack stuff
66+
const _damage = this.Me.skills.strength.level;
67+
console.log(`you attack ${(this.Them as Npc).name} for ${_damage} damage! (after the CD)`);
68+
this.Them.damage(_damage);
69+
if (this.Them.hitPoints <= 0) {
70+
(this.Them as Npc).npcEvents.emit('death', this.Me);
71+
}
72+
73+
});
74+
}
75+
public get coolDownElapsed(): boolean {
76+
if (new Date() > this._lastAttack) return true;
77+
return false;
78+
}
79+
public resetCoolDown(seconds: number): void {
80+
this._lastAttack.setSeconds(this._lastAttack.getSeconds() + seconds);
81+
}
82+
83+
84+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Actor } from '../actor';
2+
import { regionChangeActionFactory } from '@engine/world/action/region-change.action';
3+
import { Subject } from 'rxjs';
4+
import { Npc } from '../npc/npc';
5+
import { Player } from '../player/player';
6+
7+
export abstract class Behavior {
8+
//because not all interaction between npcs will be combat oriented me/them is on the base class
9+
public Me: Actor;
10+
public Them: Actor;
11+
public Distance: number;
12+
public Name: string;
13+
public Type: BehaviorType;
14+
//Be sure to call the tick super so you wont have to duplicate all the functionality
15+
public async tick() {
16+
if (this.Me.isDead) return;
17+
//If we have more than one combat target be sure to select the next one
18+
if (this.Them == null && this.Me != null && this.Me.combatTargets.length > 0) this.Them == this.Me.combatTargets[0];
19+
//update calculated variables
20+
if (this.Them != null) this.Distance = this.Me.position.distanceBetween(this.Them.position);
21+
22+
return new Promise<void>(resolve => { resolve(); });
23+
}
24+
public async init(me: Actor = null, them: Actor = null) {
25+
if (me != null && them != null) me.combatTargets.push(them);
26+
return new Promise<void>(resolve => {
27+
resolve();
28+
});
29+
}
30+
}
31+
32+
33+
export enum BehaviorType {
34+
//movement
35+
Roaming = 'roaming', //world.tickComplete
36+
Chase = 'chase', //position.distanceBetween
37+
//combat
38+
Combat = 'combat',
39+
Flee = 'flee',
40+
41+
}

0 commit comments

Comments
 (0)