Player
Player copied to clipboard
New Command: 2002 - Activate Event At [x,y]
@raw 2002, "", UseVarX,x UseVarY,y
Activate an event in map remotely, based on its x and y coordinates.
Command made by @MackValentine, I only refactored it to fit the player specifications.
The latest Maniac stuff is finally becoming useful for us: This is all kinda undocumented but after 2 hours I got this beautiful Ui working.
How hard is to start fiddling with those custom menus, do they only work on the japanese Maniacs?
Here some quick mockup I put together for this command.
I tried two days ago to patch the editor back to English. I didn't post about it so you can guess the result... The editor itself was mostly English again but the entire command window stayed Japanese. :/ So this relies on BingShan again to make it usable. Closed Source... Yeah......
The Ui is just Windows Forms. So they are just drag-and-dropped using the Visual Studio Designer. So they can contain anything you want.
Though the question is if that is worth the time. Another solution which would be also useful for our editor later is our own open source TPC replacement.
(2 commands of 154 done xD)
Teaser
Oh! that's cool! Do those scripting commands require a lot of handmade code?
Another question: Will it be interpreted as string by the player or will it be compiled as commands, as tpc currently do? Being read by the player would make it sucha powerful scripting language...
In needed two days to become used to the API and write wrappers so creating the binding code is not too ugly.
The script language is chaiscript (https://chaiscript.com/) with some custom modifications by me to make the syntax nicer. The syntax is JavaScript like. But simpler.
I will share some code when I found more event commands. Still figuring out some edge cases.
Currently this will be a code generator like TPC but I don't see a reason why this shouldn't be executable by the player directly later.
Code gen first is good for finding bugs :)
Example how to add a command (Not too useful yet as code is not released but just so you can see it).
Header:
#pragma once
#include "event_command.h"
#include "types.h"
namespace EasyScript {
class TriggerEventAt {
public:
TriggerEventAt();
TriggerEventAt X(const chaiscript::Boxed_Value& value);
TriggerEventAt Y(const chaiscript::Boxed_Value& value);
static void Register(chaiscript::ChaiScript& chai, EventCommand::List& commands);
std::shared_ptr<EventCommand> cmd = std::make_shared<EventCommand>();
};
}
Source:
#include "trigger_event_at.h"
#include "chaiscript/chaiscript.hpp"
#include "dynamic_object.h"
#include "easyscript/event_command.h"
#include <lcf/rpg/eventcommand.h>
EasyScript::TriggerEventAt::TriggerEventAt() {
cmd->SetDefaults(static_cast<EventCommand::Code>(2002), "", { 0, 0, 0, 0 });
}
EasyScript::TriggerEventAt EasyScript::TriggerEventAt::X(const chaiscript::Boxed_Value& value) {
cmd->SetValueAndMode(0, 1, value);
return *this;
}
EasyScript::TriggerEventAt EasyScript::TriggerEventAt::Y(const chaiscript::Boxed_Value& value) {
cmd->SetValueAndMode(2, 3, value);
return *this;
}
void EasyScript::TriggerEventAt::Register(chaiscript::ChaiScript& chai, EventCommand::List& commands) {
chaiscript::ModulePtr m = std::make_shared<chaiscript::Module>();
chaiscript::utility::add_class<TriggerEventAt>(*m, "__cls_TriggerEventAt",
{
chaiscript::constructor<TriggerEventAt()>()
},
{
{chaiscript::fun(&TriggerEventAt::X), "x"},
{chaiscript::fun(&TriggerEventAt::Y), "y"}
}
);
chai.add(m);
chaiscript::dispatch::Dynamic_Object o;
o["trigger"] = chaiscript::var(chaiscript::fun([&](){
auto evt = TriggerEventAt();
commands.push_back(evt.cmd);
return evt;
}));
chai.set_global(chaiscript::var(o), "@map");
}
Invocation in the script:
@map.trigger.x(123).y(345)
@map.trigger.y($v(222)).x($vv(333))
Looks interesting. I supose those commands could be generated from a .csv file. Just combining what is "type" or "...IsVar" with the respective values parameters
yeah currently I'm not auto-generating it. But will do it for most of the simple commands. Still streamlining the syntax a bit.
Before:
void EasyScript::TriggerEventAt::Register(chaiscript::ChaiScript& chai, EventCommand::List& commands) {
chaiscript::ModulePtr m = std::make_shared<chaiscript::Module>();
chaiscript::utility::add_class<TriggerEventAt>(*m, "__cls_TriggerEventAt",
{
chaiscript::constructor<TriggerEventAt()>()
},
{
{chaiscript::fun(&TriggerEventAt::X), "x"},
{chaiscript::fun(&TriggerEventAt::Y), "y"}
}
);
chai.add(m);
chaiscript::dispatch::Dynamic_Object o;
o["trigger"] = chaiscript::var(chaiscript::fun([&](){
auto evt = TriggerEventAt();
commands.push_back(evt.cmd);
return evt;
}));
chai.set_global(chaiscript::var(o), "@map");
}
After:
void EasyScript::TriggerEventAt::Register(chaiscript::ChaiScript& chai, EventCommand::List& commands) {
Bind<TriggerEventAt, void, TriggerEventAt()>(
chai, commands,
"TriggerEventAt", "map", "trigger",
&TriggerEventAt::X, "x",
&TriggerEventAt::Y, "y"
);
}
looking better!
Dummy question, what is value
at cmd->SetValueAndMode(0, 1, value);
Probably the value from user input? 🤔
That's the argument passed in to the function
@map.trigger.x(1).y($v(2))
Function X receives number 1 and Function Y receives Variable(2)
.
SetValueAndMode
checks the type of the passed in value and sets the correct mode automatically based on it.
Okay and I enhanced the API again: The binding API "introspects" now the class to bind everything. Of course will only work for events that are quite "simple" but this is the case for 90% of them ;).
Example for TriggerEventAt:
Header:
#pragma once
#include "easyscript/forward.h"
#include "easyscript/parameter.h"
namespace EasyScript {
class TriggerEventAt {
public:
TriggerEventAt();
std::shared_ptr<EventCommand> cmd = std::make_shared<EventCommand>();
static void Register(chaiscript::ChaiScript& chai, EventCommandList& commands);
static constexpr std::array name = { "TriggerEventAt", "map", "trigger" };
static constexpr const std::array param = std::to_array<Parameter>({
{ "x", 0, 1, 0 },
{ "y", 0, 3, 2 },
});
};
}
Source:
#include "trigger_event_at.h"
#include "chaiscript/chaiscript.hpp"
#include "easyscript/binding.h"
#include "easyscript/event_command.h"
EasyScript::TriggerEventAt::TriggerEventAt() {
cmd->SetDefaults(static_cast<Code>(2002), "", { 0, 0, 0, 0 });
}
void EasyScript::TriggerEventAt::Register(chaiscript::ChaiScript& chai, EventCommandList& commands) {
BindAuto<TriggerEventAt, void, TriggerEventAt()>(chai, commands);
}
More complex example: PlayBgm
Header:
#pragma once
#include "easyscript/forward.h"
#include "easyscript/parameter.h"
namespace EasyScript {
class PlayBgm {
public:
PlayBgm(StringArg value);
std::shared_ptr<EventCommand> cmd = std::make_shared<EventCommand>();
static void Register(chaiscript::ChaiScript& chai, EventCommandList& commands);
static constexpr std::array name = { "PlayBgm", "music", "play" };
static constexpr const std::array param = std::to_array<Parameter>({
{ "fadein", 0, 0, 4, 1 },
{ "volume", 100, 1, 4, 2 },
{ "tempo", 100, 2, 4, 3 },
{ "balance", 50, 3, 4, 4 }
});
static constexpr const StringParameter string_param = {nullptr, 5, 4, 0};
static std::string FromCommand(const EventCommand& command);
};
}
Source:
#include "play_bgm.h"
#include "chaiscript/chaiscript.hpp"
#include "easyscript/binding.h"
#include "easyscript/event_command.h"
#include "easyscript/forward.h"
EasyScript::PlayBgm::PlayBgm(StringArg value) {
cmd->SetDefaults(Code::PlayBGM, "", { 0, 100, 100, 50 });
string_param.Set(*cmd, value);
}
void EasyScript::PlayBgm::Register(chaiscript::ChaiScript& chai, EventCommandList& commands) {
BindAuto<PlayBgm, StringArg, PlayBgm(StringArg)>(chai, commands);
BindNamespaceFunctions(
chai, "music",
[&](){
auto evt = PlayBgm(chaiscript::Boxed_Value(std::make_shared<const std::string>("(OFF)")));
commands.push_back(evt.cmd);
return evt;
}, "stop"
);
}
std::string EasyScript::PlayBgm::FromCommand(const EventCommand& command) {
if (string_param.GetMode(command) == 0 && command.string == "(OFF)") {
return "@music.stop";
}
return {};
}
@Ghabry We have ways of activating commands right now, So it's no problem aproving those new commands. TriggerEventAt is also a good name!
Going to merge this when the build passed.
Note that starting from Player 0.8.1 this command will be subject to "opt-in" for EasyRPG extensions.
Please add this to your INI file to be future proof:
[Patch]
EasyRPG=1
I'm prototyping some enhanced debugging flags and stumbled upon this. One thing that sticks out, that this behaves exactly, as if it was the player was in front of the event and pressed the decision key:
SaveEvent::triggered_by_decision_key is set to true, so any condition branch that checks for this, will evaluate to true. Is this wanted? Game_Player::CheckEventTriggerThere always will set the event to automatically face the player, right before the commands are scheduled for the interpreter. Maybe also unwanted behavior?
And now for the reason why I looked at this command...: It might be useful, if a new field was defined for "SaveEvent" which indicates that the event was triggered this way. Something like "easyrpg_triggered_indirectly" or maybe even a bitflag field which might hold several values. Otherwise there would be no indication in the callstack how this code was started.
You are right, both of these behaviours should not happen (or at least make it possible to configure them). This shows that even simple commands need lots of testing.
I'm currently creating a TestGame to test the new commands before doing further inclusion (next one is "Wait for Single Movement" so this doesn't happen again. Sorry :/