mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-11-27 22:03:00 +08:00
feat: 勇士贴图打包
This commit is contained in:
parent
efe6931799
commit
37cd85cd1e
@ -1,3 +1,4 @@
|
|||||||
|
import { ITexture } from '@motajs/render-assets';
|
||||||
import { materials } from './ins';
|
import { materials } from './ins';
|
||||||
import { IBlockIdentifier, IIndexedIdentifier } from './types';
|
import { IBlockIdentifier, IIndexedIdentifier } from './types';
|
||||||
|
|
||||||
@ -118,8 +119,16 @@ export function fallbackLoad() {
|
|||||||
addAutotile(autotileSet, floor.fg2map);
|
addAutotile(autotileSet, floor.fg2map);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const heroTextures: ITexture[] = [];
|
||||||
|
|
||||||
|
data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.heroImages.forEach(v => {
|
||||||
|
const tex = materials.getImageByAlias(v);
|
||||||
|
if (tex) heroTextures.push(tex);
|
||||||
|
});
|
||||||
|
|
||||||
materials.buildAssets();
|
materials.buildAssets();
|
||||||
|
|
||||||
materials.cacheAutotileList(autotileSet);
|
materials.cacheAutotileList(autotileSet);
|
||||||
materials.cacheTilesetList(tilesetSet);
|
materials.cacheTilesetList(tilesetSet);
|
||||||
|
materials.buildListToAsset(heroTextures);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -430,13 +430,32 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
this.built = true;
|
this.built = true;
|
||||||
const data = this.assetBuilder.addTextureList(this.tileStore.values());
|
return this.buildListToAsset(this.tileStore.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
buildToAsset(texture: ITexture): IMaterialAssetData {
|
||||||
|
const data = this.assetBuilder.addTexture(texture);
|
||||||
|
const assetData: IMaterialAssetData = {
|
||||||
|
data: data,
|
||||||
|
identifier: data.index,
|
||||||
|
alias: `asset-${data.index}`,
|
||||||
|
store: this.assetStore
|
||||||
|
};
|
||||||
|
this.checkAssetDirty(data);
|
||||||
|
return assetData;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildListToAsset(
|
||||||
|
texture: Iterable<ITexture>
|
||||||
|
): Iterable<IMaterialAssetData> {
|
||||||
|
const data = this.assetBuilder.addTextureList(texture);
|
||||||
const arr = [...data];
|
const arr = [...data];
|
||||||
const res: IMaterialAssetData[] = [];
|
const res: IMaterialAssetData[] = [];
|
||||||
arr.forEach(v => {
|
arr.forEach(v => {
|
||||||
const alias = `asset-${v.index}`;
|
const alias = `asset-${v.index}`;
|
||||||
this.assetStore.alias(v.index, alias);
|
if (!this.assetDataStore.has(v.index)) {
|
||||||
this.assetDataStore.set(v.index, v);
|
this.assetDataStore.set(v.index, v);
|
||||||
|
}
|
||||||
const data: IMaterialAssetData = {
|
const data: IMaterialAssetData = {
|
||||||
data: v,
|
data: v,
|
||||||
identifier: v.index,
|
identifier: v.index,
|
||||||
@ -448,6 +467,9 @@ export class MaterialManager implements IMaterialManager {
|
|||||||
}
|
}
|
||||||
res.push(data);
|
res.push(data);
|
||||||
});
|
});
|
||||||
|
arr.forEach(v => {
|
||||||
|
this.checkAssetDirty(v);
|
||||||
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -397,6 +397,18 @@ export interface IMaterialManager
|
|||||||
*/
|
*/
|
||||||
buildAssets(): Iterable<IMaterialAssetData>;
|
buildAssets(): Iterable<IMaterialAssetData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将指定贴图打包进图集
|
||||||
|
* @param texture 贴图对象
|
||||||
|
*/
|
||||||
|
buildToAsset(texture: ITexture): IMaterialAssetData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一系列贴图打包进贴图对象
|
||||||
|
* @param texture 贴图列表
|
||||||
|
*/
|
||||||
|
buildListToAsset(texture: Iterable<ITexture>): Iterable<IMaterialAssetData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据图块标识符在图集中获取对应的可渲染对象
|
* 根据图块标识符在图集中获取对应的可渲染对象
|
||||||
* @param identifier 图块标识符,即图块数字
|
* @param identifier 图块标识符,即图块数字
|
||||||
|
|||||||
@ -261,7 +261,6 @@ const MainScene = defineComponent(() => {
|
|||||||
return () => (
|
return () => (
|
||||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||||
<sprite
|
<sprite
|
||||||
hidden
|
|
||||||
render={testRender}
|
render={testRender}
|
||||||
loc={[180, 0, 480, 480]}
|
loc={[180, 0, 480, 480]}
|
||||||
zIndex={1000}
|
zIndex={1000}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { IHookable, IHookBase, IHookController } from '@motajs/common';
|
import { IHookable, IHookBase, IHookController } from '@motajs/common';
|
||||||
import { IMapLayer } from '../map';
|
import { IMapLayer } from '../map';
|
||||||
|
|
||||||
|
export interface ICoreState {
|
||||||
|
/** 地图状态 */
|
||||||
|
readonly layer: ILayerState;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ILayerStateHooks extends IHookBase {
|
export interface ILayerStateHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
||||||
@ -136,7 +141,82 @@ export interface ILayerState extends IHookable<ILayerStateHooks> {
|
|||||||
getBackground(): number;
|
getBackground(): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICoreState {
|
export const enum HeroDirection {
|
||||||
/** 地图状态 */
|
Left,
|
||||||
readonly layer: ILayerState;
|
Up,
|
||||||
|
Right,
|
||||||
|
Down
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHeroStateHooks extends IHookBase {
|
||||||
|
/**
|
||||||
|
* 当设置勇士的坐标时触发
|
||||||
|
* @param controller 钩子控制器
|
||||||
|
* @param x 勇士横坐标
|
||||||
|
* @param y 勇士纵坐标
|
||||||
|
*/
|
||||||
|
onSetPosition(
|
||||||
|
controller: IHookController<this>,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当移动勇士时触发
|
||||||
|
* @param controller 钩子控制器
|
||||||
|
* @param direction 移动方向
|
||||||
|
* @param time 移动动画时长
|
||||||
|
*/
|
||||||
|
onMoveHero(
|
||||||
|
controller: IHookController<this>,
|
||||||
|
direction: HeroDirection,
|
||||||
|
time: number
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当勇士跳跃时触发
|
||||||
|
* @param controller 钩子控制器
|
||||||
|
* @param x 目标点横坐标
|
||||||
|
* @param y 目标点纵坐标
|
||||||
|
* @param time 跳跃动画时长
|
||||||
|
*/
|
||||||
|
onJumpHero(
|
||||||
|
controller: IHookController<this>,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
time: number
|
||||||
|
): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHeroState extends IHookable<IHeroStateHooks> {
|
||||||
|
/** 勇士横坐标 */
|
||||||
|
readonly x: number;
|
||||||
|
/** 勇士纵坐标 */
|
||||||
|
readonly y: number;
|
||||||
|
/** 勇士朝向 */
|
||||||
|
readonly direction: HeroDirection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置勇士位置
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
setPosition(x: number, y: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动勇士
|
||||||
|
* @param dir 移动方向
|
||||||
|
* @param time 移动动画时长,默认 100ms
|
||||||
|
* @returns 移动的 `Promise`,当相关的移动动画结束后兑现
|
||||||
|
*/
|
||||||
|
move(dir: HeroDirection, time?: number): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳跃勇士至目标点
|
||||||
|
* @param x 目标点横坐标
|
||||||
|
* @param y 目标点纵坐标
|
||||||
|
* @param time 跳跃动画时长,默认 500ms
|
||||||
|
* @returns 跳跃的 `Promise`,当相关的移动动画结束后兑现
|
||||||
|
*/
|
||||||
|
jumpHero(x: number, y: number, time?: number): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
import { logger } from '@motajs/common';
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
|
||||||
import { cloneDeep } from 'lodash-es';
|
|
||||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +18,7 @@ export function getHeroStatusOn(
|
|||||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||||
floorId?: FloorIds
|
floorId?: FloorIds
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-expect-error 推导错误
|
||||||
return getHeroStatusOf(core.status.hero, name, floorId);
|
return getHeroStatusOf(core.status.hero, name, floorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,216 +126,3 @@ export interface IHeroStatusDefault {
|
|||||||
def: number;
|
def: number;
|
||||||
hp: number;
|
hp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeroStateEvent {
|
|
||||||
set: [key: string | number | symbol, value: any];
|
|
||||||
}
|
|
||||||
|
|
||||||
type HeroStatusCalculate = (
|
|
||||||
hero: HeroState<any>,
|
|
||||||
key: string | number | symbol,
|
|
||||||
value: any
|
|
||||||
) => any;
|
|
||||||
|
|
||||||
export class HeroState<
|
|
||||||
T extends object = IHeroStatusDefault
|
|
||||||
> extends EventEmitter<HeroStateEvent> {
|
|
||||||
readonly status: T;
|
|
||||||
readonly computedStatus: T;
|
|
||||||
|
|
||||||
readonly buffable: Set<keyof T> = new Set();
|
|
||||||
readonly buffMap: Map<keyof T, number> = new Map();
|
|
||||||
|
|
||||||
private static cal: HeroStatusCalculate = (_0, _1, value) => value;
|
|
||||||
|
|
||||||
constructor(init: T) {
|
|
||||||
super();
|
|
||||||
this.status = init;
|
|
||||||
this.computedStatus = cloneDeep(init);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置某个属性的值
|
|
||||||
* @param key 要设置的属性
|
|
||||||
* @param value 属性值
|
|
||||||
* @returns 是否设置成功
|
|
||||||
*/
|
|
||||||
setStatus<K extends keyof T>(key: K, value: T[K]): boolean {
|
|
||||||
this.status[key] = value;
|
|
||||||
this.emit('set', key, value);
|
|
||||||
return this.refreshStatus(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加或减少一个属性的值,只对数字型的属性有效
|
|
||||||
* @param key 要修改的属性
|
|
||||||
* @param value 属性的增量
|
|
||||||
* @returns 是否设置成功
|
|
||||||
*/
|
|
||||||
addStatus<K extends SelectKey<T, number>>(key: K, value: number): boolean {
|
|
||||||
if (typeof this.status[key] !== 'number') {
|
|
||||||
logger.warn(14, String(key));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.setStatus<K>(key, (this.status[key] + value) as T[K]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取某个属性的原始值
|
|
||||||
* @param key 要获取的属性
|
|
||||||
* @returns 属性的值
|
|
||||||
*/
|
|
||||||
getStatus<K extends keyof T>(key: K): T[K] {
|
|
||||||
return this.status[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一个属性计算后的值,也就是2.x所说的勇士真实属性
|
|
||||||
* @param key 要获取的属性值
|
|
||||||
*/
|
|
||||||
getComputedStatus<K extends keyof T>(key: K): T[K] {
|
|
||||||
return this.computedStatus[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记某个属性为可以被buff加成
|
|
||||||
*/
|
|
||||||
markBuffable(key: SelectKey<T, number>): void {
|
|
||||||
if (typeof this.status[key] !== 'number') {
|
|
||||||
logger.warn(12, String(key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.buffable.add(key);
|
|
||||||
this.buffMap.set(key, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置某个属性的buff值
|
|
||||||
* @param key 要设置buff的属性
|
|
||||||
* @param value buff值
|
|
||||||
* @returns 是否设置成功
|
|
||||||
*/
|
|
||||||
setBuff(key: SelectKey<T, number>, value: number): boolean {
|
|
||||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
|
||||||
logger.warn(13, String(key));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.buffMap.set(key, value);
|
|
||||||
return this.refreshStatus(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加或减少一个buff值
|
|
||||||
* @param key 要修改的buff属性
|
|
||||||
* @param value buff增量
|
|
||||||
* @returns 是否修改成功
|
|
||||||
*/
|
|
||||||
addBuff(key: SelectKey<T, number>, value: number): boolean {
|
|
||||||
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
|
|
||||||
logger.warn(13, String(key));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.setBuff(key, this.buffMap.get(key)! + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新某个或所有属性,重新进行计算
|
|
||||||
* @param key 要刷新的属性名,不填表示刷新所有属性
|
|
||||||
* @returns 是否计算成功
|
|
||||||
*/
|
|
||||||
refreshStatus(key?: keyof T): boolean {
|
|
||||||
if (key === void 0) {
|
|
||||||
for (const [key, value] of Object.entries(this.status)) {
|
|
||||||
// @ts-ignore
|
|
||||||
this.computedStatus[key] = HeroState.cal(this, key, value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.computedStatus[key] = HeroState.cal(this, key, this.status[key]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算所有可以buff加成的属性
|
|
||||||
* @returns 是否计算成功
|
|
||||||
*/
|
|
||||||
refreshBuffable(): boolean {
|
|
||||||
for (const key of this.buffable) {
|
|
||||||
this.computedStatus[key] = HeroState.cal(
|
|
||||||
this,
|
|
||||||
key,
|
|
||||||
this.status[key]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
|
|
||||||
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
|
|
||||||
*/
|
|
||||||
static overrideCalculate(fn: HeroStatusCalculate) {
|
|
||||||
this.cal = fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IHeroItem {
|
|
||||||
items: Map<AllIdsOf<'items'>, number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置勇士拥有的物品数量
|
|
||||||
* @param item 物品id
|
|
||||||
* @param value 物品数量
|
|
||||||
* @returns 是否设置成功
|
|
||||||
*/
|
|
||||||
setItem(item: AllIdsOf<'items'>, value: number): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加或减少勇士拥有的物品数量
|
|
||||||
* @param item 物品id
|
|
||||||
* @param value 物品数量增量
|
|
||||||
* @returns 是否设置成功
|
|
||||||
*/
|
|
||||||
addItem(item: AllIdsOf<'items'>, value: number): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用一个物品
|
|
||||||
* @param item 物品id
|
|
||||||
* @returns 是否使用成功
|
|
||||||
*/
|
|
||||||
useItem(item: AllIdsOf<'items'>, x?: number, y?: number): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得一个物品
|
|
||||||
* @param item 物品id
|
|
||||||
* @param num 获得的数量
|
|
||||||
*/
|
|
||||||
getItem(item: AllIdsOf<'items'>, num: number): void;
|
|
||||||
/**
|
|
||||||
* 获得一个物品
|
|
||||||
* @param item 物品id
|
|
||||||
* @param x 物品所在x坐标
|
|
||||||
* @param y 物品所在y坐标
|
|
||||||
* @param floorId 物品所在楼层
|
|
||||||
* @param num 获得的数量
|
|
||||||
*/
|
|
||||||
getItem(
|
|
||||||
item: AllIdsOf<'items'>,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
floorId?: FloorIds,
|
|
||||||
num?: number
|
|
||||||
): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取某个物品的数量
|
|
||||||
* @param item 物品id
|
|
||||||
*/
|
|
||||||
itemCount(item: AllIdsOf<'items'>): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断勇士是否拥有这个物品
|
|
||||||
* @param item 物品id
|
|
||||||
*/
|
|
||||||
hasItem(item: AllIdsOf<'items'>): boolean;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -18,13 +18,6 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = {
|
|||||||
"_docs": "楼层列表",
|
"_docs": "楼层列表",
|
||||||
"_data": "在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器、浏览地图和上/下楼器的顺序"
|
"_data": "在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器、浏览地图和上/下楼器的顺序"
|
||||||
},
|
},
|
||||||
"plugin": {
|
|
||||||
"_leaf": true,
|
|
||||||
"_type": "textarea",
|
|
||||||
"_range": "thiseval instanceof Array",
|
|
||||||
"_docs": "插件列表",
|
|
||||||
"_data": "在这里按顺序放所有的插件,顺序会影响到插件的加载,越靠前越早加载"
|
|
||||||
},
|
|
||||||
"floorPartitions": {
|
"floorPartitions": {
|
||||||
"_leaf": true,
|
"_leaf": true,
|
||||||
"_type": "event",
|
"_type": "event",
|
||||||
@ -128,6 +121,19 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = {
|
|||||||
"_docs": "使用字体",
|
"_docs": "使用字体",
|
||||||
"_data": "在此存放所有可能使用的字体 \n 字体名不能使用中文,不能带空格或特殊字符"
|
"_data": "在此存放所有可能使用的字体 \n 字体名不能使用中文,不能带空格或特殊字符"
|
||||||
},
|
},
|
||||||
|
"heroImages": {
|
||||||
|
"_leaf": true,
|
||||||
|
"_type": "material",
|
||||||
|
"_range": "editor.mode.checkImages(thiseval, './project/images/')",
|
||||||
|
"_directory": "./project/images/",
|
||||||
|
"_transform": (function (one) {
|
||||||
|
if (one.endsWith('.png') || one.endsWith('.jpg') || one.endsWith('.jpeg') || one.endsWith('.gif') || one.endsWith('.webp'))
|
||||||
|
return one;
|
||||||
|
return null;
|
||||||
|
}).toString(),
|
||||||
|
"_docs": "勇士贴图",
|
||||||
|
"_data": "在这里填写游戏中所有可能使用到的勇士贴图,贴图需要先在全塔属性-使用图片中注册。如果一个贴图不会被用作勇士贴图,请不要填写进去!",
|
||||||
|
},
|
||||||
"nameMap": {
|
"nameMap": {
|
||||||
"_leaf": true,
|
"_leaf": true,
|
||||||
"_type": "event",
|
"_type": "event",
|
||||||
|
|||||||
@ -382,7 +382,12 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
|
|||||||
],
|
],
|
||||||
"font": "normal"
|
"font": "normal"
|
||||||
},
|
},
|
||||||
"splitImages": []
|
"splitImages": [],
|
||||||
|
"heroImages": [
|
||||||
|
"hero.png",
|
||||||
|
"hero1.png",
|
||||||
|
"hero2.png"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"firstData": {
|
"firstData": {
|
||||||
"title": "人类:开天辟地",
|
"title": "人类:开天辟地",
|
||||||
|
|||||||
5
src/types/declaration/data.d.ts
vendored
5
src/types/declaration/data.d.ts
vendored
@ -69,6 +69,11 @@ interface MainData {
|
|||||||
*/
|
*/
|
||||||
readonly splitImages: SplitImageData;
|
readonly splitImages: SplitImageData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 勇士贴图列表
|
||||||
|
*/
|
||||||
|
readonly heroImages: readonly ImageIds[];
|
||||||
|
|
||||||
readonly plugin: string[];
|
readonly plugin: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user