[oden][game] Multiple screens, logging, pre/post, bluescreen
Better blue screens and also logging and whatnot
This commit is contained in:
parent
95d626c15f
commit
93d4e3eb91
8 changed files with 1201 additions and 244 deletions
|
|
@ -2,7 +2,8 @@ import { load_texture } from "./assets";
|
||||||
import { btn, Button } from "./input";
|
import { btn, Button } from "./input";
|
||||||
import { Vec2, new_v2, vadd, vsub, vnorm, vmul } from "./vector";
|
import { Vec2, new_v2, vadd, vsub, vnorm, vmul } from "./vector";
|
||||||
import { color, stroke, circle, spr, use_texture, Texture } from "./graphics";
|
import { color, stroke, circle, spr, use_texture, Texture } from "./graphics";
|
||||||
import { has_collision, Level } from "./level";
|
import { has_collision, Level, World } from "./level";
|
||||||
|
import { log } from "./log";
|
||||||
|
|
||||||
export interface ActorProps {
|
export interface ActorProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -50,7 +51,15 @@ export class Actor {
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {}
|
pre_update(_world: World) {}
|
||||||
|
|
||||||
|
post_update(_world: World) {}
|
||||||
|
|
||||||
|
update(world: World) {
|
||||||
|
this.pre_update(world);
|
||||||
|
this.update_physics(world.current_level);
|
||||||
|
this.post_update(world);
|
||||||
|
}
|
||||||
|
|
||||||
// IDEAS: Make gameplay logic at 30fps and render updates at 60fps? Does
|
// IDEAS: Make gameplay logic at 30fps and render updates at 60fps? Does
|
||||||
// this mean we do some "update" in render?
|
// this mean we do some "update" in render?
|
||||||
|
|
@ -159,7 +168,7 @@ export class Robo extends Actor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
pre_update() {
|
||||||
// Acceleration from input
|
// Acceleration from input
|
||||||
let a = new_v2(0);
|
let a = new_v2(0);
|
||||||
if (btn(Button.Up)) {
|
if (btn(Button.Up)) {
|
||||||
|
|
@ -178,6 +187,50 @@ export class Robo extends Actor {
|
||||||
this.props.velocity = vadd(this.props.velocity, vmul(a, 0.07));
|
this.props.velocity = vadd(this.props.velocity, vmul(a, 0.07));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post_update(world: World) {
|
||||||
|
const level = world.current_level;
|
||||||
|
const props = this.props;
|
||||||
|
const w = level.cw - 1;
|
||||||
|
const h = level.ch - 1;
|
||||||
|
|
||||||
|
log("robo position", "cx", props.cx, "cy", props.cy, "w", w, "h", h);
|
||||||
|
let next_level = null;
|
||||||
|
if (props.cx > w || (props.cx == w && props.xr > 0.8)) {
|
||||||
|
const iid = level.neighbors.e;
|
||||||
|
next_level = iid && world.level_map.get(iid);
|
||||||
|
if (next_level) {
|
||||||
|
props.cx = 0;
|
||||||
|
props.xr = 0.2;
|
||||||
|
}
|
||||||
|
} else if (props.cy > h || (props.cy == h && props.yr > 0.8)) {
|
||||||
|
const iid = level.neighbors.s;
|
||||||
|
next_level = iid && world.level_map.get(iid);
|
||||||
|
if (next_level) {
|
||||||
|
props.cy = 0;
|
||||||
|
props.yr = 0.2;
|
||||||
|
}
|
||||||
|
} else if (props.cx < 0 || (props.cx == 0 && props.xr < 0.2)) {
|
||||||
|
const iid = level.neighbors.w;
|
||||||
|
next_level = iid && world.level_map.get(iid);
|
||||||
|
if (next_level) {
|
||||||
|
props.cx = next_level.cw - 1;
|
||||||
|
props.xr = 0.8;
|
||||||
|
}
|
||||||
|
} else if (props.cy < 0 || (props.cy == 0 && props.yr < 0.2)) {
|
||||||
|
const iid = level.neighbors.n;
|
||||||
|
next_level = iid && world.level_map.get(iid);
|
||||||
|
if (next_level) {
|
||||||
|
props.cy = next_level.ch - 1;
|
||||||
|
props.yr = 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_level) {
|
||||||
|
log("vwoop", next_level.iid);
|
||||||
|
world.current_level = next_level; // Yikes.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
draw(clock: number) {
|
draw(clock: number) {
|
||||||
if (this.bot_sprite != undefined) {
|
if (this.bot_sprite != undefined) {
|
||||||
use_texture(this.bot_sprite);
|
use_texture(this.bot_sprite);
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
// javascript. Dealing with the level code is really nice in javascript
|
// javascript. Dealing with the level code is really nice in javascript
|
||||||
// but there's a bunch of weird stuff that really feels lower level.
|
// but there's a bunch of weird stuff that really feels lower level.
|
||||||
import { load_texture } from "./assets";
|
import { load_texture } from "./assets";
|
||||||
import { Texture, print, spr, use_texture } from "./graphics";
|
import { Texture, spr, use_texture } from "./graphics";
|
||||||
import { load_string } from "./io";
|
import { load_string } from "./io";
|
||||||
|
import { log } from "./log";
|
||||||
|
|
||||||
// TODO: Use io-ts? YIKES.
|
// TODO: Use io-ts? YIKES.
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ export interface EntityLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Level {
|
export interface Level {
|
||||||
|
iid: string;
|
||||||
world_x: number;
|
world_x: number;
|
||||||
world_y: number;
|
world_y: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
|
@ -52,11 +54,19 @@ export interface Level {
|
||||||
tile_layers: TileLayer[];
|
tile_layers: TileLayer[];
|
||||||
entities: Entity[];
|
entities: Entity[];
|
||||||
values: { [key: string]: number[] };
|
values: { [key: string]: number[] };
|
||||||
|
neighbors: {
|
||||||
|
n: string | null;
|
||||||
|
s: string | null;
|
||||||
|
e: string | null;
|
||||||
|
w: string | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface World {
|
export interface World {
|
||||||
levels: Level[];
|
levels: Level[];
|
||||||
|
level_map: Map<string, Level>;
|
||||||
tilesets: Map<number, TileSet>;
|
tilesets: Map<number, TileSet>;
|
||||||
|
current_level: Level;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LDTKTilesetDef {
|
interface LDTKTilesetDef {
|
||||||
|
|
@ -78,7 +88,15 @@ async function load_tileset(def: LDTKTilesetDef): Promise<TileSet> {
|
||||||
tags.set(et.enumValueId, et.tileIds);
|
tags.set(et.enumValueId, et.tileIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Loaded tileset", def.uid, "from", relPath, "as ID", texture.id());
|
log(
|
||||||
|
"map_load",
|
||||||
|
"Loaded tileset",
|
||||||
|
def.uid,
|
||||||
|
"from",
|
||||||
|
relPath,
|
||||||
|
"as ID",
|
||||||
|
texture.id()
|
||||||
|
);
|
||||||
return { id: def.uid, texture, tags };
|
return { id: def.uid, texture, tags };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,15 +197,18 @@ function is_intgrid_layer_instance(
|
||||||
}
|
}
|
||||||
|
|
||||||
type LDTKLevel = {
|
type LDTKLevel = {
|
||||||
|
iid: string;
|
||||||
worldX: number;
|
worldX: number;
|
||||||
worldY: number;
|
worldY: number;
|
||||||
pxWid: number;
|
pxWid: number;
|
||||||
pxHei: number;
|
pxHei: number;
|
||||||
layerInstances: LDTKLayerInstance[];
|
layerInstances: LDTKLayerInstance[];
|
||||||
|
__neighbours: { levelIid: string; dir: "n" | "s" | "e" | "w" }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function load_level(tile_sets: Map<number, TileSet>, def: LDTKLevel): Level {
|
function load_level(tile_sets: Map<number, TileSet>, def: LDTKLevel): Level {
|
||||||
const result: Level = {
|
const result: Level = {
|
||||||
|
iid: def.iid,
|
||||||
world_x: def.worldX,
|
world_x: def.worldX,
|
||||||
world_y: def.worldY,
|
world_y: def.worldY,
|
||||||
width: def.pxWid,
|
width: def.pxWid,
|
||||||
|
|
@ -197,6 +218,7 @@ function load_level(tile_sets: Map<number, TileSet>, def: LDTKLevel): Level {
|
||||||
tile_layers: [],
|
tile_layers: [],
|
||||||
entities: [],
|
entities: [],
|
||||||
values: {},
|
values: {},
|
||||||
|
neighbors: { n: null, s: null, e: null, w: null },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const li of def.layerInstances) {
|
for (const li of def.layerInstances) {
|
||||||
|
|
@ -220,6 +242,18 @@ function load_level(tile_sets: Map<number, TileSet>, def: LDTKLevel): Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
result.tile_layers = result.tile_layers.reverse();
|
result.tile_layers = result.tile_layers.reverse();
|
||||||
|
const neighbors = result.neighbors;
|
||||||
|
for (const n of def.__neighbours) {
|
||||||
|
if (n.dir == "n") {
|
||||||
|
neighbors.n = n.levelIid;
|
||||||
|
} else if (n.dir == "s") {
|
||||||
|
neighbors.s = n.levelIid;
|
||||||
|
} else if (n.dir == "e") {
|
||||||
|
neighbors.e = n.levelIid;
|
||||||
|
} else if (n.dir == "w") {
|
||||||
|
neighbors.w = n.levelIid;
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +289,7 @@ function is_ldtk_map(map: unknown): map is LDTKMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load_world(path: string): Promise<World> {
|
export async function load_world(path: string): Promise<World> {
|
||||||
print("Loading map:", path);
|
log("map_load", "Loading map:", path);
|
||||||
const blob = await load_string(path);
|
const blob = await load_string(path);
|
||||||
const map = JSON.parse(blob);
|
const map = JSON.parse(blob);
|
||||||
|
|
||||||
|
|
@ -274,7 +308,17 @@ export async function load_world(path: string): Promise<World> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const levels = map.levels.map((l: any) => load_level(tilesets, l));
|
const levels = map.levels.map((l: any) => load_level(tilesets, l));
|
||||||
return { levels, tilesets };
|
const level_map = new Map();
|
||||||
|
for (const level of levels) {
|
||||||
|
level_map.set(level.iid, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
const current_level = levels.find((l) => l.world_x == 0 && l.world_y == 0);
|
||||||
|
if (!current_level) {
|
||||||
|
throw new Error("UNABLE TO FIND LEVEL AT 0,0: CANNOT START");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { levels, level_map, tilesets, current_level };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function has_collision(level: Level, cx: number, cy: number): boolean {
|
export function has_collision(level: Level, cx: number, cy: number): boolean {
|
||||||
|
|
|
||||||
30
game/log.ts
30
game/log.ts
|
|
@ -1,17 +1,35 @@
|
||||||
import { color, print } from "./graphics";
|
import { color, print } from "./graphics";
|
||||||
|
|
||||||
const lines: string[] = [];
|
interface LogEntry {
|
||||||
|
frame_age: number;
|
||||||
|
line: string;
|
||||||
|
}
|
||||||
|
|
||||||
export function log(...args: unknown[]) {
|
const lines: Map<string, LogEntry> = new Map();
|
||||||
// const line = args.join(" ");
|
|
||||||
lines.push(args.join(" "));
|
export function log(key: string, ...args: unknown[]) {
|
||||||
|
const entry = {
|
||||||
|
frame_age: 60,
|
||||||
|
line: key + " " + args.join(" "),
|
||||||
|
};
|
||||||
|
lines.set(key, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function draw_log() {
|
export function draw_log() {
|
||||||
color(1, 1, 1, 1);
|
color(1, 1, 1, 1);
|
||||||
let line_y = 3;
|
let line_y = 3;
|
||||||
for (const line of lines) {
|
|
||||||
print(3, line_y, line);
|
const keys = [...lines.keys()].sort();
|
||||||
|
for (const key of keys) {
|
||||||
|
const entry = lines.get(key);
|
||||||
|
if (entry) {
|
||||||
|
// Life is too short for me to convince typescript.
|
||||||
|
print(3, line_y, entry.line);
|
||||||
line_y += 8;
|
line_y += 8;
|
||||||
|
entry.frame_age -= 1;
|
||||||
|
if (entry.frame_age <= 0) {
|
||||||
|
lines.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
game/main.ts
26
game/main.ts
|
|
@ -1,7 +1,7 @@
|
||||||
import { cls, get_dimensions, scale } from "./graphics";
|
import { cls, get_dimensions, scale } from "./graphics";
|
||||||
import { since_start } from "./time";
|
import { since_start } from "./time";
|
||||||
import { new_v2 } from "./vector";
|
import { new_v2 } from "./vector";
|
||||||
import { load_world, World, Level, draw_level } from "./level";
|
import { load_world, World, draw_level } from "./level";
|
||||||
import {
|
import {
|
||||||
Actor,
|
Actor,
|
||||||
ActorProps,
|
ActorProps,
|
||||||
|
|
@ -16,7 +16,6 @@ import { log, draw_log } from "./log";
|
||||||
let clock = 0;
|
let clock = 0;
|
||||||
|
|
||||||
let world: World | undefined = undefined;
|
let world: World | undefined = undefined;
|
||||||
let level: Level | undefined = undefined;
|
|
||||||
let actors: Actor[] = [];
|
let actors: Actor[] = [];
|
||||||
|
|
||||||
// Note zelda overworld is 16x8 screens
|
// Note zelda overworld is 16x8 screens
|
||||||
|
|
@ -26,14 +25,11 @@ let actors: Actor[] = [];
|
||||||
function start_load_assets() {
|
function start_load_assets() {
|
||||||
// Start this load, but then...
|
// Start this load, but then...
|
||||||
load_world("./overworld.ldtk").then((w) => {
|
load_world("./overworld.ldtk").then((w) => {
|
||||||
log("World loaded at", since_start());
|
log("world loaded at", since_start());
|
||||||
world = w;
|
world = w;
|
||||||
|
|
||||||
// Assume we start at 0,0
|
// Assume we start at 0,0
|
||||||
level = world.levels.find((l) => l.world_x == 0 && l.world_y == 0);
|
const level = world.current_level;
|
||||||
if (!level) {
|
|
||||||
throw new Error("UNABLE TO FIND LEVEL AT 0,0: CANNOT START");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: SPAWN ACTORS BASED ON LEVEL.
|
// TODO: SPAWN ACTORS BASED ON LEVEL.
|
||||||
actors.length = 0;
|
actors.length = 0;
|
||||||
|
|
@ -63,7 +59,7 @@ interface Snapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function suspend(): Snapshot {
|
export function suspend(): Snapshot {
|
||||||
log("Suspend! ", actors.length, "actors");
|
log("suspend_status", "Suspend ", actors.length, "actors");
|
||||||
return {
|
return {
|
||||||
clock,
|
clock,
|
||||||
actors: actors.map((a) => {
|
actors: actors.map((a) => {
|
||||||
|
|
@ -76,23 +72,19 @@ export function resume(snapshot: Snapshot | undefined) {
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
clock = snapshot.clock || 0;
|
clock = snapshot.clock || 0;
|
||||||
actors = snapshot.actors.map((s) => spawn_actor(s.type, s.props));
|
actors = snapshot.actors.map((s) => spawn_actor(s.type, s.props));
|
||||||
log("Resume! ", actors.length, "actors");
|
log("resume_status", "Resume ", actors.length, "actors");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update() {
|
export function update() {
|
||||||
if (!level) {
|
if (!world) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clock = (clock + 1) % 20160;
|
clock = (clock + 1) % 20160;
|
||||||
|
|
||||||
for (const actor of actors) {
|
for (const actor of actors) {
|
||||||
actor.update();
|
actor.update(world);
|
||||||
}
|
|
||||||
|
|
||||||
for (const actor of actors) {
|
|
||||||
actor.update_physics(level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Bonks
|
// TODO: Bonks
|
||||||
|
|
@ -109,8 +101,8 @@ export function draw() {
|
||||||
);
|
);
|
||||||
scale(s);
|
scale(s);
|
||||||
|
|
||||||
if (level != undefined) {
|
if (world != undefined) {
|
||||||
draw_level(level, 0, 0);
|
draw_level(world.current_level, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const actor of actors) {
|
for (const actor of actors) {
|
||||||
|
|
|
||||||
Binary file not shown.
1226
game/overworld.ldtk
1226
game/overworld.ldtk
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
|
@ -1,7 +1,7 @@
|
||||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use oden_js::{
|
use oden_js::{
|
||||||
module::loader::{ModuleLoader, ModuleSource},
|
module::loader::{ModuleLoader, ModuleSource},
|
||||||
Context, ContextRef, Result, Runtime, Value,
|
Context, ContextRef, RejectedPromiseTracker, Result, Runtime, Value,
|
||||||
};
|
};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
|
@ -53,6 +53,39 @@ impl ModuleLoader for Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RejectedPromiseHandler {
|
||||||
|
error_lines: Sender<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RejectedPromiseTracker for RejectedPromiseHandler {
|
||||||
|
fn on_rejected_promise(
|
||||||
|
&self,
|
||||||
|
ctx: &ContextRef,
|
||||||
|
_promise: &oden_js::ValueRef,
|
||||||
|
reason: &oden_js::ValueRef,
|
||||||
|
is_handled: bool,
|
||||||
|
) {
|
||||||
|
if !is_handled {
|
||||||
|
let reason_str = reason.to_string(ctx).expect(
|
||||||
|
"Unhandled rejected promise: reason unknown: unable to convert reason to string",
|
||||||
|
);
|
||||||
|
|
||||||
|
let reason_str = format!("Unhandled rejected promise:\n{reason_str}");
|
||||||
|
for line in reason_str.lines().map(|l| l.to_owned()) {
|
||||||
|
let _ = self.error_lines.send(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stack = reason
|
||||||
|
.get_property(ctx, "stack")
|
||||||
|
.and_then(|stack| stack.to_string(ctx))
|
||||||
|
.unwrap_or_else(|_| String::new());
|
||||||
|
for line in stack.lines().map(|l| l.to_owned()) {
|
||||||
|
let _ = self.error_lines.send(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ScriptContext {
|
pub struct ScriptContext {
|
||||||
context: Context,
|
context: Context,
|
||||||
update: Value,
|
update: Value,
|
||||||
|
|
@ -68,12 +101,18 @@ pub struct ScriptContext {
|
||||||
input: input::InputAPI,
|
input: input::InputAPI,
|
||||||
|
|
||||||
error_lines: Vec<String>,
|
error_lines: Vec<String>,
|
||||||
|
promise_error_reciever: Receiver<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptContext {
|
impl ScriptContext {
|
||||||
pub fn new(suspend_state: Option<Vec<u8>>, reload_trigger: Sender<()>) -> Result<Self> {
|
pub fn new(suspend_state: Option<Vec<u8>>, reload_trigger: Sender<()>) -> Result<Self> {
|
||||||
|
let (promise_error_send, promise_error_recv) = channel();
|
||||||
|
|
||||||
let mut runtime = Runtime::new();
|
let mut runtime = Runtime::new();
|
||||||
runtime.set_module_loader(Loader::new(reload_trigger));
|
runtime.set_module_loader(Loader::new(reload_trigger));
|
||||||
|
runtime.set_rejected_promise_tracker(RejectedPromiseHandler {
|
||||||
|
error_lines: promise_error_send,
|
||||||
|
});
|
||||||
|
|
||||||
let mut context = Context::new(runtime);
|
let mut context = Context::new(runtime);
|
||||||
context.add_intrinsic_bigfloat();
|
context.add_intrinsic_bigfloat();
|
||||||
|
|
@ -119,6 +158,7 @@ impl ScriptContext {
|
||||||
input,
|
input,
|
||||||
|
|
||||||
error_lines: Vec::new(),
|
error_lines: Vec::new(),
|
||||||
|
promise_error_reciever: promise_error_recv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +212,11 @@ impl ScriptContext {
|
||||||
{
|
{
|
||||||
let _span = span!("process jobs");
|
let _span = span!("process jobs");
|
||||||
self.handle_result(self.context.process_all_jobs());
|
self.handle_result(self.context.process_all_jobs());
|
||||||
|
|
||||||
|
// Check to make sure we don't have any rejected promises.
|
||||||
|
while let Ok(line) = self.promise_error_reciever.try_recv() {
|
||||||
|
self.error_lines.push(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now run the update function.
|
// Now run the update function.
|
||||||
|
|
@ -197,9 +242,10 @@ impl ScriptContext {
|
||||||
GraphicsCommand::Clear(ClearCommand {
|
GraphicsCommand::Clear(ClearCommand {
|
||||||
color: [0.0, 0.0, 1.0, 1.0],
|
color: [0.0, 0.0, 1.0, 1.0],
|
||||||
}),
|
}),
|
||||||
|
GraphicsCommand::Scale([4.0, 4.0]),
|
||||||
GraphicsCommand::Print(PrintCommand {
|
GraphicsCommand::Print(PrintCommand {
|
||||||
text: "FATAL SCRIPT ERROR".to_owned(),
|
text: "FATAL SCRIPT ERROR".to_owned(),
|
||||||
pos: [6.0, 6.0],
|
pos: [8.0, 8.0],
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue