Controlling the Hero with an AI Script

Started by mrunderhill, November 19, 2013, 06:50:28 PM

Previous topic - Next topic
Hello,
    I'm working with a team for a Game AI project, and we were hoping to use Solarus as a framework for making a Zelda AI, but we're having some issues linking our AI to the engine. We were wondering what the easiest way to get the Hero to move on its own would be, if we wanted to emulate a human player with our AI. Any help on this would be appreciated, as the project checkpoint is due Friday and this is making us fall way behind.

Cheers,
~MrUnderhill

Hi,
For now the hero can be moved by the player (of course), or automatically on a predetermined path by calling hero:walk(). This is quite limited but works well.

In theory, you should also be able to create any kind of movement and start it on the hero with movement:start_movement(hero), like we always do for enemies and NPCs, but I never tested it on the hero. Call hero:freeze() first so that the player cannot control it. And it is your AI code that would set the appropriate movement.

November 20, 2013, 07:45:20 PM #2 Last Edit: November 20, 2013, 08:01:15 PM by mrunderhill
I think we've tried something similar to that, but we came across quite a few bugs with that approach: Link would alternate inconsistently between player and AI control, and it looks like there's no way to control anything other than movement from a Movement object.  We'd like to get swordfighting, items, and other elements under AI control, as well, so that may not be the best approach.

My current idea is to just expose some of the GameCommand methods to lua ("game_command_pressed" and "game_command_released," specifically). That way, player input can work like it normally does, with the only change being that the commands are coming from a Lua script instead of the keyboard/joystick. Would that work just as well?

Here's the code I'm using. It compiles and runs without errors, but it doesn't do anything at the moment. I feel like I'm close to getting it working, so any help figuring out what I'm missing would be great.

In GameCommands.h

    // These are now public instead of private.
    void game_command_pressed(Command command);
    void game_command_released(Command command);


In Game.h

    // force commands
    void force_command_pressed(GameCommands::Command command);
    void force_command_released(GameCommands::Command command);



In Game.cpp

/**
*  \brief Forces a command into the command set.
*/
void Game::force_command_pressed(GameCommands::Command command){
    commands->game_command_pressed(command);
}

/**
*  \brief Forces a command into the command set.
*/
void Game::force_command_released(GameCommands::Command command){
    commands->game_command_released(command);
}


In GameAPI.cpp

//These lines were added to methods[]
      { "force_command_pressed", game_api_force_command_pressed },
      { "force_command_released", game_api_force_command_released },
//...

//Force command functions
int LuaContext::game_api_force_command_pressed(lua_State* l){
    //Read a string from Lua
    //Compare the string with our Command enums
    Savegame& savegame = check_game(l, 1);
    GameCommands::Command command = check_enum<GameCommands::Command>(
      l, 2, GameCommands::command_names);
    Game* game = savegame.get_game();
    //Call game.force_command_pressed on the resultant command
    game->force_command_pressed(command);
    return 0;
}

int LuaContext::game_api_force_command_released(lua_State* l){
    //Read a string from Lua
    //Compare the string with our Command enums
    Savegame& savegame = check_game(l, 1);
    GameCommands::Command command = check_enum<GameCommands::Command>(
      l, 2, GameCommands::command_names);
    Game* game = savegame.get_game();
    //Call game.force_command_pressed on the resultant command
    game->force_command_released(command);
    return 0;
}


In LuaContext.h:

//These were added under "FunctionExportedToLua"
      game_api_force_command_pressed,
      game_api_force_command_released,


And lastly, in main.lua, under update():

function sol.main:on_update()
force_command_pressed("up")
force_command_pressed("attack")
--...

Okay, I did not realize that you wanted to take full control of the hero and all his actions by simulating a complete player. I thought you just wanted to move the hero instead of the player for a short time.

So yes, simulating game commands looks like a good approach, and yes, you need to patch the C++ code for this. Your patch looks very good to me. Once you get it working, can you make a pull request on github so that I integrate it officially for Solarus 1.2?

I'm not sure why it does not work. Do you have a Lua error message when you call from Lua your new function game:force_command_pressed?

November 20, 2013, 08:43:02 PM #4 Last Edit: November 21, 2013, 04:41:20 PM by Christopho
Quote from: mrunderhill on November 20, 2013, 07:45:20 PM
There is a problem here:

function sol.main:on_update()
force_command_pressed("up")
force_command_pressed("attack")
--...


You call force_command_pressed() like it was a global function, but it not. It is a function in the game object. So, wherever your game is created (in play_game.lua for ZSDX), you can try

function game:on_update()
game:force_command_pressed("up")
game:force_command_pressed("attack")
end


It should work, but it will look very weird because on_update() is called repeatedly, at every frame. You can make a first try with a timer instead, that you set up in game:on_started() for example:

function game:on_started()
        ........
        sol.timer.start(game, 3000, function()
        game:force_command_pressed("up")
        game:force_command_pressed("attack")
        end)
end

November 20, 2013, 10:11:29 PM #5 Last Edit: November 20, 2013, 10:19:27 PM by mrunderhill
I tried putting your code into game::on_started() and got this error:

Error: In callback(): [string "play_game.lua"]:19: attempt to call global 'force_command_pressed' (a nil value)

Edit: I've been getting nil values with "force_command_pressed", "game.force_command_pressed", and "game:force_command_pressed," all in game:on_started(). It's probably just a Lua problem. I'll try working with that.

Oops, yes, sorry : it is game:force_command_pressed().

I tried that. Still giving me errors.

Error: In callback(): [string "play_game.lua"]:19: attempt to call method 'force_command_pressed' (a nil value)

This is strange. If you added the methods in GameAPI.cpp like you said, they should exist on the game object.
If you send me the patch I will be happy to try.


November 21, 2013, 04:38:20 PM #10 Last Edit: November 21, 2013, 04:46:01 PM by Christopho
Your patch does not contain everything you said: this part is missing
//These lines were added to methods[]
      { "force_command_pressed", game_api_force_command_pressed },
      { "force_command_released", game_api_force_command_released },
//...

So, both methods are never added to game objects.

EDIT: it works great when I add this missing code :)

Yep, and it's working on my end now, too. I was accidentally using an old version of the executable that didn't have my changes.
One fixed shortcut later and now everything works as expected. Thanks for your help! :)