lazuli-api
Version:
Add some useful instances for Minecraft Bedrock Edition
548 lines (537 loc) • 14.8 kB
text/typescript
import { getModId } from "lazuli-core";
import { Container, Player, RawMessage } from "@minecraft/server";
import { ItemData, EntityData, DisplayCondition } from "lazuli-common";
import { getItemAmountInContainer, giveItem, modAlert } from "lazuli-utils";
import {
ActionFormData,
MessageFormData,
} from "@minecraft/server-ui";
/**
* Namespace of Task Complete Tag.
*/
let taskNameSpace: string = getModId();
/**
* Create a task.
* @catetory Need Registry
*/
export class Task {
constructor(
/**
* The unique id of the task.
*/
readonly id: string,
/**
* Title of the task, only support {@link RawMessage}.
*/
public _title: RawMessage,
/**
* Body of the task, only support {@link RawMessage}.
*/
public _body: RawMessage,
/**
* Options of the task.
*
* *You MUST assign the complete condition and the award, others are optional.*
*/
public options: TaskOptions
) {
if (!this.options.unlock) {
this.options.unlock = true;
}
}
/**
*
* @param player
* @param backTo The screen that player should return to after closing the form.
*/
protected displayInfo(player: Player, backTo?: TaskList | TaskCategory) {
const body = TaskUtils.generateBody(this.body, this.options);
const form = new MessageFormData()
.title(this.title)
.body(body)
.button1({ translate: "gui.back" });
if (this.isCompleted(player)) {
form.button2({ translate: "task.done" });
} else {
form.button2({ translate: "task.check" });
}
form.show(player).then((response) => {
if (
response.canceled ||
response.selection === undefined ||
response.selection === 0 ||
this.isCompleted(player)
) {
backTo?.display(player);
} else if (response.selection === 1) {
if (
TaskUtils.checkCondition(this.options.condition, player, this).result
) {
this.complete(player);
} else {
player.sendMessage(
TaskUtils.checkCondition(this.options.condition, player, this)
.message
);
}
}
});
}
/**
*
* @param player
* @param backTo
*/
protected displayFail(player: Player, backTo?: TaskList | TaskCategory) {
const UNLOCK = this.options.unlock;
modAlert({ translate: "task.displayFail.unlock" }, player);
if (typeof UNLOCK === "object") {
player.sendMessage(
TaskUtils.checkCondition(UNLOCK, player, this).message
);
}
}
/**
* Display ui form to the player.
* @param player
* @param backTo The screen that player should return to after closing the form.
*/
display(player: Player, backTo?: TaskList | TaskCategory): void {
if (this.isUnlocked(player)) {
this.displayInfo(player, backTo);
} else if (typeof this.options.unlock === "object") {
if (TaskUtils.checkCondition(this.options.unlock, player, this)) {
this.unlock(player);
this.displayInfo(player, backTo);
} else {
this.displayFail(player, backTo);
}
} else {
modAlert({ translate: "task.displayFail.noAccess" }, player);
}
}
/**
* Let a player complete the task and give award.
* @param player
*/
complete(player: Player): void {
player.addTag(TaskUtils.getCompleteTag(this));
player.addLevels(this.options.award.level ?? 0);
player.addExperience(this.options.award.exp ?? 0);
if (this.options.award?.item?.itemStack) {
giveItem(player, this.options.award?.item?.itemStack);
}
if (this.options.award.custom) {
this.options.award.custom(player);
}
player.playSound("random.levelup");
player.sendMessage({
translate: "task.finished",
});
}
/**
* Let a player complete the task and give award.
* @param player
*/
unlock(player: Player): void {
player.addTag(TaskUtils.getUnlockTag(this));
player.onScreenDisplay.setActionBar({
translate: "task.unlock",
});
}
/**
* Check if the player has the complete tag.
* @param player
*/
isCompleted(player: Player): boolean {
return player.hasTag(TaskUtils.getCompleteTag(this));
}
/**
* Check if the player has the unlock tag.
* @param player
*/
isUnlocked(player: Player): boolean {
const UNLOCK = this.options.unlock;
if (typeof UNLOCK === "boolean") {
return UNLOCK;
} else {
return player.hasTag(TaskUtils.getUnlockTag(this));
}
}
set body(content: RawMessage) {
this._body = content;
}
set title(content: RawMessage) {
this._title = content;
}
get body() {
return this._body;
}
get title() {
return this._title;
}
}
export class TaskCategory {
constructor(
public title: RawMessage,
public body: RawMessage,
public items: Task[],
public iconPath?: string
) {}
/**
* Display ui form to the player.
* @param player
* @param backTo The screen that player should return to after closing the form.
*/
display(player: Player, backTo?: TaskList): void {
const form = new ActionFormData().title(this.title).body(this.body);
this.items.forEach((task) => {
let rawTitle: RawMessage = { rawtext: [this.title] };
if (task.isCompleted(player)) {
rawTitle.rawtext?.push({ text: " §2✔" });
}
form.button(rawTitle, task.options.iconPath);
});
form.show(player).then((response) => {
if (response.canceled || response.selection === undefined) {
backTo?.display(player);
} else {
this.items[response.selection].display(player);
}
});
}
/**
* Returns whether the player has completed all tasks in the category.
* @param player
* @returns Whether the player has completed all tasks in the category.
*/
isCompleted(player: Player): boolean {
let value = this.items.every((task) => {
return task.isCompleted(player) === true;
});
return value;
}
}
export class TaskList {
constructor(
readonly id: string,
public title: RawMessage,
public body: RawMessage,
public options: TaskListOptions
) {}
display(player: Player) {
const form = new ActionFormData().title(this.title).body(this.body);
this.options.items.forEach((item) => {
let rawTitle: RawMessage = { rawtext: [this.title] };
if (item.isCompleted(player)) {
rawTitle.rawtext?.push({ text: " §2✔" });
}
form.button(rawTitle);
});
form.show(player).then(response=>{
if (response.canceled || response.selection === undefined) {
return;
}
this.options.items[response.selection].display(player);
})
}
}
export class TaskUtils {
/**
* Get a task's complete tag.
* @param task
* @return The task's complete tag.
*/
static getCompleteTag(task: Task) {
return `${taskNameSpace}:${task.id}`;
}
/**
* Get a task's complete tag.
* @param task
* @return The task's complete tag.
*/
static getUnlockTag(task: Task) {
return `${taskNameSpace}:${task.id}.unlock`;
}
/**
* Set name space of the task complete tag.
* @param str
*/
static setNameSpace(str: string) {
taskNameSpace = str;
}
static checkCondition(
options: TaskUnlockCompleteOptions,
player: Player,
task: Task
): checkResult {
let message: RawMessage = { rawtext: [] };
const CONTAINER: undefined | Container = player.getComponent(
"minecraft:inventory"
)?.container;
if (!CONTAINER) {
throw new Error("Task Api unable to access player's Container!");
}
if (
options.item &&
getItemAmountInContainer(CONTAINER, options.item.itemStack.typeId) <
options.item.itemStack.amount
) {
message.rawtext?.push({
rawtext: [
{
translate: "task.not_enough.item",
with: {
rawtext: [
{
text: options.item.itemStack.amount.toString(),
},
options.item.name,
],
},
}, // Following items are missing from your inventory:
{
text: "\n",
},
],
});
}
if (options.level && player.level < options.level) {
message.rawtext?.push({
rawtext: [
{
translate: "task.not_enough.level",
with: [player.level.toString()],
}, // You need %%1 more level(s)!
{
text: "\n",
},
],
});
}
if (options.exp && player.getTotalXp() < options.exp) {
message.rawtext?.push({
rawtext: [
{
translate: "task.not_enough.xp",
with: [player.getTotalXp.toString()],
}, // You need %%1 more experience!
{
text: "\n",
},
],
});
}
if (options.tasks) {
let finishList: boolean[] = [];
let taskList: RawMessage = {
rawtext: [],
};
options.tasks.forEach((task) => {
finishList.push(task.isCompleted(player));
taskList.rawtext?.push(task.title);
});
if (
!finishList.every((value) => {
return value === true;
})
) {
message.rawtext?.push({
rawtext: [
{
translate: "task.not_enough.tasks",
with: taskList,
}, // You need %%1 more experience!
{
text: "\n",
},
],
});
}
}
if (!task.isCompleted(player) && options.killEntity) {
message.rawtext?.push({
rawtext: [
{
translate: "task.not_enough.entity",
with: [options.killEntity.name],
},
],
});
}
return {
result: <number>message.rawtext?.length === 0,
message: message,
};
}
/**
* Generate the body of Task, includes descriptions, conditions, awards and tips.
* @param description
* @param options
* @return A RawMessage that includes task's descriptions, conditions, awards and tips.
*/
static generateBody(description: RawMessage, options: TaskOptions) {
const [CONDITION, AWARD] = [options.condition, options.award];
let body: RawMessage = {
rawtext: [
description,
{ text: "\n\n" },
{ translate: "task.condition.title" },
],
};
if (Object.getOwnPropertyNames(CONDITION).length === 0) {
body.rawtext?.push({
translate: "task.condition.none",
});
} else {
if (CONDITION.item) {
body.rawtext?.push({
translate: "task.item",
with: {
rawtext: [
{
text: CONDITION.item.itemStack.amount.toString(),
},
CONDITION.item.name,
],
},
});
}
if (CONDITION.exp) {
body.rawtext?.push({
translate: "task.exp",
with: [CONDITION.exp.toString()],
});
}
if (CONDITION.level) {
body.rawtext?.push({
translate: "task.level",
with: [CONDITION.level.toString()],
});
}
if (CONDITION.killEntity) {
body.rawtext?.push({
translate: "task.entity",
with: [CONDITION.killEntity.name],
});
}
if (CONDITION.tasks) {
let taskList: RawMessage = {
rawtext: [],
};
CONDITION.tasks.forEach((task) => {
taskList.rawtext?.push(task._title);
});
body.rawtext?.push({
translate: "task.tasks",
with: taskList,
});
}
}
body.rawtext?.push({ text: "\n" }, { translate: "task.award.title" });
if (Object.getOwnPropertyNames(AWARD).length === 0) {
body.rawtext?.push({
translate: "task.award.none",
});
} else {
if (AWARD.item) {
body.rawtext?.push({
translate: "task.item",
with: {
rawtext: [
{
text: AWARD.item.itemStack.amount.toString(),
},
AWARD.item.name,
],
},
});
}
if (AWARD.exp) {
body.rawtext?.push({
translate: "task.exp",
with: [AWARD.exp.toString()],
});
}
if (AWARD.level) {
body.rawtext?.push({
translate: "task.level",
with: [AWARD.level.toString()],
});
}
}
if (options.tips) {
body.rawtext?.push(
{ text: "\n" },
{ translate: "task.tips.title" },
options.tips
);
}
return body;
}
}
/**
* Contidions that control when unlock or complete the task.
*
* **NOTE: If specify more than one condition, the task will be unlocked when any condition is achieved, but it will be completed after all conditions are achieved.**
*/
export interface TaskUnlockCompleteOptions {
/**
* Unlock the task when player has the specific item(s).
*/
item?: ItemData;
/**
* The specific entity will be required to unlock the task.
*/
killEntity?: EntityData;
/**
* The specific level will be required to unlock the task.
*/
level?: number;
/**
* The specific point will be required to unlock the task.
*/
exp?: number;
/**
* The specific tasks will be required to unlock the task.
*/
tasks?: Task[];
}
/**
* Award of the task.
*/
export interface TaskAward {
/**
* Player will get these items when the task is finished.
*/
item?: ItemData;
/**
* The specific level will be given to the player.
*/
level?: number;
/**
* The specific point will be given to the player.
*/
exp?: number;
/**
* Custom award.
* @param player
*/
custom?: (player: Player) => void;
}
export interface TaskOptions {
condition: TaskUnlockCompleteOptions;
award: TaskAward;
unlock?: TaskUnlockCompleteOptions | boolean;
tips?: RawMessage;
iconPath?: string;
}
export interface TaskListOptions {
items: (Task | TaskCategory)[];
displayCondition: DisplayCondition;
iconPath?: string;
}
export interface checkResult {
result: boolean;
message: RawMessage;
}