mirror of
https://github.com/BigJk/end_of_eden.git
synced 2026-02-06 10:48:09 +00:00
feat: WASM build
This commit is contained in:
parent
b944ca2733
commit
35bbe9088f
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ node_modules/
|
||||
cpu*
|
||||
heap*
|
||||
profile*
|
||||
file_index.json
|
||||
125
cmd/game_wasm/index.html
Normal file
125
cmd/game_wasm/index.html
Normal file
@ -0,0 +1,125 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<script src="./wasm_exec.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
/* this is important */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
/* see : https://github.com/xtermjs/xterm.js/issues/3564#issuecomment-1004417440 */
|
||||
width: initial !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="terminal-container" style="height: 100%; width: 100%;">
|
||||
<div id="terminal" style="height: 100%"></div>
|
||||
</div>
|
||||
<script>
|
||||
globalThis.settings = {
|
||||
get(key, emptyValue) {
|
||||
console.log("get", key, emptyValue)
|
||||
try {
|
||||
return JSON.parse(window.localStorage.getItem(key))
|
||||
} catch (e) {
|
||||
return emptyValue !== undefined ? emptyValue : null
|
||||
}
|
||||
},
|
||||
getString(key) {
|
||||
return window.settings.get(key, "")
|
||||
},
|
||||
getInt(key) {
|
||||
return window.settings.get(key, 0)
|
||||
},
|
||||
getBool(key) {
|
||||
return window.settings.get(key, false)
|
||||
},
|
||||
getFloat(key) {
|
||||
return window.settings.get(key, 0.0)
|
||||
},
|
||||
getStrings(key) {
|
||||
return window.settings.get(key, [])
|
||||
},
|
||||
set(key, value) {
|
||||
window.localStorage.setItem(key, JSON.stringify(value))
|
||||
},
|
||||
setDefault(key, value) {
|
||||
if (window.localStorage.getItem(key) !== null) {
|
||||
return
|
||||
}
|
||||
window.localStorage.setItem(key, value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function initTerminal() {
|
||||
const term = new Terminal({
|
||||
fontSize: 18,
|
||||
fontFamily: 'Inconsolata',
|
||||
theme: {
|
||||
background: '#1a1a1a'
|
||||
}
|
||||
});
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(document.getElementById('terminal'));
|
||||
|
||||
// Register terminal resize
|
||||
fitAddon.fit();
|
||||
window.addEventListener('resize', () => (fitAddon.fit()));
|
||||
|
||||
// Initial resize
|
||||
bubbletea_resize(term.cols, term.rows)
|
||||
|
||||
// Read from bubbletea and write to xterm
|
||||
setInterval(() => {
|
||||
const read = bubbletea_read();
|
||||
if (read && read.length > 0) {
|
||||
term.write(read);
|
||||
}
|
||||
}, 1000 / 30);
|
||||
|
||||
// Resize on terminal resize
|
||||
term.onResize((size) => (bubbletea_resize(term.cols, term.rows)));
|
||||
|
||||
// Write xterm output to bubbletea
|
||||
term.onData((data) => (bubbletea_write(data)));
|
||||
}
|
||||
|
||||
function init() {
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch("./eoe.wasm"), go.importObject).then((result) => {
|
||||
// Run wasm
|
||||
go.run(result.instance).then(() => {
|
||||
console.log("wasm finished");
|
||||
});
|
||||
|
||||
// Init terminal. This should be done after bubbletea is initialized. For now, I use a timeout.
|
||||
setTimeout(() => {
|
||||
document.fonts.load('16px "Inconsolata"').then(() => {initTerminal();});
|
||||
}, 1000);
|
||||
})
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
160
cmd/game_wasm/main.go
Normal file
160
cmd/game_wasm/main.go
Normal file
@ -0,0 +1,160 @@
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/system/gen"
|
||||
"github.com/BigJk/end_of_eden/system/gen/faces"
|
||||
"github.com/BigJk/end_of_eden/system/localization"
|
||||
"github.com/BigJk/end_of_eden/system/settings"
|
||||
"github.com/BigJk/end_of_eden/system/settings/browser"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/mainmenu"
|
||||
uiset "github.com/BigJk/end_of_eden/ui/menus/settings"
|
||||
"github.com/BigJk/end_of_eden/ui/menus/warning"
|
||||
"github.com/BigJk/end_of_eden/ui/root"
|
||||
"github.com/BigJk/end_of_eden/ui/style"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
"github.com/muesli/termenv"
|
||||
"log"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MinReadBuffer struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// For some reason bubbletea doesn't like a Reader that will return 0 bytes instead of blocking,
|
||||
// so we use this hacky workaround for now. As javascript is single threaded this should be fine
|
||||
// with regard to concurrency.
|
||||
func (b *MinReadBuffer) Read(p []byte) (n int, err error) {
|
||||
for b.buf.Len() == 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return b.buf.Read(p)
|
||||
}
|
||||
|
||||
func (b *MinReadBuffer) Write(p []byte) (n int, err error) {
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
|
||||
// Creates the bubbletea program and registers the necessary functions in javascript
|
||||
func createTeaForJS(model tea.Model, option ...tea.ProgramOption) *tea.Program {
|
||||
// Create buffers for input and output
|
||||
fromJs := &MinReadBuffer{buf: bytes.NewBuffer(nil)}
|
||||
fromGo := bytes.NewBuffer(nil)
|
||||
|
||||
prog := tea.NewProgram(model, append([]tea.ProgramOption{tea.WithInput(fromJs), tea.WithOutput(fromGo)}, option...)...)
|
||||
|
||||
// Register write function in WASM
|
||||
js.Global().Set("bubbletea_write", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
fromJs.Write([]byte(args[0].String()))
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Register read function in WASM
|
||||
js.Global().Set("bubbletea_read", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
b := make([]byte, fromGo.Len())
|
||||
_, _ = fromGo.Read(b)
|
||||
fromGo.Reset()
|
||||
return string(b)
|
||||
}))
|
||||
|
||||
// Register resize function in WASM
|
||||
js.Global().Set("bubbletea_resize", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
width := args[0].Int()
|
||||
height := args[1].Int()
|
||||
prog.Send(tea.WindowSizeMsg{Width: width, Height: height})
|
||||
return nil
|
||||
}))
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
var prog *tea.Program
|
||||
var loadStyle = lipgloss.NewStyle().Bold(true).Italic(true).Foreground(style.BaseGray)
|
||||
|
||||
func main() {
|
||||
lipgloss.SetColorProfile(termenv.TrueColor)
|
||||
|
||||
fmt.Println(lipgloss.NewStyle().Bold(true).Foreground(style.BaseRed).Render("End Of Eden"))
|
||||
|
||||
// Init settings
|
||||
fmt.Println(loadStyle.Render("Initializing Settings. Please wait..."))
|
||||
{
|
||||
set := browser.Browser{}
|
||||
set.SetDefault("audio", true)
|
||||
set.SetDefault("volume", 1)
|
||||
set.SetDefault("language", "en")
|
||||
settings.SetSettings(set)
|
||||
|
||||
if err := settings.LoadSettings(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
fmt.Println(loadStyle.Render("Done!"))
|
||||
|
||||
// Init generators
|
||||
fmt.Println(loadStyle.Render("Initializing Proc-Gen. Please wait..."))
|
||||
{
|
||||
// Init face generator
|
||||
if err := faces.InitGlobal("./assets/gen/faces"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Init other gens
|
||||
gen.InitGen()
|
||||
}
|
||||
fmt.Println(loadStyle.Render("Done!"))
|
||||
|
||||
// Init Localization
|
||||
fmt.Println(loadStyle.Render("Initializing Localization. Please wait..."))
|
||||
{
|
||||
if err := localization.Global.AddFolder("./assets/locals"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
localization.SetCurrent(settings.GetString("language"))
|
||||
}
|
||||
fmt.Println(loadStyle.Render("Done!"))
|
||||
|
||||
log.Println("=================================")
|
||||
log.Println("= Started")
|
||||
log.Println("=================================")
|
||||
|
||||
// Set window title
|
||||
fmt.Println("\033]2;End of Eden\007")
|
||||
|
||||
uiSettings := []uiset.Value{
|
||||
{Key: "audio", Name: "Audio", Description: "Enable or disable audio", Type: uiset.Bool, Val: settings.GetBool("audio"), Min: nil, Max: nil},
|
||||
{Key: "volume", Name: "Volume", Description: "Change the volume", Type: uiset.Float, Val: settings.GetFloat("volume"), Min: 0.0, Max: 2.0},
|
||||
{Key: "language", Name: "Language", Description: fmt.Sprintf("Change the language (supported: %s)", strings.Join(localization.Global.GetLocales(), ", ")), Type: uiset.String, Val: settings.GetString("language")},
|
||||
}
|
||||
|
||||
// Setup game
|
||||
var baseModel tea.Model
|
||||
zones := zone.New()
|
||||
baseModel = root.New(zones, mainmenu.NewModel(zones, settings.GetGlobal(), uiSettings, func(values []uiset.Value) error {
|
||||
for i := range values {
|
||||
settings.Set(values[i].Key, values[i].Val)
|
||||
}
|
||||
localization.SetCurrent(settings.GetString("language"))
|
||||
return settings.SaveSettings()
|
||||
}))
|
||||
|
||||
baseModel = baseModel.(root.Model).PushModel(warning.New(nil, style.RedText.Render("Warning!")+"\n\nThe Browser version is still very experimental. Loading times can be long. Mouse support is clunky. For the best experience, please use the Desktop version!\n\n"+style.GrayTextDarker.Render("Press ESC to continue")))
|
||||
|
||||
// Run game
|
||||
prog = createTeaForJS(baseModel, tea.WithAltScreen(), tea.WithMouseAllMotion(), tea.WithANSICompressor())
|
||||
if _, err := prog.Run(); err != nil {
|
||||
fmt.Printf("Alas, there's been an error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
554
cmd/game_wasm/wasm_exec.js
Normal file
554
cmd/game_wasm/wasm_exec.js
Normal file
@ -0,0 +1,554 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substring(0, nl));
|
||||
outputBuf = outputBuf.substring(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
go: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
@ -7,6 +7,7 @@ import (
|
||||
teadapter "github.com/BigJk/crt/bubbletea"
|
||||
"github.com/BigJk/crt/shader"
|
||||
"github.com/BigJk/end_of_eden/cmd/testargs"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/audio"
|
||||
"github.com/BigJk/end_of_eden/system/gen"
|
||||
"github.com/BigJk/end_of_eden/system/gen/faces"
|
||||
@ -183,7 +184,7 @@ func main() {
|
||||
|
||||
// Setup grain shader
|
||||
if settings.GetBool("grain") {
|
||||
res, _ := os.ReadFile("./assets/shader/grain.go")
|
||||
res, _ := fs.ReadFile("./assets/shader/grain.go")
|
||||
grain, err := ebiten.NewShader(res)
|
||||
|
||||
if err != nil {
|
||||
|
||||
13
game/lua.go
13
game/lua.go
@ -1,12 +1,12 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/internal/lua/ludoc"
|
||||
luhelp2 "github.com/BigJk/end_of_eden/internal/lua/luhelp"
|
||||
"github.com/BigJk/end_of_eden/system/audio"
|
||||
"github.com/BigJk/end_of_eden/system/gen/faces"
|
||||
"github.com/BigJk/end_of_eden/system/localization"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -24,14 +24,19 @@ func SessionAdapter(session *Session) (*lua.LState, *ludoc.Docs) {
|
||||
|
||||
mapper := luhelp2.NewMapper(l)
|
||||
|
||||
_ = filepath.Walk("./assets/scripts/libs", func(path string, info fs.FileInfo, _ error) error {
|
||||
if info != nil && info.IsDir() || !strings.HasSuffix(path, ".lua") {
|
||||
_ = fs.Walk("./assets/scripts/libs", func(path string, isDir bool) error {
|
||||
if isDir || !strings.HasSuffix(path, ".lua") {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := strings.Split(filepath.Base(path), ".")[0]
|
||||
|
||||
mod, err := l.LoadFile(path)
|
||||
luaBytes, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod, err := l.LoadString(string(luaBytes))
|
||||
if err != nil {
|
||||
session.log.Println("Can't LoadFile module:", path)
|
||||
return nil
|
||||
|
||||
@ -2,7 +2,7 @@ package game
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -15,7 +15,7 @@ type Mod struct {
|
||||
}
|
||||
|
||||
func ModDescription(folder string) (Mod, error) {
|
||||
data, err := os.ReadFile(filepath.Join(folder, "/meta.json"))
|
||||
data, err := fs.ReadFile(filepath.Join(folder, "/meta.json"))
|
||||
if err != nil {
|
||||
return Mod{}, err
|
||||
}
|
||||
|
||||
@ -2,13 +2,12 @@ package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/internal/lua/ludoc"
|
||||
luhelp2 "github.com/BigJk/end_of_eden/internal/lua/luhelp"
|
||||
"github.com/samber/lo"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"io/fs"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -67,18 +66,20 @@ func NewResourcesManager(state *lua.LState, docs *ludoc.Docs, logger *log.Logger
|
||||
man.defineDocs(docs)
|
||||
|
||||
// Load all local scripts
|
||||
_ = filepath.Walk("./assets/scripts", func(path string, info fs.FileInfo, err error) error {
|
||||
_ = fs.Walk("./assets/scripts", func(path string, isDir bool) error {
|
||||
// Don't load libs
|
||||
if strings.Contains(path, "scripts/libs") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !isDir && strings.HasSuffix(path, ".lua") {
|
||||
luaBytes, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
// TODO: error handling
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".lua") {
|
||||
if err := man.luaState.DoFile(path); err != nil {
|
||||
if err := man.luaState.DoString(string(luaBytes)); err != nil {
|
||||
// TODO: error handling
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/internal/lua/ludoc"
|
||||
"github.com/BigJk/end_of_eden/system/gen"
|
||||
"github.com/BigJk/end_of_eden/system/gen/faces"
|
||||
@ -15,10 +16,8 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"golang.org/x/exp/slices"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"oss.terrastruct.com/d2/d2graph"
|
||||
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
|
||||
"oss.terrastruct.com/d2/d2lib"
|
||||
@ -330,18 +329,20 @@ func (s *Session) loadMods(mods []string) {
|
||||
log.Println("Loading mod:", mod.Name)
|
||||
}
|
||||
|
||||
_ = filepath.Walk(filepath.Join("./mods", mods[i]), func(path string, info fs.FileInfo, err error) error {
|
||||
_ = fs.Walk(filepath.Join("./mods", mods[i]), func(path string, isDir bool) error {
|
||||
// If we find a locals folder we add it to the localization
|
||||
if info.IsDir() && info.Name() == "locals" {
|
||||
if isDir && filepath.Base(path) == "locals" {
|
||||
_ = localization.Global.AddFolder(path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if isDir && strings.HasSuffix(path, ".lua") {
|
||||
luaBytes, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
// TODO: error handling
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".lua") {
|
||||
if err := s.luaState.DoFile(path); err != nil {
|
||||
if err := s.luaState.DoString(string(luaBytes)); err != nil {
|
||||
s.logLuaError("ModLoader", "", err)
|
||||
}
|
||||
}
|
||||
@ -466,7 +467,7 @@ func (s *Session) SetupFight() {
|
||||
if err != nil {
|
||||
s.log.Println("Error saving file:", save)
|
||||
} else {
|
||||
if err := os.WriteFile("./session.save", save, 0666); err != nil {
|
||||
if err := fs.WriteFile("./session.save", save); err != nil {
|
||||
s.log.Println("Error saving file:", save)
|
||||
}
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@ -2,6 +2,12 @@ module github.com/BigJk/end_of_eden
|
||||
|
||||
go 1.20
|
||||
|
||||
replace github.com/containerd/console => github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5
|
||||
|
||||
replace github.com/atotto/clipboard => github.com/BigJk/clipboard v0.0.0-20230514080810-a7e7bd3670a5
|
||||
|
||||
replace github.com/charmbracelet/bubbletea => github.com/BigJk/bubbletea v0.0.0-20231222122351-25c0e0b15c34
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161
|
||||
github.com/BigJk/crt v0.0.13
|
||||
@ -36,6 +42,7 @@ require (
|
||||
github.com/yuin/gopher-lua v1.1.0
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
|
||||
golang.org/x/sys v0.13.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
oss.terrastruct.com/d2 v0.4.1
|
||||
)
|
||||
|
||||
@ -109,6 +116,5 @@ require (
|
||||
gonum.org/v1/plot v0.12.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
oss.terrastruct.com/util-go v0.0.0-20230320053557-dcb5aac7d972 // indirect
|
||||
)
|
||||
|
||||
19
go.sum
19
go.sum
@ -47,6 +47,10 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BigJk/bubbletea v0.0.0-20231222122351-25c0e0b15c34 h1:I3ZTYobM/4vLCKnH6KgN1HX1EwnsaD4lbnOoc4YNhFo=
|
||||
github.com/BigJk/bubbletea v0.0.0-20231222122351-25c0e0b15c34/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/BigJk/clipboard v0.0.0-20230514080810-a7e7bd3670a5 h1:bJrh/4V+8y6u05623Zja5vJD7RLM0Km5CdUrMJnPTDY=
|
||||
github.com/BigJk/clipboard v0.0.0-20230514080810-a7e7bd3670a5/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/BigJk/crt v0.0.13 h1:qZRw2tuJCuKsk3lC93Vi+Guib//Rs1m/rcGUR/OCHFE=
|
||||
github.com/BigJk/crt v0.0.13/go.mod h1:0jGrs6QH6A7zDJz91HfLCXG8yKR12sqOUPq8LrOoDbg=
|
||||
github.com/BigJk/imeji v0.0.2 h1:h4cKLWkVzs+IVs/27DFTYGsGuA1OqQxrYDNtvlojypU=
|
||||
@ -70,8 +74,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
|
||||
github.com/anthonynsimon/bild v0.13.0 h1:mN3tMaNds1wBWi1BrJq0ipDBhpkooYfu7ZFSMhXt1C8=
|
||||
github.com/anthonynsimon/bild v0.13.0/go.mod h1:tpzzp0aYkAsMi1zmfhimaDyX1xjn2OUc1AJZK/TF0AE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
@ -83,9 +85,6 @@ github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQ
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
|
||||
github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
|
||||
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
|
||||
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
@ -108,9 +107,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 h1:Ig+OPkE3XQrrl+SKsOqAjlkrBN/zrr+Qpw7rCuDjRCE=
|
||||
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@ -298,6 +296,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
@ -582,7 +581,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -603,12 +601,14 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -619,6 +619,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
|
||||
16
internal/fs/fileinfo.go
Normal file
16
internal/fs/fileinfo.go
Normal file
@ -0,0 +1,16 @@
|
||||
package fs
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
type FileInfo struct {
|
||||
Path string `json:"path"`
|
||||
IsFile bool `json:"IsFile"`
|
||||
}
|
||||
|
||||
func (fi FileInfo) Name() string {
|
||||
return filepath.Base(fi.Path)
|
||||
}
|
||||
|
||||
func (fi FileInfo) IsDir() bool {
|
||||
return !fi.IsFile
|
||||
}
|
||||
45
internal/fs/fs.go
Normal file
45
internal/fs/fs.go
Normal file
@ -0,0 +1,45 @@
|
||||
//go:build !js
|
||||
// +build !js
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"github.com/samber/lo"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ReadDir(path string) ([]FileInfo, error) {
|
||||
dir, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lo.Map(dir, func(f os.DirEntry, i int) FileInfo {
|
||||
return FileInfo{
|
||||
Path: filepath.Join(path, f.Name()),
|
||||
IsFile: !f.IsDir(),
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func OpenFile(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
func WriteFile(path string, data []byte) error {
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
func Walk(root string, walkFn func(path string, isDir bool) error) error {
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return walkFn(path, info.IsDir())
|
||||
})
|
||||
}
|
||||
115
internal/fs/fs_js.go
Normal file
115
internal/fs/fs_js.go
Normal file
@ -0,0 +1,115 @@
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
type noOpWriteCloser struct{}
|
||||
|
||||
func (noOpWriteCloser) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (noOpWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fileIndex = make(map[string]FileInfo)
|
||||
|
||||
func init() {
|
||||
data, err := ReadFile("/assets/file_index.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var fis []FileInfo
|
||||
if err := json.Unmarshal(data, &fis); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, fi := range fis {
|
||||
fi.Path = filepath.Clean(fi.Path)
|
||||
fileIndex[fi.Path] = fi
|
||||
}
|
||||
|
||||
js.Global().Set("fsDump", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
for _, fi := range fis {
|
||||
fmt.Println(fi.Path)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func ReadDir(path string) ([]FileInfo, error) {
|
||||
cleanPath := filepath.Clean(path)
|
||||
var fis []FileInfo
|
||||
for indexPath := range fileIndex {
|
||||
if strings.HasPrefix(indexPath, cleanPath) {
|
||||
fis = append(fis, fileIndex[path])
|
||||
}
|
||||
}
|
||||
return fis, nil
|
||||
}
|
||||
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
// Check for temp file
|
||||
jsRes := js.Global().Call("fsRead", path)
|
||||
if !jsRes.IsNull() && !jsRes.IsUndefined() {
|
||||
return base64.StdEncoding.DecodeString(jsRes.String())
|
||||
}
|
||||
|
||||
// Check for asset
|
||||
res, err := http.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("could not load file %s: %s", path, res.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func OpenFile(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
|
||||
// TODO: Implement
|
||||
return noOpWriteCloser{}, nil
|
||||
}
|
||||
|
||||
func WriteFile(path string, data []byte) error {
|
||||
// TODO: error handling
|
||||
_ = js.Global().Call("fsWrite", path, base64.StdEncoding.EncodeToString(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func Walk(root string, walkFn func(path string, isDir bool) error) error {
|
||||
keys := lo.Keys(fileIndex)
|
||||
sort.Strings(keys)
|
||||
|
||||
cleanPath := filepath.Clean(root)
|
||||
for _, path := range keys {
|
||||
if !strings.HasPrefix(path, cleanPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := walkFn(path, fileIndex[path].IsDir()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
internal/misc/build_index.sh
Executable file
43
internal/misc/build_index.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if the argument is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <folder_path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
folder_path="$1"
|
||||
|
||||
# Initialize an empty array
|
||||
file_index=()
|
||||
|
||||
# Function to walk through the directory recursively
|
||||
walk() {
|
||||
local directory="$1"
|
||||
for file in "$directory"/*; do
|
||||
if [ -d "$file" ]; then
|
||||
# If it's a directory, add to the array and recursively call walk function
|
||||
file_index+=(" {\"path\": \"$file\", \"isFile\": false}")
|
||||
walk "$file"
|
||||
elif [ -f "$file" ]; then
|
||||
# If it's a file, add to the array
|
||||
file_index+=(" {\"path\": \"$file\", \"isFile\": true}")
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Start walking through the directory
|
||||
walk "$folder_path"
|
||||
|
||||
# Create JSON file with the content of the array in the same folder
|
||||
{
|
||||
printf '[\n'
|
||||
for ((i = 0; i < ${#file_index[@]}; i++)); do
|
||||
printf '%s' "${file_index[i]}"
|
||||
if [ $i -ne $(( ${#file_index[@]} - 1 )) ]; then
|
||||
printf ','
|
||||
fi
|
||||
printf '\n'
|
||||
done
|
||||
printf ']\n'
|
||||
} > "$folder_path/file_index.json"
|
||||
@ -1,12 +1,13 @@
|
||||
//go:build !no_audio
|
||||
// +build !no_audio
|
||||
//go:build !no_audio && !js
|
||||
// +build !no_audio,!js
|
||||
|
||||
// Package audio handles all audio playback. It uses the beep library to play audio files.
|
||||
package audio
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/settings"
|
||||
"io/fs"
|
||||
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -41,11 +42,7 @@ func InitAudio() {
|
||||
go func() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
_ = filepath.Walk("./assets/audio", func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = fs.Walk("./assets/audio", func(path string, isDir bool) error {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@ -53,7 +50,7 @@ func InitAudio() {
|
||||
var streamer beep.StreamSeekCloser
|
||||
var format beep.Format
|
||||
|
||||
if !info.IsDir() {
|
||||
if !isDir {
|
||||
if strings.HasSuffix(path, ".mp3") {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
|
||||
40
system/audio/audio_js.go
Normal file
40
system/audio/audio_js.go
Normal file
@ -0,0 +1,40 @@
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
// Package audio handles all audio playback. It uses the beep library to play audio files.
|
||||
package audio
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/settings"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// InitAudio initializes the audio system. Loads all audio files from the assets/audio folder.
|
||||
func InitAudio() {}
|
||||
|
||||
// Play plays a sound effect. If the sound effect is not loaded, nothing will happen.
|
||||
func Play(key string, volumeModifier ...float64) {
|
||||
fs.Walk("./assets/audio", func(path string, isDir bool) error {
|
||||
if !isDir && strings.HasPrefix(filepath.Base(path), key) {
|
||||
js.Global().Call("playSound", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PlayMusic plays a music track. If the music track is not loaded, nothing will happen.
|
||||
func PlayMusic(key string) {
|
||||
if settings.GetFloat("volume") == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fs.Walk("./assets/audio", func(path string, isDir bool) error {
|
||||
if !isDir && strings.HasPrefix(filepath.Base(path), key) {
|
||||
js.Global().Call("loopMusic", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
//go:build !no_audio
|
||||
// +build !no_audio
|
||||
//go:build !no_audio && !js
|
||||
// +build !no_audio,!js
|
||||
|
||||
package audio
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ package faces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/samber/lo"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -63,7 +63,7 @@ func New(dataFolder string) (*FaceGenerator, error) {
|
||||
data: map[int][][]string{},
|
||||
}
|
||||
for i := 0; i < 7; i++ {
|
||||
bytes, err := os.ReadFile(filepath.Join(dataFolder, fmt.Sprintf("/Face%d.txt", i)))
|
||||
bytes, err := fs.ReadFile(filepath.Join(dataFolder, fmt.Sprintf("/Face%d.txt", i)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -13,14 +13,14 @@ var data = map[string][]string{}
|
||||
// The data is stored in a map with the type as key and a slice of strings, which are the lines
|
||||
// of the file, as value.
|
||||
func InitGen() {
|
||||
files, err := os.ReadDir("./assets/gen")
|
||||
files, err := fs.ReadDir("./assets/gen")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && strings.HasSuffix(file.Name(), ".txt") {
|
||||
bytes, err := os.ReadFile("./assets/gen/" + file.Name())
|
||||
bytes, err := fs.ReadFile("./assets/gen/" + file.Name())
|
||||
if err != nil {
|
||||
log.Println("Error reading file:", err.Error())
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/samber/lo"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -20,7 +20,7 @@ func init() {
|
||||
// search paths, an error is returned.
|
||||
func hashFile(path string) (string, error) {
|
||||
for i := range searchPaths {
|
||||
data, err := ioutil.ReadFile(filepath.Join(searchPaths[i], path))
|
||||
data, err := fs.ReadFile(filepath.Join(searchPaths[i], path))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -43,7 +43,7 @@ func hash(name string, options Options) (string, error) {
|
||||
// getCache returns the cached data for the given hash.
|
||||
func getCache(hash string) (interface{}, error) {
|
||||
path := filepath.Join("./cache", hash)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -75,5 +75,5 @@ func setCache(hash string, data interface{}) error {
|
||||
}
|
||||
|
||||
path := filepath.Join("./cache", hash)
|
||||
return ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644)
|
||||
return fs.WriteFile(path, []byte(strings.Join(lines, "\n")))
|
||||
}
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/imeji"
|
||||
"github.com/BigJk/imeji/charmaps"
|
||||
"github.com/charmbracelet/log"
|
||||
@ -11,8 +13,11 @@ import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -44,6 +49,11 @@ func buildOption(options ...Option) (Options, []imeji.Option) {
|
||||
data.tag += os.Getenv("EOE_IMG_PATTERN")
|
||||
}
|
||||
|
||||
if runtime.GOOS == "js" {
|
||||
imejiOptions = append(imejiOptions, imeji.WithTrueColor())
|
||||
data.tag += "truecolor"
|
||||
}
|
||||
|
||||
switch termenv.DefaultOutput().Profile {
|
||||
case termenv.TrueColor:
|
||||
imejiOptions = append(imejiOptions, imeji.WithTrueColor())
|
||||
@ -82,7 +92,17 @@ func Fetch(name string, options ...Option) (string, error) {
|
||||
}
|
||||
|
||||
for i := range searchPaths {
|
||||
res, err := imeji.FileString(filepath.Join(searchPaths[i], name), imejiOptions...)
|
||||
file, err := fs.ReadFile(filepath.Join(searchPaths[i], name))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
image, _, err := image.Decode(bytes.NewBuffer(file))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
res, err := imeji.ImageString(image, imejiOptions...)
|
||||
if err == nil {
|
||||
if err := setCache(hash, res); err != nil {
|
||||
log.Warn("could not cache image: %s", err)
|
||||
@ -113,13 +133,12 @@ func FetchAnimation(name string, options ...Option) ([]string, error) {
|
||||
|
||||
var frames []string
|
||||
for i := range searchPaths {
|
||||
f, err := os.Open(filepath.Join(searchPaths[i], name))
|
||||
fileBytes, err := fs.ReadFile(filepath.Join(searchPaths[i], name))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
g, err := gif.DecodeAll(f)
|
||||
g, err := gif.DecodeAll(bytes.NewBuffer(fileBytes))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ package localization
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
@ -44,8 +44,8 @@ func (l *Localization) Add(locale string, translations map[string]string) {
|
||||
// AddFolder adds all locales from the given folder. Will walk through all
|
||||
// sub-folders and add all .yaml and .yml files.
|
||||
func (l *Localization) AddFolder(folder string) error {
|
||||
return filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return fs.Walk(folder, func(path string, isDir bool) error {
|
||||
if isDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ func (l *Localization) AddFolder(folder string) error {
|
||||
func (l *Localization) AddFile(file string) error {
|
||||
var parsed map[string]map[string]any
|
||||
|
||||
data, err := os.ReadFile(file)
|
||||
data, err := fs.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
74
system/settings/browser/browser.go
Normal file
74
system/settings/browser/browser.go
Normal file
@ -0,0 +1,74 @@
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
package browser
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
type Browser struct{}
|
||||
|
||||
func (b Browser) LoadSettings() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b Browser) SaveSettings() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b Browser) Get(key string) any {
|
||||
return js.Global().Get("settings").Call("get", key)
|
||||
}
|
||||
|
||||
func (b Browser) GetString(key string) string {
|
||||
return js.Global().Get("settings").Call("getString", key).String()
|
||||
}
|
||||
|
||||
func (b Browser) GetStrings(key string) []string {
|
||||
val := js.Global().Get("settings").Call("getStrings", key)
|
||||
if val.Type() == js.TypeObject {
|
||||
var result []string
|
||||
for i := 0; i < val.Length(); i++ {
|
||||
result = append(result, val.Index(i).String())
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b Browser) GetInt(key string) int {
|
||||
return js.Global().Get("settings").Call("getInt", key).Int()
|
||||
}
|
||||
|
||||
func (b Browser) GetFloat(key string) float64 {
|
||||
return js.Global().Get("settings").Call("getFloat", key).Float()
|
||||
}
|
||||
|
||||
func (b Browser) GetBool(key string) bool {
|
||||
val := js.Global().Get("settings").Call("getBool", key)
|
||||
if val.Type() == js.TypeBoolean {
|
||||
return val.Bool()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b Browser) Set(key string, value any) {
|
||||
js.Global().Get("settings").Call("set", key, value)
|
||||
}
|
||||
|
||||
func (b Browser) GetKeys() []string {
|
||||
val := js.Global().Get("settings").Call("getKeys")
|
||||
if val.Type() == js.TypeObject {
|
||||
var result []string
|
||||
for i := 0; i < val.Length(); i++ {
|
||||
result = append(result, val.Index(i).String())
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b Browser) SetDefault(key string, value any) {
|
||||
js.Global().Get("settings").Call("setDefault", key, value)
|
||||
}
|
||||
@ -3,6 +3,7 @@ package eventview
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/game"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/audio"
|
||||
image2 "github.com/BigJk/end_of_eden/system/image"
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
@ -17,7 +18,6 @@ import (
|
||||
"github.com/muesli/reflow/wordwrap"
|
||||
"github.com/samber/lo"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -182,7 +182,7 @@ func (m Model) eventUpdateContent() Model {
|
||||
var res string
|
||||
|
||||
if strings.HasSuffix(file, ".ans") {
|
||||
ansRes, err := os.ReadFile("./assets/images/" + file)
|
||||
ansRes, err := fs.ReadFile("./assets/images/" + file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package mainmenu
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BigJk/end_of_eden/game"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/audio"
|
||||
image2 "github.com/BigJk/end_of_eden/system/image"
|
||||
"github.com/BigJk/end_of_eden/system/settings"
|
||||
@ -91,8 +92,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case ChoiceContinue:
|
||||
audio.Play("btn_menu")
|
||||
|
||||
if saved, err := os.ReadFile("./session.save"); err == nil {
|
||||
f, err := os.OpenFile("./logs/S "+strings.ReplaceAll(time.Now().Format(time.DateTime), ":", "-")+".txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if saved, err := fs.ReadFile("./session.save"); err == nil {
|
||||
f, err := fs.OpenFile("./logs/S "+strings.ReplaceAll(time.Now().Format(time.DateTime), ":", "-")+".txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -119,7 +120,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
audio.Play("btn_menu")
|
||||
|
||||
_ = os.Mkdir("./logs", 0777)
|
||||
f, err := os.OpenFile("./logs/S "+strings.ReplaceAll(time.Now().Format(time.DateTime), ":", "-")+".txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
f, err := fs.OpenFile("./logs/S "+strings.ReplaceAll(time.Now().Format(time.DateTime), ":", "-")+".txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package mods
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/game"
|
||||
"github.com/BigJk/end_of_eden/internal/fs"
|
||||
"github.com/BigJk/end_of_eden/system/audio"
|
||||
"github.com/BigJk/end_of_eden/system/settings"
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
"github.com/samber/lo"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
@ -182,7 +182,7 @@ func (m Model) modActive(mod string) bool {
|
||||
}
|
||||
|
||||
func (m Model) fetchMods() Model {
|
||||
entries, err := os.ReadDir("./mods")
|
||||
entries, err := fs.ReadDir("./mods")
|
||||
if err != nil {
|
||||
log.Println("Error while reading mods directory:", err)
|
||||
return m
|
||||
|
||||
39
ui/menus/warning/warning.go
Normal file
39
ui/menus/warning/warning.go
Normal file
@ -0,0 +1,39 @@
|
||||
package warning
|
||||
|
||||
import (
|
||||
"github.com/BigJk/end_of_eden/ui"
|
||||
"github.com/BigJk/end_of_eden/ui/components"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ui.MenuBase
|
||||
|
||||
parent tea.Model
|
||||
text string
|
||||
}
|
||||
|
||||
func New(parent tea.Model, text string) Model {
|
||||
return Model{parent: parent, text: text}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.Size = msg
|
||||
case tea.KeyMsg:
|
||||
if msg.Type == tea.KeyEscape {
|
||||
return m.parent, nil
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return components.Error(m.Size.Width, m.Size.Height, m.text)
|
||||
}
|
||||
@ -51,6 +51,8 @@ func (m Model) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.size = msg
|
||||
@ -73,6 +75,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.stack[curIndex], cmd = m.stack[curIndex].Update(msg)
|
||||
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if menu, ok := m.stack[curIndex].(ui.Menu); ok && !menu.HasSize() {
|
||||
return m, tea.Batch(cmd, func() tea.Msg {
|
||||
return m.size
|
||||
@ -80,10 +86,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
if m.stack[curIndex] == nil {
|
||||
// If we remove the top model, we need to send a window size message to the new top model
|
||||
// to avoid the layout to be broken.
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return tea.WindowSizeMsg{
|
||||
Width: m.size.Width,
|
||||
Height: m.size.Height,
|
||||
}
|
||||
})
|
||||
m.stack = m.stack[:len(m.stack)-1]
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user