From a3cbe455a1dac4b9df4acf56b209314a4730fbc8 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Mon, 7 Dec 2020 11:11:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=B2=92=E5=AD=90?= =?UTF-8?q?=E7=9B=B8=E5=85=B3API(=E6=9C=AA=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MiaoWoo --- packages/api/src/particle.ts | 494 ++++++++++++++++++++++++++++++++ packages/bukkit/src/particle.ts | 31 ++ packages/sponge/src/particle.ts | 32 +++ 3 files changed, 557 insertions(+) create mode 100644 packages/api/src/particle.ts create mode 100644 packages/bukkit/src/particle.ts create mode 100644 packages/sponge/src/particle.ts diff --git a/packages/api/src/particle.ts b/packages/api/src/particle.ts new file mode 100644 index 00000000..55386869 --- /dev/null +++ b/packages/api/src/particle.ts @@ -0,0 +1,494 @@ +import { Autowired, injectable } from '@ccms/container' + +import { task, plugin } from './index' + +const UUID = Java.type('java.util.UUID') +const Math = Java.type('java.lang.Math') + +export namespace particle { + /** + * 表示一个特效对象 + * + * @author Zoyn + */ + export abstract class Particle { + private spawner: ParticleSpawner + private readonly uuid: string + + private particle: any /* Particle */ + private count: number = 1; + private offsetX: number = 0; + private offsetY: number = 0; + private offsetZ: number = 0; + private extra: number = 0; + private data: Object = null; + + constructor() { + this.uuid = UUID.randomUUID().toString() + } + abstract show(location: any) + + getUUID() { + return this.uuid + } + + getSpawner() { + return this.spawner + } + + setSpawner(spawner: ParticleSpawner) { + this.spawner = spawner + return this + } + + getParticle() { + return this.particle + } + + setParticle(particle: any, data: any = null) { + this.particle = particle + this.data = data + return this + } + + getCount() { + return this.count + } + + setCount(count: number) { + this.count = count + return this + } + + getOffsetX() { + return this.offsetX + } + + setOffsetX(offsetX: number) { + this.offsetX = offsetX + return this + } + + getOffsetY() { + return this.offsetY + } + + setOffsetY(offsetY: number) { + this.offsetY = offsetY + return this + } + + getOffsetZ() { + return this.offsetZ + } + + setOffsetZ(offsetZ: number) { + this.offsetZ = offsetZ + return this + } + + getExtra() { + return this.extra + } + + setExtra(extra: number) { + this.extra = extra + return this + } + + getData() { + return this.data + } + + setData(data) { + this.data = data + return this + } + + /** + * 通过给定一个坐标就可以使用已经指定的参数来播放粒子 + * + * @param location 坐标 + */ + spawn(location: any) { + if (!this.spawner) throw new Error(`particle ${this.uuid} not set spawner can't spawn!`) + this.spawner.spawn(location, this) + } + } + /** + * 表示一条线 + * + * @author Zoyn + */ + export class Line extends Particle { + private vector: any + private start: any + private end: any + /** + * 步长 + */ + private step: number + /** + * 向量长度 + */ + private length: number + + /** + * 构造一条线 + * + * @param start 线的起点 + * @param end 线的终点 + */ + constructor(start: any, end: any) + /** + * 构造一条线 + * + * @param start 线的起点 + * @param end 线的终点 + * @param step 每个粒子之间的间隔 (也即步长) + */ + constructor(start: any, end: any, step: number = 0.1) { + super() + this.start = start + this.end = end + this.step = step + // 对向量进行重置 + this.resetVector() + } + + show() { + for (let i = 0; i < this.length; i += this.step) { + let vectorTemp = this.vector.clone().multiply(i) + this.spawn(this.start.clone().add(vectorTemp)) + } + } + + /** + * 获取线的起始坐标 + * + * @return {@link Location} + */ + getStart() { + return this.start + } + + /** + * 利用给定的坐标设置线的起始坐标 + * + * @param start 起始坐标 + * @return {@link Line} + */ + setStart(start) { + this.start = start + this.resetVector() + return this + } + + /** + * 获取线的终点坐标 + * + * @return {@link Location} + */ + getEnd() { + return this.end + } + + /** + * 利用给定的坐标设置线的终点坐标 + * + * @param end 终点 + * @return {@link Line} + */ + setEnd(end) { + this.end = end + this.resetVector() + return this + } + + /** + * 获取每个粒子之间的间隔 + * + * @return 也就是循环的步长 + */ + getStep() { + return this.step + } + + /** + * 设置每个粒子之间的间隔 + * + * @param step 间隔 + * @return {@link Line} + */ + setStep(step) { + this.step = step + this.resetVector() + return this + } + + /** + * 手动重设线的向量 + */ + resetVector() { + this.vector = this.end.clone().subtract(this.start).toVector() + this.length = this.vector.length() + this.vector.normalize() + } + + public static buildLine(locA: any, locB: any, step: number, particle: any) { + let vectorAB = locB.clone().subtract(locA).toVector() + let vectorLength = vectorAB.length() + vectorAB.normalize() + for (let i = 0; i < vectorLength; i += step) { + ParticleManager.globalSpawner.spawn(locA.clone().add(vectorAB.clone().multiply(i)), particle) + } + } + } + /** + * 表示一个弧 + * + * @author Zoyn + */ + export class Arc extends Particle { + private angle: number + private radius: number + private step: number + + /** + * 构造一个弧 + * + * @param origin 弧所在的圆的圆点 + * @param angle 弧所占的角度 + * @param radius 弧所在的圆的半径 + * @param step 每个粒子的间隔(也即步长) + */ + constructor(angle: number = 30, radius: number = 1, step: number = 1) { + super() + this.angle = angle + this.radius = radius + this.step = step + } + + show(location: any) { + for (let i = 0; i < this.angle; i += this.step) { + let radians: number = Math.toRadians(i) + let x: number = this.radius * Math.cos(radians) + let z: number = this.radius * Math.sin(radians) + + super.spawn(location.clone().add(x, 0, z)) + } + } + + getAngle(): number { + return this.angle + } + + setAngle(angle: number): Arc { + this.angle = angle + return this + } + + getRadius(): number { + return this.radius + } + + setRadius(radius: number): Arc { + this.radius = radius + return this + } + + getStep(): number { + return this.step + } + + setStep(step: number): Arc { + this.step = step + return this + } + } + /** + * 表示一个圆 + * + * @author Zoyn + */ + export class Circle extends Arc { + constructor(radius: number) + constructor(radius: number, step: number) + /** + * 构造一个圆 + * + * @param origin 圆的圆点 + * @param radius 圆的半径 + * @param step 每个粒子的间隔(也即步长) + * @param period 特效周期(如果需要可以使用) + */ + constructor(radius: number = 1, step: number = 1) { + // Circle只需要控制这个fullArc就可以满足所有的要求 + super(360, radius, step) + } + } + const AtomicInteger = Java.type("java.util.concurrent.atomic.AtomicInteger") + + @injectable() + export abstract class ParticleManager { + public static globalSpawner: ParticleSpawner = undefined + @Autowired() + private taskManager: task.TaskManager + + protected taskId: java.util.concurrent.atomic.AtomicInteger + protected cacheTasks = new Map() + protected pluginCacheTasks = new Map>() + + constructor() { + this.taskId = new AtomicInteger(0) + } + + /** + * 获得自增的任务ID + */ + public nextId() { + return this.taskId.incrementAndGet() + } + + public getTaskManager() { + return this.taskManager + } + + public create(particle: Particle, plugin?: plugin.Plugin) { + let uuid = particle.getUUID() + if (this.cacheTasks.has(uuid)) { + return this.cacheTasks.get(uuid) + } + let task = this.create0(plugin, particle) + this.cacheTasks.set(uuid, task) + if (plugin) { + if (!this.pluginCacheTasks.has(plugin.description.name)) { + this.pluginCacheTasks.set(plugin.description.name, new Map()) + } + this.pluginCacheTasks.get(plugin.description.name).set(task.getTaskId(), task) + } + return task + } + + public cancel(particle: Particle) { + let uuid = particle.getUUID() + if (this.cacheTasks.has(uuid)) { + this.cacheTasks.get(uuid).cancel() + this.cacheTasks.delete(uuid) + } else { + throw new Error(`particle ${uuid} not found!`) + } + } + + disable(plugin: plugin.Plugin) { + if (this.pluginCacheTasks.has(plugin.description.name)) { + this.pluginCacheTasks + .get(plugin.description.name) + .forEach((task) => task.cancel()) + this.pluginCacheTasks.delete(plugin.description.name) + } + } + protected create0(owner: plugin.Plugin, particle: Particle): ParticleTask { + particle.setSpawner(this.getGlobalSpawner()) + return new ParticleTask(owner, particle, this) + } + protected abstract getGlobalSpawner(): ParticleSpawner + } + + export class ParticleTask { + + private particle: Particle + private isAsync: boolean = false + private interval: number = 0 + private _location: any + private _follow: any + + private owner: plugin.Plugin + private taskId: number + private task: task.Task + private taskManager: task.TaskManager + protected particleManager: ParticleManager + + constructor(owner: plugin.Plugin, particle: Particle, particleManager: ParticleManager) { + this.owner = owner + this.taskId = particleManager.nextId() + this.particle = particle + this.taskManager = particleManager.getTaskManager() + this.particleManager = particleManager + } + + getOwner() { + return this.task.getOwner() + } + + getTaskId() { + return this.taskId + } + + getParticle() { + return this.particle + } + + async(isAsync: boolean = true) { + this.isAsync = isAsync + return this + } + + timer(tick: number) { + this.interval = tick + return this + } + + follow(entity: { getLocation: () => any }) { + this._follow = entity + this._location = undefined + return this + } + + location(location: any) { + this._location = location + this._follow = undefined + return this + } + + submit() { + this.cancel() + if (this._follow && !this.interval) throw new Error(`enable follow entity but interval is ${this.interval}!`) + this.taskManager.create(() => { + this.task = this.taskManager + .create(() => { + try { + if (this._follow) { + if (!this._follow.isOnline()) return this.cancel() + this.particle.show(this._follow.getLocation().clone().add(0, 1, 0)) + } else if (this._location) { + this.particle.show(this._location) + } else { + console.warn(`ParticleTask ${this.taskId} particle ${this.particle.getUUID()} cancel because entity and location both undefined!`) + this.task.cancel() + } + } catch (error) { + console.error(`§6插件 §a${this.owner.description.name} §c播放粒子发送异常 §4粒子播放任务已终止!`) + console.ex(error) + this.cancel() + } + }, this.owner) + .async(this.isAsync) + .timer(this.interval) + .submit() + }, this.owner).later(2).submit() + return this + } + + cancel() { + if (this.task != null) { + this.task.cancel() + } + } + } + + export abstract class ParticleSpawner { + abstract spawnParticle(location: any, particle: any, count: number) + abstract spawn(location: any, particle: Particle) + } +} diff --git a/packages/bukkit/src/particle.ts b/packages/bukkit/src/particle.ts new file mode 100644 index 00000000..1f92c17a --- /dev/null +++ b/packages/bukkit/src/particle.ts @@ -0,0 +1,31 @@ +import { provideSingleton } from '@ccms/container' +import { particle, plugin } from '@ccms/api' + +@provideSingleton(particle.ParticleManager) +export class BukkitParticleManager extends particle.ParticleManager { + private globalSpawner = new BukkitParticleSpawner() + constructor() { + super() + particle.ParticleManager.globalSpawner = this.globalSpawner + } + protected getGlobalSpawner() { + return this.globalSpawner + } +} +export class BukkitParticleSpawner extends particle.ParticleSpawner { + spawnParticle(location: any, particle: any, count: number = 1) { + location.getWorld().spawnParticle(particle, location, count) + } + spawn(location: any, particle: particle.Particle) { + location.getWorld().spawnParticle( + particle.getParticle(), + location, + particle.getCount(), + particle.getOffsetX(), + particle.getOffsetY(), + particle.getOffsetZ(), + particle.getExtra(), + particle.getData() + ) + } +} diff --git a/packages/sponge/src/particle.ts b/packages/sponge/src/particle.ts new file mode 100644 index 00000000..28dd44c4 --- /dev/null +++ b/packages/sponge/src/particle.ts @@ -0,0 +1,32 @@ +import { provideSingleton } from '@ccms/container' +import { particle, plugin } from '@ccms/api' + +@provideSingleton(particle.ParticleManager) +export class SpongeParticleManager extends particle.ParticleManager { + private globalSpawner = new SpongeParticleSpawner() + constructor() { + super() + particle.ParticleManager.globalSpawner = this.globalSpawner + } + protected getGlobalSpawner() { + return this.globalSpawner + } +} +export class SpongeParticleSpawner extends particle.ParticleSpawner { + spawnParticle(location: org.spongepowered.api.world.Location, particle: any, count: number = 1) { + location.getPosition() + // location.getWorld().spawnParticle(particle, location, count) + } + spawn(location: any, particle: particle.Particle) { + location.getWorld().spawnParticle( + particle.getParticle(), + location, + particle.getCount(), + particle.getOffsetX(), + particle.getOffsetY(), + particle.getOffsetZ(), + particle.getExtra(), + particle.getData() + ) + } +}