Feature Priority Suggestion
Hi zesterer! You wanted to know what parts of Forge would be most important for a user to have, so here's an example Forge script I'd like to write a Rust library for. The API I implemented here needs some more work, but it quickly showed me what I'd need Forge to have before I could implement it.
# kill a troll to be able to complete the NPC's quest and get a reward.
let troll_slain = false;
# spawn three goblins, and spawn the troll when all three are dead.
let GOBLIN_COUNT = 3;
let goblins_slain = 0;
for i in 0..GOBLIN_COUNT {
# these are nodes placed in using the map editor
let spawn_position = get_entity_pos_from_name("goblin spawn" + i);
let goblin = assemble("goblin")
:at(spawn_position)
:with_death_listener(|| {
goblins_slain += 1;
if goblins_slain == GOBLIN_COUNT {
# might as well spawn the boss at where the goblin who was last to die
# spawned; getting its death position would require callbacks with
# passed values, which the readme says is unsupported right now.
let troll_boss = assemble("troll")
:at(spawn_position)
:with_death_listener(|| {
troll_slain = true
})
:build();
}
})
:build();
}
let old_man = get_npc_from_name("old man");
# shoot, earlier I was trying to avoid needing to pass parameters to callback closures,
# but I think there's really no sane way to avoid needing them here.
old_man:on_interact(|client| {
let window = client:window()
:with_title("Heads will roll!")
:with_content("Avenge my carrots! Bring me his head!");
if troll_slain {
let btn = window:with_finish_button("Complete Quest");
btn:on_click(|| {
window:remove();
# TODO: put all of the quest data in a struct that can be cleared to restart the quest.
# allocate one of these structs per client currently taking the quest. this will require
# that people talk to the NPC before embarking upon it. Also, provide a function for
# a reward selection window. such a window will likely be used often enough to justify
# the specificity.
});
}
# can I assign window to a different thing here? let's hope so.
let window = window:build();
});
To me, it looks like forge would need
-
to be able to have parameters passed from Rust to its closures. (:on_interact(|client| {}))
-
structs and methods, which can be defined in, or at least call a lot of, Rust.
Past that though, it seems Forge doesn't need a whole lot more to be generally useful.
Let me know if there's anything I can do to help!
One thing you rely on quite a bit is the ability for a closure to capture its environment in a similar manner to Rust. This isn't an unreasonable request, but it will require quite a bit of work to manage this behaviour correctly. In Rust, move semantic analysis is used to determine that the value should be moved to the payload of the closure. I could do a similar thing in Forge, but it would necessarily mean that the original value in the enclosing scope would be de-declared. For example, this would not be valid and would produce a runtime error on the last line:
var x = 0;
var f = || {
x += 1;
};
print x;
The alternative to this approach is to implicitly convert the original value into some sort of reference value. That way, the enclosing scope and the closure itself would both hold a reference to the same value. This would make the above code valid, but also means that a closure declaration can implicitly alter things in enclosing scopes. This might not be a problem of course.
I know I already said this on the discord, but just for the record's sake, I really prefer the alternative. My reasoning is that this is a scripting language, and having to worry or think about whether or not I want to move this value into this closure and how to get it out if I need to use it again is a pain.
Okay, that's reasonable. I'll start implementing these features tomorrow.