Skip to content

Commit

Permalink
feat: tp player and boss back when in pvp
Browse files Browse the repository at this point in the history
  • Loading branch information
leaftail1880 committed Sep 2, 2024
1 parent 4877fdb commit 2949419
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 110 deletions.
2 changes: 1 addition & 1 deletion src/lib/extensions/enviroment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Array.prototype.randomElement = function (this: unknown[]) {

expand(Array, {
equals(one, two) {
return one.every((e, i) => e === two[i])
return one.length === two.length && one.every((e, i) => e === two[i])
},
})

Expand Down
20 changes: 11 additions & 9 deletions src/lib/region/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
world,
} from '@minecraft/server'
import { MinecraftItemTypes } from '@minecraft/vanilla-data'
import { PlayerEvents } from 'lib/assets/player-json'
import { PlayerEvents, PlayerProperties } from 'lib/assets/player-json'
import { isBuilding } from 'lib/game-utils'
import { EventSignal } from '../event-signal'
import { BLOCK_CONTAINERS, DOORS, GATES, INTERACTABLE_ENTITIES, NOT_MOB_ENTITIES, SWITCHES, TRAPDOORS } from './config'
Expand Down Expand Up @@ -154,21 +154,23 @@ system.runInterval(
() => {
for (const player of world.getAllPlayers()) {
const previous = RegionEvents.playerInRegionsCache.get(player) ?? []
const nearest = Region.nearestRegions(player.location, player.dimension.type)
const newest = Region.nearestRegions(player.location, player.dimension.type)

if (nearest.length !== previous.length || previous.some((region, i) => region !== nearest[i])) {
EventSignal.emit(RegionEvents.onPlayerRegionsChange, { player, previous, newest: nearest })
if (!Array.equals(newest, previous)) {
EventSignal.emit(RegionEvents.onPlayerRegionsChange, { player, previous, newest })
}

RegionEvents.playerInRegionsCache.set(player, nearest)
const currentRegion = nearest[0]
RegionEvents.playerInRegionsCache.set(player, newest)
const currentRegion = newest[0]

if (typeof currentRegion !== 'undefined') {
if (!currentRegion.permissions.pvp && !isBuilding(player)) {
if (typeof currentRegion !== 'undefined' && !isBuilding(player)) {
if (currentRegion.permissions.pvp === false) {
player.triggerEvent(
player.database.inv === 'spawn' ? PlayerEvents['player:spawn'] : PlayerEvents['player:safezone'],
)
}
} else if (currentRegion.permissions.pvp === 'pve') {
player.setProperty(PlayerProperties['lw:newbie'], true)
} else if (!player.database.survival.newbie) player.setProperty(PlayerProperties['lw:newbie'], true)
}

EventSignal.emit(RegionEvents.onInterval, { player, currentRegion })
Expand Down
7 changes: 7 additions & 0 deletions src/lib/region/kinds/boss-arena.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Entity } from '@minecraft/server'
import { Area } from '../areas/area'
import { Region, RegionCreationOptions, type RegionPermissions } from './region'
import { Vector } from 'lib/vector'

interface BossArenaRegionOptions extends RegionCreationOptions {
bossName: string
Expand Down Expand Up @@ -29,4 +31,9 @@ export class BossArenaRegion extends Region {
super(area, options, key)
this.bossName = options.bossName
}

returnEntity(entity: Entity) {
const vector = Vector.subtract(entity.location, this.area.center)
entity.applyKnockback(-vector.x, -vector.z, 5, 0.6)
}
}
5 changes: 3 additions & 2 deletions src/lib/region/kinds/safe-area.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { addAddableRegion } from 'lib/region/command'
import { Area } from '../areas/area'
import { RegionEvents } from '../events'
import { BossArenaRegion } from './boss-arena'
import { Region, RegionCreationOptions, RegionPermissions } from './region'

interface SafeAreaRegionOptions extends RegionCreationOptions {
Expand Down Expand Up @@ -47,8 +48,8 @@ export class SafeAreaRegion extends Region {
}
addAddableRegion('Мирные зоны', SafeAreaRegion)
RegionEvents.onPlayerRegionsChange.subscribe(({ player, previous, newest }) => {
const been = previous.length && previous[0] instanceof SafeAreaRegion
const now = newest.length && newest[0] instanceof SafeAreaRegion
const been = previous.length && (previous[0] instanceof SafeAreaRegion || previous[0] instanceof BossArenaRegion)
const now = newest.length && (newest[0] instanceof SafeAreaRegion || newest[0] instanceof BossArenaRegion)
const gamemode = player.getGameMode()
const adventure = gamemode === GameMode.adventure
const survival = gamemode === GameMode.survival
Expand Down
48 changes: 33 additions & 15 deletions src/lib/rpg/boss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface BossOptions {
loot: LootTable
spawnEvent: boolean | string
respawnTime: number
allowedEntities: string[] | 'all'
}

export class Boss {
Expand All @@ -44,8 +45,10 @@ export class Boss {
typeId: (typeId: string) => ({
loot: (loot: LootTable) => ({
respawnTime: (respawnTime: number) => ({
spawnEvent: (spawnEvent: BossOptions['spawnEvent']) =>
new Boss({ place, typeId, loot, respawnTime, spawnEvent }),
allowedEntities: (allowedEntities: string[] | 'all') => ({
spawnEvent: (spawnEvent: BossOptions['spawnEvent']) =>
new Boss({ place, typeId, loot, respawnTime, spawnEvent, allowedEntities }),
}),
}),
}),
}),
Expand Down Expand Up @@ -93,12 +96,15 @@ export class Boss {
*/
constructor(private options: BossOptions) {
this.options.loot.id = `§7${this.options.place.group.id} §fBoss ${this.options.place.id}`

if (Array.isArray(this.options.allowedEntities)) this.options.allowedEntities.push(options.typeId)

this.location = location(options.place)
this.location.onLoad.subscribe(center => {
this.check()
this.region = BossArenaRegion.create(
new SphereArea({ center, radius: 40 }, this.options.place.group.dimensionId),
{ bossName: this.options.place.name },
{ bossName: this.options.place.name, permissions: { allowedEntities: this.options.allowedEntities } },
)
this.region.permissions.allowedEntities = [this.options.typeId, MinecraftEntityTypes.Player]
})
Expand All @@ -114,6 +120,13 @@ export class Boss {
return this.options.place.group.dimensionId
}

private onInterval?: (boss: this) => void

interval(interval: (boss: this) => void) {
this.onInterval = interval
return this
}

check() {
if (!this.location.valid) return
if (isChunkUnloaded(this as LocationInDimension)) return
Expand All @@ -139,7 +152,7 @@ export class Boss {
} else if (this.location.valid) {
this.floatingText.update(
Vector.add(this.location, { x: 0, y: 2, z: 0 }),
`${this.options.place.name}\n${t.time`Осталось ${this.options.respawnTime - (Date.now() - db.date)}`}`,
`${this.options.place.name}\n${t.time`До появления\n§7осталось ${this.options.respawnTime - (Date.now() - db.date)}`}`,
)
}
}
Expand All @@ -149,7 +162,7 @@ export class Boss {

// Get type id
const entityTypeId = this.options.typeId + (this.options.spawnEvent ? '<lw:boss>' : '')
this.logger.debug`Spawn: ${entityTypeId}`
this.logger.info`Spawn: ${entityTypeId}`

// Spawn entity
this.entity = world[this.dimensionId].spawnEntity(entityTypeId, this.location)
Expand Down Expand Up @@ -179,7 +192,7 @@ export class Boss {

/** Ensures that entity exists and if not calls onDie method */
ensureEntity(db: BossDB) {
const entity = world[this.dimensionId]
this.entity ??= world[this.dimensionId]
.getEntities({
type: this.options.typeId,
// location: this.location,
Expand All @@ -188,16 +201,21 @@ export class Boss {

.find(e => e.id === db.id)

if (!entity) {
// Boss went out of location or in unloaded chunk, respawn after interval
if (!this.entity) {
// Boss died or unloaded, respawn after interval
this.onDie({ dropLoot: false })
}
} else {
if (
this.region &&
this.location.valid &&
!this.region.area.isVectorIn(this.entity.location, this.entity.dimension.type)
)
this.entity.teleport(this.location)

if (entity) {
entity.nameTag = this.options.place.name
this.onInterval?.(this)
this.entity.nameTag = this.options.place.name
this.floatingText.hide()
}
this.entity = entity
}

onDie({ dropLoot = true } = {}) {
Expand All @@ -207,7 +225,7 @@ export class Boss {
`Died. Got hits from ${[...this.damage.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(e => `${Player.name(e[0])}: ${e[1]}`)
.map(e => `§l${Player.name(e[0])}§r§f: §6${e[1]}§f`)
.join(', ')}`,
)
const location = this.entity?.isValid() ? this.entity.location : this.location
Expand All @@ -220,7 +238,7 @@ export class Boss {
}

if (dropLoot) {
world.say('§6Убит босс §f' + this.options.place.name + '!')
world.say(`§6Убит босс §f${this.options.place.name}!`)

this.options.loot.generate().forEach(e => e && world[this.dimensionId].spawnItem(e, location))
const players = world.getAllPlayers()
Expand All @@ -229,7 +247,7 @@ export class Boss {
const player = players.find(e => e.id === playerId)
if (!player) return

givePlayerMoneyAndXp(player, damage, ~~(damage / 10))
givePlayerMoneyAndXp(player, ~~damage * 2, ~~(damage / 50))
}
this.damage.clear()
}
Expand Down
5 changes: 2 additions & 3 deletions src/lib/rpg/floating-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ export class FloatingText {
private entity: Entity | undefined

hide() {
if (!this.entity?.isValid()) return

this.entity.remove()
if (!this.entity) this.entity = this.find()
if (this.entity?.isValid()) this.entity.remove()
}

update(location: Vector3, nameTag: string) {
Expand Down
30 changes: 21 additions & 9 deletions src/lib/rpg/npc.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { Entity, EntityLifetimeState, PlayerInteractWithEntityBeforeEvent, system, world } from '@minecraft/server'
import {
Entity,
EntityLifetimeState,
LocationInUnloadedChunkError,
PlayerInteractWithEntityBeforeEvent,
system,
world,
} from '@minecraft/server'

import { MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { Temporary, Vector } from 'lib'
import { developersAreWarned } from 'lib/assets/text'
import { Core } from 'lib/extensions/core'
import { location, SafeLocation } from 'lib/location'
import { Place } from './place'
import { createLogger } from 'lib/utils/logger'
import { Place } from './place'

export declare namespace Npc {
type OnInteract = (event: Omit<PlayerInteractWithEntityBeforeEvent, 'cancel'>) => boolean
Expand Down Expand Up @@ -62,16 +69,21 @@ export class Npc {
throw new TypeError(`§cNpc(§r${this.id}§r§c): Location is not valid, spawn is impossible. Set location first`)
}

this.entity = world[this.dimensionId].spawnEntity(Npc.type, this.location)
try {
this.entity = world[this.dimensionId].spawnEntity(Npc.type, this.location)

new Temporary(({ world, cleanup }) => {
world.afterEvents.entitySpawn.subscribe(({ entity }) => {
if (entity.id !== this.entity?.id) return
new Temporary(({ world, cleanup }) => {
world.afterEvents.entitySpawn.subscribe(({ entity }) => {
if (entity.id !== this.entity?.id) return

this.configureNpcEntity(entity)
cleanup()
this.configureNpcEntity(entity)
cleanup()
})
})
})
} catch (e) {
if (e instanceof LocationInUnloadedChunkError) return
Npc.logger.error(e)
}
}

private configureNpcEntity(entity: Entity) {
Expand Down
21 changes: 17 additions & 4 deletions src/modules/indicator/pvp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EntityDamageCause, EntityHurtAfterEvent, Player, system, world } from '@minecraft/server'
import { Boss, LockAction, Settings } from 'lib'
import { Boss, BossArenaRegion, LockAction, Settings } from 'lib'
import { emoji } from 'lib/assets/emoji'
import { Core } from 'lib/extensions/core'
import { ActionbarPriority } from 'lib/extensions/on-screen-display'
import { RegionEvents } from 'lib/region/events'
import { HealthIndicatorConfig } from './config'

// █
Expand Down Expand Up @@ -45,7 +46,7 @@ const getPlayerSettings = Settings.player('PvP/PvE', 'pvp', {
},
})

new LockAction(p => p.scores.pvp > 0, 'Вы находитесь в режиме сражения!')
const lockAction = new LockAction(p => p.scores.pvp > 0, 'Вы находитесь в режиме сражения!')

world.afterEvents.entityDie.subscribe(({ deadEntity }) => {
if (deadEntity.isPlayer()) deadEntity.scores.pvp = 0
Expand All @@ -68,6 +69,16 @@ system.runInterval(
20,
)

RegionEvents.onPlayerRegionsChange.subscribe(({ player, newest, previous }) => {
if (
lockAction.isLocked(player) &&
previous.some(e => e instanceof BossArenaRegion) &&
!newest.some(e => e instanceof BossArenaRegion)
) {
if (previous[0] instanceof BossArenaRegion) previous[0].returnEntity(player)
}
})

Core.afterEvents.worldLoad.subscribe(() => {
system.runPlayerInterval(
player => {
Expand Down Expand Up @@ -122,9 +133,11 @@ function onDamage(
)
return

const hurtBoss = Boss.isBoss(hurtEntity)

if (
!hurtEntity.typeId.startsWith('minecraft:') ||
!(hurtEntity.isPlayer() || hurtEntity.matches({ families: ['monster'] })) ||
!(hurtEntity.isPlayer() || hurtEntity.matches({ families: ['monster'] }) || hurtBoss) ||
!options.pvpEnabled ||
HealthIndicatorConfig.disabled.includes(hurtEntity.id)
)
Expand All @@ -140,7 +153,7 @@ function onDamage(
const cooldown =
damagingEntity?.isPlayer() && hurtEntity.isPlayer()
? options.pvpPlayerCooldown
: Boss.isBoss(hurtEntity) || (damagingEntity && Boss.isBoss(damagingEntity))
: hurtBoss || (damagingEntity && Boss.isBoss(damagingEntity))
? options.pvpBossCooldown
: options.pvpMonsterCooldown

Expand Down
31 changes: 4 additions & 27 deletions src/modules/places/stone-quarry/stone-quarry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { system } from '@minecraft/server'
import { MinecraftBlockTypes, MinecraftEntityTypes } from '@minecraft/vanilla-data'
import { Boss, Loot, ms } from 'lib'
import { MinecraftBlockTypes } from '@minecraft/vanilla-data'
import { Loot } from 'lib'
import { AuntZina } from 'modules/places/stone-quarry/aunt-zina'
import { Barman } from 'modules/places/stone-quarry/barman'
import { Horseman } from 'modules/places/stone-quarry/horseman'
Expand All @@ -10,6 +9,7 @@ import { Stoner } from '../lib/npc/stoner'
import { Woodman } from '../lib/npc/woodman'
import { Furnacer } from './furnacer'
import { Gunsmith } from './gunsmith'
import { createBossWither } from './wither.boss'

class StoneQuarryBuilder extends City {
constructor() {
Expand All @@ -29,14 +29,7 @@ class StoneQuarryBuilder extends City {

stoner = new Stoner(this.group)

w = Boss.create()
.group(this.group)
.id('wither')
.name('Камнедробилка')
.typeId(MinecraftEntityTypes.Wither)
.loot(new Loot('wither drop').item('NetherStar').build)
.respawnTime(ms.from('hour', 1))
.spawnEvent(true)
wither = createBossWither(this.group)

commonOvener = Furnacer.create()
.group(this.group)
Expand All @@ -60,22 +53,6 @@ class StoneQuarryBuilder extends City {
gunsmith = new Gunsmith(this.group)

private create() {
this.w.location.onLoad.subscribe(() => {
if (this.w.region) this.w.region.permissions.allowedEntities = 'all'
})

system.runInterval(
() => {
if (!this.w.region) return
if (!this.w.entity) return
if (!this.w.region.area.isVectorIn(this.w.entity.location, this.w.region.dimensionId)) {
this.w.entity.teleport(this.w.region.area.center)
}
},
'asdasda',
40,
)

this.createKits(new Loot().item('RedTerracotta').build, new Loot().item('RedTerracotta').build)
}
}
Expand Down
Loading

0 comments on commit 2949419

Please sign in to comment.