Skip to content

Commit

Permalink
custom scriptevent library (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
amg-12 committed Dec 17, 2023
1 parent 671631a commit 8dc47ce
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 31 deletions.
72 changes: 45 additions & 27 deletions add-on/bp/scripts/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { system, world, Player, EntityInventoryComponent } from "@minecraft/server"
import { system, world, Player, EntityInventoryComponent, BlockPermutation } from "@minecraft/server"
import { getArgs, ArrayType, Vector3Type } from "./parser"

const overworld = world.getDimension("overworld")

Expand All @@ -12,19 +13,29 @@ function sendEvent(player: Player, message: string) {
}
}

function getEntities(ids: string[]) {
let result = []
ids.forEach(id => {
try {
result.push(world.getEntity(id))
} catch { }
})
return result
}

world.afterEvents.playerSpawn.subscribe(data => {
data.player.getTags().filter(x => x[0] == "_").forEach(tag => {
data.player.removeTag(tag)
})
})

world.afterEvents.entityHurt.subscribe(data => {
let player = data.damageSource.damagingEntity as Player
if (player.typeId == "minecraft:player") {
let cause = data.damageSource.cause
let inventory = (player.getComponent('inventory') as EntityInventoryComponent).container
let weapon = inventory.getItem(player.selectedSlot).typeId
// let projectile = data.damageSource.damagingProjectile.typeId
const player = data.damageSource.damagingEntity
if (player instanceof Player) {
const cause = data.damageSource.cause
const inventory = (player.getComponent('inventory') as EntityInventoryComponent).container
const weapon = inventory.getItem(player.selectedSlot).typeId
// const projectile = data.damageSource.damagingProjectile.typeId
// projectile must persist in order to be checked
if (cause == "projectile") {
data.hurtEntity.addTag(`_shot_by_${player.name}`)
Expand All @@ -37,26 +48,33 @@ world.afterEvents.entityHurt.subscribe(data => {
})

system.afterEvents.scriptEventReceive.subscribe(data => {
let player = data.sourceEntity as Player
switch (data.id) {
case "tcz:multiplayer":
switch (data.message) {
case "true":
multiplayer = true
break
case "false":
multiplayer = false
break
default:
overworld.runCommand(`say received ${data.message}`)
try {
switch (data.id) {
case "tcz:multiplayer":
if (data.message) {
[multiplayer] = getArgs(data.message, Boolean)
} else {
overworld.runCommand(`say multiplayer: ${multiplayer}`)
break
}
break
case "tcz:rename":
let args = data.message.split("|")
world.getEntity(args[0]).nameTag = args[1]
break
default:
overworld.runCommand(`w ${player.name} no scriptevent named ${data.id}`)
}
break
case "tcz:rename":
const [ids, name] = getArgs(data.message, ArrayType(String), String)
getEntities(ids).forEach(entity => entity.nameTag = name)
break
case "tcz:test":
const [coors, block] = getArgs(data.message, ArrayType(Vector3Type), String)
coors.forEach(coor => {
overworld.getBlock(coor).setPermutation(BlockPermutation.resolve(block))
})
break
default:
throw new Error(`no scriptevent named ${data.id}`)
}
} catch (err: unknown) {
if (err instanceof Error) {
overworld.runCommand(`say ${err.message}`)
}
}

})
231 changes: 231 additions & 0 deletions add-on/bp/scripts/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// https://www.sitepen.com/blog/unlocking-the-power-of-parser-combinators-a-beginners-guide

interface Vector3 {
x: number,
y: number,
z: number
}

type ParsedType = Array<ParsedType> | number | string | boolean | Vector3

interface SuccessResult {
success: true
value: string
rest: string
captures: ParsedType[]
}

interface FailureResult {
success: false
}

type CombinatorResult = SuccessResult | FailureResult
type Combinator = (str: string) => CombinatorResult

// parsers

function charOrNot(c: string, expected: boolean): Combinator {
return (str: string) => {
return (str[0] === c) === expected ?
{
success: true,
value: str[0],
rest: str.substring(1),
captures: []
}
:
{ success: false }
}
}

function char(c: string): Combinator {
return charOrNot(c, true)
}

function notChar(c: string): Combinator {
return charOrNot(c, false)
}

function either(...combinators: Combinator[]): Combinator {
return (str: string) => {
const found = combinators.find(c => c(str).success)
return found ? found(str) : { success: false }
}
}

function optional(combinator: Combinator): Combinator {
return either(combinator, str => {
return {
success: true,
value: "",
rest: str,
captures: []
}
})
}

function sequence(...combinators: Combinator[]): Combinator {
return (str: string) => {
let r = {
success: true,
value: "",
rest: str,
captures: [] as ParsedType[]
}
for (const combinator of combinators) {
const result = combinator(r.rest)
if (result.success) {
r.value += result.value
r.rest = result.rest
r.captures = r.captures.concat(result.captures)
} else return { success: false }
}
return r
}
}

function repeated(combinator: Combinator): Combinator {
return (str: string) => {
let r = {
success: true,
value: "",
rest: str,
captures: [] as ParsedType[]
}
while (true) {
const result = combinator(r.rest)
if (result.success) {
r.value += result.value
r.rest = result.rest
r.captures = r.captures.concat(result.captures)
continue
} else break
}
return r
}
}

function string(str: string): Combinator {
return sequence(...[...str].map(char))
}

function capture(combinator: Combinator, fn: (r: SuccessResult) => ParsedType[]): Combinator {
return (str: string) => {
const result = combinator(str)
if (result.success) {
result.captures = fn(result)
return result
} else return { success: false }
}
}

function lazy(fn: () => Combinator): Combinator {
return (str: string) => {
return fn()(str)
}
}

// types

const value = lazy(() => either(array, number, stringVal, boolean, vector3))

const array = capture(
sequence(
char("{"),
optional(sequence(
value,
repeated(sequence(
string(", "),
value
))
)),
char("}")
),
(x => [x.captures])
)

const digit = either(...[..."0123456789"].map(char))
const digits = sequence(digit, repeated(digit))
const integer = sequence(optional(char("-")), digits)
const number = capture(
sequence(
integer,
optional(sequence(
char("."),
digits
))
),
(x => { return [Number(x.value)] })
)

const stringVal = sequence(
char("\""),
capture(repeated(notChar("\"")), x => [x.value]),
char("\"")

)

const boolean = either(
capture(string("true"), (x => [true])),
capture(string("false"), (x => [false]))
)

const vector3 = capture(
sequence(
char("("),
number,
char(" "),
number,
char(" "),
number,
char(")")
),
result => {
const [x, y, z] = result.captures as number[]
return [{ x, y, z }]
}
)

// validation

function parse(combinator: Combinator, str: string) {
const result = combinator(str)
if (!result.success) throw new Error(`Failed to parse \"${str}\"`)
else if (result.rest) throw new Error(`Unparsed remainder \"${result.rest}\"`)
else if (result.captures.length == 0) throw new Error(`Nothing captured from \"${str}\"`)
else return result.captures[0]
}

type TypeConstructor<T> = () => T

export const Vector3Type: TypeConstructor<Vector3> = () => ({ x: 0, y: 0, z: 0 })

type ArrayTypeConstructor<T> = TypeConstructor<T[]> & { itemType: TypeConstructor<T> }
export const ArrayType: <T>(itemType: TypeConstructor<T>) => ArrayTypeConstructor<T> = (itemType) => {
const arrayTypeConstructor = () => [];
arrayTypeConstructor.itemType = itemType
return arrayTypeConstructor as ArrayTypeConstructor<T>
}

function isArrayTypeConstructor<T>(constructor: TypeConstructor<any>): constructor is ArrayTypeConstructor<T> {
return typeof (constructor as any).itemType === 'function';
}

function validate(object: unknown, constructor: TypeConstructor<any>) {
console.log(`validating ${object} as ${constructor.name}`);
if (isArrayTypeConstructor(constructor) && Array.isArray(object)) {
object.forEach((item) => validate(item, constructor.itemType));
} else if (!(typeof object === typeof constructor())) {
throw new Error(`Expected ${constructor.name} but got ${object}`);
}
}

export function getArgs<Types extends TypeConstructor<any>[]>(str: string, ...types: Types): { [K in keyof Types]: ReturnType<Types[K]> } {
const args = parse(array, str)
if (!Array.isArray(args)) throw new Error("what")
else if (args.length !== types.length) throw new Error(`Expected ${types.length} arguments but got ${args.length}`)
else for (let i = 0; i < args.length; i++) {
validate(args[i], types[i])
}
return args as any
}
1 change: 1 addition & 0 deletions pxt.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"files": [
"pxt/modded.ts",
"pxt/scriptevent.ts"
"pxt/entities.ts"
],
"testFiles": [],
Expand Down
5 changes: 1 addition & 4 deletions pxt/modded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ namespace modded {
//% block="rename %target=minecraftTarget to $name"
//% group=Other
export function rename(target: TargetSelector, name: string): void {
let results = mobs.queryTarget(target)
if (results.length > 0) {
player.execute(`scriptevent tcz:rename ${results[0].uniqueId}|${name}`)
}
scriptevent.send("rename", [target, name])
}

}
44 changes: 44 additions & 0 deletions pxt/scriptevent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
function formatArray(array: any[]): string {
return `{${array.map(format).join(", ")}}`
}

function formatTargetSelector(target: TargetSelector) {
return `{${mobs.queryTarget(target).map(x => `\"${x.uniqueId}\"`).join(", ")}}`
// recursion is too expensive here
}

function formatPosition(pos: Position) {
return `(${pos.toWorld()})`
}

function format(arg: any): string {
if (Array.isArray(arg)) {
return formatArray(arg as any[])
} else switch (typeof arg) {
case "string":
return `\"${arg}\"`
case "object":
if (`${arg}`[0] == "@") { // horrible
return formatTargetSelector(arg as TargetSelector)
} else {
return formatPosition(arg as Position)
}
default:
return `${arg}`
}

}

namespace scriptevent {

export function send(name: string, ...args: any[]) {
const msg = format(args)
player.execute(`scriptevent tcz:${name} ${msg}`)
}

//% block
export function test(coors: Position[], block: string) {
send("test", [coors, block])
}

}
Binary file modified tcz.mcaddon
Binary file not shown.

0 comments on commit 8dc47ce

Please sign in to comment.