yoni-mcscripts-lib
Version:
为 Minecraft Script API 中的部分接口创建了 wrapper,并提供简单的事件管理器和任务管理器,另附有一些便于代码编写的一些小工具。
230 lines (198 loc) • 6.94 kB
text/typescript
import { LegacyEventSignal as EventSignal, LegacyEventTriggerBuilder as EventTriggerBuilder } from "../../legacy_event.js";
import { EntityEvent } from "./EntityEvent.js";
import { EntityUtils as EntityBase, YoniEntity,
Location, YoniScheduler, Schedule, world } from "../../index.js";
import { logger } from "../logger.js";
import { EntityValue } from "../../types";
export class EntityMovementEventSignal extends EventSignal {
static warnEventAbuse = (function (){
let hasBeenWarned = false;
return function warnEventAbuse(){
if (hasBeenWarned)
return;
hasBeenWarned = true;
logger.warning("[EntityMovementEventSignal] 不加筛选的监听所有实体的移动可能带来严重的性能问题");
}
})();
subscribe(callback: (arg: EntityMovementEvent) => void, options?: EntityMovementEventOption){
super.subscribe(callback, options);
if (options == null){
EntityMovementEventSignal.warnEventAbuse();
}
filters.set(callback, options ?? null);
return callback;
}
unsubscribe(callback: (arg: EntityMovementEvent) => void){
super.unsubscribe(callback);
filters.delete(callback);
return callback;
}
}
//这个事件非常卡,我相信你们不会想要使用它的
export class EntityMovementEvent extends EntityEvent {
#cancelled = false;
get cancel(){
return this.#cancelled;
}
/**
* 如果取消跨维度移动事件的话,可能会导致游戏崩溃
*/
set cancel(bool){
if (this.#cancelled) return;
if (bool){
this.#cancelled = true;
this.entity.teleport(this.from);
}
}
get from(){
return this.#from.clone();
}
get to(){
return this.#to.clone();
}
get movementKeys(){
return this.#movementKeys.slice(0);
}
constructor(entity: YoniEntity, from: Location, to: Location, movementKeys: MovementKey[]){
super(entity);
this.#from = from;
this.#to = to;
this.#movementKeys = movementKeys;
}
#movementKeys;
#to;
#from;
}
let entityLocationRecords = new WeakMap<YoniEntity, Location>();
const filters: Map<any, null | EntityMovementEventOption> = new Map();
type MovementKey = "dimension" | "x" | "y" | "z" | "location" | "rx" | "ry" | "rotation";
interface EntityMovementEventOption {
entities?: EntityValue[];
entityTypes?: string[];
movementKeys?: MovementKey[];
}
function getFilters(){
return new Set(filters.values());
}
function getTargetEntities(): Iterable<YoniEntity> {
const filters = getFilters();
if (filters.size === 0
|| filters.has(null)){
return world.getLoadedEntities();
}
const targets: YoniEntity[] = [];
const typedEntities: Map<string, YoniEntity[]> = new Map();
for (const filter of filters){
if (!filter) continue; // impossible unless you are cheating
if (filter.entities){
targets.push(
...Array.from(filter.entities)
.map(function toYoniEntity(entity){
return EntityBase.from(filter.entities);
})
.filter(v => v != null) as YoniEntity[]
);
}
if (filter.entityTypes){
for (const entityType of Array.from(filter.entityTypes)){
if (typedEntities.has(entityType))
continue;
typedEntities.set(entityType, Array.from(
world.selectEntities({
type: entityType
})
));
}
}
}
return new Set(targets.concat(Array.from(typedEntities.values()).flat())); //as unknown as Iterable<YoniEntity>;
}
const schedule = new Schedule ({
async: false,
type: Schedule.cycleTickSchedule,
period: 1,
delay: 0
}, function compareTargetsLocation(){
for (const entity of getTargetEntities()){
// not location record, save and continue next
let oldLoc = entityLocationRecords.get(entity);
if (oldLoc === undefined){
entityLocationRecords.set(entity, entity.location);
continue;
}
let newLoc = entity.location;
// generate movement status keys
let movementKeys: MovementKey[] = [];
if (newLoc.dimension !== oldLoc.dimension){
movementKeys.push("x", "y", "z", "rx", "ry", "dimension", "location", "rotation");
} else {
if (newLoc.x !== oldLoc.x){
movementKeys.push("x", "location");
}
if (newLoc.y !== oldLoc.y){
movementKeys.push("y", "location");
}
if (newLoc.z !== oldLoc.z){
movementKeys.push("z", "location");
}
if (newLoc.rx !== oldLoc.rx){
movementKeys.push("rx", "rotation");
}
if (newLoc.ry !== oldLoc.ry){
movementKeys.push("ry", "rotation");
}
}
// no difference, continue next
if (movementKeys.length === 0){
continue;
}
// save new location
entityLocationRecords.set(entity, newLoc);
// remove duplication
movementKeys = Array.from(new Set(movementKeys));
// trigger event
trigger.triggerEvent(entity, oldLoc, newLoc, movementKeys);
}
});
function resolveFilter(
values: [YoniEntity, Location, Location, MovementKey[]],
filterValues: EntityMovementEventOption): boolean
{
const [entity, from, to, movementKeys] = values;
if (filterValues.movementKeys != null){
for (const key of filterValues.movementKeys){
if ( ! movementKeys.includes(key)){
return false;
}
}
}
if (filterValues.entities != null){
let found = false;
for (let filterEntity of filterValues.entities){
if (EntityBase.isSameEntity(filterEntity, entity)){
found = true;
break;
}
}
if (!found){
return false;
}
}
if (filterValues.entityTypes != null){
return filterValues.entityTypes.includes(entity.typeId);
}
return true;
}
const trigger = new EventTriggerBuilder()
.id("yoni:entityMovement")
.eventSignalClass(EntityMovementEventSignal)
.eventClass(EntityMovementEvent)
.filterResolver(resolveFilter)
.whenFirstSubscribe(()=>{
YoniScheduler.addSchedule(schedule);
})
.whenLastUnsubscribe(()=>{
YoniScheduler.removeSchedule(schedule);
})
.build()
.registerEvent();