diff --git a/packages/client/data/sprites.json b/packages/client/data/sprites.json index 312ae03ce0..745ab5c82c 100755 --- a/packages/client/data/sprites.json +++ b/packages/client/data/sprites.json @@ -510,6 +510,51 @@ { "id": "mobs/cowwarrior", "width": 42, "height": 42, "offsetX": -13, "offsetY": -18 }, { "id": "mobs/rooster", "width": 42, "height": 42, "offsetX": -13, "offsetY": -18 }, { "id": "mobs/adherer", "width": 32, "height": 32, "offsetX": -8, "offsetY": -12 }, + { + "id": "mobs/goldgolem", + "width": 32, + "height": 32, + "animations": { + "atk_right": { + "length": 5, + "row": 0 + }, + "walk_right": { + "length": 4, + "row": 1 + }, + "idle_right": { + "length": 2, + "row": 2 + }, + "atk_up": { + "length": 5, + "row": 3 + }, + "walk_up": { + "length": 4, + "row": 4 + }, + "idle_up": { + "length": 2, + "row": 5 + }, + "atk_down": { + "length": 5, + "row": 6 + }, + "walk_down": { + "length": 4, + "row": 7 + }, + "idle_down": { + "length": 2, + "row": 8 + } + }, + "offsetX": -8, + "offsetY": -24 + }, { "id": "mobs/ant", "width": 48, diff --git a/packages/client/src/controllers/input.ts b/packages/client/src/controllers/input.ts index ac7ed893fb..80d82c88f9 100644 --- a/packages/client/src/controllers/input.ts +++ b/packages/client/src/controllers/input.ts @@ -471,7 +471,9 @@ export default class InputController { if (this.player.canAttackTarget()) this.game.socket.send(Packets.Target, [ Opcodes.Target.Attack, - this.entity.instance + this.entity.instance, + this.entity.gridX, + this.entity.gridY ]); } return; diff --git a/packages/client/src/entity/character/character.ts b/packages/client/src/entity/character/character.ts index 927c9f69aa..fca869bf67 100644 --- a/packages/client/src/entity/character/character.ts +++ b/packages/client/src/entity/character/character.ts @@ -233,7 +233,7 @@ export default class Character extends Entity { */ public trade(entity: Entity): void { - if (this.dead) return; + if (this.dead || this.teleporting) return; this.trading = true; @@ -252,7 +252,7 @@ export default class Character extends Entity { if (Date.now() - this.lastFollow < 300) return; // Prevent following when entity is stunned or dead. - if (this.dead || this.isStunned()) return; + if (this.dead || this.isStunned() || this.teleporting) return; this.lastFollow = Date.now(); @@ -270,7 +270,7 @@ export default class Character extends Entity { */ public pursue(character: Character): void { - if (this.dead || this.isStunned()) return; + if (this.dead || this.isStunned() || this.teleporting) return; this.setTarget(character); this.move(character.gridX, character.gridY); diff --git a/packages/client/src/game.ts b/packages/client/src/game.ts index 69e2d57c08..974811c1e3 100644 --- a/packages/client/src/game.ts +++ b/packages/client/src/game.ts @@ -346,8 +346,7 @@ export default class Game { position.gridY!, radius ), - closest: Entity | undefined, - boundary = this.map.tileSize - 2; + closest: Entity | undefined; /** * The `position` parameter contains the absolute x and y coordinates @@ -361,10 +360,15 @@ export default class Game { // Skip pets from the search. if (entity.isPet()) continue; - let entityX = entity.x - entity.sprite.offsetX / 2, - entityY = entity.y - entity.sprite.offsetY / 2, - distance = Utils.distance(position.x, position.y, entityX, entityY); + let { x, y, sprite } = entity, + { width, height, offsetX, offsetY } = sprite, + largest = width > height ? width : height, + toX = x - offsetX / 2, + toY = y + offsetY / 2, + distance = Utils.distance(position.x, position.y, toX, toY), + boundary = largest / 2 + Utils.halfTile + (width > 32 ? this.map.tileSize : 0); + // Skip if the distance is greater than the boundary. if (distance > boundary) continue; if (!closest || distance < closest.distance) { diff --git a/packages/client/src/map/map.ts b/packages/client/src/map/map.ts index 1e594fcd18..918832f2fb 100644 --- a/packages/client/src/map/map.ts +++ b/packages/client/src/map/map.ts @@ -72,7 +72,7 @@ export default class Map { // Store tile size globally into the utils. Utils.tileSize = this.tileSize; Utils.sideLength = this.width / Modules.Constants.MAP_DIVISION_SIZE; - Utils.thirdTile = this.tileSize / 3; + Utils.halfTile = this.tileSize / 2; Utils.tileAndAQuarter = this.tileSize * 1.25; } diff --git a/packages/client/src/network/connection.ts b/packages/client/src/network/connection.ts index 3f3d4849c9..4b1e922d5c 100644 --- a/packages/client/src/network/connection.ts +++ b/packages/client/src/network/connection.ts @@ -458,7 +458,7 @@ export default class Connection { let currentPlayer = entity.instance === this.game.player.instance; // Stop and freeze the player until teleprtation is complete. - entity.stop(true); + entity.stop(); entity.frozen = true; // Clears all bubbles when our main entity teleports. diff --git a/packages/client/src/renderer/renderer.ts b/packages/client/src/renderer/renderer.ts index a09a0963c8..575e330cba 100644 --- a/packages/client/src/renderer/renderer.ts +++ b/packages/client/src/renderer/renderer.ts @@ -1698,7 +1698,7 @@ export default class Renderer { this.camera.forEachVisiblePosition((x, y) => { if (!this.map.isOutOfBounds(x, y) && grids.renderingGrid[y][x]) for (let entity of Object.values(grids.renderingGrid[y][x])) callback(entity); - }); + }, 10); } /** diff --git a/packages/client/src/utils/util.ts b/packages/client/src/utils/util.ts index d0cbae4e85..11631f4234 100644 --- a/packages/client/src/utils/util.ts +++ b/packages/client/src/utils/util.ts @@ -13,7 +13,7 @@ export let isInt = (n: number): boolean => n % 1 === 0; export default { tileSize: -1, sideLength: -1, - thirdTile: -1, + halfTile: -1, tileAndAQuarter: -1, /** diff --git a/packages/server/data/plugins/mobs/ogrelord.ts b/packages/server/data/plugins/mobs/ogrelord.ts index b79984c643..99d02f3c67 100644 --- a/packages/server/data/plugins/mobs/ogrelord.ts +++ b/packages/server/data/plugins/mobs/ogrelord.ts @@ -85,6 +85,8 @@ export default class OgreLord extends Default { let minion = super.spawn(key, position.x, position.y), target = super.getTarget(); + minion.roamDistance = 24; + // Have the minions attack one of the boss' attackers. if (target) minion.combat.attack(target); } diff --git a/packages/server/data/plugins/mobs/piratecaptain.ts b/packages/server/data/plugins/mobs/piratecaptain.ts index d3dc4597ee..a71cf65894 100644 --- a/packages/server/data/plugins/mobs/piratecaptain.ts +++ b/packages/server/data/plugins/mobs/piratecaptain.ts @@ -42,6 +42,7 @@ export default class PirateCaptain extends Default { for (let minion of Object.values(this.minions)) minion.deathCallback?.(); this.minionsSpawned = 0; + this.mob.attackRange = 1; } /** @@ -69,7 +70,7 @@ export default class PirateCaptain extends Default { // Stop all players from attacking the pirate captain. this.mob.world.cleanCombat(this.mob); - this.mob.attackRange = 14; + this.mob.attackRange = 16; // Update the position and teleport the captain with an animation. this.mob.teleport(position.x, position.y, true); @@ -87,7 +88,7 @@ export default class PirateCaptain extends Default { target = super.getTarget(); // Pick a random target from the attackers // Update the roam distance for minions (so they don't go back to spawn point). - minion.roamDistance = this.mob.roamDistance; + minion.roamDistance = 20; // Set the target for the minion. if (target) minion.combat.attack(target); diff --git a/packages/server/data/plugins/mobs/skeletonking.ts b/packages/server/data/plugins/mobs/skeletonking.ts index e61111e22b..431ca323e6 100644 --- a/packages/server/data/plugins/mobs/skeletonking.ts +++ b/packages/server/data/plugins/mobs/skeletonking.ts @@ -63,7 +63,7 @@ export default class SkeletonKing extends Default { target = super.getTarget(); // Minions have the same roaming distance as the skeleton king. - minion.roamDistance = this.mob.roamDistance; + minion.roamDistance = 24; if (target) minion.combat.attack(target); diff --git a/packages/server/src/controllers/commands.ts b/packages/server/src/controllers/commands.ts index 39cfb8da92..852510771a 100644 --- a/packages/server/src/controllers/commands.ts +++ b/packages/server/src/controllers/commands.ts @@ -1267,6 +1267,10 @@ export default class Commands { case 'crystalcave': { return this.player.teleport(886, 622, true, false, true); } + + case 'piratecaptain': { + return this.player.teleport(930, 752, true, false, true); + } } break; diff --git a/packages/server/src/game/entity/character/character.ts b/packages/server/src/game/entity/character/character.ts index c3284a073a..2e5f7bf608 100644 --- a/packages/server/src/game/entity/character/character.ts +++ b/packages/server/src/game/entity/character/character.ts @@ -66,7 +66,7 @@ export default abstract class Character extends Entity { public projectileName = 'arrow'; public lastStep = -1; - public lastMovement = -1; + public lastMovement = Date.now(); public lastRegionChange = -1; private healingInterval?: NodeJS.Timeout | undefined; diff --git a/packages/server/src/game/entity/character/combat/combat.ts b/packages/server/src/game/entity/character/combat/combat.ts index b70034b38d..d6e0721451 100644 --- a/packages/server/src/game/entity/character/combat/combat.ts +++ b/packages/server/src/game/entity/character/combat/combat.ts @@ -152,7 +152,8 @@ export default class Combat { this.character.follow(); this.lastFollow = Date.now(); - if (this.shouldTeleportNearby()) this.character.findAdjacentTile(); + if (this.shouldTeleportNearby()) + this.character.setPosition(this.character.target!.x, this.character.target!.y); } } @@ -270,13 +271,13 @@ export default class Combat { /** * Mob positions may not be updated if there is not a spectator and the player switches * tabs. In this case we just teleport the mob next to the player. Because the lastMovement - * exceeds 3 seconds and the mob is still not nearby, we can conclude it's not able to + * exceeds 5 seconds and the mob is still not nearby, we can conclude it's not able to * move and we should teleport it. - * @returns Whether the character is a mob and it hasn't moved in the last 3 seconds. + * @returns Whether the character is a mob and it hasn't moved in the last 5 seconds. */ private shouldTeleportNearby(): boolean { - return this.character.isMob() && Date.now() - this.character.lastMovement > 3000; + return this.character.isMob() && Date.now() - this.character.lastMovement > 5000; } /** diff --git a/packages/server/src/game/entity/character/mob/mob.ts b/packages/server/src/game/entity/character/mob/mob.ts index 334dc1657f..26dd23e7a3 100644 --- a/packages/server/src/game/entity/character/mob/mob.ts +++ b/packages/server/src/game/entity/character/mob/mob.ts @@ -11,7 +11,7 @@ import PluginIndex from '../../../../../data/plugins/mobs'; import log from '@kaetram/common/util/log'; import Utils from '@kaetram/common/util/utils'; import { Modules, Opcodes } from '@kaetram/common/network'; -import { HealPacket, MovementPacket } from '@kaetram/common/network/impl'; +import { HealPacket, MovementPacket, TeleportPacket } from '@kaetram/common/network/impl'; import { SpecialEntityTypes } from '@kaetram/common/network/modules'; import type Area from '../../../map/areas/area'; @@ -292,6 +292,32 @@ export default class Mob extends Character { this.world.entities.spawnLootBag(this.x, this.y, player.username, items); } + /** + * Override for the teleport functionality with added support for stopping + * all additional packets from being sent to the regions. + * @param x The x grid coordinate. + * @param y The y grid coordinate. + * @param withAnimation Whether or not to teleport with an animation. + */ + + public override teleport(x: number, y: number, withAnimation = false): void { + this.setPosition(x, y, false); + + this.teleporting = true; + + this.sendToRegions( + new TeleportPacket({ + instance: this.instance, + x, + y, + withAnimation + }) + ); + + // Untoggle the teleporting flag after 500ms. + setTimeout(() => (this.teleporting = false), 500); + } + /** * Moves the mob and broadcasts the action * to all the adjacent regions. @@ -301,6 +327,8 @@ export default class Mob extends Character { */ public override setPosition(x: number, y: number, withPacket = true): void { + if (this.teleporting) return; + super.setPosition(x, y); this.calculateOrientation(); @@ -314,6 +342,8 @@ export default class Mob extends Character { y }) }); + + this.lastMovement = Date.now(); } /** diff --git a/packages/server/src/game/entity/character/player/incoming.ts b/packages/server/src/game/entity/character/player/incoming.ts index 6988f29850..bb296f5076 100644 --- a/packages/server/src/game/entity/character/player/incoming.ts +++ b/packages/server/src/game/entity/character/player/incoming.ts @@ -369,16 +369,14 @@ export default class Incoming { // Prevent crazy position updates, add some leeway to the roaming distance. if (entity.outsideRoaming(requestX!, requestY!, entity.roamDistance * 2)) return; - entity.lastMovement = Date.now(); - // For mobs update the position without a packet. return entity.setPosition(requestX!, requestY!, false); } } } - private handleTarget(message: [Opcodes.Target, string]): void { - let [opcode, instance] = message; + private handleTarget(message: [Opcodes.Target, string, number?, number?]): void { + let [opcode, instance, x, y] = message; switch (opcode) { case Opcodes.Target.Talk: { @@ -402,7 +400,7 @@ export default class Incoming { } case Opcodes.Target.Attack: { - return this.player.handleTargetAttack(instance); + return this.player.handleTargetAttack(instance, x, y); } case Opcodes.Target.Object: { diff --git a/packages/server/src/game/entity/character/player/player.ts b/packages/server/src/game/entity/character/player/player.ts index 4fef8124f5..c223e4adad 100644 --- a/packages/server/src/game/entity/character/player/player.ts +++ b/packages/server/src/game/entity/character/player/player.ts @@ -745,7 +745,7 @@ export default class Player extends Character { * @param instance The instance of the target we are attacking. */ - public handleTargetAttack(instance: string): void { + public handleTargetAttack(instance: string, x?: number, y?: number): void { // Prevent targetting yourself. if (instance === this.instance) return; @@ -757,6 +757,8 @@ export default class Player extends Character { // Ensure that the player can actually attack the target. if (!this.canAttack(target)) return; + if (target.isMob() && !target.combat.started && x && y) target.setPosition(x, y); + // Clear the cheat score this.cheatScore = 0;