forge icon indicating copy to clipboard operation
forge copied to clipboard

Feature Priority Suggestion

Open cedric-h opened this issue 6 years ago • 3 comments

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!

cedric-h avatar Mar 02 '19 02:03 cedric-h

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.

zesterer avatar Mar 02 '19 16:03 zesterer

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.

cedric-h avatar Mar 02 '19 23:03 cedric-h

Okay, that's reasonable. I'll start implementing these features tomorrow.

zesterer avatar Mar 02 '19 23:03 zesterer