Formatted with nph v0.3-11-gaeceee1

This commit is contained in:
Scott Wadden 2024-02-02 13:56:53 -04:00
parent 5b304e9697
commit 48639ef4af
110 changed files with 2364 additions and 1648 deletions

View File

@ -1,29 +1,30 @@
import nimibook
var book = init_book_with_toc:
section "Introduction", "intro":
entry "Tutorial", "intro/tutorial"
entry "Install or Build", "intro/building"
entry "Controls and Usage", "intro/controls"
entry "Examples", "intro/examples"
entry "Multiplayer (Experimental)", "intro/multiplayer"
entry "Config", "intro/config"
var book =
init_book_with_toc:
section "Introduction", "intro":
entry "Tutorial", "intro/tutorial"
entry "Install or Build", "intro/building"
entry "Controls and Usage", "intro/controls"
entry "Examples", "intro/examples"
entry "Multiplayer (Experimental)", "intro/multiplayer"
entry "Config", "intro/config"
section "Coding Enu", "coding":
entry "Concepts", "coding/concepts"
entry "Built-in Commands", "coding/commands"
entry "Shorthand Commands", "coding/shorthand"
entry "Random Numbers", "coding/random_numbers"
section "Coding Enu", "coding":
entry "Concepts", "coding/concepts"
entry "Built-in Commands", "coding/commands"
entry "Shorthand Commands", "coding/shorthand"
entry "Random Numbers", "coding/random_numbers"
section "Command Loops", "command_loops":
entry "Writing Commands", "command_loops/commands"
entry "Arrows", "command_loops/arrows"
entry "Loops", "command_loops/loops"
entry "Child Loops", "command_loops/child_loops"
entry "More About Command Loops", "command_loops/more"
section "Command Loops", "command_loops":
entry "Writing Commands", "command_loops/commands"
entry "Arrows", "command_loops/arrows"
entry "Loops", "command_loops/loops"
entry "Child Loops", "command_loops/child_loops"
entry "More About Command Loops", "command_loops/more"
entry "Goals", "goals"
entry "Demos", "demos"
entry "TODO", "todo"
entry "Goals", "goals"
entry "Demos", "demos"
entry "TODO", "todo"
nimibook_cli(book)

View File

@ -2,7 +2,8 @@ import nimib, nimibook
import enuib
nb_init(theme = use_enu)
nb_text: """
nb_text:
"""
# Introduction

View File

@ -1,6 +1,6 @@
import std / [sugar, strutils, os, enumerate, pathnorm, macros]
import pkg / [pretty, nimibook, nimib, nimib / themes]
import pkg / nimibook / [types, commands, entries, toc_render]
import std/[sugar, strutils, os, enumerate, pathnorm, macros]
import pkg/[pretty, nimibook, nimib, nimib/themes]
import pkg/nimibook/[types, commands, entries, toc_render]
export pathutils, pretty
# adapted from https://raw.githubusercontent.com/pietroppeter/nimibook/ef700f646db8ec0bbe8a3319cbb3561aaac89a34/src/nimibook/themes.nim
@ -8,7 +8,8 @@ export pathutils, pretty
const document* = hl_html static_read("./book/template.html.mustache")
proc use_enu*(doc: var NbDoc) =
doc.context["path_to_root"] = doc.src_dir_rel.string & "/" # I probably should make sure to have / at the end
doc.context["path_to_root"] = doc.src_dir_rel.string & "/"
# I probably should make sure to have / at the end
# templates are in memory
doc.partials["document"] = document
@ -36,7 +37,9 @@ proc use_enu*(doc: var NbDoc) =
var this_entry: Entry
# process toc
for i, entry in enumerate(book.toc.entries.mitems):
if normalize_path(entry.url) == normalize_path(doc.filename.replace('\\', '/')): # replace needed for windows
if normalize_path(entry.url) ==
normalize_path(doc.filename.replace('\\', '/')):
# replace needed for windows
this_entry = entry
entry.is_active = true
let
@ -57,10 +60,10 @@ template load_md*(file) =
nb_init(theme = use_enu)
nb_text(text)
template caller_path: string =
template caller_path(): string =
instantiation_info(0, true).filename
proc root_dir: string =
proc root_dir(): string =
caller_path().splitfile.dir
template md*(file: string) =

View File

@ -1,2 +1,2 @@
import controllers / [node_controllers, script_controllers]
import controllers/[node_controllers, script_controllers]
export node_controllers, script_controllers

View File

@ -1,13 +1,15 @@
import std / [tables, typetraits]
import std/[tables, typetraits]
import pkg/godot except print
import godotapi / [node, spatial]
import core, models, nodes / [bot_node, build_node, sign_node, player_node]
import godotapi/[node, spatial]
import core, models, nodes/[bot_node, build_node, sign_node, player_node]
proc remove_from_scene(unit: Unit) =
debug "removing unit", unit = unit.id
assert ?unit.node
if unit == previous_build: previous_build = nil
if unit == current_build: current_build = nil
if unit == previous_build:
previous_build = nil
if unit == current_build:
current_build = nil
for zid in unit.zids:
Zen.thread_ctx.untrack zid
@ -45,8 +47,8 @@ proc add_to_scene(unit: Unit) =
node.transform = unit.transform
if node.owner != nil:
fail \"{T.name} node shouldn't be owned. unit = {unit.id}"
unit.node.visible = Visible in unit.global_flags and
ScriptInitializing notin unit.global_flags
unit.node.visible =
Visible in unit.global_flags and ScriptInitializing notin unit.global_flags
parent_node.add_child(unit.node)
unit.node.owner = parent_node
@ -55,14 +57,15 @@ proc add_to_scene(unit: Unit) =
unit.main_thread_joined
unit.global_flags += Ready
let parent_node = if Global in unit.global_flags:
state.nodes.data
else:
unit.parent.node
let parent_node =
if Global in unit.global_flags: state.nodes.data else: unit.parent.node
if unit of Bot: Bot(unit).add(BotNode, parent_node)
elif unit of Build: Build(unit).add(BuildNode, parent_node)
elif unit of Sign: Sign(unit).add(SignNode, parent_node)
if unit of Bot:
Bot(unit).add(BotNode, parent_node)
elif unit of Build:
Build(unit).add(BuildNode, parent_node)
elif unit of Sign:
Sign(unit).add(SignNode, parent_node)
elif unit of Player:
let player = Player(unit)
# TODO: PlayerNode should work for connected players as well
@ -84,11 +87,13 @@ proc set_global(unit: Unit, global: bool) =
if global:
state.nodes.data.add_child(unit.node)
unit.node.owner = state.nodes.data
unit.transform_value.origin = unit.transform.origin + unit.start_transform.origin
unit.transform_value.origin =
unit.transform.origin + unit.start_transform.origin
else:
unit.parent.node.add_child(unit.node)
unit.node.owner = unit.parent.node
unit.transform_value.origin = unit.transform.origin - unit.start_transform.origin
unit.transform_value.origin =
unit.transform.origin - unit.start_transform.origin
proc reset_nodes() =
current_build = nil

View File

@ -1,5 +1,5 @@
import core
import ./ script_controllers / worker
import ./script_controllers/worker
proc init*(T: type ScriptController): ScriptController =
result = ScriptController()

View File

@ -1,18 +1,18 @@
import std / [os, macros, math, asyncfutures, hashes]
import std/[os, macros, math, asyncfutures, hashes]
import locks except Lock
import pkg / godot except print
import pkg / compiler / vm except get_int
from pkg / compiler / vm {.all.} import stack_trace_aux
import pkg / compiler / ast except new_node
import pkg / compiler / [vmdef, renderer, msgs]
import pkg/godot except print
import pkg/compiler/vm except get_int
from pkg/compiler/vm {.all.} import stack_trace_aux
import pkg/compiler/ast except new_node
import pkg/compiler/[vmdef, renderer, msgs]
import godotapi / [spatial, ray_cast]
import core, models / [states, bots, builds, units, colors, signs, serializers]
import libs / [interpreters, eval]
import shared / errors
import godotapi/[spatial, ray_cast]
import core, models/[states, bots, builds, units, colors, signs, serializers]
import libs/[interpreters, eval]
import shared/errors
import ./ [vars, scripting]
include ./ host_bridge_utils
import ./[vars, scripting]
include ./host_bridge_utils
proc get_last_error(self: Worker): ErrorData =
result = self.last_exception.from_exception
@ -32,7 +32,8 @@ proc unmap_unit*(self: Worker, unit: Unit) =
proc link_dependency(self: Worker, dep: Unit) =
let dep = dep.find_root
let active = self.active_unit.find_root
debug "linking dependency", dependency = dep.script_ctx.module_name,
debug "linking dependency",
dependency = dep.script_ctx.module_name,
will_reload = active.script_ctx.module_name
dep.script_ctx.dependents.incl active.script_ctx.module_name
@ -41,8 +42,9 @@ proc write_stack_trace(self: Worker) =
let ctx = self.active_unit.script_ctx
{.gcsafe.}:
msg_writeln(ctx.ctx.config, "stack trace: (most recent call last)",
{msg_no_unit_sep})
msg_writeln(
ctx.ctx.config, "stack trace: (most recent call last)", {msg_no_unit_sep}
)
stack_trace_aux(ctx.ctx, ctx.tos, ctx.pc)
@ -87,18 +89,21 @@ proc register_active(self: Worker, pnode: PNode) =
self.map_unit(self.active_unit, pnode)
proc new_instance(self: Worker, src: Unit, dest: PNode) =
let id = src.id & "_" & self.active_unit.id & "_instance_" &
$(self.active_unit.units.len + 1)
let id =
src.id & "_" & self.active_unit.id & "_instance_" &
$(self.active_unit.units.len + 1)
var clone = src.clone(self.active_unit, id)
assert not clone.is_nil
clone.script_ctx = ScriptCtx.init(owner = clone, clone_of = src,
interpreter = self.interpreter)
clone.script_ctx =
ScriptCtx.init(
owner = clone, clone_of = src, interpreter = self.interpreter
)
self.map_unit(clone, dest)
debug "adding to active unit", unit = clone.id,
active_unit = self.active_unit.id
debug "adding to active unit",
unit = clone.id, active_unit = self.active_unit.id
self.active_unit.units.add(clone)
@ -111,7 +116,8 @@ proc exec_instance(self: Worker, unit: Unit) =
ctx.timeout_at = get_mono_time() + script_timeout
ctx.running = ctx.call_proc("run_script", self.node_map[unit], true).paused
proc active_unit(self: Worker): Unit = self.active_unit
proc active_unit(self: Worker): Unit =
self.active_unit
proc wake(self: Unit) =
self.script_ctx.timer = get_mono_time()
@ -143,18 +149,26 @@ proc reset_level(self: Worker) =
self.exit(self.active_unit.script_ctx, 0)
after_boop:
let current_level = state.config.level_dir
state.config_value.value: level_dir = ""
state.config_value.value:
level_dir = ""
remove_dir current_level
state.config_value.value:
level_dir = current_level
proc world_name: string = state.config.world
proc world_name(): string =
state.config.world
proc level_name: string = state.config.level
proc begin_turn(self: Worker, unit: Unit, direction: Vector3, degrees: float,
lean: bool, move_mode: int): string =
proc level_name(): string =
state.config.level
proc begin_turn(
self: Worker,
unit: Unit,
direction: Vector3,
degrees: float,
lean: bool,
move_mode: int,
): string =
assert not degrees.is_nan
var degrees = floor_mod(degrees, 360)
let ctx = self.active_unit.script_ctx
@ -163,9 +177,9 @@ proc begin_turn(self: Worker, unit: Unit, direction: Vector3, degrees: float,
if not ctx.callback.is_nil:
self.pause_script()
proc begin_move(self: Worker, unit: Unit, direction: Vector3, steps: float,
move_mode: int) =
proc begin_move(
self: Worker, unit: Unit, direction: Vector3, steps: float, move_mode: int
) =
var steps = steps
var direction = direction
let ctx = self.active_unit.script_ctx
@ -179,14 +193,15 @@ proc begin_move(self: Worker, unit: Unit, direction: Vector3, steps: float,
proc sleep_impl(self: Worker, ctx: ScriptCtx, seconds: float) =
var duration = 0.0
ctx.callback = proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if seconds > 0 and duration < seconds:
Running
elif seconds <= 0 and duration <= 0.5 and ctx.timer > get_mono_time():
Running
else:
Done
ctx.callback =
proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if seconds > 0 and duration < seconds:
Running
elif seconds <= 0 and duration <= 0.5 and ctx.timer > get_mono_time():
Running
else:
Done
ctx.last_ran = MonoTime.default
self.pause_script()
@ -209,7 +224,8 @@ proc `action_running=`(self: Unit, value: bool) =
self.script_ctx.timer = MonoTime.high
self.script_ctx.action_running = value
proc id(self: Unit): string = self.id
proc id(self: Unit): string =
self.id
proc global(self: Unit): bool =
Global in self.global_flags
@ -310,7 +326,9 @@ proc `rotation=`(self: Unit, degrees: float) =
t.origin = self.transform.origin
self.transform = t
proc sees(self: Worker, unit: Unit, target: Unit, distance: float): Future[bool] =
proc sees(
self: Worker, unit: Unit, target: Unit, distance: float
): Future[bool] =
result = Future.init(bool, "sees")
if target == state.player and Flying in state.local_flags:
@ -324,18 +342,20 @@ proc sees(self: Worker, unit: Unit, target: Unit, distance: float): Future[bool]
return
let future = result
unit.script_ctx.callback = proc(delta: float, timeout: MonoTime): TaskStates =
let query = unit.sight_query
if ?query.answer:
future.complete(query.answer.get)
result = Done
else:
result = Running
unit.script_ctx.callback =
proc(delta: float, timeout: MonoTime): TaskStates =
let query = unit.sight_query
if ?query.answer:
future.complete(query.answer.get)
result = Done
else:
result = Running
unit.script_ctx.last_ran = MonoTime.default
self.pause_script()
proc frame_count(): int = state.frame_count
proc frame_count(): int =
state.frame_count
proc frame_created(unit: Unit): int =
unit.frame_created
@ -353,11 +373,15 @@ proc drop_transform(unit: Unit): Transform =
proc reset(self: Unit, clear: bool) =
if clear:
if self of Build: Build(self).reset()
elif self of Bot: Bot(self).reset()
if self of Build:
Build(self).reset()
elif self of Bot:
Bot(self).reset()
else:
if self of Build: Build(self).reset_state()
elif self of Bot: Bot(self).reset_state()
if self of Build:
Build(self).reset_state()
elif self of Bot:
Bot(self).reset_state()
# Bot bindings
@ -405,11 +429,11 @@ proc draw_position_set(self: Build, position: Vector3) =
proc save(self: Build, name: string) =
self.save_points[name] =
(self.draw_transform, self.color_value.value, self.drawing)
(self.draw_transform, self.color_value.value, self.drawing)
proc restore(self: Build, name: string) =
(self.draw_transform, self.color_value.value, self.drawing) =
self.save_points[name]
self.save_points[name]
# Player binding
@ -451,22 +475,43 @@ proc `open_sign=`(self: Unit, value: Sign) =
# Sign bindings
proc new_markdown_sign(self: Worker,
unit: Unit, pnode: PNode, message: string, more: string, width: float,
height: float, size: int, billboard: bool): Unit =
result = Sign.init(
message, more = more, owner = self.active_unit,
transform = drop_transform(unit), width = width, height = height,
size = size, billboard = billboard)
proc new_markdown_sign(
self: Worker,
unit: Unit,
pnode: PNode,
message: string,
more: string,
width: float,
height: float,
size: int,
billboard: bool,
): Unit =
result =
Sign.init(
message,
more = more,
owner = self.active_unit,
transform = drop_transform(unit),
width = width,
height = height,
size = size,
billboard = billboard,
)
info "creating sign", id = result.id
self.map_unit(result, pnode)
unit.units.add(result)
proc update_markdown_sign(self: Worker, unit: Sign, message: string,
more: string, width: float, height: float, size: int, billboard: bool) =
proc update_markdown_sign(
self: Worker,
unit: Sign,
message: string,
more: string,
width: float,
height: float,
size: int,
billboard: bool,
) =
unit.width = width
unit.height = height
unit.size = size
@ -511,7 +556,6 @@ proc `coding=`(self: Unit, value: Unit) =
# End of bindings
proc bridge_to_vm*(worker: Worker) =
# host_bridge_utils.nim is expecting a var called `result`. Fix this.
var result = worker
@ -530,8 +574,7 @@ proc bridge_to_vm*(worker: Worker) =
begin_turn, begin_move, sleep_impl, position_set, new_markdown_sign,
update_markdown_sign
result.bridged_from_vm "bots",
play, all_bots
result.bridged_from_vm "bots", play, all_bots
result.bridged_from_vm "builds",
drawing, `drawing=`, initial_position, save, restore, all_builds,
@ -542,5 +585,5 @@ proc bridge_to_vm*(worker: Worker) =
size, `size=`, open, `open=`, billboard, `billboard=`
result.bridged_from_vm "players",
playing, `playing=`, god, `god=`, flying, `flying=`, tool, `tool=`,
coding, `coding=`, running, `running=`, open_sign, `open_sign=`
playing, `playing=`, god, `god=`, flying, `flying=`, tool, `tool=`, coding,
`coding=`, running, `running=`, open_sign, `open_sign=`

View File

@ -1,50 +1,72 @@
proc get_int(a: VmArgs; i: Natural): int = int vm.get_int(a, i)
proc get_int(a: VmArgs, i: Natural): int =
int vm.get_int(a, i)
proc get_colors(a: VmArgs; i: Natural): Colors = Colors(vm.get_int(a, 1))
proc get_colors(a: VmArgs, i: Natural): Colors =
Colors(vm.get_int(a, 1))
proc get_pnode(a: VmArgs, pos: int): PNode {.inline.} = a.get_node(pos)
proc get_pnode(a: VmArgs, pos: int): PNode {.inline.} =
a.get_node(pos)
proc get_vector3(a: VmArgs, pos: int): Vector3 =
let fields = a.get_node(pos).sons
result = vec3(fields[0].float_val, fields[1].float_val, fields[2].float_val)
# adapted from https://github.com/h0lley/embeddedNimScript/blob/6101fb37d4bd3f947db86bac96f53b35d507736a/embeddedNims/enims.nim#L31
proc to_node(val: int): PNode = new_int_node(nkIntLit, val)
proc to_node(val: float): PNode = new_float_node(nkFloatLit, val)
proc to_node(val: string): PNode = new_str_node(nkStrLit, val)
proc to_node(a: bool): Pnode = new_int_node(nkIntLit, a.BiggestInt)
proc to_node(val: enum): PNode = val.ord.to_node
proc to_node(val: int): PNode =
new_int_node(nkIntLit, val)
proc to_node(list: open_array[int|float|string|bool|enum]): PNode =
proc to_node(val: float): PNode =
new_float_node(nkFloatLit, val)
proc to_node(val: string): PNode =
new_str_node(nkStrLit, val)
proc to_node(a: bool): Pnode =
new_int_node(nkIntLit, a.BiggestInt)
proc to_node(val: enum): PNode =
val.ord.to_node
proc to_node(list: open_array[int | float | string | bool | enum]): PNode =
result = nkBracket.new_node
result.sons.initialize(list.len)
for i in 0..list.high: result.sons[i] = list[i].to_node()
for i in 0 .. list.high:
result.sons[i] = list[i].to_node()
proc to_node(tree: tuple|object): PNode =
proc to_node(tree: tuple | object): PNode =
result = nkPar.new_tree
for field in tree.fields:
result.sons.add(field.to_node)
proc to_node(a: PNode): PNode = a
proc to_node(a: PNode): PNode =
a
proc to_node(tree: ref tuple|ref object): PNode =
proc to_node(tree: ref tuple | ref object): PNode =
result = nkPar.new_tree
if tree.is_nil: return result
if tree.is_nil:
return result
for field in tree.fields:
result.sons.add(field.to_node)
proc to_result(val: float): BiggestFloat = BiggestFloat(val)
proc to_result(val: SomeOrdinal or enum or bool): BiggestInt = BiggestInt(val)
proc to_result(val: Vector3 or string or tuple): PNode = val.to_node
proc to_result(val: PNode): PNode = result = val
proc to_result(val: float): BiggestFloat =
BiggestFloat(val)
proc to_result(val: SomeOrdinal or enum or bool): BiggestInt =
BiggestInt(val)
proc to_result(val: Vector3 or string or tuple): PNode =
val.to_node
proc to_result(val: PNode): PNode =
result = val
proc await_future[T](future: Future[T], a: VmArgs) =
future.add_callback proc(future: Future[T]) =
set_result(a, to_result(future.read))
macro bridged_from_vm(self: Worker,
module_name: string, proc_refs: varargs[untyped]): untyped =
macro bridged_from_vm(
self: Worker, module_name: string, proc_refs: varargs[untyped]
): untyped =
result = new_stmt_list()
result.add quote do:
when not declared_in_scope(script_engine):
@ -53,57 +75,66 @@ macro bridged_from_vm(self: Worker,
for proc_ref in proc_refs:
let
symbol = bind_sym($proc_ref)
proc_impl = (if symbol.kind == nnkSym: symbol else: symbol[0]).get_impl
proc_impl = (if symbol.kind == nnkSym: symbol
else: symbol[0]
).get_impl
proc_name = proc_impl[0].str_val
proc_impl_name = proc_name.replace("=", "_set") & "_impl"
return_node = proc_impl[3][0]
arg_nodes = proc_impl[3][1..^1]
arg_nodes = proc_impl[3][1 ..^ 1]
let args = collect:
block:
var pos = -1
for ident_def in arg_nodes:
let typ = ident_def[1].str_val
if typ == $Worker.type:
ident"script_engine"
elif typ == "VmArgs":
ident"a"
elif typ == "ScriptCtx":
quote do: script_engine.active_unit.script_ctx
elif typ in ["Unit", "Bot", "Build", "Sign"]:
let getter = "get_" & typ
pos.inc
new_call(bind_sym(getter), ident"script_engine",
ident"a", new_lit(pos))
else:
let getter = "get_" & typ
pos.inc
new_call(bind_sym(getter), ident"a", new_lit(pos))
let args =
collect:
block:
var pos = -1
for ident_def in arg_nodes:
let typ = ident_def[1].str_val
if typ == $Worker.type:
ident"script_engine"
elif typ == "VmArgs":
ident"a"
elif typ == "ScriptCtx":
quote:
script_engine.active_unit.script_ctx
elif typ in ["Unit", "Bot", "Build", "Sign"]:
let getter = "get_" & typ
pos.inc
new_call(
bind_sym(getter), ident"script_engine", ident"a", new_lit(pos)
)
else:
let getter = "get_" & typ
pos.inc
new_call(bind_sym(getter), ident"a", new_lit(pos))
var call = new_call(proc_ref, args)
if return_node.kind == nnk_sym:
if return_node.str_val in ["Unit", "Bot", "Build", "Sign"]:
call = new_call(bind_sym"set_result", ident"a",
new_call(bind_sym"to_node", ident"script_engine", call))
call =
new_call(
bind_sym"set_result",
ident"a",
new_call(bind_sym"to_node", ident"script_engine", call),
)
else:
call = new_call(bind_sym"set_result", ident"a",
new_call(bind_sym"to_result", call))
call =
new_call(
bind_sym"set_result", ident"a", new_call(bind_sym"to_result", call)
)
elif return_node.kind == nnk_bracket_expr and return_node.len == 2 and
return_node[0].str_val == "Future":
call = new_call(bind_sym"await_future", call, ident"a")
result.add quote do:
mixin implement_routine
`self`.interpreter.implement_routine "enu", `module_name`, `proc_impl_name`,
proc(a {.inject.}: VmArgs) {.gcsafe.} =
debug "calling routine", name = `proc_name`
try:
`call`
except Exception as e:
error "Exception calling into host", kind = $e.type, msg = e.msg
echo e.get_stack_trace()
script_engine.last_exception = e
`self`.interpreter.implement_routine "enu",
`module_name`,
`proc_impl_name`,
proc(a {.inject.}: VmArgs) {.gcsafe.} =
debug "calling routine", name = `proc_name`
try:
`call`
except Exception as e:
error "Exception calling into host", kind = $e.type, msg = e.msg
echo e.get_stack_trace()
script_engine.last_exception = e

View File

@ -1,27 +1,36 @@
import std / [os, re]
import std/[os, re]
import pkg / godot except print
import pkg / compiler / ast except new_node
import pkg / compiler / [lineinfos, renderer, msgs, vmdef]
import godotapi / [spatial, ray_cast, voxel_terrain]
import core, models / [states, bots, builds, units, signs, players]
import libs / [interpreters, eval]
import ./ vars
import pkg/godot except print
import pkg/compiler/ast except new_node
import pkg/compiler/[lineinfos, renderer, msgs, vmdef]
import godotapi/[spatial, ray_cast, voxel_terrain]
import core, models/[states, bots, builds, units, signs, players]
import libs/[interpreters, eval]
import ./vars
proc init*(_: type ScriptCtx, owner: Unit, clone_of: Unit = nil,
interpreter: Interpreter): ScriptCtx =
result = ScriptCtx(
module_name: if ?clone_of: clone_of.id else: "",
interpreter: interpreter,
timeout_at: MonoTime.high,
timer: MonoTime.high
)
proc init*(
_: type ScriptCtx,
owner: Unit,
clone_of: Unit = nil,
interpreter: Interpreter,
): ScriptCtx =
result =
ScriptCtx(
module_name: if ?clone_of: clone_of.id else: "",
interpreter: interpreter,
timeout_at: MonoTime.high,
timer: MonoTime.high,
)
proc extract_file_info(msg: string): tuple[name: string, info: TLineInfo] =
if msg =~ re"unhandled exception: (.*)\((\d+), (\d+)\)":
result = (matches[0], TLineInfo(line: matches[1].parse_int.uint16, col:
matches[2].parse_int.int16))
result =
(
matches[0],
TLineInfo(
line: matches[1].parse_int.uint16, col: matches[2].parse_int.int16
),
)
proc script_error*(self: Worker, unit: Unit, e: ref VMQuit) =
var msg = e.msg
@ -36,33 +45,34 @@ proc script_error*(self: Worker, unit: Unit, e: ref VMQuit) =
proc init_interpreter*[T](self: Worker, _: T) {.gcsafe.} =
private_access ScriptCtx
var interpreter = Interpreter.init(state.config.script_dir,
state.config.lib_dir)
var interpreter =
Interpreter.init(state.config.script_dir, state.config.lib_dir)
let controller = self
self.interpreter = interpreter
interpreter.config.spell_suggest_max = 0
interpreter.register_error_hook proc(config, info, msg,
severity: auto) {.gcsafe.} =
interpreter.register_error_hook proc(
config, info, msg, severity: auto
) {.gcsafe.} =
var info = info
var msg = msg
let ctx = controller.active_unit.script_ctx
let errors = controller.active_unit.errors
if severity == Severity.Error and config.error_counter >= config.error_max:
var file_name = if info.file_index.int >= 0:
config.m.file_infos[info.file_index.int].full_path.string
else:
"???"
var file_name =
if info.file_index.int >= 0:
config.m.file_infos[info.file_index.int].full_path.string
else:
"???"
if file_exists(file_name) and ?ctx.file_name:
if file_name.get_file_info != ctx.file_name.get_file_info:
(file_name, info) = extract_file_info msg
# msg = msg.replace(re"unhandled exception:.*\) Error\: ", "")
# else:
# msg = msg.replace(re"(?ms);.*", "")
# msg = msg.replace(re"(?ms);.*", "")
else:
error "File not found handling error", file_name
@ -73,45 +83,48 @@ proc init_interpreter*[T](self: Worker, _: T) {.gcsafe.} =
raise (ref VMQuit)(info: info, msg: msg, location: loc)
var count: byte = 0
interpreter.enter_hook = proc(c: PCtx, pc: int, tos: PStackFrame, instr: TInstr) =
assert ?controller
assert ?controller.active_unit
assert ?controller.active_unit.script_ctx
interpreter.enter_hook =
proc(c: PCtx, pc: int, tos: PStackFrame, instr: TInstr) =
assert ?controller
assert ?controller.active_unit
assert ?controller.active_unit.script_ctx
let ctx = controller.active_unit.script_ctx
let info = c.debug[pc]
inc count
if count == 255:
# don't call get_mono_time for every instruction for a 5-10% speedup.
count = 0
let now = get_mono_time()
if ctx.timeout_at < now:
let ctx = controller.active_unit.script_ctx
let info = c.debug[pc]
inc count
if count == 255:
# don't call get_mono_time for every instruction for a 5-10% speedup.
count = 0
let now = get_mono_time()
if ctx.timeout_at < now:
let duration = script_timeout
raise (ref VMQuit)(
info: info,
kind: Timeout,
msg:
\"Timeout. Script {ctx.script} executed for too long without " &
\"yielding: {duration}",
)
let duration = script_timeout
raise (ref VMQuit)(info: info, kind: Timeout,
msg: \"Timeout. Script {ctx.script} executed for too long without " &
\"yielding: {duration}")
# We don't care about the line info if we're not in our enu script.
# Store the file index the first time we hit our file and only change
# current_line/previous_line if the current instruction has that index.
if ctx.file_index == -1 and info.file_index.int >= 0 and
info.file_index.int < interpreter.config.m.file_infos.len:
let file_name =
interpreter.config.m.file_infos[info.file_index.int].full_path.string
if file_name == ctx.file_name:
ctx.file_index = info.file_index.int
elif ctx.file_index == info.file_index.int:
if ctx.previous_line != info:
(ctx.previous_line, ctx.current_line) = (ctx.current_line, info)
# We don't care about the line info if we're not in our enu script.
# Store the file index the first time we hit our file and only change
# current_line/previous_line if the current instruction has that index.
if ctx.file_index == -1 and info.file_index.int >= 0 and
info.file_index.int < interpreter.config.m.file_infos.len:
let file_name =
interpreter.config.m.file_infos[info.file_index.int].full_path.string
if file_name == ctx.file_name:
ctx.file_index = info.file_index.int
elif ctx.file_index == info.file_index.int:
if ctx.previous_line != info:
(ctx.previous_line, ctx.current_line) = (ctx.current_line, info)
if ctx.pause_requested:
ctx.ctx = c
ctx.pc = pc
ctx.tos = tos
ctx.pause_requested = false
raise VMPause.new_exception("vm paused")
if ctx.pause_requested:
ctx.ctx = c
ctx.pc = pc
ctx.tos = tos
ctx.pause_requested = false
raise VMPause.new_exception("vm paused")
proc load_script*(self: Worker, unit: Unit, timeout = script_timeout) =
let ctx = unit.script_ctx
@ -125,10 +138,11 @@ proc load_script*(self: Worker, unit: Unit, timeout = script_timeout) =
var others = self.module_names
self.module_names.incl module_name
others.excl module_name
let imports = if others.card > 0:
"import " & others.to_seq.join(", ")
else:
""
let imports =
if others.card > 0:
"import " & others.to_seq.join(", ")
else:
""
let code = unit.code_template(imports)
ctx.timeout_at = get_mono_time() + timeout
ctx.file_index = -1
@ -141,7 +155,6 @@ proc load_script*(self: Worker, unit: Unit, timeout = script_timeout) =
if not ctx.running and not ?unit.clone_of:
unit.collect_garbage
unit.ensure_visible
except VMQuit as e:
ctx.running = false
self.interpreter.reset_module(unit.script_ctx.module_name)

View File

@ -1,8 +1,8 @@
import std / [locks, os, random, net]
import std / times except seconds, minutes
from pkg / netty import Reactor
import core, models, models / [serializers], libs / [interpreters, eval]
import ./ [vars, host_bridge, scripting]
import std/[locks, os, random, net]
import std/times except seconds, minutes
from pkg/netty import Reactor
import core, models, models/[serializers], libs/[interpreters, eval]
import ./[vars, host_bridge, scripting]
var
worker_lock: locks.Lock
@ -24,16 +24,17 @@ proc advance_unit(self: Worker, unit: Unit, timeout: MonoTime): bool =
let now = get_mono_time()
let delta = if ?ctx.last_ran:
(now - ctx.last_ran).in_microseconds.float / 1000000.0
else:
0.0
let delta =
if ?ctx.last_ran:
(now - ctx.last_ran).in_microseconds.float / 1000000.0
else:
0.0
ctx.last_ran = now
if ctx.callback == nil or
(task_state = ctx.callback(delta, timeout);
task_state in {Done, NextTask}):
if ctx.callback == nil or (;
task_state = ctx.callback(delta, timeout)
task_state in {Done, NextTask}
):
ctx.timer = MonoTime.high
ctx.action_running = false
self.active_unit = unit
@ -45,7 +46,6 @@ proc advance_unit(self: Worker, unit: Unit, timeout: MonoTime): bool =
unit.current_line = 0
result = ctx.running and task_state == NextTask
elif now >= ctx.timer:
ctx.timer = now + advance_step
ctx.saved_callback = ctx.callback
@ -53,7 +53,6 @@ proc advance_unit(self: Worker, unit: Unit, timeout: MonoTime): bool =
self.active_unit = unit
ctx.timeout_at = now + script_timeout
discard ctx.resume()
except VMQuit as e:
self.interpreter.reset_module(unit.script_ctx.module_name)
self.script_error(unit, e)
@ -116,18 +115,20 @@ proc watch_code(self: Worker, unit: Unit) =
self.script_error(unit, e)
if unit.script_ctx.is_nil:
unit.script_ctx = ScriptCtx.init(owner = unit,
interpreter = self.interpreter)
unit.script_ctx =
ScriptCtx.init(owner = unit, interpreter = self.interpreter)
unit.script_ctx.script = script_file_for unit
proc watch_units(self: Worker,
units: ZenSeq[Unit],
parent: Unit,
body: proc(unit: Unit, change: Change[Unit], added: bool,
removed: bool) {.gcsafe.}
proc watch_units(
self: Worker,
units: ZenSeq[Unit],
parent: Unit,
body:
proc(unit: Unit, change: Change[Unit], added: bool, removed: bool) {.
gcsafe
.},
) {.gcsafe.} =
units.track proc(changes: seq[Change[Unit]]) {.gcsafe.} =
for change in changes:
let unit = change.item
@ -143,10 +144,11 @@ proc watch_units(self: Worker,
self.watch_units(unit.units, unit, body)
template for_all_units(self: Worker, body: untyped) {.dirty.} =
self.watch_units state.units, parent = nil,
proc(unit: Unit, change: Change[Unit], added: bool,
removed: bool) {.gcsafe.} =
self.watch_units state.units,
parent = nil,
proc(
unit: Unit, change: Change[Unit], added: bool, removed: bool
) {.gcsafe.} =
body
proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
@ -154,9 +156,14 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
worker_lock.acquire
var listen_address = main_thread_state.config.listen_address
let worker_ctx = ZenContext.init(id = \"work-{generate_id()}",
chan_size = 500, buffer = false, listen_address = listen_address,
label = "worker")
let worker_ctx =
ZenContext.init(
id = \"work-{generate_id()}",
chan_size = 500,
buffer = false,
listen_address = listen_address,
label = "worker",
)
Zen.thread_ctx = worker_ctx
ctx.subscribe(Zen.thread_ctx)
@ -192,8 +199,7 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
unit.script_ctx.running = false
unit.script_ctx.callback = nil
if not (unit of Player) and LoadingScript notin state.local_flags and
not ?unit.clone_of:
not ?unit.clone_of:
remove_file unit.script_ctx.script
remove_dir unit.data_dir
@ -240,7 +246,6 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
level_dir = change.item.level_dir
if level_dir != "":
worker.load_level(level_dir)
else:
var timeout_at = get_mono_time() + 30.seconds
var connected = false
@ -257,9 +262,18 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
player.script_ctx.interpreter = worker.interpreter
worker.load_script_and_dependents(player)
var sign = Sign.init("", "", width = 4, height = 3.05, owner = state.player,
size = 244, billboard = true, text_only = true,
transform = Transform.init(origin = vec3(0, 4, 0)))
var sign =
Sign.init(
"",
"",
width = 4,
height = 3.05,
owner = state.player,
size = 244,
billboard = true,
text_only = true,
transform = Transform.init(origin = vec3(0, 4, 0)),
)
state.player.units += sign
sign.global_flags -= Visible
@ -306,14 +320,14 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
break
var to_process: seq[Unit]
state.units.value.walk_tree proc(unit: Unit) = to_process.add unit
state.units.value.walk_tree proc(unit: Unit) =
to_process.add unit
to_process.shuffle
var batched: HashSet[Unit]
while Zen.thread_ctx.pressure < 0.9 and to_process.len > 0 and state.voxel_tasks <= 10 and
get_mono_time() < timeout:
while Zen.thread_ctx.pressure < 0.9 and to_process.len > 0 and
state.voxel_tasks <= 10 and get_mono_time() < timeout:
let units = to_process
to_process = @[]
for unit in units:
@ -338,10 +352,9 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
if now < wait_until:
sleep int((wait_until - get_mono_time()).in_milliseconds)
except Exception as e:
error "Unhandled worker thread exception", kind = $e.type, msg = e.msg,
stacktrace = e.get_stack_trace
error "Unhandled worker thread exception",
kind = $e.type, msg = e.msg, stacktrace = e.get_stack_trace
state.push_flag(NeedsRestart)
@ -356,8 +369,9 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
except Exception:
discard
proc launch_worker*(ctx: ZenContext, state: GameState): system.Thread[tuple[
ctx: ZenContext, state: GameState]] =
proc launch_worker*(
ctx: ZenContext, state: GameState
): system.Thread[tuple[ctx: ZenContext, state: GameState]] =
worker_lock.acquire
result.create_thread(worker_thread, (ctx, state))
work_done.wait(worker_lock)

View File

@ -1,8 +1,8 @@
import types
export types
import pkg / model_citizen / utils
import std / [sequtils, strutils, sugar, macros, asyncfutures, importutils]
import pkg/model_citizen/utils
import std/[sequtils, strutils, sugar, macros, asyncfutures, importutils]
export utils, sequtils, strutils, sugar, importutils
### Globals ###
@ -13,9 +13,9 @@ var state* {.threadvar.}: GameState
### Sugar ###
from sugar import dup, dump, collect
import std / [with, sets, monotimes, tables]
import std / times except seconds
import pkg / [pretty, flatty]
import std/[with, sets, monotimes, tables]
import std/times except seconds
import pkg/[pretty, flatty]
export with, sets, tables, pretty, flatty
@ -26,10 +26,11 @@ proc minutes*(m: float | int): Duration {.inline.} =
export dump
import pkg / chronicles
import pkg/chronicles
export chronicles
template nim_filename*: string = instantiation_info(full_paths = true).filename
template nim_filename*(): string =
instantiation_info(full_paths = true).filename
### options ###
@ -44,16 +45,10 @@ proc `||=`*[T](opt: var Option[T], val: T): T {.discardable.} =
result = opt.get()
proc `||`*[T](a: Option[T], b: T): T =
if ?a:
a.get
else:
b
if ?a: a.get else: b
proc `||`*[T](a, b: T): T =
if ?a:
a
else:
b
if ?a: a else: b
converter from_option*[T](val: Option[T]): T =
val.get()
@ -121,9 +116,9 @@ proc `z=`*(self: var Basis, value: Vector3) {.inline.} =
proc surrounding*(point: Vector3): seq[Vector3] =
collect(new_seq):
for x in 0..2:
for y in 0..2:
for z in 0..2:
for x in 0 .. 2:
for y in 0 .. 2:
for z in 0 .. 2:
point + vec3(x - 1, y - 1, z - 1)
# math
@ -145,10 +140,10 @@ proc wrap*[T](value, min, max: T): float =
# output
when not defined(no_godot):
import pkg / godot
import pkg/godot
default_chronicles_stream.output.writer =
proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} =
proc(logLevel: LogLevel, msg: LogOutputStr) {.gcsafe.} =
when defined(release):
godot.print msg
else:
@ -159,12 +154,12 @@ when not defined(no_godot):
proc init*(_: type Future, T: type, proc_name = ""): Future[T] =
return new_future[T](proc_name)
import pkg / core / transforms
import pkg/core/transforms
export transforms
import pkg / godot
import pkg/godot
import pkg / model_citizen
import pkg/model_citizen
export model_citizen
proc global_from*(self: Vector3, unit: Unit): Vector3 =
@ -200,7 +195,8 @@ proc `basis=`*(self: ZenValue[Transform], value: Basis) =
transform.basis = value
self.value = transform
proc init*(_: type Basis): Basis = init_basis()
proc init*(_: type Basis): Basis =
init_basis()
proc init*(_: type Transform, origin = vec3()): Transform =
result = init_transform()
@ -221,15 +217,18 @@ proc update_action_index*(state: GameState, change: int) =
template watch*[T, O](zen: Zen[T, O], unit: untyped, body: untyped) =
when unit is Unit:
mixin thread_ctx
let zid = zen.changes:
body
let zid =
zen.changes:
body
unit.zids.add(zid)
make_discardable(zid)
else:
{. error:
"Watch needs a Unit object to bind its lifetime to. The Unit " &
"can be passed explicitly, or found implicitly by evaluating " &
"`self.model`, then `self`." .}
{.
error:
"Watch needs a Unit object to bind its lifetime to. The Unit " &
"can be passed explicitly, or found implicitly by evaluating " &
"`self.model`, then `self`."
.}
template watch*[T, O](zen: Zen[T, O], body: untyped) =
when compiles(self.model):
@ -246,7 +245,8 @@ macro enum_fields*(n: typed): untyped =
case f.kind
of nnk_sym, nnk_ident:
result.add new_lit(f.str_val)
else: discard
else:
discard
template value*(self: ZenValue, body: untyped) {.dirty.} =
block:
@ -260,7 +260,7 @@ template after_boop*(body: untyped) =
deferred.add proc() =
body
proc run_deferred* =
proc run_deferred*() =
for fn in deferred:
fn()
deferred.set_len(0)

View File

@ -1,11 +1,17 @@
{.warning[UnusedImport]: off.}
import libs / segfaults
import libs/segfaults
import core, game
import ui / [editor, console, toolbar, action_button, preview_maker,
markdown_label, right_panel]
import nodes / [player_node, aim_target, selection_area, bot_node, ground_node,
build_node, sign_node]
import
ui/[
editor, console, toolbar, action_button, preview_maker, markdown_label,
right_panel
]
import
nodes/[
player_node, aim_target, selection_area, bot_node, ground_node, build_node,
sign_node
]
Zen.bootstrap

View File

@ -1,13 +1,15 @@
import std / [monotimes, os, json, math, random, net]
import pkg / [godot, metrics, metrics / stdlib_httpserver]
import std/[monotimes, os, json, math, random, net]
import pkg/[godot, metrics, metrics/stdlib_httpserver]
from dotenv import nil
import godotapi / [input, input_event, gd_os, node, scene_tree, packed_scene,
sprite, control, viewport, viewport_texture, performance, label, theme,
dynamic_font, resource_loader, main_loop, project_settings, input_map,
input_event_action, input_event_key, global_constants, scroll_container,
voxel_server]
import
godotapi/[
input, input_event, gd_os, node, scene_tree, packed_scene, sprite, control,
viewport, viewport_texture, performance, label, theme, dynamic_font,
resource_loader, main_loop, project_settings, input_map, input_event_action,
input_event_key, global_constants, scroll_container, voxel_server
]
import core, types, globals, controllers, models / [serializers, units, colors]
import core, types, globals, controllers, models/[serializers, units, colors]
if file_exists(".env"):
dotenv.overload()
@ -18,8 +20,9 @@ when defined(metrics):
ZenContext.init_metrics "main", "worker"
# saved state when restarting worker thread
const savable_flags = {ConsoleVisible, MouseCaptured, Flying, God, AltWalkSpeed,
AltFlySpeed}
const savable_flags = {
ConsoleVisible, MouseCaptured, Flying, God, AltWalkSpeed, AltFlySpeed
}
var saved_transform {.threadvar.}: Transform
var saved_rotation {.threadvar.}: float
@ -57,7 +60,8 @@ gdobj Game of Node:
state.units.value.walk_tree proc(unit: Unit) =
inc unit_count
self.stats.text = \"""
self.stats.text =
\"""
FPS: {fps}
scale_factor: {state.scale_factor}
@ -68,7 +72,8 @@ level: {state.level_name}
{get_stats()}
"""
state.voxel_tasks = parse_int($get_stats()["tasks"].as_dictionary["main_thread"])
state.voxel_tasks =
parse_int($get_stats()["tasks"].as_dictionary["main_thread"])
if time > self.rescale_at:
self.rescale_at = MonoTime.high
@ -79,8 +84,8 @@ level: {state.level_name}
proc rescale*() =
let vp = self.get_viewport().size
state.scale_factor = sqrt(state.config.mega_pixels *
1_000_000.0 / (vp.x * vp.y))
state.scale_factor =
sqrt(state.config.mega_pixels * 1_000_000.0 / (vp.x * vp.y))
self.scaled_viewport.size = vp * state.scale_factor
@ -91,7 +96,7 @@ level: {state.level_name}
if what == main_loop.NOTIFICATION_WM_ABOUT:
alert \"Enu {enu_version}\n\n© 2023 Scott Wadden", "Enu"
proc add_platform_input_actions =
proc add_platform_input_actions() =
let suffix = "." & host_os
for action in get_actions():
let action = action.as_string()
@ -105,19 +110,24 @@ level: {state.level_name}
action_add_event(name, event)
erase_action(action)
proc init* =
proc init*() =
self.process_priority = -100
let
screen_scale = if host_os == "macos":
let screen_scale =
if host_os == "macos":
get_screen_scale(-1)
else:
get_screen_dpi(-1).float / 96.0
var initial_user_config = load_user_config(get_user_data_dir())
Zen.thread_ctx = ZenContext.init(id = \"main-{generate_id()}",
chan_size = 2000, buffer = true, label = "main")
Zen.thread_ctx =
ZenContext.init(
id = \"main-{generate_id()}",
chan_size = 2000,
buffer = true,
label = "main",
)
state = GameState.init
state.nodes.game = self
@ -140,11 +150,15 @@ level: {state.level_name}
listen_address = ""
if host_os == "macosx" and not restarting:
global_menu_add_item("Help", "Documentation", "help".to_variant, "".to_variant)
global_menu_add_item(
"Help", "Documentation", "help".to_variant, "".to_variant
)
global_menu_add_item("Help", "Web Site", "site".to_variant, "".to_variant)
if connect_address == "":
global_menu_add_separator("Help")
global_menu_add_item("Help", "Launch Tutorial", "tutorial".to_variant, "".to_variant)
global_menu_add_item(
"Help", "Launch Tutorial", "tutorial".to_variant, "".to_variant
)
state.config_value.value:
work_dir = get_user_data_dir()
@ -156,8 +170,8 @@ level: {state.level_name}
mega_pixels = uc.mega_pixels ||= 2.0
start_full_screen = uc.start_full_screen ||= true
semicolon_as_colon = uc.semicolon_as_colon ||= false
lib_dir = join_path(get_executable_path().parent_dir(), "..", "..", "..",
"vmlib")
lib_dir =
join_path(get_executable_path().parent_dir(), "..", "..", "..", "vmlib")
connect_address = connect_address
listen_address = listen_address
@ -176,10 +190,11 @@ level: {state.level_name}
set_window_fullscreen state.config.start_full_screen
when defined(metrics):
let metrics_port = if ?get_env("ENU_METRICS_PORT"):
get_env("ENU_METRICS_PORT").parse_int
else:
8000
let metrics_port =
if ?get_env("ENU_METRICS_PORT"):
get_env("ENU_METRICS_PORT").parse_int
else:
8000
{.cast(gcsafe).}:
start_metrics_http_server("0.0.0.0", Port(metrics_port))
@ -191,11 +206,9 @@ level: {state.level_name}
if host_os == "macosx":
state.config_value.value:
lib_dir = join_path(exe_dir.parent_dir, "Resources", "vmlib")
elif host_os == "windows":
state.config_value.value:
lib_dir = join_path(exe_dir, "vmlib")
elif host_os == "linux":
state.config_value.value:
lib_dir = join_path(exe_dir.parent_dir, "lib", "vmlib")
@ -218,18 +231,17 @@ level: {state.level_name}
theme_holder = self.find_node("LeftPanel").as(Container)
theme = theme_holder.theme
font = theme.default_font.as(DynamicFont)
bold_font = theme.get_font("bold_font", "RichTextLabel")
.as(DynamicFont)
bold_font = theme.get_font("bold_font", "RichTextLabel").as(DynamicFont)
font.size = size
bold_font.size = size
theme_holder.theme = theme
method ready* =
method ready*() =
state.nodes.data = state.nodes.game.find_node("Level").get_node("data")
assert not state.nodes.data.is_nil
self.scaled_viewport =
self.get_node("ViewportContainer/Viewport") as Viewport
self.get_node("ViewportContainer/Viewport") as Viewport
self.bind_signals(self.get_viewport(), "size_changed")
self.bind_signals(self.get_tree(), "global_menu_action")
@ -277,7 +289,8 @@ level: {state.level_name}
discard self.get_tree.reload_current_scene()
if Connecting.added:
state.status_message = \"""
state.status_message =
\"""
# Connecting...
@ -320,7 +333,8 @@ Trying to connect to {state.config.connect_address}.
elif action == "site":
discard shell_open("http://getenu.com")
elif action == "tutorial":
state.config_value.value: level_dir = ""
state.config_value.value:
level_dir = ""
state.player.transform = Transform.init(origin = vec3(0, 2, 0))
state.player.rotation = 0
change_loaded_level("tutorial-1", "tutorial")
@ -331,34 +345,35 @@ Trying to connect to {state.config.connect_address}.
var level = config.level
let prefix = config.world & "-"
level.remove_prefix(prefix)
var num = try:
level.parse_int
except ValueError:
1
var num =
try:
level.parse_int
except ValueError:
1
num += diff
change_loaded_level(prefix & $num, state.config.world)
else:
# force a reload of the current world
let current_level = state.config.level_dir
state.config_value.value: level_dir = ""
state.config_value.value: level_dir = current_level
state.config_value.value:
level_dir = ""
state.config_value.value:
level_dir = current_level
method unhandled_input*(event: InputEvent) =
if event of InputEventKey:
let event = InputEventKey(event)
# Left alt support. raw_code is an enu specific addition
if (host_os == "macosx" and event.raw_code == 58) or
(host_os == "windows" and event.raw_code == 56) or
(host_os == "linux" and event.raw_code == 65513):
if (host_os == "macosx" and event.raw_code == 58) or (
host_os == "windows" and event.raw_code == 56
) or (host_os == "linux" and event.raw_code == 65513):
if event.pressed:
state.push_flag CommandMode
else:
state.pop_flag CommandMode
if EditorVisible in state.local_flags or DocsVisible in state.local_flags or
ConsoleVisible in state.local_flags:
ConsoleVisible in state.local_flags:
if event.is_action_pressed("zoom_in"):
self.set_font_size state.config.font_size + 1
elif event.is_action_pressed("zoom_out"):
@ -371,11 +386,11 @@ Trying to connect to {state.config.connect_address}.
state.update_action_index(-1)
# NOTE: alt+enter isn't being picked up on windows if the editor is
# open. Needs investigation.
if event.is_action_pressed("toggle_fullscreen") or (host_os == "windows" and
CommandMode in state.local_flags and EditorVisible in state.local_flags and
event of InputEventKey and
event.as(InputEventKey).scancode == KEY_ENTER):
if event.is_action_pressed("toggle_fullscreen") or (
host_os == "windows" and CommandMode in state.local_flags and
EditorVisible in state.local_flags and event of InputEventKey and
event.as(InputEventKey).scancode == KEY_ENTER
):
set_window_fullscreen not is_window_fullscreen()
var user_config = load_user_config()
state.config_value.value:
@ -393,7 +408,6 @@ Trying to connect to {state.config.connect_address}.
self.switch_world(0)
state.pop_flag ResettingVM
self.get_tree().set_input_as_handled()
elif event.is_action_pressed("pause"):
state.paused = not state.paused
elif event.is_action_pressed("clear_console"):
@ -435,15 +449,13 @@ Trying to connect to {state.config.connect_address}.
method on_meta_clicked(url: string) =
if url.starts_with("nim://"):
assert ?state.open_sign
state.open_sign.owner.eval = url[6..^1]
state.open_sign.owner.eval = url[6 ..^ 1]
elif url.starts_with("unit://"):
let id = url[7..^1]
let id = url[7 ..^ 1]
for unit in state.units:
if unit.id == id:
state.open_unit = unit
return
logger("err", \"Unable to open unit {id}")
elif shell_open(url) != godotcoretypes.Error.OK:
logger("err", \"Unable to open url {url}")

View File

@ -1,14 +1,11 @@
import std / [strformat]
import pkg / [godot]
import godotapi / [node, scene_tree, voxel_buffer]
import core, models / [states]
import std/[strformat]
import pkg/[godot]
import godotapi/[node, scene_tree, voxel_buffer]
import core, models/[states]
export strformat.`&`, states, types
proc bind_signals*(receiver, sender: Object, signals: varargs[string]) =
let send_node = if sender == nil:
state.nodes.game
else:
sender
let send_node = if sender == nil: state.nodes.game else: sender
for signal in signals:
let meth = "_on_" & signal
@ -19,7 +16,9 @@ proc bind_signals*(receiver, sender: Object, signals: varargs[string]) =
proc bind_signals*(receiver: Node, signals: varargs[string]) =
bind_signals(receiver, nil, signals)
proc trigger*(node: Object, signal: string, args: varargs[Variant, `new_variant`]) =
proc trigger*(
node: Object, signal: string, args: varargs[Variant, `new_variant`]
) =
if not node.has_user_signal(signal):
node.add_user_signal(signal)
node.emit_signal(signal, args)

View File

@ -1,9 +1,9 @@
import std / options
import compiler / [syntaxes, reorder, vmdef, msgs]
import compiler / passes {.all.}
import std/options
import compiler/[syntaxes, reorder, vmdef, msgs]
import compiler/passes {.all.}
{.warning[UnusedImport]: off.}
include compiler / [nimeval, pipelines]
include compiler/[nimeval, pipelines]
export Interpreter, VmArgs, PCtx, PStackFrame, TLineInfo
@ -16,10 +16,15 @@ export Interpreter, VmArgs, PCtx, PStackFrame, TLineInfo
# https://github.com/nim-lang/Nim/blob/v2.0.2/compiler/pipelines.nim#L88
# Normal module loading procedure, but makes PContext a param so it can be
# passed to extend_module
proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
stream: PLLStream, ctx: var PContext): bool {.discardable.} =
if graph.stopCompile(): return true
proc processModule*(
graph: ModuleGraph,
module: PSym,
idgen: IdGenerator,
stream: PLLStream,
ctx: var PContext,
): bool {.discardable.} =
if graph.stopCompile():
return true
let bModule = setupEvalGen(graph, module, idgen)
var
@ -40,26 +45,35 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
while true:
syntaxes.openParser(p, fileIdx, s, graph.cache, graph.config)
if not belongsToStdlib(graph, module) or (belongsToStdlib(graph, module) and module.name.s == "distros"):
if not belongsToStdlib(graph, module) or (
belongsToStdlib(graph, module) and module.name.s == "distros"
):
# XXX what about caching? no processing then? what if I change the
# modules to include between compilation runs? we'd need to track that
# in ROD files. I think we should enable this feature only
# for the interactive mode.
if module.name.s != "nimscriptapi":
processImplicitImports graph, graph.config.implicitImports, nkImportStmt, module, ctx, bModule, idgen
processImplicitImports graph, graph.config.implicitIncludes, nkIncludeStmt, module, ctx, bModule, idgen
processImplicitImports graph,
graph.config.implicitImports, nkImportStmt, module, ctx, bModule,
idgen
processImplicitImports graph,
graph.config.implicitIncludes, nkIncludeStmt, module, ctx, bModule,
idgen
checkFirstLineIndentation(p)
block processCode:
if graph.stopCompile(): break processCode
if graph.stopCompile():
break processCode
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break processCode
if n.kind == nkEmpty:
break processCode
# read everything, no streaming possible
var sl = newNodeI(nkStmtList, n.info)
sl.add n
while true:
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break
if n.kind == nkEmpty:
break
sl.add n
prePass(ctx, sl)
@ -67,7 +81,8 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
discard processPipeline(graph, semNode, bModule)
closeParser(p)
if s.kind != llsStdIn: break
if s.kind != llsStdIn:
break
assert graph.pipelinePass == EvalPass
let finalNode = closePContext(graph, ctx, nil)
@ -81,8 +96,12 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
result = true
# from nimeval. Added moduleName
proc selectUniqueSymbol*(i: Interpreter; name: string;
symKinds: set[TSymKind] = {skLet, skVar}; moduleName: string): PSym =
proc selectUniqueSymbol*(
i: Interpreter,
name: string,
symKinds: set[TSymKind] = {skLet, skVar},
moduleName: string,
): PSym =
## Can be used to access a unique symbol of ``name`` and
## the given ``symKinds`` filter.
assert i != nil
@ -98,18 +117,25 @@ proc selectUniqueSymbol*(i: Interpreter; name: string;
result = nil
while s != nil:
if s.kind in symKinds:
if result == nil: result = s
else: return nil # ambiguous
if result == nil:
result = s
else:
return nil # ambiguous
s = nextModuleIter(it, i.graph)
# from nimeval. Added moduleName
proc selectRoutine*(i: Interpreter; name: string, module_name: string): PSym =
proc selectRoutine*(i: Interpreter, name: string, module_name: string): PSym =
## Selects a declared routine (proc/func/etc) from the main module.
## The routine needs to have the export marker ``*``. The only matching
## routine is returned and ``nil`` if it is overloaded.
{.gcsafe.}:
result = selectUniqueSymbol(i, name, {skTemplate, skMacro, skFunc,
skMethod, skProc, skConverter}, moduleName)
result =
selectUniqueSymbol(
i,
name,
{skTemplate, skMacro, skFunc, skMethod, skProc, skConverter},
moduleName,
)
proc resetModule*(i: Interpreter, moduleName: string) =
for iface in i.graph.ifaces:
@ -118,17 +144,16 @@ proc resetModule*(i: Interpreter, moduleName: string) =
iface.module.ast = nil
break
proc loadModule*(i: Interpreter, fileName, code: string, ctx: var PContext
) {.gcsafe.} =
proc loadModule*(
i: Interpreter, fileName, code: string, ctx: var PContext
) {.gcsafe.} =
assert i != nil
var module: PSym
let moduleName = fileName.splitFile.name
for iface in i.graph.ifaces:
if iface.module != nil and iface.module.name.s == moduleName and
fileName == toFullPath(i.graph.config, iface.module.info):
fileName == toFullPath(i.graph.config, iface.module.info):
module = iface.module
break
@ -152,10 +177,15 @@ proc loadModule*(i: Interpreter, fileName, code: string, ctx: var PContext
# adapted from
# https://github.com/nim-lang/Nim/blob/v2.0.2/compiler/pipelines.nim#L88
proc extendModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
stream: PLLStream, ctx: var PContext): bool {.discardable.} =
if graph.stopCompile(): return true
proc extendModule*(
graph: ModuleGraph,
module: PSym,
idgen: IdGenerator,
stream: PLLStream,
ctx: var PContext,
): bool {.discardable.} =
if graph.stopCompile():
return true
let bModule = setupEvalGen(graph, module, idgen)
var
@ -169,15 +199,18 @@ proc extendModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
checkFirstLineIndentation(p)
assert graph.pipelinePass == EvalPass
block processCode:
if graph.stopCompile(): break processCode
if graph.stopCompile():
break processCode
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break processCode
if n.kind == nkEmpty:
break processCode
# read everything, no streaming possible
var sl = newNodeI(nkStmtList, n.info)
sl.add n
while true:
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break
if n.kind == nkEmpty:
break
sl.add n
prePass(ctx, sl)
@ -186,7 +219,8 @@ proc extendModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
discard processPipeline(graph, semNode, bModule)
closeParser(p)
if s.kind != llsStdIn: break
if s.kind != llsStdIn:
break
result = true
@ -204,15 +238,25 @@ proc eval*(i: Interpreter, ctx: var PContext, fileName, code: string) =
let s = llStreamOpen(code)
extendModule(i.graph, module, i.idgen, s, ctx)
proc config*(i: Interpreter): ConfigRef = i.graph.config
proc config*(i: Interpreter): ConfigRef =
i.graph.config
proc `exit_hook=`*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame)) =
proc `exit_hook=`*(
i: Interpreter, hook: proc(c: PCtx, pc: int, tos: PStackFrame)
) =
(PCtx i.graph.vm).exitHook = hook
proc `enter_hook=`*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame, instr: TInstr)) =
proc `enter_hook=`*(
i: Interpreter,
hook: proc(c: PCtx, pc: int, tos: PStackFrame, instr: TInstr),
) =
(PCtx i.graph.vm).enterHook = hook
proc `error_hook=`*(i: Interpreter, hook: proc(config: ConfigRef,
info: TLineInfo; msg: string, severity: Severity) {.gcsafe.}) =
proc `error_hook=`*(
i: Interpreter,
hook:
proc(config: ConfigRef, info: TLineInfo, msg: string, severity: Severity) {.
gcsafe
.},
) =
i.registerErrorHook(hook)

View File

@ -1,6 +1,6 @@
import std / [os, strformat, importutils]
import pkg / compiler / ast except new_node
import pkg / compiler / [vm, vmdef]
import std/[os, strformat, importutils]
import pkg/compiler/ast except new_node
import pkg/compiler/[vm, vmdef]
import core, eval
export Interpreter, VmArgs, set_result
@ -8,9 +8,9 @@ export Interpreter, VmArgs, set_result
log_scope:
topics = "scripting"
const
STDLIB_PATHS = [".", "core", "pure", "pure/collections", "pure/concurrency",
"std", "fusion"]
const STDLIB_PATHS = [
".", "core", "pure", "pure/collections", "pure/concurrency", "std", "fusion"
]
private_access ScriptCtx
@ -18,8 +18,12 @@ proc init*(_: type Interpreter, script_dir, vmlib: string): Interpreter =
let std_paths = STDLIB_PATHS.map_it join_path(vmlib, "stdlib", it)
let source_paths = std_paths & join_path(vmlib, "enu") & @[script_dir]
{.gcsafe.}:
result = create_interpreter("base_api.nim", source_paths, defines =
@{"nimscript": "true", "nimconfig": "true"})
result =
create_interpreter(
"base_api.nim",
source_paths,
defines = @{"nimscript": "true", "nimconfig": "true"},
)
result.config.max_loop_iterations_vm = int.high
proc pause*(ctx: ScriptCtx) =
@ -69,22 +73,29 @@ proc eval*(self: ScriptCtx, code: string): bool =
self.exit_code = some(99)
raise
proc call_proc*(self: ScriptCtx, proc_name: string, args: varargs[PNode, `to_node`]): tuple[paused: bool, result: PNode] =
let foreign_proc = self.interpreter.select_routine(proc_name, module_name = self.module_name)
proc call_proc*(
self: ScriptCtx, proc_name: string, args: varargs[PNode, `to_node`]
): tuple[paused: bool, result: PNode] =
let foreign_proc =
self.interpreter.select_routine(proc_name, module_name = self.module_name)
if foreign_proc == nil:
raise new_exception(VMError, \"script does not export a proc of the name: '{proc_name}'")
result = try:
{.gcsafe.}:
(false, self.interpreter.call_routine(foreign_proc, args))
except VMPause:
(self.exit_code.is_none, nil)
except CatchableError:
self.running = false
self.exit_code = some(99)
raise
raise new_exception(
VMError, \"script does not export a proc of the name: '{proc_name}'"
)
result =
try:
{.gcsafe.}:
(false, self.interpreter.call_routine(foreign_proc, args))
except VMPause:
(self.exit_code.is_none, nil)
except CatchableError:
self.running = false
self.exit_code = some(99)
raise
proc get_var*(self: ScriptCtx, var_name: string, module_name: string): PNode =
let sym = self.interpreter.select_unique_symbol(var_name, module_name = module_name)
let sym =
self.interpreter.select_unique_symbol(var_name, module_name = module_name)
self.interpreter.get_global_value(sym)
proc resume*(self: ScriptCtx): bool =
@ -93,13 +104,14 @@ proc resume*(self: ScriptCtx): bool =
assert not self.tos.is_nil
trace "resuming", script = self.file_name, module = self.module_name
result = try:
{.gcsafe.}:
discard exec_from_ctx(self.ctx, self.pc, self.tos)
false
except VMPause:
self.exit_code.is_none
except CatchableError:
self.running = false
self.exit_code = some(99)
raise
result =
try:
{.gcsafe.}:
discard exec_from_ctx(self.ctx, self.pc, self.tos)
false
except VMPause:
self.exit_code.is_none
except CatchableError:
self.running = false
self.exit_code = some(99)
raise

View File

@ -36,17 +36,22 @@ when defined(windows):
EXCEPTION_CONTINUE_SEARCH = LONG(0)
type
PEXCEPTION_RECORD = ptr object
exceptionCode: DWORD # other fields left out
PEXCEPTION_RECORD =
ptr object
exceptionCode: DWORD # other fields left out
PEXCEPTION_POINTERS = ptr object
exceptionRecord: PEXCEPTION_RECORD
contextRecord: pointer
PEXCEPTION_POINTERS =
ptr object
exceptionRecord: PEXCEPTION_RECORD
contextRecord: pointer
VectoredHandler = proc (p: PEXCEPTION_POINTERS): LONG {.stdcall.}
proc addVectoredExceptionHandler(firstHandler: ULONG,
handler: VectoredHandler): pointer {.
importc: "AddVectoredExceptionHandler", stdcall, dynlib: "kernel32.dll".}
VectoredHandler = proc(p: PEXCEPTION_POINTERS): LONG {.stdcall.}
proc addVectoredExceptionHandler(
firstHandler: ULONG, handler: VectoredHandler
): pointer {.
importc: "AddVectoredExceptionHandler", stdcall, dynlib: "kernel32.dll"
.}
{.push stackTrace: off.}
proc segfaultHandler(p: PEXCEPTION_POINTERS): LONG {.stdcall.} =
@ -55,6 +60,7 @@ when defined(windows):
raise se
else:
result = EXCEPTION_CONTINUE_SEARCH
{.pop.}
discard addVectoredExceptionHandler(0, segfaultHandler)
@ -64,9 +70,9 @@ when defined(windows):
proc segfaultHandler(sig: cint) {.noconv.} =
{.gcsafe.}:
rawRaise se
{.pop.}
c_signal(SIGSEGV, segfaultHandler)
else:
import posix
@ -82,6 +88,7 @@ else:
raise se
else:
quit(1)
{.pop.}
discard sigemptyset(sa.sa_mask)

View File

@ -1,3 +1,3 @@
import models / [states, units, builds, bots, ground, colors, players, signs]
import models/[states, units, builds, bots, ground, colors, players, signs]
export states, units, builds, bots, ground, colors, players, signs

View File

@ -1,48 +1,57 @@
import std / [math, sugar, monotimes, base64]
import godotapi / spatial
import core, models / [states, units, colors]
import std/[math, sugar, monotimes, base64]
import godotapi/spatial
import core, models/[states, units, colors]
include "bot_code_template.nim.nimf"
method code_template*(self: Bot, imports: string): string =
result = bot_code_template(read_file(self.script_ctx.script).encode(
safe = true), self.script_ctx.script, imports)
method on_begin_move*(self: Bot, direction: Vector3, steps: float,
moving_mode: int): Callback =
result =
bot_code_template(
read_file(self.script_ctx.script).encode(safe = true),
self.script_ctx.script,
imports,
)
method on_begin_move*(
self: Bot, direction: Vector3, steps: float, moving_mode: int
): Callback =
# move_mode param is ignored
var duration = 0.0
let
moving = -self.transform.basis.z
finish_time = 1.0 / self.speed * steps
result = proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if duration >= finish_time:
self.velocity_value.touch(vec3())
self.transform_value.origin = self.transform.origin.snapped(vec3(0.1, 0.1, 0.1))
return Done
else:
self.velocity_value.touch(moving * self.speed)
return Running
method on_begin_turn*(self: Bot, axis: Vector3, degrees: float, lean: bool,
move_mode: int): Callback =
result =
proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if duration >= finish_time:
self.velocity_value.touch(vec3())
self.transform_value.origin =
self.transform.origin.snapped(vec3(0.1, 0.1, 0.1))
return Done
else:
self.velocity_value.touch(moving * self.speed)
return Running
method on_begin_turn*(
self: Bot, axis: Vector3, degrees: float, lean: bool, move_mode: int
): Callback =
# move mode param is ignored
let degrees = degrees * -axis.x
var duration = 0.0
var final_basis = self.transform.basis.rotated(UP, deg_to_rad(degrees))
result = proc(delta: float, _: MonoTime): TaskStates =
duration += delta
self.transform_value.basis = self.transform.basis.rotated(UP,
deg_to_rad(degrees * delta * self.speed))
result =
proc(delta: float, _: MonoTime): TaskStates =
duration += delta
self.transform_value.basis =
self.transform.basis.rotated(
UP, deg_to_rad(degrees * delta * self.speed)
)
if duration <= 1.0 / self.speed:
Running
else:
self.transform_value.basis = final_basis
Done
if duration <= 1.0 / self.speed:
Running
else:
self.transform_value.basis = final_basis
Done
proc bot_at*(state: GameState, position: Vector3): Bot =
for unit in state.units:
@ -64,28 +73,35 @@ method reset*(self: Bot) =
method destroy*(self: Bot) =
self.destroy_impl
proc init*(_: type Bot, id = "bot_" & generate_id(), transform = Transform.init,
clone_of: Bot = nil, global = true, parent: Unit = nil): Bot =
var self = Bot(
id: id,
start_transform: transform,
animation_value: ~"auto",
speed: 1.0,
clone_of: clone_of,
start_color: action_colors[black],
parent: parent
)
proc init*(
_: type Bot,
id = "bot_" & generate_id(),
transform = Transform.init,
clone_of: Bot = nil,
global = true,
parent: Unit = nil,
): Bot =
var self =
Bot(
id: id,
start_transform: transform,
animation_value: ~"auto",
speed: 1.0,
clone_of: clone_of,
start_color: action_colors[black],
parent: parent,
)
self.init_unit
if global: self.global_flags += Global
if global:
self.global_flags += Global
result = self
method clone*(self: Bot, clone_to: Unit, id: string): Unit =
var transform = clone_to.transform
result = Bot.init(id = id, transform = transform, clone_of = self,
parent = clone_to)
result =
Bot.init(id = id, transform = transform, clone_of = self, parent = clone_to)
method on_collision*(self: Unit, partner: Model, normal: Vector3) =
self.collisions.add (partner.id, normal)
@ -97,8 +113,12 @@ method off_collision*(self: Unit, partner: Model) =
method worker_thread_joined*(self: Bot) =
state.local_flags.watch:
debug "state flag changed", zid, changes = change.changes,
item = change.item, unit = self.id, zen_id = self.local_flags.id
debug "state flag changed",
zid,
changes = change.changes,
item = change.item,
unit = self.id,
zen_id = self.local_flags.id
if Hover in self.local_flags:
if PrimaryDown.added and state.tool == CodeMode:
@ -118,15 +138,21 @@ method worker_thread_joined*(self: Bot) =
self.parent.units -= self
self.local_flags.watch:
debug "self flag changed", zid, changes = change.changes,
item = change.item, unit = self.id, zen_id = self.local_flags.id
debug "self flag changed",
zid,
changes = change.changes,
item = change.item,
unit = self.id,
zen_id = self.local_flags.id
if Hover.added:
state.push_flag ReticleVisible
if state.tool in {CodeMode, PlaceBot}:
let root = self.find_root(true)
root.walk_tree proc(unit: Unit) = unit.local_flags += Highlight
root.walk_tree proc(unit: Unit) =
unit.local_flags += Highlight
elif Hover.removed:
let root = self.find_root(true)
root.walk_tree proc(unit: Unit) = unit.local_flags -= Highlight
root.walk_tree proc(unit: Unit) =
unit.local_flags -= Highlight
state.pop_flag ReticleVisible

View File

@ -1,7 +1,10 @@
import std / [tables, sets, options, sequtils, math, wrapnils, monotimes, sugar,
deques, macros, base64]
import godotapi / spatial
import core, models / [states, bots, colors, units]
import
std/[
tables, sets, options, sequtils, math, wrapnils, monotimes, sugar, deques,
macros, base64
]
import godotapi/spatial
import core, models/[states, bots, colors, units]
const ChunkSize = vec3(16, 16, 16)
include "build_code_template.nim.nimf"
@ -19,10 +22,15 @@ var
proc draw*(self: Build, position: Vector3, voxel: VoxelInfo) {.gcsafe.}
method code_template*(self: Build, imports: string): string =
result = build_code_template(read_file(self.script_ctx.script).encode(
safe = true), self.script_ctx.script, imports)
result =
build_code_template(
read_file(self.script_ctx.script).encode(safe = true),
self.script_ctx.script,
imports,
)
proc buffer(position: Vector3): Vector3 = (position / ChunkSize).floor
proc buffer(position: Vector3): Vector3 =
(position / ChunkSize).floor
proc contains*(self: Build, position: Vector3): bool =
let buf = position.buffer
@ -33,10 +41,11 @@ proc voxel_info*(self: Build, position: Vector3): VoxelInfo =
proc find_voxel*(self: Build, position: Vector3): Option[VoxelInfo] =
let buf = position.buffer
result = if buf in self:
some(self.chunks[buf][position])
else:
none(VoxelInfo)
result =
if buf in self:
some(self.chunks[buf][position])
else:
none(VoxelInfo)
proc find_first*(units: ZenSeq[Unit], positions: open_array[Vector3]): Build =
for unit in units:
@ -67,9 +76,9 @@ proc add_build(self, source: Build) =
source.parent.units -= source
dont_join = false
proc maybe_join_previous_build(self: Build,
position: Vector3, voxel: VoxelInfo) =
proc maybe_join_previous_build(
self: Build, position: Vector3, voxel: VoxelInfo
) =
if self != current_build:
previous_build = current_build
current_build = self
@ -121,7 +130,6 @@ proc add_voxel(self: Build, position: Vector3, voxel: VoxelInfo) =
if self.batching:
if position notin self.chunks[buffer] or
self.chunks[buffer][position] != voxel:
if buffer notin self.batched_voxels:
self.batched_voxels[buffer] = init_table[Vector3, VoxelInfo]()
self.batched_voxels[buffer][position] = voxel
@ -163,15 +171,12 @@ proc draw*(self: Build, position: Vector3, voxel: VoxelInfo) {.gcsafe.} =
var locations = self.shared.edits[self.id]
locations.del position
self.shared.edits[self.id] = locations
elif ? self.clone_of and position in
self.clone_of.shared.edits[self.clone_of.id] and
self.clone_of.shared.edits[self.clone_of.id][position].kind ==
Hole:
elif ?self.clone_of and
position in self.clone_of.shared.edits[self.clone_of.id] and
self.clone_of.shared.edits[self.clone_of.id][position].kind == Hole:
return
else:
self.add_voxel(position, voxel)
else:
self.global_flags += Dirty
# :( Crash fix hack. Why would shared be nil?
@ -204,16 +209,19 @@ proc remove(self: Build) =
if state.tool notin {CodeMode, PlaceBot}:
state.skip_block_paint = true
draw_normal = self.target_normal
let point = self.target_point - self.target_normal -
(self.target_normal.inverse_normalized * 0.5)
let point =
self.target_point - self.target_normal - (
self.target_normal.inverse_normalized * 0.5
)
skip_point = vec3()
last_point = self.target_point
self.draw(point, (Hole, action_colors[eraser]))
if self.units.len == 0 and not self.chunks.any_it(
it.value.any_it(it.value.color != action_colors[eraser])):
if self.units.len == 0 and
not self.chunks.any_it(
it.value.any_it(it.value.color != action_colors[eraser])
):
if self.parent.is_nil:
state.units -= self
else:
@ -230,7 +238,6 @@ proc fire(self: Build) =
self.draw(point, (Manual, state.selected_color))
elif state.tool == PlaceBot and BlockTargetVisible in state.local_flags and
state.bot_at(global_point).is_nil:
let transform = Transform.init(origin = global_point)
state.units += Bot.init(transform = transform)
elif state.tool == CodeMode:
@ -252,9 +259,9 @@ method apply_changes*(self: Build) =
self.batched_voxels.clear
self.batching = false
method on_begin_move*(self: Build,
direction: Vector3, steps: float, move_mode: int): Callback =
method on_begin_move*(
self: Build, direction: Vector3, steps: float, move_mode: int
): Callback =
let move = self.is_moving(move_mode)
if move:
let steps = steps.float
@ -264,16 +271,17 @@ method on_begin_move*(self: Build,
finish = self.transform.origin + moving * steps
finish_time = 1.0 / self.speed * steps
result = proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if duration >= finish_time:
self.transform_value.origin = finish
return Done
else:
self.transform_value.origin = self.transform.origin +
(moving * self.speed * delta)
result =
proc(delta: float, _: MonoTime): TaskStates =
duration += delta
if duration >= finish_time:
self.transform_value.origin = finish
return Done
else:
self.transform_value.origin =
self.transform.origin + (moving * self.speed * delta)
return Running
return Running
else:
if self.speed == 0:
self.voxels_per_frame = float.high
@ -282,32 +290,29 @@ method on_begin_move*(self: Build,
self.voxels_per_frame = self.speed
var count = 0
result = proc(delta: float, timeout: MonoTime): TaskStates =
while count.float < steps and self.voxels_remaining_this_frame >= 1 and
get_mono_time() < timeout:
result =
proc(delta: float, timeout: MonoTime): TaskStates =
while count.float < steps and self.voxels_remaining_this_frame >= 1 and
get_mono_time() < timeout:
if steps < 1:
self.draw_transform =
self.draw_transform.translated(direction * steps)
else:
self.draw_transform = self.draw_transform.translated(direction)
inc count
self.voxels_remaining_this_frame -= 1
self.drop_block()
if steps < 1:
self.draw_transform =
self.draw_transform.translated(direction * steps)
else:
self.draw_transform =
self.draw_transform.translated(direction)
inc count
self.voxels_remaining_this_frame -= 1
self.drop_block()
if count.float >= steps: NextTask else: Running
if count.float >= steps:
NextTask
else:
Running
method on_begin_turn*(self: Build,
axis: Vector3, degrees: float, lean: bool, move_mode: int): Callback =
let map = if lean:
{LEFT: BACK, RIGHT: FORWARD, BACK: RIGHT, FORWARD: LEFT}.to_table
else:
{LEFT: UP, RIGHT: DOWN, UP: RIGHT, DOWN: LEFT}.to_table
method on_begin_turn*(
self: Build, axis: Vector3, degrees: float, lean: bool, move_mode: int
): Callback =
let map =
if lean:
{LEFT: BACK, RIGHT: FORWARD, BACK: RIGHT, FORWARD: LEFT}.to_table
else:
{LEFT: UP, RIGHT: DOWN, UP: RIGHT, DOWN: LEFT}.to_table
let axis = map[axis]
let move = self.is_moving(move_mode)
if move:
@ -316,23 +321,28 @@ method on_begin_turn*(self: Build,
let axis = self.transform.basis.orthonormalized.xform(axis)
let scale = self.scale
var final_transform = self.transform
final_transform.basis = final_transform.basis.rotated(axis,
deg_to_rad(degrees)).orthonormalized.scaled(vec3(scale, scale, scale))
final_transform.basis =
final_transform.basis
.rotated(axis, deg_to_rad(degrees)).orthonormalized
.scaled(vec3(scale, scale, scale))
result = proc(delta: float, _: MonoTime): TaskStates =
duration += delta
self.transform_value.basis = self.transform.basis.rotated(axis,
deg_to_rad(degrees * delta * self.speed))
result =
proc(delta: float, _: MonoTime): TaskStates =
duration += delta
self.transform_value.basis =
self.transform.basis.rotated(
axis, deg_to_rad(degrees * delta * self.speed)
)
if duration <= 1.0 / self.speed:
Running
else:
self.transform = final_transform
Done
if duration <= 1.0 / self.speed:
Running
else:
self.transform = final_transform
Done
else:
let axis = self.draw_transform.basis.xform(axis)
self.draw_transform_value.basis =
self.draw_transform.basis.rotated(axis, deg_to_rad(degrees))
self.draw_transform.basis.rotated(axis, deg_to_rad(degrees))
self.draw_transform = self.draw_transform.orthonormalized()
@ -368,40 +378,49 @@ method ensure_visible*(self: Build) =
# the unit will still exist but will have no presence in the world, and is
# therefor impossible to select or modify. In that case we want to draw a
# single block.
if self.units.len == 0 and not self.chunks.any_it(
it.value.any_it(it.value.color != action_colors[eraser])):
let color = if self.start_color == action_colors[eraser]:
action_colors[blue]
else:
self.start_color
if self.units.len == 0 and
not self.chunks.any_it(
it.value.any_it(it.value.color != action_colors[eraser])
):
let color =
if self.start_color == action_colors[eraser]:
action_colors[blue]
else:
self.start_color
self.draw(vec3(), (Computed, color))
method destroy*(self: Build) =
self.destroy_impl
proc init*(_: type Build,
id = "build_" & generate_id(), transform = Transform.init,
color = default_color, clone_of: Unit = nil, global = true,
bot_collisions = true, parent: Unit = nil): Build =
var self = Build(
id: id,
chunks: ~(Table[Vector3, Chunk], {SyncLocal, SyncRemote}),
start_transform: transform,
draw_transform_value: ~(Transform.init, flags = {}),
start_color: color,
drawing: true,
bounds_value: ~init_aabb(vec3(), vec3(-1, -1, -1)),
speed: 1.0,
clone_of: clone_of,
bot_collisions: bot_collisions,
parent: parent
)
proc init*(
_: type Build,
id = "build_" & generate_id(),
transform = Transform.init,
color = default_color,
clone_of: Unit = nil,
global = true,
bot_collisions = true,
parent: Unit = nil,
): Build =
var self =
Build(
id: id,
chunks: ~(Table[Vector3, Chunk], {SyncLocal, SyncRemote}),
start_transform: transform,
draw_transform_value: ~(Transform.init, flags = {}),
start_color: color,
drawing: true,
bounds_value: ~init_aabb(vec3(), vec3(-1, -1, -1)),
speed: 1.0,
clone_of: clone_of,
bot_collisions: bot_collisions,
parent: parent,
)
self.init_unit
if global: self.global_flags += Global
if global:
self.global_flags += Global
self.reset()
result = self
@ -412,20 +431,23 @@ method main_thread_joined*(self: Build) =
if Hover.added and state.tool == CodeMode:
if Playing notin state.local_flags:
let root = self.find_root(true)
root.walk_tree proc(unit: Unit) = unit.local_flags += Highlight
root.walk_tree proc(unit: Unit) =
unit.local_flags += Highlight
elif Hover.removed:
let root = self.find_root(true)
root.walk_tree proc(unit: Unit) = unit.local_flags -= Highlight
root.walk_tree proc(unit: Unit) =
unit.local_flags -= Highlight
if TargetMoved.touched:
let length = (self.target_point * self.target_normal -
last_point * self.target_normal).length
let length =
(
self.target_point * self.target_normal -
last_point * self.target_normal
).length
if state.skip_block_paint:
state.skip_block_paint = false
elif state.draw_unit_id == self.id and
self.target_normal == draw_normal and
elif state.draw_unit_id == self.id and self.target_normal == draw_normal and
length <= 5 and self.target_point != skip_point:
if SecondaryDown in state.local_flags:
self.remove
elif PrimaryDown in state.local_flags:
@ -473,9 +495,16 @@ method clone*(self: Build, clone_to: Unit, id: string): Unit =
# we need this off for Potato Zombies, but on for the
# tutorials. Make it configurable somehow.
let bot_collisions = true #not (clone_to of Bot)
let clone = Build.init(id = id, transform = transform, clone_of = self,
global = global, color = self.start_color,
bot_collisions = bot_collisions, parent = clone_to)
let clone =
Build.init(
id = id,
transform = transform,
clone_of = self,
global = global,
color = self.start_color,
bot_collisions = bot_collisions,
parent = clone_to,
)
for loc, info in self.shared.edits[self.id]:
if info.kind != Hole and loc notin clone.shared.edits[clone.id]:

View File

@ -4,20 +4,37 @@ import pkg/chroma
export chroma
converter to_chroma_color*(self: godot.Color): chroma.Color =
cast[chroma.Color](self)
cast[chroma.Color](self)
converter to_godot_color*(self: chroma.Color): godot.Color =
cast[godot.Color](self)
cast[godot.Color](self)
proc col*(hex: string): chroma.Color = hex.parse_hex
proc col*(hex: string): chroma.Color =
hex.parse_hex
type
Colors* = enum
eraser, blue, red, green, black, white, brown
eraser
blue
red
green
black
white
brown
Theme* = enum
normal, comment, entity, keyword, operator, class, storage, constant,
text, number, variable, invalid
normal
comment
entity
keyword
operator
class
storage
constant
text
number
variable
invalid
const ir_black* = [
normal: col"F6F3E8",

View File

@ -1,4 +1,4 @@
import godotapi / spatial
import godotapi/spatial
import core, states, bots, builds
var add_to {.threadvar.}: Build
@ -12,12 +12,15 @@ proc fire(self: Ground, append = false) {.gcsafe.} =
let local = point.local_to(add_to)
add_to.draw(local, (Manual, state.selected_color))
else:
add_to = Build.init(transform = Transform.init(origin = point),
global = true, color = state.selected_color)
add_to =
Build.init(
transform = Transform.init(origin = point),
global = true,
color = state.selected_color,
)
state.units += add_to
add_to.global_flags += Dirty
elif state.tool == PlaceBot and state.bot_at(self.target_point).is_nil:
var t = Transform.init(origin = self.target_point)
var bot = Bot.init(transform = t)
@ -25,10 +28,11 @@ proc fire(self: Ground, append = false) {.gcsafe.} =
bot.global_flags += Dirty
proc init*(_: type Ground, node: Spatial): Ground =
let self = Ground(
global_flags: ~set[GlobalModelFlags],
local_flags: ~(set[LocalModelFlags], {SyncLocal})
)
let self =
Ground(
global_flags: ~set[GlobalModelFlags],
local_flags: ~(set[LocalModelFlags], {SyncLocal}),
)
state.local_flags.changes:
if PrimaryDown.added and Hover in self.local_flags:

View File

@ -1,23 +1,28 @@
import std / [math]
import godotapi / spatial
import core, models / units
import std/[math]
import godotapi/spatial
import core, models/units
proc init*(_: type Player): Player =
result = Player(
id: \"player-{Zen.thread_ctx.id}",
rotation_value: ~0.0,
start_transform: Transform.init(origin = vec3(0, 1, 0)),
input_direction_value: ~Vector3,
cursor_position_value: ~((0, 0))
)
result =
Player(
id: \"player-{Zen.thread_ctx.id}",
rotation_value: ~0.0,
start_transform: Transform.init(origin = vec3(0, 1, 0)),
input_direction_value: ~Vector3,
cursor_position_value: ~((0, 0)),
)
result.init_unit(shared = false)
result.global_flags += Global
method on_begin_turn*(self: Player, direction: Vector3, degrees: float,
lean: bool, move_mode: int): Callback =
method on_begin_turn*(
self: Player, direction: Vector3, degrees: float, lean: bool, move_mode: int
): Callback =
let rotation = floor_mod(self.rotation, 360)
let degrees = if direction == LEFT: -degrees else: degrees
let degrees =
if direction == LEFT:
-degrees
else:
degrees
self.rotation_value.touch rotation - degrees
self.transform = Transform.init(origin = self.transform.origin)

View File

@ -1,8 +1,8 @@
import std / [json, jsonutils, sugar, tables, strutils, os, times, algorithm]
import pkg / zippy / ziparchives_v1
import std/[json, jsonutils, sugar, tables, strutils, os, times, algorithm]
import pkg/zippy/ziparchives_v1
import core except to_json
import models
import controllers / script_controllers / scripting
import controllers/script_controllers/scripting
var load_chunks {.threadvar.}: bool
@ -10,13 +10,14 @@ type LevelInfo = object
enu_version, format_version: string
proc to_json_hook(self: Color): JsonNode =
result = if self == action_colors[eraser]:
%""
else:
for i, color in Colors.enum_fields:
if self == action_colors[Colors(i)]:
return %color
%self.to_html_hex
result =
if self == action_colors[eraser]:
%""
else:
for i, color in Colors.enum_fields:
if self == action_colors[Colors(i)]:
return %color
%self.to_html_hex
proc from_json_hook(self: var Color, json: JsonNode) =
let hex = json.get_str
@ -41,9 +42,9 @@ proc from_json_hook(self: var Vector3, json: JsonNode) =
self.y = json[1].get_float
self.z = json[2].get_float
proc from_json_hook(self: var ZenTable[Vector3, VoxelInfo],
json: JsonNode) {.gcsafe.} =
proc from_json_hook(
self: var ZenTable[Vector3, VoxelInfo], json: JsonNode
) {.gcsafe.} =
assert load_chunks
self = ~Table[Vector3, VoxelInfo]
for chunks in json:
@ -52,9 +53,9 @@ proc from_json_hook(self: var ZenTable[Vector3, VoxelInfo],
let info = chunk[1].json_to(VoxelInfo)
self[location] = info
proc from_json_hook(self: var ZenTable[string, ZenTable[Vector3, VoxelInfo]],
json: JsonNode) =
proc from_json_hook(
self: var ZenTable[string, ZenTable[Vector3, VoxelInfo]], json: JsonNode
) =
assert not load_chunks
for id, edits in json:
for edit in edits:
@ -68,18 +69,23 @@ proc from_json_hook(self: var ZenTable[string, ZenTable[Vector3, VoxelInfo]],
proc from_json_hook(self: var Transform, json: JsonNode) =
self = Transform.init(origin = json["origin"].json_to(Vector3))
let elements = if json["basis"].kind == JObject:
# old way
json["basis"]["elements"]
else:
# new way
json["basis"]
let elements =
if json["basis"].kind == JObject:
# old way
json["basis"]["elements"]
else:
# new way
json["basis"]
self.basis.elements.from_json(elements)
proc from_json_hook(self: var Build, json: JsonNode) =
let color = json["start_color"].json_to(Color)
self = Build.init(id = json["id"].json_to(string), transform =
json["start_transform"].json_to(Transform), color = color)
self =
Build.init(
id = json["id"].json_to(string),
transform = json["start_transform"].json_to(Transform),
color = color,
)
if load_chunks:
var edit = ~Table[Vector3, VoxelInfo]()
@ -89,8 +95,11 @@ proc from_json_hook(self: var Build, json: JsonNode) =
self.shared.edits.from_json(json["edits"])
proc from_json_hook(self: var Bot, json: JsonNode) =
self = Bot.init(id = json["id"].json_to(string), transform =
json["start_transform"].json_to(Transform))
self =
Bot.init(
id = json["id"].json_to(string),
transform = json["start_transform"].json_to(Transform),
)
if not load_chunks:
self.shared.edits.from_json(json["edits"])
@ -98,7 +107,7 @@ proc from_json_hook(self: var Bot, json: JsonNode) =
proc `$`(self: Color): string =
$json_utils.to_json(self)
proc `$`(self: VoxelInfo): string =
proc `$`(self: VoxelInfo): string =
\"[{self.kind.ord}, \"{self.color}\"]"
proc `$`(self: Vector3): string =
@ -108,20 +117,23 @@ proc `$`(self: tuple[voxel: Vector3, info: VoxelInfo]): string =
\"[{self.voxel}, [{int self.info.kind}, {self.info.color}]]"
proc `$`(self: ZenTable[string, ZenTable[Vector3, VoxelInfo]]): string =
let edits = collect:
for id, edit in self.value:
let json = collect:
for voxel, info in edit.value:
$(voxel, info)
if json.len > 0:
let elements = json.join(",\n").indent(2)
\"\"{id}\": [\n{elements}\n]"
let edits =
collect:
for id, edit in self.value:
let json =
collect:
for voxel, info in edit.value:
$(voxel, info)
if json.len > 0:
let elements = json.join(",\n").indent(2)
\"\"{id}\": [\n{elements}\n]"
result = edits.join(",\n")
proc `$`(self: Unit): string =
let elements = self.start_transform.basis.elements.map_it($it).join(",\n")
let edits = $self.shared.edits
result = \"""
result =
\"""
{{
"id": "{self.id}",
@ -157,14 +169,12 @@ proc save_level*(level_dir: string, save_all = false) =
if Server in state.local_flags:
debug "saving level"
let level = LevelInfo(enu_version: enu_version, format_version: "v0.9.2")
write_file level_dir / "level.json",
jsonutils.to_json(level).pretty
write_file level_dir / "level.json", jsonutils.to_json(level).pretty
for unit in state.units:
if save_all or Dirty in unit.global_flags:
unit.save
unit.global_flags -= Dirty
else:
debug "not server. Skipping save."
@ -173,25 +183,22 @@ proc backup_level*(level_dir: string) =
let backup_dir = state.config.world_dir / "backups"
create_dir backup_dir
let backup_file = backup_dir / state.config.level & "_" &
let backup_file =
backup_dir / state.config.level & "_" &
times.now().format("yyyy-MM-dd-HH-mm-ss") & ".zip"
let backups = walk_files(backup_dir /
state.config.level & "_????-??-??-??-??-??.zip").to_seq.sorted
let backups =
walk_files(backup_dir / state.config.level & "_????-??-??-??-??-??.zip").to_seq.sorted
if backups.len > 19:
for file in backups[0..^20]:
for file in backups[0 ..^ 20]:
remove_file file
create_zip_archive(level_dir, backup_file)
proc load_units(parent: Unit) =
let opts = JOptions(allow_missing_keys: true)
let path =
if ?parent:
parent.data_dir
else:
state.config.data_dir
let path = if ?parent: parent.data_dir else: state.config.data_dir
for dir in walk_dirs(path / "*"):
let unit_id = dir.split_path.tail
let file_name = dir / unit_id & ".json"

View File

@ -1,25 +1,34 @@
import godotapi / spatial
import core, states, bots, builds, models / [colors, units]
import godotapi/spatial
import core, states, bots, builds, models/[colors, units]
proc init*(_: type Sign, message: string, more = "", owner: Unit,
transform = Transform.init, width = 1.0, height = 1.0, size = 32,
billboard = false, text_only = false): Sign =
var self = Sign(
id: "sign_" & generate_id(),
message_value: ~message,
more_value: ~more,
width_value: ~width,
height_value: ~height,
size_value: ~size,
billboard_value: ~billboard,
frame_created: state.frame_count,
start_color: action_colors[black],
start_transform: transform,
owner_value: ~owner,
text_only: text_only,
parent: owner
)
proc init*(
_: type Sign,
message: string,
more = "",
owner: Unit,
transform = Transform.init,
width = 1.0,
height = 1.0,
size = 32,
billboard = false,
text_only = false,
): Sign =
var self =
Sign(
id: "sign_" & generate_id(),
message_value: ~message,
more_value: ~more,
width_value: ~width,
height_value: ~height,
size_value: ~size,
billboard_value: ~billboard,
frame_created: state.frame_count,
start_color: action_colors[black],
start_transform: transform,
owner_value: ~owner,
text_only: text_only,
parent: owner,
)
self.init_unit
result = self

View File

@ -1,19 +1,21 @@
import std / [tables, strutils, sequtils, sets, sugar]
import core, models / [colors]
import std/[tables, strutils, sequtils, sets, sugar]
import core, models/[colors]
log_scope:
topics = "state"
ctx = Zen.thread_ctx.id
# only one flag from the group is active at a time
const groups = @[
{EditorFocused, ConsoleFocused, DocsFocused},
{ReticleVisible, BlockTargetVisible},
{Playing, Flying}
]
const groups =
@[
{EditorFocused, ConsoleFocused, DocsFocused},
{ReticleVisible, BlockTargetVisible},
{Playing, Flying}
]
proc resolve_flags(self: GameState) =
debug "resolving flags", flags = self.local_flags.value, wants = self.wants.value
debug "resolving flags",
flags = self.local_flags.value, wants = self.wants.value
var result: set[LocalStateFlags]
for flag in self.wants:
for group in groups:
@ -92,11 +94,13 @@ proc toggle_flag*(self: GameState, flag: LocalStateFlags) =
else:
self.pop_flag flag
proc `+=`*(self: ZenSet[LocalStateFlags], flag: LocalStateFlags) {.error:
"Use `push_flag`, `pop_flag` and `replace_flag`".}
proc `+=`*(
self: ZenSet[LocalStateFlags], flag: LocalStateFlags
) {.error: "Use `push_flag`, `pop_flag` and `replace_flag`".}
proc `-=`*(self: ZenSet[LocalStateFlags], flag: LocalStateFlags) {.error:
"Use `push_flag`, `pop_flag` and `replace_flag`".}
proc `-=`*(
self: ZenSet[LocalStateFlags], flag: LocalStateFlags
) {.error: "Use `push_flag`, `pop_flag` and `replace_flag`".}
proc selected_color*(self: GameState): Color =
action_colors[Colors(ord self.tool)]
@ -120,23 +124,24 @@ proc err*(self: GameState, args: varargs[string, `$`]) =
proc init*(_: type GameState): GameState =
let flags = {SyncLocal}
let self = GameState(
player_value: ~(Player, flags),
local_flags: ~(set[LocalStateFlags], flags),
global_flags: ~(set[GlobalStateFlags], id = "state_global_flags"),
units: ~(seq[Unit], id = "root_units"),
open_unit_value: ~(Unit, flags),
config_value: ~(Config, flags, id = "config"),
tool_value: ~(BlueBlock, flags),
gravity: -80.0,
console: ConsoleModel(log: ~(seq[string], flags)),
open_sign_value: ~(Sign, flags),
wants: ~(seq[LocalStateFlags], flags),
level_name_value: ~("", id = "level_name"),
queued_action_value: ~("", flags),
status_message_value: ~("", flags),
voxel_tasks_value: ~(0, flags)
)
let self =
GameState(
player_value: ~(Player, flags),
local_flags: ~(set[LocalStateFlags], flags),
global_flags: ~(set[GlobalStateFlags], id = "state_global_flags"),
units: ~(seq[Unit], id = "root_units"),
open_unit_value: ~(Unit, flags),
config_value: ~(Config, flags, id = "config"),
tool_value: ~(BlueBlock, flags),
gravity: -80.0,
console: ConsoleModel(log: ~(seq[string], flags)),
open_sign_value: ~(Sign, flags),
wants: ~(seq[LocalStateFlags], flags),
level_name_value: ~("", id = "level_name"),
queued_action_value: ~("", flags),
status_message_value: ~("", flags),
voxel_tasks_value: ~(0, flags),
)
result = self
self.open_unit_value.changes:
if added and change.item != nil:
@ -157,10 +162,10 @@ proc init*(_: type GameState): GameState =
result = self
when is_main_module:
import pkg / print
import pkg/print
on_unhandled_exception = nil
import std / [unittest, sequtils]
import std/[unittest, sequtils]
type Node = ref object
var state = GameState.init
@ -198,8 +203,10 @@ when is_main_module:
added = {}
removed = {}
for change in changes:
if Added in change.changes: added.incl change.item
if Removed in change.changes: removed.incl change.item
if Added in change.changes:
added.incl change.item
if Removed in change.changes:
removed.incl change.item
state.push_flag CommandMode
check:

View File

@ -1,7 +1,7 @@
import std / [os, with, tables]
import godotapi / spatial
from pkg / core / godotcoretypes import Basis
import core, models / [states, colors], libs / interpreters
import std/[os, with, tables]
import godotapi/spatial
from pkg/core/godotcoretypes import Basis
import core, models/[states, colors], libs/interpreters
proc fix_parents*(self: Unit, parent: Unit) =
self.parent = parent
@ -56,9 +56,9 @@ proc find_root*(self: Unit, all_clones = false): Unit =
while parent != nil:
result = parent
if (all_clones and not ?parent.clone_of) or
(not all_clones and Global in parent.global_flags):
if (all_clones and not ?parent.clone_of) or (
not all_clones and Global in parent.global_flags
):
parent = nil
else:
parent = parent.parent
@ -80,22 +80,26 @@ proc data_dir*(self: Unit): string =
proc data_file*(self: Unit): string =
self.data_dir / self.id & ".json"
method main_thread_joined*(self: Unit) {.base, gcsafe.} = discard
method main_thread_joined*(self: Unit) {.base, gcsafe.} =
discard
method worker_thread_joined*(self: Unit) {.base, gcsafe.} = discard
method worker_thread_joined*(self: Unit) {.base, gcsafe.} =
discard
method batch_changes*(self: Unit): bool {.base, gcsafe.} = discard
method batch_changes*(self: Unit): bool {.base, gcsafe.} =
discard
method apply_changes*(self: Unit) {.base, gcsafe.} = discard
method on_begin_move*(self: Unit, direction: Vector3, steps: float,
move_mode: int): Callback {.base, gcsafe.} =
method apply_changes*(self: Unit) {.base, gcsafe.} =
discard
method on_begin_move*(
self: Unit, direction: Vector3, steps: float, move_mode: int
): Callback {.base, gcsafe.} =
fail "override me"
method on_begin_turn*(self: Unit, direction: Vector3, degrees: float,
lean: bool, move_mode: int): Callback {.base, gcsafe.} =
method on_begin_turn*(
self: Unit, direction: Vector3, degrees: float, lean: bool, move_mode: int
): Callback {.base, gcsafe.} =
fail "override me"
method clone*(self: Unit, clone_to: Unit, id: string): Unit {.base, gcsafe.} =
@ -122,7 +126,9 @@ method collect_garbage*(self: Unit) {.base, gcsafe.} =
method ensure_visible*(self: Unit) {.base, gcsafe.} =
discard
method on_collision*(self: Model, partner: Model, normal: Vector3) {.base, gcsafe.} =
method on_collision*(
self: Model, partner: Model, normal: Vector3
) {.base, gcsafe.} =
discard
method off_collision*(self: Model, partner: Model) {.base, gcsafe.} =

View File

@ -1,6 +1,6 @@
import std / [strutils, wrapnils]
import pkg / [godot]
import godotapi / [sprite_3d, ray_cast, spatial]
import std/[strutils, wrapnils]
import pkg/[godot]
import godotapi/[sprite_3d, ray_cast, spatial]
import globals, core, nodes/helpers, models
gdobj AimTarget of Sprite3D:
@ -27,21 +27,20 @@ gdobj AimTarget of Sprite3D:
proc update*(ray: RayCast) =
ray.force_raycast_update()
let collider = if ray.is_colliding():
ray.get_collider() as Spatial
else:
nil
let collider =
if ray.is_colliding():
ray.get_collider() as Spatial
else:
nil
let unit = ?.collider.model
if ?self.target_model:
# :(
if ?self.target_model.global_flags and
self.target_model.global_flags.destroyed:
self.target_model = nil
elif ?self.target_model.local_flags and
self.target_model.local_flags.destroyed:
self.target_model = nil
if unit != self.target_model:
@ -50,10 +49,12 @@ gdobj AimTarget of Sprite3D:
state.pop_flag BlockTargetVisible
self.target_model = unit
# :(
if not (unit == nil or (unit of Sign and Sign(unit).more == "") or
(God notin state.local_flags and (unit of Bot or unit of Build) and
Lock in Unit(unit).find_root.global_flags)):
if not (
unit == nil or (unit of Sign and Sign(unit).more == "") or (
God notin state.local_flags and (unit of Bot or unit of Build) and
Lock in Unit(unit).find_root.global_flags
)
):
unit.local_flags += Hover
if unit of Build or unit of Ground:
state.push_flag BlockTargetVisible
@ -66,8 +67,8 @@ gdobj AimTarget of Sprite3D:
local_collision_point = collider.to_local(ray.get_collision_point())
basis = collider.global_transform.basis
half = vec3(0.5, 0.5, 0.5)
local_normal = (basis.xform_inv(global_normal) / collider.scale)
.snapped(half)
local_normal =
(basis.xform_inv(global_normal) / collider.scale).snapped(half)
factor = local_normal.inverse_normalized() * 0.5
if not local_normal.is_axis_aligned:
@ -75,17 +76,20 @@ gdobj AimTarget of Sprite3D:
# If it isn't, we probably got a corner or something.
return
local_point = (local_collision_point - factor).snapped(vec3(1, 1, 1)) + factor
local_point =
(local_collision_point - factor).snapped(vec3(1, 1, 1)) + factor
global_normal = basis.xform(local_normal) / collider.scale
self.translation = collider.to_global local_point + (local_normal * 0.01) / collider.scale
self.translation =
collider.to_global local_point + (local_normal * 0.01) / collider.scale
self.scale = collider.scale
let align_normal = self.transform.origin + global_normal
self.look_at(align_normal, self.transform.basis.x)
if ?unit:
if (unit.target_point, unit.target_normal) != (local_point, local_normal):
if (unit.target_point, unit.target_normal) !=
(local_point, local_normal):
unit.target_point = local_point
unit.target_normal = local_normal
unit.local_flags.touch TargetMoved

View File

@ -1,16 +1,19 @@
import std / [tables, math]
import std/[tables, math]
import pkg/godot except print
import pkg / [chroma]
import godotapi / [scene_tree, kinematic_body, material, mesh_instance, spatial,
input_event, animation_player, resource_loader, packed_scene,
spatial_material, text_edit]
import globals, core, models / [colors], ui / markdown_label
import ./ queries
import pkg/[chroma]
import
godotapi/[
scene_tree, kinematic_body, material, mesh_instance, spatial, input_event,
animation_player, resource_loader, packed_scene, spatial_material, text_edit
]
import globals, core, models/[colors], ui/markdown_label
import ./queries
gdobj BotNode of KinematicBody:
var
model*: Unit
material* {.gdExport.}, highlight_material* {.gdExport.},
material* {.gdExport.},
highlight_material* {.gdExport.},
selected_material* {.gdExport.}: Material
skin: Spatial
mesh: MeshInstance
@ -31,7 +34,7 @@ gdobj BotNode of KinematicBody:
self.mesh = self.skin.get_node("root/Skeleton/body001").as(MeshInstance)
self.set_default_material()
self.animation_player =
self.skin.get_node("AnimationPlayer").as(AnimationPlayer)
self.skin.get_node("AnimationPlayer").as(AnimationPlayer)
if self.model of Player:
# hack so player model doesn't hover
self.skin.translate DOWN * 0.8
@ -52,7 +55,7 @@ gdobj BotNode of KinematicBody:
debug "setting bot color", color, adjusted
SpatialMaterial(self.material).albedo_color = adjusted
proc set_visibility =
proc set_visibility() =
var color = self.model.color
if Visible in self.model.global_flags:
self.visible = true
@ -90,9 +93,10 @@ gdobj BotNode of KinematicBody:
self.set_default_material()
self.model.global_flags.watch:
if (change.item == Visible and ScriptInitializing notin
self.model.global_flags) or ScriptInitializing.removed:
if (
change.item == Visible and
ScriptInitializing notin self.model.global_flags
) or ScriptInitializing.removed:
self.set_visibility
self.model.local_flags.watch:
@ -108,16 +112,16 @@ gdobj BotNode of KinematicBody:
var velocity_zid: ZID
if self.model of Bot:
let bot = Bot(self.model)
velocity_zid = bot.velocity_value.watch:
if touched:
if bot.animation == "auto":
self.set_walk_animation(change.item.length, false)
velocity_zid =
bot.velocity_value.watch:
if touched:
if bot.animation == "auto":
self.set_walk_animation(change.item.length, false)
bot.animation_value.watch:
if added or touched and change.item in ["", "auto"]:
self.animation_player.play("idle")
elif added:
self.animation_player.play(change.item)
elif self.model of Player:
let player = Player(self.model)
player.rotation_value.watch:
@ -127,8 +131,9 @@ gdobj BotNode of KinematicBody:
player.velocity_value.watch:
if added:
var velocity = change.item.length
self.set_walk_animation(change.item.length,
player.input_direction.z > 0.0)
self.set_walk_animation(
change.item.length, player.input_direction.z > 0.0
)
player.cursor_position_value.watch:
if added:
@ -147,9 +152,10 @@ gdobj BotNode of KinematicBody:
if added:
self.set_color(change.item)
self.transform_zid = self.model.transform_value.watch:
if added:
self.transform = change.item
self.transform_zid =
self.model.transform_value.watch:
if added:
self.transform = change.item
self.model.sight_query_value.watch:
if added:
@ -157,7 +163,7 @@ gdobj BotNode of KinematicBody:
query.run(self.model)
self.model.sight_query = query
proc setup* =
proc setup*() =
self.set_color(self.model.color)
self.track_changes
self.model.sight_ray = self.get_node("SightRay") as RayCast

View File

@ -1,9 +1,12 @@
import std / [tables, bitops]
import pkg / godot except print, Color
import godotapi / [node, voxel_terrain, voxel_mesher_blocky, voxel_tool,
voxel_library, shader_material,resource_loader, packed_scene, ray_cast]
import core, models / [units, builds, colors], globals
import ./ queries
import std/[tables, bitops]
import pkg/godot except print, Color
import
godotapi/[
node, voxel_terrain, voxel_mesher_blocky, voxel_tool, voxel_library,
shader_material, resource_loader, packed_scene, ray_cast
]
import core, models/[units, builds, colors], globals
import ./queries
const
highlight_glow = 1.0
@ -29,19 +32,20 @@ gdobj BuildNode of VoxelTerrain:
self.bind_signals self, "block_loaded", "block_unloaded"
self.default_view_distance = self.max_view_distance.int
proc prepare_materials =
proc prepare_materials() =
if self.model.shared.materials.len == 0:
# generate our own copy of the library materials, so we can manipulate
# them without impacting other builds.
for i in 0..int.high:
for i in 0 .. int.high:
let m = self.get_material(i)
if m.is_nil:
break
else:
let m = m.duplicate.as(ShaderMaterial)
m.set_shader_param("emission_energy", default_glow.to_variant)
self.model.shared.emission_colors.add(m.get_shader_param("emission").
as_color)
self.model.shared.emission_colors.add(
m.get_shader_param("emission").as_color
)
self.model.shared.materials.add(m)
@ -57,25 +61,26 @@ gdobj BuildNode of VoxelTerrain:
proc set_glow(glow: float) =
let library = self.mesher.as(VoxelMesherBlocky).library
for i in 0..<library.voxel_count.int:
for i in 0 ..< library.voxel_count.int:
let m = self.get_material(i).as(ShaderMaterial)
if not m.is_nil:
m.set_shader_param("emission_energy", glow.to_variant)
proc set_highlight() =
let library = self.mesher.as(VoxelMesherBlocky).library
for i in 0..<library.voxel_count.int:
for i in 0 ..< library.voxel_count.int:
let m = self.get_material(i).as(ShaderMaterial)
if not m.is_nil:
if self.error_highlight_on:
m.set_shader_param("emission", action_colors[red].to_variant)
else:
m.set_shader_param("emission", self.model.shared.emission_colors[i].
to_variant)
if Highlight in self.model.local_flags or (HighlightError in
self.model.local_flags and self.error_highlight_on):
m.set_shader_param(
"emission", self.model.shared.emission_colors[i].to_variant
)
if Highlight in self.model.local_flags or (
HighlightError in self.model.local_flags and self.error_highlight_on
):
m.set_shader_param("emission_energy", highlight_glow.to_variant)
else:
m.set_shader_param("emission_energy", self.model.glow.to_variant)
@ -83,13 +88,14 @@ gdobj BuildNode of VoxelTerrain:
proc track_chunk(chunk_id: Vector3) =
if chunk_id in self.model.chunks:
self.draw_block(self.model.chunks[chunk_id])
self.active_chunks[chunk_id] = self.model.chunks[chunk_id].watch:
# `and not modified` isn't required, but the block will be
# replaced on the next iteration anyway.
if removed and not modified:
self.draw(change.item.key, action_colors[eraser])
elif added:
self.draw(change.item.key, change.item.value.color)
self.active_chunks[chunk_id] =
self.model.chunks[chunk_id].watch:
# `and not modified` isn't required, but the block will be
# replaced on the next iteration anyway.
if removed and not modified:
self.draw(change.item.key, action_colors[eraser])
elif added:
self.draw(change.item.key, change.item.value.color)
self.draw_block(self.model.chunks[chunk_id])
else:
self.active_chunks[chunk_id] = empty_zid
@ -105,7 +111,7 @@ gdobj BuildNode of VoxelTerrain:
self.model.chunks[chunk_id].untrack(zid)
self.active_chunks.del(chunk_id)
proc set_visibility =
proc set_visibility() =
if Visible in self.model.global_flags:
self.visible = true
@ -120,13 +126,14 @@ gdobj BuildNode of VoxelTerrain:
self.visible = false
proc track_chunks() =
self.chunks_zid = self.model.chunks.watch:
let id = change.item.key
if id in self.active_chunks:
if added:
self.track_chunk(change.item.key)
elif removed:
self.active_chunks[id] = empty_zid
self.chunks_zid =
self.model.chunks.watch:
let id = change.item.key
if id in self.active_chunks:
if added:
self.track_chunk(change.item.key)
elif removed:
self.active_chunks[id] = empty_zid
proc untrack_chunks() =
Zen.thread_ctx.untrack(self.chunks_zid)
@ -148,17 +155,16 @@ gdobj BuildNode of VoxelTerrain:
self.track_chunks()
self.model.global_flags.watch:
if (change.item == Visible and ScriptInitializing notin
self.model.global_flags) or ScriptInitializing.removed:
if (
change.item == Visible and
ScriptInitializing notin self.model.global_flags
) or ScriptInitializing.removed:
self.set_visibility
elif Resetting.added:
self.untrack_chunks()
let model = self.model
self.generator = nil
self.stream = nil
elif Resetting.removed:
self.generator = gdnew[VoxelGeneratorFlat]()
self.track_chunks()
@ -185,9 +191,10 @@ gdobj BuildNode of VoxelTerrain:
self.model.transform = self.transform
self.max_view_distance = int(self.default_view_distance.float / scale)
self.transform_zid = self.model.transform_value.watch:
if added:
self.transform = change.item
self.transform_zid =
self.model.transform_value.watch:
if added:
self.transform = change.item
self.model.sight_query_value.watch:
if added:
@ -210,7 +217,7 @@ gdobj BuildNode of VoxelTerrain:
self.toggle_error_highlight_at = get_mono_time() + error_flash_time
self.set_highlight()
proc setup* =
proc setup*() =
let was_skipping_join = dont_join
dont_join = true

View File

@ -1,4 +1,4 @@
import godotapi / [mesh_instance]
import godotapi/[mesh_instance]
import pkg/godot except print
import core, globals, models

View File

@ -3,13 +3,14 @@ import godotapi/node
import core, bot_node, build_node, ground_node, selection_area, sign_node
proc model*(self: Object): Model =
result = if self of SelectionArea:
SelectionArea(self).bot.model
elif self of BuildNode:
BuildNode(self).model
elif self of GroundNode:
GroundNode(self).model
elif self of StaticBody and StaticBody(self).name == "SignBody":
SignNode(StaticBody(self).get_parent.get_parent).model
else:
nil
result =
if self of SelectionArea:
SelectionArea(self).bot.model
elif self of BuildNode:
BuildNode(self).model
elif self of GroundNode:
GroundNode(self).model
elif self of StaticBody and StaticBody(self).name == "SignBody":
SignNode(StaticBody(self).get_parent.get_parent).model
else:
nil

View File

@ -1,16 +1,19 @@
import std / [math, sugar]
from std / times import `+`
import pkg / godot except print
import godotapi / [kinematic_body, spatial, input, input_event,
input_event_mouse_motion, input_event_joypad_motion, ray_cast, scene_tree,
input_event_pan_gesture, viewport, camera, global_constants,
collision_shape, kinematic_collision, packed_scene, resource_loader]
import core, globals, nodes / helpers
import std/[math, sugar]
from std/times import `+`
import pkg/godot except print
import
godotapi/[
kinematic_body, spatial, input, input_event, input_event_mouse_motion,
input_event_joypad_motion, ray_cast, scene_tree, input_event_pan_gesture,
viewport, camera, global_constants, collision_shape, kinematic_collision,
packed_scene, resource_loader
]
import core, globals, nodes/helpers
import aim_target, models
proc handle_collisions(self: Player, collisions:
seq[KinematicCollision]) {.inline, gcsafe.} =
proc handle_collisions(
self: Player, collisions: seq[KinematicCollision]
) {.inline, gcsafe.} =
var colliders: HashSet[Model]
for collision in collisions:
let collider = collision.collider
@ -67,8 +70,10 @@ gdobj PlayerNode of KinematicBody:
state.set_flag Flying, value
proc get_look_direction(): Vector2 =
vec2(get_action_strength("look_right") - get_action_strength("look_left"),
get_action_strength("look_up") - get_action_strength("look_down"))
vec2(
get_action_strength("look_right") - get_action_strength("look_left"),
get_action_strength("look_up") - get_action_strength("look_down"),
)
proc update_rotation(offset: Vector2) =
var r = self.camera_rig.rotation
@ -79,12 +84,18 @@ gdobj PlayerNode of KinematicBody:
self.camera_rig.rotation = r
proc get_input_direction(): Vector3 =
vec3(get_action_strength("move_right") - get_action_strength("move_left"),
get_action_strength("jump") - get_action_strength("crouch"),
get_action_strength("move_back") - get_action_strength("move_front"))
vec3(
get_action_strength("move_right") - get_action_strength("move_left"),
get_action_strength("jump") - get_action_strength("crouch"),
get_action_strength("move_back") - get_action_strength("move_front"),
)
proc calculate_velocity(velocity_current: Vector3, move_direction: Vector3,
delta: float, flying, alt_speed: bool): Vector3 =
proc calculate_velocity(
velocity_current: Vector3,
move_direction: Vector3,
delta: float,
flying, alt_speed: bool,
): Vector3 =
let speed =
if not flying and not (alt_speed xor AltWalkSpeed in state.local_flags):
vec3(state.config.walk_speed)
@ -98,17 +109,20 @@ gdobj PlayerNode of KinematicBody:
result = move_direction * delta * speed
if not flying:
let float_time = if alt_speed:
float_time + float_time
else:
float_time
let floating = self.jump_down and ?self.jump_time and
self.jump_time.get + float_time > get_mono_time()
let float_time =
if alt_speed:
float_time + float_time
else:
float_time
let floating =
self.jump_down and ?self.jump_time and
self.jump_time.get + float_time > get_mono_time()
let gravity = if floating:
state.gravity / 4
else:
state.gravity
let gravity =
if floating:
state.gravity / 4
else:
state.gravity
result.y = velocity_current.y + gravity * delta
method ready*() =
@ -146,24 +160,24 @@ gdobj PlayerNode of KinematicBody:
self.transform = change.item
self.camera_rig.rotation = vec3(0, deg_to_rad self.model.rotation, 0)
self.rotation_zid = self.model.rotation_value.watch:
if added or touched:
self.camera_rig.rotation = vec3(0, deg_to_rad change.item, 0)
self.rotation_zid =
self.model.rotation_value.watch:
if added or touched:
self.camera_rig.rotation = vec3(0, deg_to_rad change.item, 0)
self.velocity_zid = self.model.velocity_value.watch:
if added:
self.velocity = change.item
self.velocity_zid =
self.model.velocity_value.watch:
if added:
self.velocity = change.item
proc current_raycast*: RayCast =
if MouseCaptured in state.local_flags:
self.aim_ray
else:
self.world_ray
proc current_raycast*(): RayCast =
if MouseCaptured in state.local_flags: self.aim_ray else: self.world_ray
method process*(delta: float) =
self.model.velocity_value.pause self.velocity_zid:
self.model.velocity = self.velocity
if EditorVisible notin state.local_flags or CommandMode in state.local_flags:
if EditorVisible notin state.local_flags or
CommandMode in state.local_flags:
var transform = self.camera_rig.global_transform
transform.origin = self.global_transform.origin + self.position_start
@ -186,11 +200,12 @@ gdobj PlayerNode of KinematicBody:
let ray_length = if state.tool == CodeMode: 200.0 else: 100.0
if MouseCaptured notin state.local_flags:
let
mouse_pos = self.get_viewport().get_mouse_position() *
float state.scale_factor
mouse_pos =
self.get_viewport().get_mouse_position() * float state.scale_factor
cast_from = self.camera.project_ray_origin(mouse_pos)
cast_to = self.aim_ray.translation +
self.camera.project_ray_normal(mouse_pos) * ray_length
cast_to =
self.aim_ray.translation +
self.camera.project_ray_normal(mouse_pos) * ray_length
self.world_ray.cast_to = cast_to
self.world_ray.translation = cast_from
@ -210,7 +225,10 @@ gdobj PlayerNode of KinematicBody:
process_input =
EditorVisible notin state.local_flags or CommandMode in state.local_flags
input_direction =
if process_input: self.get_input_direction() else: vec3()
if process_input:
self.get_input_direction()
else:
vec3()
basis = self.camera_rig.global_transform.basis
right = basis.x * input_direction.x
@ -225,17 +243,20 @@ gdobj PlayerNode of KinematicBody:
move_direction.y = 0
move_direction += up
var velocity = self.calculate_velocity(self.velocity, move_direction,
delta, self.flying, self.alt_speed)
var velocity =
self.calculate_velocity(
self.velocity, move_direction, delta, self.flying, self.alt_speed
)
self.model.input_direction = input_direction
self.velocity = self.move_and_slide(velocity, UP)
self.model.transform = self.transform
let collisions = collect:
for i in 0..(self.get_slide_count - 1):
self.get_slide_collision(i)
let collisions =
collect:
for i in 0 .. (self.get_slide_count - 1):
self.get_slide_collision(i)
handle_collisions(self.model, collisions)
@ -247,8 +268,12 @@ gdobj PlayerNode of KinematicBody:
self.down_ray.translation = move_direction * 0.3 + vec3(0, 1, 0)
if self.down_ray.is_colliding():
let length = 1.85
let diff = length - (self.down_ray.global_transform.origin -
self.down_ray.get_collision_point).y
let diff =
length -
(
self.down_ray.global_transform.origin -
self.down_ray.get_collision_point
).y
if diff > 0 and (self.is_on_floor() or not self.boosted):
let boost = 16.1 * cbrt(diff)
if boost > self.velocity.y:
@ -260,12 +285,11 @@ gdobj PlayerNode of KinematicBody:
self.translation = vec3(0, 100, 0)
proc has_active_input(device: int): bool =
for axis in 0..JOY_AXIS_MAX:
for axis in 0 .. JOY_AXIS_MAX:
if axis != JOY_ANALOG_L2 and axis != JOY_ANALOG_R2 and
get_joy_axis(device, axis).abs >= 0.2:
return true
for button in 0..JOY_BUTTON_MAX:
for button in 0 .. JOY_BUTTON_MAX:
if is_joy_button_pressed(device, button):
return true
@ -280,9 +304,9 @@ gdobj PlayerNode of KinematicBody:
self.input_relative += event.as(InputEventMouseMotion).relative()
else:
self.skip_next_mouse_move = false
if EditorVisible in state.local_flags and not self.skip_release and
(event of InputEventJoypadButton or event of InputEventJoypadMotion):
if EditorVisible in state.local_flags and not self.skip_release and (
event of InputEventJoypadButton or event of InputEventJoypadMotion
):
let active_input = self.has_active_input(event.device.int)
if CommandMode in state.local_flags and not active_input:
self.command_timer = input_command_timeout
@ -326,9 +350,7 @@ gdobj PlayerNode of KinematicBody:
elif event.is_action_released("run"):
self.alt_speed = false
if event of InputEventPanGesture and
state.tool notin {CodeMode, PlaceBot}:
if event of InputEventPanGesture and state.tool notin {CodeMode, PlaceBot}:
let pan = event as InputEventPanGesture
self.pan_delta += pan.delta.y
if self.pan_delta > 2:
@ -351,7 +373,8 @@ gdobj PlayerNode of KinematicBody:
elif event.is_action_released("remove"):
state.pop_flag SecondaryDown
proc get_player*(): PlayerNode = PlayerNode(state.nodes.player)
proc get_player*(): PlayerNode =
PlayerNode(state.nodes.player)
var scene {.threadvar.}: PackedScene
proc init*(_: type PlayerNode): PlayerNode =

View File

@ -1,6 +1,6 @@
import pkg / godot
import godotapi / [ray_cast]
import core, models / [units]
import pkg/godot
import godotapi/[ray_cast]
import core, models/[units]
proc run*(query: var SightQuery, source: Unit) =
assert not ?query.answer

View File

@ -1,10 +1,9 @@
import pkg / [godot]
import godotapi / [area, control]
import pkg/[godot]
import godotapi/[area, control]
import core, globals, nodes/bot_node
gdobj SelectionArea of Area:
var
bot*: BotNode
var bot*: BotNode
method ready*() =
self.bot = self.get_node("..") as BotNode

View File

@ -1,8 +1,10 @@
import pkg / [godot]
import godotapi / [spatial, resource_loader, packed_scene, collision_shape,
mesh_instance, quad_mesh, spatial_material, viewport, style_box_flat,
text_edit]
import ui / [markdown_label, editor]
import pkg/[godot]
import
godotapi/[
spatial, resource_loader, packed_scene, collision_shape, mesh_instance,
quad_mesh, spatial_material, viewport, style_box_flat, text_edit
]
import ui/[markdown_label, editor]
import core
const
@ -20,7 +22,7 @@ gdobj SignNode of Spatial:
var counter: int
var expanded: bool
proc set_visibility =
proc set_visibility() =
if Hide in self.model.local_flags:
self.visible = false
elif Visible in self.model.global_flags:
@ -47,7 +49,7 @@ gdobj SignNode of Spatial:
self.quad.size = vec2(self.model.width, self.model.width * ratio)
self.shape.scale = vec3(self.model.width, self.model.width * ratio, 1)
proc setup* =
proc setup*() =
debug "sign setup", sign = self.model.id
var mesh = self.get_node("MeshInstance") as MeshInstance
@ -67,7 +69,7 @@ gdobj SignNode of Spatial:
# hide scrollbars
ScrollBar(child).rect_scale = vec2()
proc resize =
proc resize() =
debug "sign resize", sign = self.model.id
var
@ -100,10 +102,7 @@ gdobj SignNode of Spatial:
resize()
self.material.params_billboard_mode =
if self.model.billboard:
BILLBOARD_ENABLED
else:
BILLBOARD_DISABLED
if self.model.billboard: BILLBOARD_ENABLED else: BILLBOARD_DISABLED
if self.model.text_only:
text_edit.text = self.model.message
@ -130,9 +129,10 @@ gdobj SignNode of Spatial:
self.transform = change.item
self.model.global_flags.watch:
if (change.item == Visible and ScriptInitializing notin
self.model.global_flags) or ScriptInitializing.removed:
if (
change.item == Visible and
ScriptInitializing notin self.model.global_flags
) or ScriptInitializing.removed:
self.set_visibility
state.local_flags.watch:

View File

@ -1,12 +1,12 @@
import std / [tables, monotimes, sets, options, macros]
import godotapi / [spatial, ray_cast]
import std/[tables, monotimes, sets, options, macros]
import godotapi/[spatial, ray_cast]
import pkg/core/godotcoretypes except Color
import pkg / core / [vector3, basis, aabb, godotbase]
import pkg / compiler / [ast, lineinfos, semdata]
import pkg / [model_citizen]
import models / colors, libs / [eval]
import pkg/core/[vector3, basis, aabb, godotbase]
import pkg/compiler/[ast, lineinfos, semdata]
import pkg/[model_citizen]
import models/colors, libs/[eval]
from pkg / godot import NimGodotObject
from pkg/godot import NimGodotObject
export Vector3, Transform, vector3, basis, AABB, aabb
export godotbase except print
@ -15,27 +15,64 @@ export Interpreter
type
EnuError* = object of CatchableError
LocalStateFlags* = enum
CommandMode, EditorVisible, ConsoleVisible,
BlockTargetVisible, ReticleVisible, DocsVisible, MouseCaptured,
PrimaryDown, SecondaryDown, EditorFocused, ConsoleFocused, DocsFocused,
Playing, Flying, God, AltWalkSpeed, AltFlySpeed,
LoadingScript, Server, Quitting, ResettingVM, NeedsRestart, Connecting
CommandMode
EditorVisible
ConsoleVisible
BlockTargetVisible
ReticleVisible
DocsVisible
MouseCaptured
PrimaryDown
SecondaryDown
EditorFocused
ConsoleFocused
DocsFocused
Playing
Flying
God
AltWalkSpeed
AltFlySpeed
LoadingScript
Server
Quitting
ResettingVM
NeedsRestart
Connecting
GlobalStateFlags* = enum
LoadingLevel
LocalModelFlags* = enum
Hover, TargetMoved, Highlight, HighlightError, Hide
Hover
TargetMoved
Highlight
HighlightError
Hide
GlobalModelFlags* = enum
Global, Visible, Lock, Ready, ScriptInitializing, Dirty, Resetting
Global
Visible
Lock
Ready
ScriptInitializing
Dirty
Resetting
Tools* = enum
CodeMode, BlueBlock, RedBlock, GreenBlock, BlackBlock, WhiteBlock,
BrownBlock, PlaceBot, Disabled
CodeMode
BlueBlock
RedBlock
GreenBlock
BlackBlock
WhiteBlock
BrownBlock
PlaceBot
Disabled
TaskStates* = enum
Running, Done, NextTask
Running
Done
NextTask
ConsoleModel* = ref object
log*: ZenSeq[string]
@ -48,11 +85,7 @@ type
open_unit_value*: ZenValue[Unit]
tool_value*: ZenValue[Tools]
gravity*: float
nodes*: tuple[
game: Node,
data: Node,
player: Node
]
nodes*: tuple[game: Node, data: Node, player: Node]
player_value*: ZenValue[Player]
units*: ZenSeq[Unit]
ground*: Ground
@ -135,11 +168,11 @@ type
text_only*: bool
VoxelKind* = enum
Hole, Manual, Computed
Hole
Manual
Computed
VoxelInfo* = tuple
kind: VoxelKind
color: Color
VoxelInfo* = tuple[kind: VoxelKind, color: Color]
Chunk* = ZenTable[Vector3, VoxelInfo]
@ -234,7 +267,8 @@ type
VMError* = object of CatchableError
QuitKind* = enum
Unknown, Timeout
Unknown
Timeout
VMQuit* = object of VMError
info*: TLineInfo

View File

@ -1,6 +1,6 @@
import godotapi / [button, style_box_flat]
import godotapi/[button, style_box_flat]
import godot
import ".." / [core, globals]
import ".."/[core, globals]
gdobj ActionButton of Button:
method ready*() =

View File

@ -1,12 +1,14 @@
import godotapi / [text_edit, scene_tree, node, input_event, input_event_key,
rich_text_label, global_constants]
import
godotapi/[
text_edit, scene_tree, node, input_event, input_event_key, rich_text_label,
global_constants
]
import godot
import std / strutils
import std/strutils
import core, globals
gdobj Console of RichTextLabel:
var
default_mouse_filter: int64
var default_mouse_filter: int64
proc init*() =
state.local_flags.changes:

View File

@ -1,8 +1,11 @@
import std / [strutils, tables]
import pkg / [godot]
import pkg / compiler / [lineinfos]
import godotapi / [text_edit, scene_tree, node, input_event, global_constants,
input_event_key, style_box_flat, gd_os]
import std/[strutils, tables]
import pkg/[godot]
import pkg/compiler/[lineinfos]
import
godotapi/[
text_edit, scene_tree, node, input_event, global_constants, input_event_key,
style_box_flat, gd_os
]
import core, globals
import models except Color
@ -24,7 +27,7 @@ gdobj Editor of TextEdit:
let column = int self.cursor_get_column - 1
if column > 0:
let
line = self.get_line(self.cursor_get_line)[0..column]
line = self.get_line(self.cursor_get_line)[0 .. column]
stripped = line.strip()
if stripped.high > 0:
@ -40,9 +43,7 @@ gdobj Editor of TextEdit:
if not event.is_nil and event.pressed:
if event.scancode == KEY_ENTER:
self.indent_new_line()
if event.scancode == KEY_SEMICOLON and
state.config.semicolon_as_colon:
if event.scancode == KEY_SEMICOLON and state.config.semicolon_as_colon:
self.insert_text_at_cursor(":")
self.get_tree.set_input_as_handled()
elif event.scancode == KEY_HOME:
@ -53,17 +54,19 @@ gdobj Editor of TextEdit:
self.get_tree.set_input_as_handled()
method unhandled_input*(event: InputEvent) =
if EditorFocused in state.local_flags and event.is_action_pressed("ui_cancel"):
if not (event of InputEventJoypadButton) or CommandMode notin state.local_flags:
if EditorFocused in state.local_flags and
event.is_action_pressed("ui_cancel"):
if not (event of InputEventJoypadButton) or
CommandMode notin state.local_flags:
state.open_unit.code = Code.init(self.text)
state.open_unit = nil
self.get_tree().set_input_as_handled()
proc clear_errors =
for i in 0..<self.get_line_count():
proc clear_errors() =
for i in 0 ..< self.get_line_count():
self.set_line_as_marked(i, false)
proc highlight_errors =
proc highlight_errors() =
self.clear_executing_line()
if ?state.open_unit:
for err in state.open_unit.errors:
@ -82,7 +85,7 @@ gdobj Editor of TextEdit:
state.player.cursor_position =
(int self.cursor_get_line, int self.cursor_get_column)
method ready* =
method ready*() =
self.bind_signals(self, "text_changed", "cursor_changed")
var stylebox = self.get_stylebox("normal").as(StyleBoxFlat)
self.og_bg_color = stylebox.bg_color
@ -104,13 +107,14 @@ gdobj Editor of TextEdit:
self.visible = false
state.player.open_code = ""
else:
line_zid = unit.current_line_value.changes:
if added:
# only update the executing line if the code hasn't been changed.
if self.text == state.open_unit.code.nim:
self.executing_line = change.item - 1
else:
self.clear_executing_line()
line_zid =
unit.current_line_value.changes:
if added:
# only update the executing line if the code hasn't been changed.
if self.text == state.open_unit.code.nim:
self.executing_line = change.item - 1
else:
self.clear_executing_line()
self.visible = true
self.text = state.open_unit.code.nim
state.player.open_code = self.text
@ -137,7 +141,6 @@ gdobj Editor of TextEdit:
self.modulate = dimmed_alpha
self.release_focus
elif CommandMode.removed:
if EditorVisible in state.local_flags:
self.modulate = solid_alpha

View File

@ -1,9 +1,12 @@
import std / [lists, algorithm, tables]
import pkg / [godot, markdown]
import godotapi / [rich_text_label, scroll_container, text_edit, theme,
dynamic_font, dynamic_font_data, style_box_flat, main_loop]
import core, globals, ui / editor
import models / colors except Color
import std/[lists, algorithm, tables]
import pkg/[godot, markdown]
import
godotapi/[
rich_text_label, scroll_container, text_edit, theme, dynamic_font,
dynamic_font_data, style_box_flat, main_loop
]
import core, globals, ui/editor
import models/colors except Color
export scroll_container
@ -19,7 +22,7 @@ gdobj MarkdownLabel of ScrollContainer:
var
markdown* {.gd_export, hint: MultilineText.}: string
old_markdown: string
default_font* {.gd_export.}:DynamicFont
default_font* {.gd_export.}: DynamicFont
italic_font* {.gd_export.}: DynamicFont
bold_font* {.gd_export.}: DynamicFont
bold_italic_font* {.gd_export.}: DynamicFont
@ -46,12 +49,8 @@ gdobj MarkdownLabel of ScrollContainer:
self.current_label.visible = true
state.nodes.game.bind_signals(self.current_label, "meta_clicked")
proc set_font_sizes =
var size =
if self.size > 0:
self.size
else:
state.config.font_size
proc set_font_sizes() =
var size = if self.size > 0: self.size else: state.config.font_size
self.local_default_font.size = size
self.local_italic_font.size = size
@ -115,13 +114,14 @@ gdobj MarkdownLabel of ScrollContainer:
self.og_text_edit.add_font_override("font", self.local_mono_font)
self.og_label.add_font_override("normal_font", self.local_default_font)
self.zid = state.config_value.changes:
if added:
self.set_font_sizes()
self.zid =
state.config_value.changes:
if added:
self.set_font_sizes()
self.update
method on_resized =
method on_resized() =
if not self.resized:
self.set_font_sizes()
self.resized = true
@ -139,49 +139,45 @@ gdobj MarkdownLabel of ScrollContainer:
self.needs_margin = false
if t of Heading:
label.with(push_font self.local_header_font,
push_color ir_black[keyword])
label.with(
push_font self.local_header_font, push_color ir_black[keyword]
)
self.render_markdown t
label.with(pop, pop, newline)
self.needs_margin = true
elif t of Em:
label.push_font self.local_italic_font
self.render_markdown t
label.pop
elif t of Strong:
label.push_font self.local_bold_font
self.render_markdown t
label.pop
elif t of CodeSpan:
label.with(push_font self.local_mono_font, push_color ir_black[number],
add_text t.doc)
label.with(
push_font self.local_mono_font,
push_color ir_black[number],
add_text t.doc,
)
self.render_markdown t
label.with(pop, pop)
elif t of CodeBlock:
self.needs_margin = false
var editor = self.add_text_edit()
editor.text = t.doc[0..^2]
editor.text = t.doc[0 ..^ 2]
editor.visible = true
self.container.add_child editor
self.add_label()
elif t of Paragraph:
self.render_markdown(t, inline_blocks = inline_blocks)
if not inline_blocks:
label.newline
self.needs_margin = true
elif t of OL:
self.render_markdown(t, 1)
elif t of UL:
self.render_markdown(t)
elif t of LI:
label.with(push_table 2, push_cell, push_font self.local_mono_font)
@ -193,7 +189,6 @@ gdobj MarkdownLabel of ScrollContainer:
label.with(pop, pop, push_cell)
self.render_markdown(t, inline_blocks = true)
label.with(pop, pop, newline)
elif t of Link:
let t = Link(t)
label.push_color(ir_black[variable])
@ -204,13 +199,10 @@ gdobj MarkdownLabel of ScrollContainer:
label.pop
label.pop
label.pop
elif t of Text:
label.add_text(t.doc.replace("\n", " "))
elif t of SoftBreak:
label.add_text " "
else:
self.render_markdown(t)
@ -229,7 +221,7 @@ gdobj MarkdownLabel of ScrollContainer:
self.current_label = nil
self.old_markdown = self.markdown
var root = Document()
discard markdown(self.markdown.dedent, root=root)
discard markdown(self.markdown.dedent, root = root)
self.needs_margin = false
self.render_markdown(root)
self.set_font_sizes()

View File

@ -1,7 +1,10 @@
import godotapi / [viewport, camera, mesh_instance, material, camera,
viewport_texture, image, resource_loader]
import
godotapi/[
viewport, camera, mesh_instance, material, camera, viewport_texture, image,
resource_loader
]
import godot
import ".." / [core, globals]
import ".."/[core, globals]
gdobj PreviewMaker of Viewport:
var
@ -26,7 +29,9 @@ gdobj PreviewMaker of Viewport:
self.callback = nil
self.skip_next = false
proc generate_block_preview*(material_name: string, callback: proc(preview: Image) {.gcsafe.}) =
proc generate_block_preview*(
material_name: string, callback: proc(preview: Image) {.gcsafe.}
) =
let material = load(\"res://materials/{material_name}.tres") as Material
self.cube.visible = true
self.bot.visible = false
@ -37,7 +42,9 @@ gdobj PreviewMaker of Viewport:
self.callback = callback
self.skip_next = true
proc generate_object_preview*(object_name: string, callback: proc(preview: Image) {.gcsafe.}) =
proc generate_object_preview*(
object_name: string, callback: proc(preview: Image) {.gcsafe.}
) =
self.cube.visible = false
self.bot.visible = true
self.render_target_update_mode = UPDATE_ONCE

View File

@ -1,7 +1,7 @@
import pkg / [godot]
import godotapi / [margin_container, input_event, scene_tree]
import ui / markdown_label
import core, models / [states, colors]
import pkg/[godot]
import godotapi/[margin_container, input_event, scene_tree]
import ui/markdown_label
import core, models/[states, colors]
proc set_filter(self: Control, filter: int64) =
self.mouse_filter = filter
@ -20,7 +20,7 @@ gdobj RightPanel of MarginContainer:
var label: MarkdownLabel
var zid: ZID
method ready* =
method ready*() =
self.label = self.find_node("MarkdownLabel") as MarkdownLabel
state.status_message_value.changes:
@ -40,10 +40,11 @@ gdobj RightPanel of MarginContainer:
var sign = change.item
self.label.markdown = md(sign, sign.more)
self.label.update
self.zid = sign.more_value.changes:
if added:
self.label.markdown = md(sign, change.item)
self.label.update
self.zid =
sign.more_value.changes:
if added:
self.label.markdown = md(sign, change.item)
self.label.update
if removed and change.item != nil:
if change.item.more_value.valid:
change.item.more_value.untrack(self.zid)
@ -62,10 +63,9 @@ gdobj RightPanel of MarginContainer:
self.modulate = solid_alpha
method unhandled_input*(event: InputEvent) =
if DocsFocused in state.local_flags and event.is_action_pressed("ui_cancel"):
if not (event of InputEventJoypadButton) or CommandMode notin state.local_flags:
if DocsFocused in state.local_flags and
event.is_action_pressed("ui_cancel"):
if not (event of InputEventJoypadButton) or
CommandMode notin state.local_flags:
state.open_sign = nil
self.get_tree().set_input_as_handled()

View File

@ -1,10 +1,9 @@
import godotapi / [h_box_container, scene_tree, button, image_texture]
import pkg / [godot]
import godotapi/[h_box_container, scene_tree, button, image_texture]
import pkg/[godot]
import core
import globals, ui/preview_maker
type
PreviewResult = tuple[color: string, preview: Image]
type PreviewResult = tuple[color: string, preview: Image]
gdobj Toolbar of HBoxContainer:
var
@ -28,11 +27,12 @@ gdobj Toolbar of HBoxContainer:
self.visible = true
state.tool = BlueBlock
self.zid = state.tool_value.changes:
if added:
let b = self.get_child(int(change.item)) as Button
if ?b:
b.set_pressed true
self.zid =
state.tool_value.changes:
if added:
let b = self.get_child(int(change.item)) as Button
if ?b:
b.set_pressed true
method process*(delta: float) =
if self.preview_result.is_some:
@ -47,23 +47,33 @@ gdobj Toolbar of HBoxContainer:
if not self.waiting and self.blocks.len > 0:
var color = self.blocks.pop()
self.waiting = true
self.preview_maker.generate_block_preview \"{color}-block-grid", proc(preview: Image) =
self.preview_result = some (color: color, preview: preview)
self.waiting = false
self.preview_maker.generate_block_preview \"{color}-block-grid",
proc(preview: Image) =
self.preview_result = some (color: color, preview: preview)
self.waiting = false
if not self.waiting and self.blocks.len == 0 and self.objects.len > 0:
let obj = self.objects.pop()
self.waiting = true
self.preview_maker.generate_object_preview obj, proc(preview: Image) =
self.preview_result = some (color: obj, preview: preview)
self.preview_maker.generate_object_preview obj,
proc(preview: Image) =
self.preview_result = some (color: obj, preview: preview)
method on_action_changed*(button_name: string) =
state.tool_value.pause(self.zid):
case button_name[7..^1]:
of "code": state.tool = CodeMode
of "blue": state.tool = BlueBlock
of "red": state.tool = RedBlock
of "green": state.tool = GreenBlock
of "black": state.tool = BlackBlock
of "white": state.tool = WhiteBlock
of "brown": state.tool = BrownBlock
of "bot": state.tool = PlaceBot
case button_name[7 ..^ 1]
of "code":
state.tool = CodeMode
of "blue":
state.tool = BlueBlock
of "red":
state.tool = RedBlock
of "green":
state.tool = GreenBlock
of "black":
state.tool = BlackBlock
of "white":
state.tool = WhiteBlock
of "brown":
state.tool = BrownBlock
of "bot":
state.tool = PlaceBot

View File

@ -1,11 +1,9 @@
import enu/types, enu/class_macros
import macros except name
import pkg / print
import pkg/print
import enu/base_api
import enu/state_machine
let instance_global_by_default = false
var move_mode = 1
load_enu_script "potato_code.nim", Bot, speed, global, color

View File

@ -1,11 +1,11 @@
name potato(length = 5, width = 2, color = red, label = "hi", friendly = true)
- wander:
-wander:
forward 5
turn right
- run_away(fast=true, msg="Help!") string:
- say_hello() string:
-run_away(fast = true, msg = "Help!") string:
-say_hello() string:
echo "hello"
"goodbye"
echo say_hello()
@ -15,7 +15,7 @@ name potato(length = 5, width = 2, color = red, label = "hi", friendly = true)
forward 100
block:
- test_me:
-test_me:
echo "hi"
test_me()

View File

@ -1,7 +1,9 @@
#proc quit*(exit_code = 0) = discard
include robot
proc script_echo*(msg: string) = discard
proc script_echo*(msg: string) =
discard
proc echo(x: varargs[string, `$`]) =
script_echo x.join

View File

@ -1,8 +1,5 @@
#quit()
var
speed* = 1.0
scale* = 1.0

View File

@ -1,7 +1,7 @@
name Box1
# import typetraits
#echo "SELF: ", me.type.name
def box(length=10, height=20):
def box(length = 10, height = 20):
height.times:
4.times:
forward length

View File

@ -1,6 +1,6 @@
name box2
def box(length=10, height=20):
def box(length = 10, height = 20):
height.times:
4.times:
forward length

View File

@ -1,2 +1,3 @@
place = "script 1 body"
proc in_script_1_body*() = echo "in script 1 body"
proc in_script_1_body*() =
echo "in script 1 body"

View File

@ -1,2 +1,3 @@
place = "script 2 body"
proc in_script_2_body*() = echo "in script 2 body"
proc in_script_2_body*() =
echo "in script 2 body"

View File

@ -1,7 +1,11 @@
proc callback*(name: string) = discard
proc quit*(code = 0) = discard
proc callback*(name: string) =
discard
proc quit*(code = 0) =
discard
echo "running test1"
for i in 0..10:
for i in 0 .. 10:
echo "test1 ", i
callback("test1")

View File

@ -1,7 +1,11 @@
proc callback*(name: string) = discard
proc quit*(code = 0) = discard
proc callback*(name: string) =
discard
proc quit*(code = 0) =
discard
echo "running test2"
for i in 0..10:
for i in 0 .. 10:
echo "test2 ", i
callback("test2")

View File

@ -11,19 +11,21 @@ let
e1.load("scripts", "test1", read_file script_dir & "/test1.nim", vmlib)
e2.load("scripts", "test2", read_file script_dir & "/test2.nim", vmlib)
e1.expose "callback", proc(a: VmArgs): bool =
assert a.get_string(0) == "test1"
result = true
e1.expose "callback",
proc(a: VmArgs): bool =
assert a.get_string(0) == "test1"
result = true
e2.expose "callback", proc(a: VmArgs): bool =
assert a.get_string(0) == "test2"
result = true
e2.expose "callback",
proc(a: VmArgs): bool =
assert a.get_string(0) == "test2"
result = true
for _ in 0..1:
for _ in 0 .. 1:
assert e1.run()
assert e2.run()
for _ in 0..9:
for _ in 0 .. 9:
assert e1.resume()
assert e2.resume()

View File

@ -1,18 +1,20 @@
import engine/engine
import core
import std / [os, strutils]
import std/[os, strutils]
var output = ""
let
vmlib = "vmlib"
e = Engine()
script_dir = nim_filename().parent_dir & "/scripts/instancing"
script_1 = """
script_1 =
"""
var place = "script 1 header"
proc in_script_1_header*() = echo "in script 1 header"
include "script_1.nim"
""".dedent
script_2 = """
script_2 =
"""
import script_1
var place = "script 2 header"
proc in_script_2_header*() = echo "in script 2 header"

View File

@ -1,14 +1,16 @@
import std / [sugar, os, monotimes]
import std/[sugar, os, monotimes]
import controllers/script_controllers, core, models
import libs / [interpreters, eval]
import libs/[interpreters, eval]
var state = GameState.active
state.logger = proc(level, msg: string) =
echo level, ": ", msg
state.logger =
proc(level, msg: string) =
echo level, ": ", msg
state.config.script_dir = current_source_path().parent_dir / "scripts" / "instancing"
state.config.script_dir =
current_source_path().parent_dir / "scripts" / "instancing"
state.config.lib_dir = current_source_path().parent_dir / ".." / ".." / "vmlib"
let controller = ScriptController.init
@ -23,4 +25,3 @@ let bot2 = create("bot_script_2")
state.units.add bot2
let bot3 = create("bot_script_3")
state.units.add bot3

View File

@ -1,14 +1,16 @@
import enu / [types, base_api]
import enu/[types, base_api]
import pkg/print
let unit = Unit()
register_active(unit)
unit.seed = 122341
let f: float = 1..100
let f: float = 1 .. 100
echo "float: ", f
var position = vec3(1.0, 1.0, 1.0)
position = vec3(position.x + -2.0..2.0, position.y + -2.0..2.0, position.z + -2.0..2.0)
position =
vec3(
position.x + -2.0 .. 2.0, position.y + -2.0 .. 2.0, position.z + -2.0 .. 2.0
)
echo position

View File

@ -1,5 +1,5 @@
import engine/engine, core
import std / [strformat, sugar, os]
import std/[strformat, sugar, os]
let
vmlib = "vmlib"
@ -12,10 +12,11 @@ var
e1.load("scripts", "bot", read_file script_dir & "/bots.nim", vmlib)
e1.expose("yield_script", a => true)
e1.expose "script_echo", proc(a: VmArgs): bool =
let str = a.get_string(0)
output &= str
echo str
e1.expose "script_echo",
proc(a: VmArgs): bool =
let str = a.get_string(0)
output &= str
echo str
assert e1.run()
while e1.resume():

View File

@ -1,41 +1,70 @@
include core
type
Direction = object
type Direction = object
#const skip_3d = false
proc forward(steps = 1.0) = discard
proc back(steps = 1.0) = discard
proc left(steps = 1.0): Direction {.discardable.} = discard
proc right(steps = 1.0): Direction {.discardable.} = discard
proc l(steps = 1.0): Direction {.discardable.} = discard
proc r(steps = 1.0): Direction {.discardable.} = discard
proc forward(steps = 1.0) =
discard
proc back(steps = 1.0) =
discard
proc left(steps = 1.0): Direction {.discardable.} =
discard
proc right(steps = 1.0): Direction {.discardable.} =
discard
proc l(steps = 1.0): Direction {.discardable.} =
discard
proc r(steps = 1.0): Direction {.discardable.} =
discard
when not declared(skip_3d):
proc up(steps = 1.0): Direction {.discardable.} = discard
proc u(steps = 1.0): Direction {.discardable.} = discard
proc down(steps = 1.0): Direction {.discardable.} = discard
proc d(steps = 1.0): Direction {.discardable.} = discard
proc up(steps = 1.0): Direction {.discardable.} =
discard
proc u(steps = 1.0): Direction {.discardable.} =
discard
proc down(steps = 1.0): Direction {.discardable.} =
discard
proc d(steps = 1.0): Direction {.discardable.} =
discard
proc turn(direction: proc(steps = 1.0): Direction, degrees = 90.0) =
var axis = if direction == r: RIGHT
elif direction == right: RIGHT
elif direction == l: LEFT
elif direction == left: LEFT
else: vec3()
var axis =
if direction == r:
RIGHT
elif direction == right:
RIGHT
elif direction == l:
LEFT
elif direction == left:
LEFT
else:
vec3()
when not declared(skip_3d):
if axis == vec3():
axis = if direction == u: UP
elif direction == up: UP
elif direction == d: DOWN
elif direction == down: DOWN
else: vec3()
axis =
if direction == u:
UP
elif direction == up:
UP
elif direction == d:
DOWN
elif direction == down:
DOWN
else:
vec3()
assert axis != vec3(), "Invalid direction"
echo "turning ", axis
proc t(direction: proc(steps = 1.0): Direction, degrees = 90.0) = turn(direction, degrees)
proc t(direction: proc(steps = 1.0): Direction, degrees = 90.0) =
turn(direction, degrees)
turn left
t l

View File

@ -1,6 +1,6 @@
import engine/engine
import core
import std / [os, strutils]
import std/[os, strutils]
var output = ""
let
@ -8,12 +8,14 @@ let
e = Engine()
script_dir = nim_filename().parent_dir & "/scripts"
prefix = "proc log*(s:string) = discard\n"
user_classes1 = """
user_classes1 =
"""
type
Type1* = object
name*: string
""".dedent
user_classes2 = """
user_classes2 =
"""
type
Type1* = object
name*: string
@ -21,12 +23,16 @@ let
Type2* = object
name*: string
""".dedent
script1 = prefix & """
script1 =
prefix &
"""
import user_classes
let a = Type1(name: "type1")
log "a=" & a.repr
""".dedent
script2 = prefix & """
script2 =
prefix &
"""
import user_classes
let a = Type1(name: "type1", size: 5)
let b = Type2(name: "type2")
@ -38,10 +44,11 @@ e.load(script_dir, script_dir & "/user_classes.nim", user_classes1, vmlib)
assert not e.run()
e.load(script_dir, "script", script1, vmlib)
e.expose "log", proc(a: VmArgs): bool =
let msg = a.get_string(0)
echo msg
output &= msg & "\n"
e.expose "log",
proc(a: VmArgs): bool =
let msg = a.get_string(0)
echo msg
output &= msg & "\n"
assert not e.run()
e.load(script_dir, script_dir & "/user_classes.nim", user_classes2, vmlib)
@ -50,7 +57,8 @@ assert not e.run()
e.load(script_dir, "script", script2, vmlib)
assert not e.run()
assert output == """
assert output ==
"""
a=(name: "type1")
a=(name: "type1", size: 5)
b=(name: "type2")

View File

@ -1,6 +1,6 @@
import enu/types, enu/class_macros
import macros except name
import pkg / print
import pkg/print
import enu/base_api
import enu/state_machine
@ -8,4 +8,3 @@ let instance_global_by_default = false
var move_mode = 1
load_enu_script "zombie_code.nim", Bot, speed, global, color

View File

@ -1,6 +1,6 @@
name zombie(unit: Unit)
- hello(height=22):
-hello(height = 22):
echo height.type
hello()

View File

@ -1,14 +1,19 @@
import globals, os
proc echo(args:varargs[string, `$`]) =
proc echo(args: varargs[string, `$`]) =
stdout.write "Enu: " & args.join() & "\n"
echo_console = proc(msg: string) =
echo msg
echo_console =
proc(msg: string) =
echo msg
logger = proc(level, msg: string) =
echo level, ": ", msg
logger =
proc(level, msg: string) =
echo level, ": ", msg
game_node = gdnew[Node]()
config = Config(lib_dir: get_current_dir() & "/../vmlib",
script_dir: nim_filename().parent_dir & "/scripts")
config =
Config(
lib_dir: get_current_dir() & "/../vmlib",
script_dir: nim_filename().parent_dir & "/scripts",
)

View File

@ -1,7 +1,9 @@
#proc quit*(exit_code = 0) = discard
include robot
proc script_echo*(msg: string) = discard
proc script_echo*(msg: string) =
discard
proc echo(x: varargs[string, `$`]) =
script_echo x.join

View File

@ -1,8 +1,9 @@
proc vec_proc*(vec: Vector3): Vector3 = discard
proc vec_proc*(vec: Vector3): Vector3 =
discard
name = "box"
echo "in script"
def box(length=10, height=20):
def box(length = 10, height = 20):
echo "box", " ", "box 2"
height.times:
4.times:

View File

@ -1,7 +1,11 @@
proc callback*(name: string) = discard
proc quit*(code = 0) = discard
proc callback*(name: string) =
discard
proc quit*(code = 0) =
discard
echo "running test1"
for i in 0..10:
for i in 0 .. 10:
echo "test1 ", i
callback("test1")

View File

@ -1,7 +1,11 @@
proc callback*(name: string) = discard
proc quit*(code = 0) = discard
proc callback*(name: string) =
discard
proc quit*(code = 0) =
discard
echo "running test2"
for i in 0..10:
for i in 0 .. 10:
echo "test2 ", i
callback("test2")

View File

@ -1,10 +1,10 @@
import godot
import godotapi / [objects]
import engine / [engine, contexts]
import godotapi/[objects]
import engine/[engine, contexts]
import core, world/bot_node, globals
import os
include helper
import compiler / [vm, vmdef, options, lineinfos, ast, astalgo, renderer]
import compiler/[vm, vmdef, options, lineinfos, ast, astalgo, renderer]
config.script_dir &= "/instancing"
let b = gdnew[BotNode]()
@ -12,13 +12,14 @@ let b = gdnew[BotNode]()
let script = nim_filename().parent_dir & "/scripts/instancing/box.nim"
b.paused = false
b.load_script(script)
b.engine.expose "vec_proc", proc(a: VmArgs): bool =
let v = a.get_vec3(0)
b.engine.expose "vec_proc",
proc(a: VmArgs): bool =
let v = a.get_vec3(0)
dump v
let n = vec3(10.0, 9.0, 8.0).to_node
a.set_result(n)
result = false
dump v
let n = vec3(10.0, 9.0, 8.0).to_node
a.set_result(n)
result = false
assert b.engine.run()
while b.running:

View File

@ -1,11 +1,11 @@
import std/unittest
import pkg / [print, model_citizen]
import pkg/[print, model_citizen]
import godot except print
import godotapi/node
import world/node_factories, models / [states, bots, types, builds]
import world/node_factories, models/[states, bots, types, builds]
when is_main_module:
proc tests =
proc tests() =
var state = GameState.init(Node)
state.nodes.game = gdnew[Node]()
state.nodes.data = gdnew[Node]()
@ -20,4 +20,5 @@ when is_main_module:
u2.units += u4
u2.units -= u4
u3.flags += Targeted
tests()

View File

@ -1,8 +1,8 @@
#!/usr/bin/env nim r --warnings:off --hints:off
# bits of the build process that don't work from NimScript
import std / [os, cpuinfo, strformat, httpclient]
import pkg / [compiler/nimeval, cligen]
import std/[os, cpuinfo, strformat, httpclient]
import pkg/[compiler/nimeval, cligen]
import godotapigen
include "../installer/export_presets.cfg.nimf"
@ -10,11 +10,14 @@ include "../installer/Info.plist.nimf"
const stdlib = find_nim_std_lib_compile_time()
proc core_count = echo count_processors()
proc core_count() =
echo count_processors()
proc find_test(test = ""): string =
try: ".last_test".read_file
except: quit("Nothing to do")
try:
".last_test".read_file
except:
quit("Nothing to do")
proc save_test_file(file: string) =
write_file(".last_test", file)
@ -33,10 +36,9 @@ proc copy_stdlib(destination: string) =
copy_dir join_path(stdlib, path), join_path(destination, path)
for file in @["system.nim", "stdlib.nimble", "system" / "compilation.nim"]:
copy_file join_path(stdlib, file),
join_path(destination, file)
copy_file join_path(stdlib, file), join_path(destination, file)
proc run_tests =
proc run_tests() =
discard
proc generate_api(directory = "godotapi", json = "api.json") =
@ -46,7 +48,15 @@ proc write_export_presets(enu_version: string) =
write_file("app/export_presets.cfg", generate_export_presets(enu_version))
proc write_info_plist(enu_version: string) =
write_file("dist/Enu.app/Contents/Info.plist", generate_info_plist(enu_version))
write_file(
"dist/Enu.app/Contents/Info.plist", generate_info_plist(enu_version)
)
dispatch_multi [generate_api], [core_count], [copy_stdlib], [write_export_presets],
[write_info_plist], [save_test_file], [build_last_test], [run_tests]
dispatch_multi [generate_api],
[core_count],
[copy_stdlib],
[write_export_presets],
[write_info_plist],
[save_test_file],
[build_last_test],
[run_tests]

View File

@ -1,5 +1,5 @@
import system except echo
import std / [strformat, math, importutils, strutils, options, sets]
import std/[strformat, math, importutils, strutils, options, sets]
import random as rnd except rand
import types, state_machine, base_bridge, base_bridge_private
@ -11,14 +11,18 @@ proc echo*(args: varargs[string, `$`]) =
proc quit*(code = 0, msg = "") =
exit(code, msg)
proc `position=`*(self: Unit, position: Vector3) = self.position_set(position)
proc `position=`*(self: Unit, unit: Unit) = self.position_set(unit.position)
proc `position=`*(self: Unit, position: Vector3) =
self.position_set(position)
proc `position=`*(self: Unit, unit: Unit) =
self.position_set(unit.position)
proc link_dependency*(dep: Unit) =
if not dep.is_nil:
link_dependency_impl(dep)
proc link_dependency*(dep: not Unit) = discard
proc link_dependency*(dep: not Unit) =
discard
proc `seed=`*(self: Unit, seed: int) =
private_access Unit
@ -85,26 +89,59 @@ template down*(self: Unit, steps = 1.0) =
mixin wait, begin_move
wait self.begin_move(DOWN, steps, move_mode)
template l*(self: Unit, steps = 1.0) = self.left(steps)
template r*(self: Unit, steps = 1.0) = self.right(steps)
template u*(self: Unit, steps = 1.0) = self.up(steps)
template d*(self: Unit, steps = 1.0) = self.down(steps)
template f*(self: Unit, steps = 1.0) = self.forward(steps)
template b*(self: Unit, steps = 1.0) = self.back(steps)
template l*(self: Unit, steps = 1.0) =
self.left(steps)
template forward*(steps = 1.0) = enu_target.forward(steps)
template back*(steps = 1.0) = enu_target.back(steps)
template left*(steps = 1.0) = enu_target.left(steps)
template right*(steps = 1.0) = enu_target.right(steps)
template up*(steps = 1.0) = enu_target.up(steps)
template down*(steps = 1.0) = enu_target.down(steps)
template r*(self: Unit, steps = 1.0) =
self.right(steps)
template l*(steps = 1.0) = enu_target.left(steps)
template r*(steps = 1.0) = enu_target.right(steps)
template u*(steps = 1.0) = enu_target.up(steps)
template d*(steps = 1.0) = enu_target.down(steps)
template f*(steps = 1.0) = enu_target.forward(steps)
template b*(steps = 1.0) = enu_target.back(steps)
template u*(self: Unit, steps = 1.0) =
self.up(steps)
template d*(self: Unit, steps = 1.0) =
self.down(steps)
template f*(self: Unit, steps = 1.0) =
self.forward(steps)
template b*(self: Unit, steps = 1.0) =
self.back(steps)
template forward*(steps = 1.0) =
enu_target.forward(steps)
template back*(steps = 1.0) =
enu_target.back(steps)
template left*(steps = 1.0) =
enu_target.left(steps)
template right*(steps = 1.0) =
enu_target.right(steps)
template up*(steps = 1.0) =
enu_target.up(steps)
template down*(steps = 1.0) =
enu_target.down(steps)
template l*(steps = 1.0) =
enu_target.left(steps)
template r*(steps = 1.0) =
enu_target.right(steps)
template u*(steps = 1.0) =
enu_target.up(steps)
template d*(steps = 1.0) =
enu_target.down(steps)
template f*(steps = 1.0) =
enu_target.forward(steps)
template b*(steps = 1.0) =
enu_target.back(steps)
template see*(target: Unit, less_than = 100.0): bool =
enu_target.see(target, less_than)
@ -128,7 +165,8 @@ template forward*(self: Unit, value: PositionOffset) =
let steps = self.local_position.z - value.position.z + value.offset
wait self.begin_move(FORWARD, steps, move_mode)
template forward*(offset: PositionOffset) = enu_target.forward(offset)
template forward*(offset: PositionOffset) =
enu_target.forward(offset)
proc back*(self: Unit, value: PositionOffset, move_mode: int) =
let steps = self.position.z - value.position.z - value.offset
@ -139,7 +177,8 @@ template back*(self: Unit, value: PositionOffset) =
let steps = self.local_position.z - value.position.z - value.offset
wait self.begin_move(FORWARD, steps, move_mode)
template back*(offset: PositionOffset) = enu_target.back(offset)
template back*(offset: PositionOffset) =
enu_target.back(offset)
proc left*(self: Unit, value: PositionOffset, move_mode: int) =
let steps = self.local_position.x - value.position.x + value.offset
@ -150,7 +189,8 @@ template left*(self: Unit, value: PositionOffset) =
let steps = self.local_position.x - value.position.x + value.offset
wait self.begin_move(LEFT, steps, move_mode)
template left*(offset: PositionOffset) = enu_target.left(offset)
template left*(offset: PositionOffset) =
enu_target.left(offset)
proc right*(self: Unit, value: PositionOffset, move_mode: int) =
let steps = self.local_position.x - value.position.x - value.offset
@ -161,7 +201,8 @@ template right*(self: Unit, value: PositionOffset) =
let steps = self.local_position.x - value.position.x - value.offset
wait self.begin_move(LEFT, steps, move_mode)
template right*(offset: PositionOffset) = enu_target.right(offset)
template right*(offset: PositionOffset) =
enu_target.right(offset)
proc down*(self: Unit, value: PositionOffset, move_mode: int) =
let steps = self.local_position.y - value.position.y + value.offset
@ -172,7 +213,8 @@ template down*(self: Unit, value: PositionOffset) =
let steps = self.local_position.y - value.position.y + value.offset
wait self.begin_move(DOWN, steps, move_mode)
template down*(offset: PositionOffset) = enu_target.down(offset)
template down*(offset: PositionOffset) =
enu_target.down(offset)
proc up*(self: Unit, value: PositionOffset, move_mode: int) =
let steps = self.local_position.y - value.position.y - value.offset
@ -183,7 +225,8 @@ template up*(self: Unit, value: PositionOffset) =
let steps = self.local_position.y - value.position.y - value.offset
wait self.begin_move(DOWN, steps, move_mode)
template up*(offset: PositionOffset) = enu_target.up(offset)
template up*(offset: PositionOffset) =
enu_target.up(offset)
type NegativeNode = ref object
node: Unit
@ -203,7 +246,8 @@ proc angle_to*(self: Unit, enu_target: Unit): float =
self.angle_to(enu_target.position)
proc vec3(direction: Directions): Vector3 =
result = case direction:
result =
case direction
of Directions.forward, Directions.f: FORWARD
of Directions.back, Directions.b: BACK
of Directions.left, Directions.l: LEFT
@ -340,8 +384,11 @@ proc near*(node: Unit | Vector3, less_than = 5.0): bool =
proc far*(node: Unit | Vector3, greater_than = 100.0): bool =
result = node.distance > greater_than
proc height*(self: Vector3): float = self.y
proc height*(self: Unit): float = self.position.y
proc height*(self: Vector3): float =
self.y
proc height*(self: Unit): float =
self.position.y
template go_home*() =
enu_target.go_home
@ -354,14 +401,14 @@ proc rng(): var Rand =
var unit = active_unit()
if unit.seed == 0:
randomize()
unit.seed = rnd. rand(int.high)
unit.seed = rnd.rand(int.high)
unit.rng = init_rand(unit.seed)
unit.rng
proc rand*[T: int | float](range: Slice[T]): T =
rnd.rand rng(),
if range.a > range.b:
range.b..range.a
range.b .. range.a
else:
range
@ -378,10 +425,11 @@ converter float_slice_to_float*(range: Slice[float]): float =
rand(range)
proc fuzzed*(self, range: float): float =
result = if range > 0:
self + (rand(0.0..range) - (range / 2.0))
else:
self
result =
if range > 0:
self + (rand(0.0 .. range) - (range / 2.0))
else:
self
proc fuzzed*(self, range: Vector3): Vector3 =
vec3(self.x.fuzzed(range.x), self.y.fuzzed(range.y), self.z.fuzzed(range.z))
@ -393,23 +441,25 @@ proc fuzzed*(self: Vector3, x, y, z: float): Vector3 =
self.fuzzed(vec3(x, y, z))
template times*(count: int, body: untyped): untyped =
for x in 0..<count:
for x in 0 ..< count:
let first {.inject.} = (x == 0)
let last {.inject.} = (x == count - 1)
body
template times*(count: int, name: untyped, body: untyped): untyped =
for name {.inject.} in 0..<count:
for name {.inject.} in 0 ..< count:
let first {.inject.} = (name == 0)
let last {.inject.} = (name == count - 1)
body
template x*(count: int, body: untyped): untyped = times(count, body)
template x*(count: int, body: untyped): untyped =
times(count, body)
macro dump*(x: typed): untyped =
let s = x.toStrLit
let r = quote do:
echo(`s` & " = " & $`x`)
let r =
quote:
echo(`s` & " = " & $`x`)
return r
template cycle*[T](args: varargs[T]): T =
@ -425,11 +475,11 @@ template cycle*[T](args: varargs[T]): T =
args[positions[key]]
proc random*[T](args: varargs[T]): T =
let i = rnd.rand(rng(), 0..(args.len - 1))
let i = rnd.rand(rng(), 0 .. (args.len - 1))
args[i]
proc contains*(max, chance: int): bool =
var r = rnd.rand(rng(), 1..max)
var r = rnd.rand(rng(), 1 .. max)
result = r <= chance
template forever*(body) =
@ -456,8 +506,11 @@ proc go*(unit: Unit) =
active_unit().forward((position - active_unit().position).length, 2)
active_unit().down(active_unit().height - height, 2)
proc even*(self: int): bool = self mod 2 == 0
proc odd*(self: int): bool = not self.even
proc even*(self: int): bool =
self mod 2 == 0
proc odd*(self: int): bool =
not self.even
template `\`*(s: string): string =
var f = fmt(s)
@ -466,18 +519,35 @@ template `\`*(s: string): string =
f.remove_suffix("\n\n")
f
template `?`*(self: ref): bool = not self.is_nil
template `?`*(self: object): bool = self != self.type.default
template `?`*[T](option: Option[T]): bool = option.is_some
template `?`*(self: SomeNumber): bool = self != 0
template `?`*(self: string): bool = self != ""
template `?`*[T](self: open_array[T]): bool = self.len > 0
template `?`*[T](self: set[T]): bool = self.card > 0
template `?`*[T](self: HashSet[T]): bool = self.card > 0
template `?`*(self: ref): bool =
not self.is_nil
template `?`*(self: object): bool =
self != self.type.default
template `?`*[T](option: Option[T]): bool =
option.is_some
template `?`*(self: SomeNumber): bool =
self != 0
template `?`*(self: string): bool =
self != ""
template `?`*[T](self: open_array[T]): bool =
self.len > 0
template `?`*[T](self: set[T]): bool =
self.card > 0
template `?`*[T](self: HashSet[T]): bool =
self.card > 0
proc `or`*[T: not bool](a, b: T): T =
if ?a: result = a
else: result = b
if ?a:
result = a
else:
result = b
template reset*(clear = false) =
enu_target.reset(clear)

View File

@ -2,13 +2,20 @@ import types, vm_bridge_utils
# NOTE: overridden by ScriptController. Only for tests.
var current_active_unit: Unit
proc register_active_impl(self: Unit) = current_active_unit = self
proc active_unit_impl(): Unit = current_active_unit
proc register_active_impl(self: Unit) =
current_active_unit = self
proc register_active*(self: Unit) = register_active_impl(self)
proc active_unit*(): Unit = active_unit_impl()
proc active_unit_impl(): Unit =
current_active_unit
proc sees_impl*(self: Unit, target: Unit, less_than = 100.0): bool = discard
proc register_active*(self: Unit) =
register_active_impl(self)
proc active_unit*(): Unit =
active_unit_impl()
proc sees_impl*(self: Unit, target: Unit, less_than = 100.0): bool =
discard
bridged_to_host:
proc write_stack_trace*()
@ -40,8 +47,8 @@ bridged_to_host:
proc press_action*(name: string)
proc load_level*(level: string, world = "")
proc reset_level*()
proc level_name*: string
proc world_name*: string
proc level_name*(): string
proc world_name*(): string
# TODO: These should be in base_bridge_private, but are currently needed outside of base_api.
proc echo_console*(msg: string)

View File

@ -7,15 +7,34 @@ bridged_to_host:
proc action_running*(self: Unit): bool
proc `action_running=`*(self: Unit, value: bool)
proc yield_script*(self: Unit)
proc begin_move*(self: Unit, direction: Vector3, steps: float, move_mode: int)
proc begin_move*(
self: Unit, direction: Vector3, steps: float, move_mode: int
)
proc begin_turn*(
self: Unit, axis: Vector3, steps: float, lean: bool, move_mode: int)
self: Unit, axis: Vector3, steps: float, lean: bool, move_mode: int
)
proc sleep_impl*(seconds = 1.0)
proc position_set*(self: Unit, position: Vector3)
proc new_markdown_sign*(self: Unit, instance: Sign, message: string,
more = "", width = 1.0, height = 1.0, size = 32, billboard = false)
proc new_markdown_sign*(
self: Unit,
instance: Sign,
message: string,
more = "",
width = 1.0,
height = 1.0,
size = 32,
billboard = false,
)
proc update_markdown_sign*(self: Sign, message: string,
more = "", width = 1.0, height = 1.0, size = 32, billboard = false)
proc update_markdown_sign*(
self: Sign,
message: string,
more = "",
width = 1.0,
height = 1.0,
size = 32,
billboard = false,
)

View File

@ -1,11 +1,12 @@
import std / [strutils, math]
import std/[strutils, math]
import types, base_api, vm_bridge_utils
bridged_to_host:
proc all_bots(): seq[Bot]
proc play*(self: Bot, animation_name: string)
proc all*(_: type Bot): seq[Bot] = all_bots()
proc all*(_: type Bot): seq[Bot] =
all_bots()
proc walk*(self: Bot) =
self.speed = 1.0

View File

@ -1,4 +1,4 @@
import std / [strutils]
import std/[strutils]
import types, base_api, vm_bridge_utils
bridged_to_host:
@ -14,7 +14,8 @@ bridged_to_host:
proc `draw_position=`*(self: Build, unit: Unit) =
self.draw_position = unit.position
proc all*(_: type Build): seq[Build] = all_builds()
proc all*(_: type Build): seq[Build] =
all_builds()
proc go_home*(self: Build) =
self.rotation = 0
@ -25,8 +26,7 @@ proc go_home*(self: Build) =
self.down self.position.y - self.start_position.y, 2
proc fill_square*(self: Build, length = 1) =
for l in 0..length:
for i in 0..3:
for l in 0 .. length:
for i in 0 .. 3:
self.forward(length - l, 2)
self.right(1, 2)

View File

@ -1,10 +1,12 @@
import std / [macros, strutils, sequtils, base64]
import std/[macros, strutils, sequtils, base64]
import types
import base_api, macro_helpers
const private_props = ["lock"]
const public_props = ["position", "start_position", "speed", "scale", "glow",
"global", "seed", "color", "height", "show", "sign"]
const public_props = [
"position", "start_position", "speed", "scale", "glow", "global", "seed",
"color", "height", "show", "sign"
]
proc params_to_assignments(nodes: seq[NimNode]): NimNode =
result = new_stmt_list()
@ -28,8 +30,9 @@ proc params_to_ident_defs(nodes: seq[NimNode]): seq[NimNode] =
elif node.kind == nnkExprColonExpr:
result.add nnkIdentDefs.new_tree(node[0], node[1], new_empty_node())
else:
error("expected `my_param = 1`, `my_param: int` kind: " & $node.kind,
node)
error(
"expected `my_param = 1`, `my_param: int` kind: " & $node.kind, node
)
proc params_to_properties(nodes: seq[NimNode]): NimNode =
result = new_nim_node(kind = nnkRecList)
@ -39,14 +42,15 @@ proc params_to_properties(nodes: seq[NimNode]): NimNode =
let prop = node[0]
if prop.str_val notin ["global", "speed", "color"]:
if node.kind == nnkExprEqExpr:
result.add nnkIdentDefs.new_tree(node[0], new_call(ident"type",
node[1]), empty)
result.add nnkIdentDefs.new_tree(
node[0], new_call(ident"type", node[1]), empty
)
elif node.kind == nnkExprColonExpr:
result.add nnkIdentDefs.new_tree(node[0], node[1], empty)
else:
error("expected `my_param = 1`, `my_param: int` kind: " & $node.kind,
node)
error(
"expected `my_param = 1`, `my_param: int` kind: " & $node.kind, node
)
proc params_to_accessors(type_name: NimNode, nodes: seq[NimNode]): NimNode =
result = new_stmt_list()
@ -56,10 +60,11 @@ proc params_to_accessors(type_name: NimNode, nodes: seq[NimNode]): NimNode =
let getter = node[0]
if getter.str_val notin ["global", "speed", "color"]:
let setter = ident(getter.str_val & "=")
let typ = if node.kind == nnkExprEqExpr:
new_call(ident"type", node[1])
else:
node[1]
let typ =
if node.kind == nnkExprEqExpr:
new_call(ident"type", node[1])
else:
node[1]
result.add quote do:
proc `getter`*(self: `type_name`): `typ` =
@ -73,15 +78,16 @@ proc params_to_accessors(type_name: NimNode, nodes: seq[NimNode]): NimNode =
self.`getter` = value
self.wake
proc build_ctors(name_str: string, type_name: NimNode, params: seq[NimNode]):
NimNode =
var ctor_body = quote do:
assert not instance.is_nil
link_dependency(instance)
result = `type_name`()
result.seed = active_unit().seed
new_instance(instance, result)
proc build_ctors(
name_str: string, type_name: NimNode, params: seq[NimNode]
): NimNode =
var ctor_body =
quote:
assert not instance.is_nil
link_dependency(instance)
result = `type_name`()
result.seed = active_unit().seed
new_instance(instance, result)
for param in params:
let prop = param[0]
@ -96,8 +102,10 @@ proc build_ctors(name_str: string, type_name: NimNode, params: seq[NimNode]):
var global = "global".ident
if "global" notin var_names:
params &= new_ident_defs(global, new_empty_node(),
ident"instance_global_by_default")
params &=
new_ident_defs(
global, new_empty_node(), ident"instance_global_by_default"
)
ctor_body.add quote do:
result.global = `global`
@ -121,24 +129,29 @@ proc build_ctors(name_str: string, type_name: NimNode, params: seq[NimNode]):
# add baked in constructor params for speed, color, etc.
# probably shouldn't be here.
result = new_proc(
name = "new".ident.postfix("*"),
params = params,
pragmas = nnkPragma.new_tree("discardable".ident),
body = ctor_body
)
result =
new_proc(
name = "new".ident.postfix("*"),
params = params,
pragmas = nnkPragma.new_tree("discardable".ident),
body = ctor_body,
)
proc extract_class_info(name_node: NimNode): tuple[name: string,
params: seq[NimNode]] =
result = if name_node.kind == nnkIdent:
proc extract_class_info(
name_node: NimNode
): tuple[name: string, params: seq[NimNode]] =
result =
if name_node.kind == nnkIdent:
(name_node.str_val, @[])
elif name_node.kind in [nnkCall, nnkCommand, nnkObjConstr]:
name_node[0].expect_kind nnkIdent
(name_node[0].str_val, name_node[1..^1])
(name_node[0].str_val, name_node[1 ..^ 1])
else:
error("expected `name my_name` or `name my_name(my_param1 = 1, " &
"my_param2 = 2, ...)`", name_node)
error(
"expected `name my_name` or `name my_name(my_param1 = 1, " &
"my_param2 = 2, ...)`",
name_node,
)
return
@ -153,8 +166,9 @@ proc build_class(name_node: NimNode, base_type: NimNode): NimNode =
result = new_stmt_list()
let name_str = name
var type_def = quote do:
type `type_name`* = ref object of `base_type`
var type_def =
quote:
type `type_name`* = ref object of `base_type`
type_def[0][2][0][2] = params_to_properties(params)
let accessors = params_to_accessors(type_name, params)
@ -176,18 +190,20 @@ proc pop_name_node(ast: NimNode): tuple[start: NimNode, name_node: NimNode] =
for i, node in ast:
if node.kind in [nnkCommand, nnkCall]:
if node.len == 2 and node[1].kind in [nnkIdent, nnkCall, nnkObjConstr] and
node[0].eq_ident(ident_name):
node[0].eq_ident(ident_name):
result.name_node = node[1]
ast.del(i)
break
result.start.add node
for i, node in result.start:
ast.del(i)
proc visit_tree(parent: NimNode, convert: open_array[string], receiver: string,
alias: ptr seq[NimNode]) =
ast.del(i)
proc visit_tree(
parent: NimNode,
convert: open_array[string],
receiver: string,
alias: ptr seq[NimNode],
) =
for i, node in parent:
if node.kind in [nnkProcDef, nnkBlockStmt, nnkIfExpr, nnkIfStmt]:
# The alias list should only live as long as a scope. We need to make a
@ -203,9 +219,8 @@ proc visit_tree(parent: NimNode, convert: open_array[string], receiver: string,
elif i == 2 and node notin alias[]:
parent[i] = new_dot_expr(ident receiver, node)
elif $node in convert and node notin alias[] and
parent.kind != nnk_expr_eq_expr and not
(parent.kind == nnk_dot_expr and i == 1):
parent.kind != nnk_expr_eq_expr and
not (parent.kind == nnk_dot_expr and i == 1):
parent[i] = new_dot_expr(ident receiver, node)
visit_tree(node, convert, receiver, alias)
@ -213,9 +228,9 @@ proc visit_tree(parent: NimNode, convert: open_array[string], receiver: string,
# Anything for `enu_target` must work for all units. `me` can be class specific.
# This tries to take aliasing into account. If a variable called `speed` is
# created, anywhere it's in scope won't get `me` prefixed.
proc auto_insert_receiver(ast: NimNode, class_specific_props:
open_array[string]): NimNode =
proc auto_insert_receiver(
ast: NimNode, class_specific_props: open_array[string]
): NimNode =
var alias: seq[NimNode] = @[]
visit_tree(ast, class_specific_props, "me", addr alias)
visit_tree(ast, private_props, "me", addr alias)
@ -225,24 +240,24 @@ proc auto_insert_receiver(ast: NimNode, class_specific_props:
proc build_proc(sig, body: NimNode, return_type = new_empty_node()): NimNode =
let (name, params, vars) = sig.parse_sig(return_type)
let new_body = new_stmt_list(vars, body)
result = new_proc(
name = ident(name),
params = params,
body = new_body,
pragmas = new_nim_node(nnkPragma).add(ident"discardable")
)
result =
new_proc(
name = ident(name),
params = params,
body = new_body,
pragmas = new_nim_node(nnkPragma).add(ident"discardable"),
)
proc transform_commands(parent: NimNode): NimNode =
for i, node in parent:
if parent.kind == nnkStmtList and node.kind == nnkPrefix and
node[0] == ident"-":
node[0] == ident"-":
if node[1].kind in [nnkIdent, nnkCall]:
let new_proc = build_proc(node[1], transform_commands node[2])
parent[i] = new_proc
elif node[1].kind == nnkCommand:
let new_proc = build_proc(node[1][0], transform_commands node[2],
node[1][1])
let new_proc =
build_proc(node[1][0], transform_commands node[2], node[1][1])
parent[i] = new_proc
else:
@ -251,9 +266,12 @@ proc transform_commands(parent: NimNode): NimNode =
parent[i] = transform_commands(node)
parent
macro load_enu_script*(base64_code: string, file_name: string, base_type: untyped,
class_specific_props: varargs[untyped]): untyped =
macro load_enu_script*(
base64_code: string,
file_name: string,
base_type: untyped,
class_specific_props: varargs[untyped],
): untyped =
var class_specific_props = class_specific_props.map_it($it)
let file_name = file_name.str_val
# `static_read` has been disabled for security, so we can't read the code
@ -280,7 +298,6 @@ macro load_enu_script*(base64_code: string, file_name: string, base_type: untype
let assignments = params_to_assignments(params)
inner.add quote do:
`assignments`
else:
ast = ast.auto_insert_receiver(class_specific_props)
result.add quote do:
@ -302,4 +319,5 @@ macro load_enu_script*(base64_code: string, file_name: string, base_type: untype
# If a new instance doesn't ever yield the interpreter can crash. Unsure
# why, but probably fixable. Sleep before exit as a workaround.
sleep 0
run_script(me, false)

View File

@ -1,10 +1,12 @@
var context: Context
me.advance_state_machine = proc(): bool =
result = if not context.is_nil:
context.advance()
else:
true
me.advance_state_machine =
proc(): bool =
result =
if not context.is_nil:
context.advance()
else:
true
proc loop_started(ctx: Context, main_loop: bool) =
if main_loop:

View File

@ -1,6 +1,8 @@
import std / [macros, strformat, strutils]
import std/[macros, strformat, strutils]
proc parse_sig*(sig: NimNode, return_type = new_empty_node()): (string, seq[NimNode], NimNode) =
proc parse_sig*(
sig: NimNode, return_type = new_empty_node()
): (string, seq[NimNode], NimNode) =
var
name = ""
args = @[return_type]
@ -9,7 +11,7 @@ proc parse_sig*(sig: NimNode, return_type = new_empty_node()): (string, seq[NimN
name = $sig
else:
name = $sig[0]
for i, arg in sig[1..^1]:
for i, arg in sig[1 ..^ 1]:
case arg.kind
of nnkExprColonExpr:
args.add new_ident_defs(arg[0], arg[1])

View File

@ -4,23 +4,44 @@ let player* = PlayerType()
register_active(player)
bridged_to_host:
proc tool*(self: PlayerType): Tools = discard
proc `tool=`*(self: PlayerType, value: Tools) = discard
proc tool*(self: PlayerType): Tools =
discard
proc playing*(self: PlayerType): bool = discard
proc `playing=`*(self: PlayerType, value: bool) = discard
proc `tool=`*(self: PlayerType, value: Tools) =
discard
proc flying*(self: PlayerType): bool = discard
proc `flying=`*(self: PlayerType, value: bool) = discard
proc playing*(self: PlayerType): bool =
discard
proc running*(self: PlayerType): bool = discard
proc `running=`*(self: PlayerType, value: bool) = discard
proc `playing=`*(self: PlayerType, value: bool) =
discard
proc god*(self: PlayerType): bool = discard
proc `god=`*(self: PlayerType, value: bool) = discard
proc flying*(self: PlayerType): bool =
discard
proc coding*(self: PlayerType): Unit = discard
proc `coding=`*(self: PlayerType, value: Unit) = discard
proc `flying=`*(self: PlayerType, value: bool) =
discard
proc open_sign*(self: PlayerType): Sign = discard
proc `open_sign=`*(self: PlayerType, value: Sign) = discard
proc running*(self: PlayerType): bool =
discard
proc `running=`*(self: PlayerType, value: bool) =
discard
proc god*(self: PlayerType): bool =
discard
proc `god=`*(self: PlayerType, value: bool) =
discard
proc coding*(self: PlayerType): Unit =
discard
proc `coding=`*(self: PlayerType, value: Unit) =
discard
proc open_sign*(self: PlayerType): Sign =
discard
proc `open_sign=`*(self: PlayerType, value: Sign) =
discard

View File

@ -1,20 +1,31 @@
type
ErrorData* = tuple[id: int, msg: string]
type ErrorData* = tuple[id: int, msg: string]
# :( there must be a way to do this with a table or an array
proc to_exception*(self: ErrorData): ref Exception =
case self.id:
of 0: nil
of 1: (ref NilAccessDefect)(msg: self.msg)
of 2: (ref DivByZeroDefect)(msg: self.msg)
of 3: (ref AssertionDefect)(msg: self.msg)
of 4: (ref KeyError)(msg: self.msg)
else: raise_assert "Unknown error id " & $self.id
case self.id
of 0:
nil
of 1:
(ref NilAccessDefect)(msg: self.msg)
of 2:
(ref DivByZeroDefect)(msg: self.msg)
of 3:
(ref AssertionDefect)(msg: self.msg)
of 4:
(ref KeyError)(msg: self.msg)
else:
raise_assert "Unknown error id " & $self.id
proc from_exception*(self: ref Exception): ErrorData =
if self == nil: (0, "")
elif self of ref NilAccessDefect: (1, self.msg)
elif self of ref DivByZeroDefect: (2, self.msg)
elif self of ref AssertionDefect: (3, self.msg)
elif self of ref KeyError: (4, self.msg)
else: raise_assert "Unknown error type"
if self == nil:
(0, "")
elif self of ref NilAccessDefect:
(1, self.msg)
elif self of ref DivByZeroDefect:
(2, self.msg)
elif self of ref AssertionDefect:
(3, self.msg)
elif self of ref KeyError:
(4, self.msg)
else:
raise_assert "Unknown error type"

View File

@ -1,34 +1,60 @@
import system except echo
import std / [strutils, math, wrapnils, options]
import std/[strutils, math, wrapnils, options]
import types, base_api, vm_bridge_utils, base_bridge_private
bridged_to_host:
proc message*(self: Sign): string = discard
proc `message=`*(self: Sign, value: string) = discard
proc message*(self: Sign): string =
discard
proc more*(self: Sign): string = discard
proc `more=`*(self: Sign, value: string) = discard
proc `message=`*(self: Sign, value: string) =
discard
proc width*(self: Sign): float = discard
proc `width=`*(self: Sign, value: float) = discard
proc more*(self: Sign): string =
discard
proc height*(self: Sign): float = discard
proc `height=`*(self: Sign, value: float) = discard
proc `more=`*(self: Sign, value: string) =
discard
proc size*(self: Sign): int = discard
proc `size=`*(self: Sign, value: int) = discard
proc width*(self: Sign): float =
discard
proc open*(self: Sign): bool = discard
proc `open=`*(self: Sign, value: bool) = discard
proc `width=`*(self: Sign, value: float) =
discard
proc billboard*(self: Sign): bool = discard
proc `billboard=`*(self: Sign, value: bool) = discard
proc height*(self: Sign): float =
discard
proc say*(self: Unit, message: string, more = "", width = float.high,
height = float.high, size = int.high, billboard = none(bool)
): Sign {.discardable.} =
proc `height=`*(self: Sign, value: float) =
discard
let defaults: tuple[width: float, height: float, size: int, billboard: bool] =
proc size*(self: Sign): int =
discard
proc `size=`*(self: Sign, value: int) =
discard
proc open*(self: Sign): bool =
discard
proc `open=`*(self: Sign, value: bool) =
discard
proc billboard*(self: Sign): bool =
discard
proc `billboard=`*(self: Sign, value: bool) =
discard
proc say*(
self: Unit,
message: string,
more = "",
width = float.high,
height = float.high,
size = int.high,
billboard = none(bool),
): Sign {.discardable.} =
let defaults: tuple[width: float, height: float, size: int, billboard: bool] =
if ?self.sign:
(self.sign.width, self.sign.height, self.sign.size, self.sign.billboard)
elif self of Bot:
@ -36,24 +62,24 @@ proc say*(self: Unit, message: string, more = "", width = float.high,
else:
(2.0, 2.0, 250, false)
let
let
width = if width == float.high: defaults.width else: width
height = if height == float.high: defaults.height else: height
size = if size == int.high: defaults.size else: size
billboard = billboard.get(defaults.billboard)
if message == "":
if ?self.sign:
self.sign.show = false
elif self of Bot and ?self.sign:
result = self.sign
result.update_markdown_sign(message, more, width, height, size, billboard)
result.show = true
else:
result = Sign()
self.new_markdown_sign(result, message, more, width, height, size,
billboard)
self.new_markdown_sign(
result, message, more, width, height, size, billboard
)
self.sign = result
@ -61,7 +87,13 @@ proc say*(self: Unit, message: string, more = "", width = float.high,
result.position = result.position + (UP * (height - 1.0))
elif self of Bot:
result.position = result.position + (UP * 2) + (LEFT * 1)
template say*(message: string, more = "", width = float.high,
height = float.high, size = int.high, billboard = none(bool)): Sign =
template say*(
message: string,
more = "",
width = float.high,
height = float.high,
size = int.high,
billboard = none(bool),
): Sign =
enu_target.say(message, more, width, height, size, billboard)

View File

@ -1,4 +1,4 @@
import std / [macros, strformat, strutils, sequtils, tables]
import std/[macros, strformat, strutils, sequtils, tables]
import types, macro_helpers, base_api
proc current_loop(value: Loop = nil): Loop =
@ -11,15 +11,14 @@ proc advance*(ctx: Context, frame: Frame = nil): bool =
## advance state machine. Returns true if statemachine is still running.
## It's ok to run this from within the statemachine, but Halt exceptions
## must be allowed to propagate.
let
stack = ctx.stack
let stack = ctx.stack
for i, stack_frame in stack:
let active = i + 1 == stack.len
try:
discard stack_frame.manager(active)
except Halt:
# shrink the stack to the current position
ctx.stack = ctx.stack[0..i]
ctx.stack = ctx.stack[0 .. i]
if stack_frame.action == nil and stack_frame == ctx.stack[^1]:
discard ctx.stack.pop()
if frame != stack_frame:
@ -58,6 +57,7 @@ template loop_body(body: untyped) =
while true:
body
return true
validate_loop()
frame.manager = manager
ctx.stack.add frame
@ -103,36 +103,35 @@ template loop_body(body: untyped) =
macro loop*(body: untyped) =
discard current_loop(Loop())
result = new_stmt_list()
let body = if body.kind == nnkNilLit: new_stmt_list() else: body
let body =
if body.kind == nnkNilLit:
new_stmt_list()
else:
body
result.add(get_ast loop_body(body))
result = new_block_stmt(result)
macro loop*(sig: untyped, body: untyped): untyped =
macro loop*(sig: untyped, body: untyped): untyped =
var (name, params, vars) = sig.parse_sig
let proc_body = quote do:
const this_state {.inject.} = `name.ast_to_str`
`vars`
loop:
`body`
let proc_body =
quote:
const this_state {.inject.} = `name . ast_to_str`
`vars`
loop:
`body`
result = new_stmt_list()
params.add new_ident_defs(ident"ctx", ident"Context", new_nil_lit())
result.add new_proc(
name = ident(name),
params = params,
body = proc_body
)
result.add new_proc(name = ident(name), params = params, body = proc_body)
macro smart_call*(call: untyped) =
var call_without_ctx = call.copy_nim_tree
call_without_ctx.del call_without_ctx.len - 1
result = quote do:
when compiles(`call`):
`call`
else:
`call_without_ctx`
result =
quote:
when compiles(`call`): `call` else: `call_without_ctx`
proc transition(from_state, to_state, body, immediate: NimNode): NimNode =
var
@ -171,11 +170,13 @@ proc transition(from_state, to_state, body, immediate: NimNode): NimNode =
let ctx_arg = new_nim_node(nnkExprEqExpr)
ctx_arg.add(ident"ctx")
ctx_arg.add(ident"ctx")
if to_state.kind == nnkInfix and to_state[0] == ident"as" and to_state[2].kind == nnkIdent:
if to_state.kind == nnkInfix and to_state[0] == ident"as" and
to_state[2].kind == nnkIdent:
to_state_name = $to_state[2]
if to_state[1].kind == nnkIdent:
let search_name = $to_state[1]
if search_name in current_loop().states and not current_loop().states[search_name].is_nil:
if search_name in current_loop().states and
not current_loop().states[search_name].is_nil:
to_state = current_loop().states[search_name]
else:
to_state = new_call(to_state[1], ctx_arg)
@ -185,7 +186,8 @@ proc transition(from_state, to_state, body, immediate: NimNode): NimNode =
current_loop().states[to_state_name] = copy_nim_tree to_state
elif to_state.kind == nnkIdent:
to_state_name = $to_state
if to_state_name in current_loop().states and not current_loop().states[to_state_name].is_nil:
if to_state_name in current_loop().states and
not current_loop().states[to_state_name].is_nil:
to_state = current_loop().states[to_state_name]
else:
to_state = new_call(to_state, ctx_arg) #
@ -194,7 +196,6 @@ proc transition(from_state, to_state, body, immediate: NimNode): NimNode =
to_state.add(ctx_arg)
else:
error "to_state must be identifier or call", to_state
else:
to_state_name = "nil"
to_state = new_stmt_list()
@ -207,29 +208,35 @@ proc transition(from_state, to_state, body, immediate: NimNode): NimNode =
includes_str = includes.join(",")
excludes_str = excludes.join(",")
result = quote do:
when not declared(current_state) or not declared(ctx):
{.error: "`->` or `==>` must be inside a `loop` ".}
if `immediate` or (active and done):
let
from_includes: seq[string] = `includes_str`.split(",")
from_excludes: seq[string] = `excludes_str`.split(",")
if (current_state in from_includes or
(current_state != "nil" and
(("others" in from_includes and `to_state_name` != current_state) or
"any" in from_includes))) and current_state notin from_excludes:
current_state = `to_state_name`
first_iteration = true
proc action() =
if first_iteration:
`body`
smart_call(`to_state`)
first_iteration = false
if `to_state_name` != "nil":
frame.action = action
else:
frame.action = nil
raise (ref Halt)()
result =
quote:
when not declared(current_state) or not declared(ctx):
{.error: "`->` or `==>` must be inside a `loop` ".}
if `immediate` or (active and done):
let
from_includes: seq[string] = `includes_str`.split(",")
from_excludes: seq[string] = `excludes_str`.split(",")
if (
current_state in from_includes or (
current_state != "nil" and (
("others" in from_includes and `to_state_name` != current_state) or
"any" in from_includes
)
)
) and current_state notin from_excludes:
current_state = `to_state_name`
first_iteration = true
proc action() =
if first_iteration:
`body`
smart_call(`to_state`)
first_iteration = false
if `to_state_name` != "nil":
frame.action = action
else:
frame.action = nil
raise (ref Halt)()
macro `==>`*(from_state: untyped, to_state: untyped, body: untyped = nil) =
result = transition(from_state, to_state, body, ident"true")
@ -238,14 +245,13 @@ macro `->`*(from_state: untyped, to_state: untyped, body: untyped = nil) =
result = transition(from_state, to_state, body, ident"false")
when is_main_module:
proc task1 =
proc task1() =
echo "task1"
proc task2 =
proc task2() =
echo "task2"
var
count = 0
var count = 0
loop:
inc count
echo "count: " & $count
@ -261,6 +267,7 @@ when is_main_module:
proc forward(length = 1) =
echo "forward ", length
proc turn_right() =
discard

View File

@ -1,4 +1,4 @@
import std / [strutils, strformat, macros, math, hashes, tables, random]
import std/[strutils, strformat, macros, math, hashes, tables, random]
var global_default* = false
@ -6,11 +6,21 @@ const yes* = true
const no* = false
type
Vector3* = tuple
x, y, z: float
Vector3* = tuple[x, y, z: float]
Directions* = enum
up, u, down, d, left, l, right, r, forward, f, back, b
up
u
down
d
left
l
right
r
forward
f
back
b
Unit* = ref object of RootObj
id: int
@ -32,7 +42,13 @@ type
Sign* = ref object of Unit
Colors* = enum
eraser, blue, red, green, black, white, brown
eraser
blue
red
green
black
white
brown
PlayerType* = ref object of Unit
@ -40,7 +56,7 @@ type
stack*: seq[Frame]
Frame* = ref object
manager*: proc(active: bool):bool
manager*: proc(active: bool): bool
action*: proc()
Halt* = object of CatchableError
@ -50,10 +66,17 @@ type
from_states*: seq[(string, NimNode)]
Tools* = enum
CodeMode, BlueBlock, RedBlock, GreenBlock, BlackBlock, WhiteBlock,
BrownBlock, PlaceBot
CodeMode
BlueBlock
RedBlock
GreenBlock
BlackBlock
WhiteBlock
BrownBlock
PlaceBot
proc vec3*(x, y, z: float): Vector3 {.inline.} = (x:x, y:y, z:z)
proc vec3*(x, y, z: float): Vector3 {.inline.} =
(x: x, y: y, z: z)
const
UP* = vec3(0, 1, 0)
@ -67,7 +90,7 @@ const
const EPSILON = 0.00001'f32
proc isEqualApprox*(a, b: float32): bool {.inline, noinit.} =
proc isEqualApprox*(a, b: float32): bool {.inline, noinit.} =
abs(a - b) < EPSILON
proc isEqualApprox*(a, b: float64): bool {.inline, noinit.} =
@ -93,8 +116,6 @@ proc stepify*(value, step: float32): float32 {.inline, noinit.} =
# Vector3 math. https://github.com/pragmagic/godot-nim/blob/7fb22f69af92aa916e56dba14ba3938fc7fa1dd1/godot/core/vector3.nim
proc `+`*(a, b: Vector3): Vector3 {.inline.} =
result.x = a.x + b.x
result.y = a.y + b.y
@ -120,20 +141,20 @@ proc `*`*(a, b: Vector3): Vector3 {.inline.} =
result.y = a.y * b.y
result.z = a.z * b.z
proc `*=`*(a: var Vector3, b: Vector3) {.inline.}=
proc `*=`*(a: var Vector3, b: Vector3) {.inline.} =
a.x *= b.x
a.y *= b.y
a.z *= b.z
proc `*`*(a: Vector3; b: float32): Vector3 {.inline.} =
proc `*`*(a: Vector3, b: float32): Vector3 {.inline.} =
result.x = a.x * b
result.y = a.y * b
result.z = a.z * b
proc `*`*(b: float32; a: Vector3): Vector3 {.inline.} =
proc `*`*(b: float32, a: Vector3): Vector3 {.inline.} =
a * b
proc `*=`*(a: var Vector3; b: float32) {.inline.} =
proc `*=`*(a: var Vector3, b: float32) {.inline.} =
a.x *= b
a.y *= b
a.z *= b
@ -143,17 +164,17 @@ proc `/`*(a, b: Vector3): Vector3 =
result.y = a.y / b.y
result.z = a.z / b.z
proc `/=`*(a: var Vector3; b: Vector3) {.inline.} =
proc `/=`*(a: var Vector3, b: Vector3) {.inline.} =
a.x /= b.x
a.y /= b.y
a.z /= b.z
proc `/`*(a: Vector3; b: float32): Vector3 =
proc `/`*(a: Vector3, b: float32): Vector3 =
result.x = a.x / b
result.y = a.y / b
result.z = a.z / b
proc `/=`*(a: var Vector3; b: float32) {.inline.} =
proc `/=`*(a: var Vector3, b: float32) {.inline.} =
a.x /= b
a.y /= b
a.z /= b
@ -229,7 +250,8 @@ proc cross*(self, other: Vector3): Vector3 {.inline.} =
vec3(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x)
self.x * other.y - self.y * other.x,
)
proc dot*(self, other: Vector3): float32 {.inline.} =
self.x * other.x + self.y * other.y + self.z * other.z
@ -238,7 +260,7 @@ proc abs*(self: Vector3): Vector3 {.inline.} =
vec3(abs(self.x), abs(self.y), abs(self.z))
proc sign*(self: Vector3): Vector3 {.inline.} =
vec3(sign(self.x), sign(self.y), sign(self.z))
vec3(sign(self.x), sign(self.y), sign(self.z))
proc floor*(self: Vector3): Vector3 {.inline.} =
vec3(floor(self.x), floor(self.y), floor(self.z))
@ -250,7 +272,7 @@ proc lerp*(self: Vector3, other: Vector3, t: float32): Vector3 {.inline.} =
vec3(
self.x + t * (other.x - self.x),
self.y + t * (other.y - self.y),
self.z + t * (other.z - self.z)
self.z + t * (other.z - self.z),
)
proc distanceTo*(self, other: Vector3): float32 {.inline.} =
@ -282,8 +304,7 @@ proc snapped*(self: Vector3, other: Vector3): Vector3 =
result = self
result.snap(other)
proc cubicInterpolate*(self, b, preA, postB: Vector3;
t: float32): Vector3 =
proc cubicInterpolate*(self, b, preA, postB: Vector3, t: float32): Vector3 =
let p0 = preA
let p1 = self
let p2 = b
@ -292,10 +313,11 @@ proc cubicInterpolate*(self, b, preA, postB: Vector3;
let t2 = t * t
let t3 = t2 * t
result = 0.5 * ((p1 * 2.0) +
(-p0 + p2) * t +
(2.0 * p0 - 5.0 * p1 + 4 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3)
result =
0.5 * (
(p1 * 2.0) + (-p0 + p2) * t + (2.0 * p0 - 5.0 * p1 + 4 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3
)
proc moveToward*(vFrom, to: Vector3, delta: float32): Vector3 =
let
@ -312,5 +334,7 @@ proc moveToward*(vFrom, to: Vector3, delta: float32): Vector3 =
converter vec3_to_bool*(v: Vector3): bool =
v != vec3(0, 0, 0)
proc init*[T: Exception](kind: type[T], message: string, parent: ref Exception = nil): ref Exception =
proc init*[T: Exception](
kind: type[T], message: string, parent: ref Exception = nil
): ref Exception =
(ref kind)(msg: message, parent: parent)

View File

@ -1,16 +1,17 @@
import std / [macros, strutils]
import shared / errors
import std/[macros, strutils]
import shared/errors
proc get_last_error_impl(): ErrorData =
raise_assert "get_last_error_impl must be implemented by host"
proc check_errors* =
proc check_errors*() =
let err = get_last_error_impl().to_exception
if err != nil:
raise err
proc get_name(node: NimNode): string =
result = case node.kind:
result =
case node.kind
of nnk_ident:
$node.str_val
of nnk_postfix:
@ -30,21 +31,23 @@ macro bridged_to_host*(body: untyped): untyped =
let params = node.params
var call = new_call impl_proc
var def = copy_nim_tree(node)
def.body = quote do:
raise_assert `impl_proc` & " must be implemented by host"
def.body =
quote:
raise_assert `impl_proc` & " must be implemented by host"
def[0] = ident(impl_proc)
if params.len > 1:
for defs in params[1..^1]:
call.add(defs[0..^3])
node.body = if params[0].kind == nnk_empty:
quote do:
`call`
check_errors()
else:
quote do:
result = `call`
check_errors()
for defs in params[1 ..^ 1]:
call.add(defs[0 ..^ 3])
node.body =
if params[0].kind == nnk_empty:
quote:
`call`
check_errors()
else:
quote:
result = `call`
check_errors()
defs.add def
calls.add node
@ -58,7 +61,8 @@ when is_main_module:
Vector3 = object
Colors = object
proc check_errors = discard
proc check_errors() =
discard
bridged_to_host:
proc write_stack_trace*()

View File

@ -3,7 +3,8 @@ let menu* = me
speed = 0
lock = true
let overview = \"""
let overview =
\"""
World `{world_name()}`
@ -11,7 +12,8 @@ Level `{level_name()}`
"""
let details = \"""
let details =
\"""
# Menu

View File

@ -6,7 +6,7 @@ var test_mode = false
var bot_id = ""
var maze: MazeType
proc get_bot: Bot =
proc get_bot(): Bot =
# each time the code for the bot is reloaded we need to
# grab a new reference to it. Eventually an easy way to
# do this will be built in.
@ -16,13 +16,13 @@ proc get_bot: Bot =
elif not test_mode and bot.id == bot_id:
return bot
- setup:
-setup:
reset()
position = position + LEFT
speed = 10
turn player
- approach:
-approach:
sleep 2
say "# - Hi!", width = 1.0
sleep 2
@ -33,10 +33,12 @@ proc get_bot: Bot =
say "- Welcome to **Enu**!", width = 2.0
sleep 1.5
say "- Click this text for a short tutorial.", "# Welcome to Enu!"
while not sign.open: turn(player)
while not sign.open:
turn(player)
- movement_info:
say "# . . .", """
-movement_info:
say "# . . .",
"""
# Welcome to Enu!
@ -62,14 +64,16 @@ controller, or by holding down left `alt/option (⌥)` on your keyboard.
Sneak behind the `Bot` to continue the tutorial.
""", width = 0.6
""",
width = 0.6
menu.show = true
while me.angle_to(player).abs notin 150..210:
while me.angle_to(player).abs notin 150 .. 210:
sleep 0.5
- flying_info:
say "- Good!", """
-flying_info:
say "- Good!",
"""
# Jumping and Flying
@ -85,24 +89,28 @@ ever get yourself stuck you can probably fly your way out.
Jump, fly, then returning to the ground to continue the tutorial.
""", width = 1.4
""",
width = 1.4
speed = 1
turn player
sign.open = true
while not player.flying: sleep()
while not player.flying:
sleep()
- flying_done:
-flying_done:
say "- Now drop!"
while player.flying: sleep()
while player.flying:
sleep()
- good_job:
-good_job:
sign.open = false
say "- Good Job!", "# Good Job!"
sleep 2
- tool_info:
say "- Switch to `Place Bot`", """
-tool_info:
say "- Switch to `Place Bot`",
"""
# Changing tools
@ -120,14 +128,17 @@ friendly robots. Tools `2` - `7` are the `blue`, `red`, `green`, `black`,
Select the `Place Bot` tool to continue the tutorial.
""", width = 2.5
""",
width = 2.5
sign.open = true
while player.tool != PlaceBot: sleep()
while player.tool != PlaceBot:
sleep()
sleep 0.5
- bot_info:
say "- Place a `Bot`", """
-bot_info:
say "- Place a `Bot`",
"""
# Bots
@ -138,7 +149,8 @@ They can be placed with the `left` mouse button or the `R1` gamepad trigger.
Place a `Bot` on the ground to continue the tutorial.
""", width = 2.0
""",
width = 2.0
sign.open = true
var last_bot_frame = frame_count()
@ -151,20 +163,21 @@ Place a `Bot` on the ground to continue the tutorial.
last_bot_frame = bot.frame_created
bot_id = bot.id
- draw_maze:
-draw_maze:
var bot = get_bot()
turn bot
maze = Maze.new(global = true)
maze.show = true
maze.rotation = 0
maze.position = bot.position + (LEFT * 4.0) + (FORWARD * 1.0)
maze.position = bot.position + (LEFT * 4.0) + (FORWARD * 1.0)
while not maze.finished:
sleep 0.5
sleep 1
- code_info:
say "- Coding", """
-code_info:
say "- Coding",
"""
# Coding Enu
@ -182,8 +195,9 @@ Switch to the `Code` tool to continue the tutorial.
turn player
sleep 0.5
- open_code:
sign.more = """
-open_code:
sign.more =
"""
# Coding Enu
@ -201,8 +215,9 @@ Open the code for your `Bot` to continue the tutorial.
turn player
sleep 0.5
- bot_navigation:
sign.more = """
-bot_navigation:
sign.more =
"""
# Coding Bots
@ -246,11 +261,12 @@ Good luck! We'll conclude this tutorial when your `Bot` goes past the
bot = get_bot()
turn bot
- win:
-win:
maze.won = true
say "- Great Job!", "# Great Job!"
sleep 15
say "- All Done!", """
say "- All Done!",
"""
# Great Job!

View File

@ -3,7 +3,7 @@ name Confetti(arms = 31, count = 99)
if not is_instance:
show = false
quit()
speed = 0
var arm_speed = 0.7
@ -12,9 +12,9 @@ arms.times:
let h = cycle(3, 4, 5, 4)
up h
turn 360 / arms
Streamer.new(speed = arm_speed, count = me.count)
arm_speed += 1.0 / arms
down h
back 2

View File

@ -1,7 +1,7 @@
name Maze(finished = false, won = false)
lock = true
proc make_sign: Sign =
proc make_sign(): Sign =
say("` `", width = 10.0, height = 3.0, size = 2600)
drawing = true
@ -94,13 +94,15 @@ proc scroller(sign: Sign, msg: string, pause = 0, len = msg.len): proc() =
var current = msg
var counter = 0
result = proc() =
if current == msg and counter < pause:
inc counter
else:
counter = 0
current = current[1..^1] & current[0]
sign.message = "`" & current[0..(len - 1)].strip(leading = false) & "`"
result =
proc() =
if current == msg and counter < pause:
inc counter
else:
counter = 0
current = current[1 ..^ 1] & current[0]
sign.message =
"`" & current[0 .. (len - 1)].strip(leading = false) & "`"
var start_scroller1 = start_sign1.scroller(" START HERE! ", 0, 7)

View File

@ -10,7 +10,7 @@ count.times:
if 1 in 3:
speed = base_speed
color = random(green, black, blue, white, red, green, white, green)
drawing = true
back 3
left 1
@ -19,6 +19,6 @@ count.times:
drawing = false
speed = 0
forward 2
forward 1..4
turn -35..35
lean -45..35
forward 1 .. 4
turn -35 .. 35
lean -45 .. 35

View File

@ -4,7 +4,8 @@ speed = 0
show = false
lock = true
let overview = """
let overview =
"""
# ` ` Menu
- `Reset Tutorial`
@ -13,7 +14,8 @@ let overview = """
"""
let details = """
let details =
"""
# Menu

View File

@ -2,7 +2,7 @@ name Win
if not is_instance:
show = false
quit()
seed = 7
lock = true
move me
@ -10,4 +10,4 @@ speed = 0.02
Confetti.new(arms = 30, count = 124)
forever:
turn right
turn right

View File

@ -11,21 +11,20 @@ let hellos = [
let goodbyes = [
"Uh, I gotta get going. Uh, it's good to see you, Phil.",
"Whoa-ho-ho! Watch out for that first step! It's a doozy!",
"Where are we going?",
"Phil, this is the best day of my life."
"Where are we going?", "Phil, this is the best day of my life."
]
- wander:
-wander:
speed = 3
forward 2..10
turn -45..45
forward 2 .. 10
turn -45 .. 45
- chase:
-chase:
speed = 6
turn player
forward 5
- caught:
-caught:
say ""
sleep 2
say random(goodbyes)
@ -35,22 +34,22 @@ loop:
nil -> wander
(go_home, wander_home) -> wander
caught -> go_home
if start_position.far(20):
# `go_home`, but we want to be able to interrupt and
# switch to `chase` if the player is near. Alias to
# `wander_home`, which switches the same as `wander`.
wander -> go_home as wander_home
if player.near(10) and start_position.near(30):
# `chase` if the player is near and we're not too far
# from home base
(wander, wander_home) ==> chase:
# this will be called when the command switches
say cycle(hellos)
if player.far(20):
chase -> go_home
if player.near(3):
chase -> caught

View File

@ -1,6 +1,7 @@
lock = true
let text = """
let text =
"""
# Towers. Too Many Towers.
@ -10,4 +11,4 @@ This is an example of a [Prototype](https://ē.nu/docs/coding/concepts.html).
"""
say text, text, width = 3, height = 3
say text, text, width = 3, height = 3

View File

@ -1,7 +1,8 @@
lock = true
turn left
let text = """
let text =
"""
# Annoying Robot
@ -11,4 +12,4 @@ This is an example of a [Command Loop](https://ē.nu/docs/command_loops.html).
"""
say text, text, height = 4
say text, text, height = 4

View File

@ -1,7 +1,8 @@
lock = true
turn right
let text = """
let text =
"""
# ???

View File

@ -4,4 +4,4 @@ height.times:
sides.times:
forward length
turn 360 / sides + twist
up 1
up 1

View File

@ -3,12 +3,12 @@ seed = 213
speed = 0
turn right
10.times:
forward 20..40
turn 20..50
forward 20 .. 40
turn 20 .. 50
tower.new(
height = 40..100,
length = 5..10,
sides = 3..10,
twist = -2..2,
color = cycle(red, green, blue, black)
)
height = 40 .. 100,
length = 5 .. 10,
sides = 3 .. 10,
twist = -2 .. 2,
color = cycle(red, green, blue, black),
)

View File

@ -1,9 +1,11 @@
lock = true
turn 180
let blurb = "Here are some things you can build with Enu. Play with their code, or build something new from scratch."
let blurb =
"Here are some things you can build with Enu. Play with their code, or build something new from scratch."
let text = \"""
let text =
\"""
# Examples
@ -15,7 +17,8 @@ let text = \"""
"""
let more = \"""
let more =
\"""
# Menu

Some files were not shown because too many files have changed in this diff Show More