Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions YNOCOMMANDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# YNO-specific commands

ynoengine provides the following commands:

###### `YnoAsyncRpc`

```js
// sends (42, 69, "frobnicate") to server and continue execution
@raw 5000, 0, 42, 0, 69, "frobnicate"
// when response is received, assigns the status code to V[25]
@raw 5000, 0, 42, 0, 69,
0, 25, "..."
// assigns the server's response to Str[27]
// and repeat this command until response is received
// parallel events execution recommended
@raw 5000, 0, 42, 0, 69, 0, 0,
0, 7, 0, 27, "..."
// sends (42, 69, Str[29]) to server
@raw 5000, 0, 42, 0, 69, 0, 0, 0, 0, 0, 0,
0, 29, ""
```

## Usage

Add this to your EasyRPG.ini, which will also enable Maniacs:

```ini
[Patch]
YNO = 1
```
1 change: 1 addition & 0 deletions src/game_config_game.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct Game_ConfigGame {
BoolConfigParam patch_rpg2k3_commands{ "RPG2k3 Event Commands", "Enable support for RPG2k3 event commands", "Patch", "RPG2k3Commands", false };
ConfigParam<int> patch_anti_lag_switch{ "Anti-Lag Switch", "Disable event page refreshes when switch is set", "Patch", "AntiLagSwitch", 0 };
ConfigParam<int> patch_direct_menu{ "Direct Menu", " Allows direct access to subscreens of the default menu", "Patch", "DirectMenu", 0 };
BoolConfigParam patch_yno{ "YNOproject Extensions", "", "Patch", "YNO", false };

// Command line only
BoolConfigParam patch_support{ "Support patches", "When OFF all patch support is disabled", "", "", true };
Expand Down
92 changes: 92 additions & 0 deletions src/game_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
#include <cassert>
#include "game_interpreter.h"
#include "async_handler.h"
#include "async_op.h"
#include "audio.h"
#include "game_dynrpg.h"
#include "filefinder.h"
#include "game_destiny.h"
#include "game_interpreter_shared.h"
#include "game_map.h"
#include "game_event.h"
#include "game_enemyparty.h"
Expand Down Expand Up @@ -92,6 +94,7 @@ void Game_Interpreter::Clear() {
_state = {};
_keyinput = {};
_async_op = {};
pending_request = 0;
}

// Is interpreter running.
Expand Down Expand Up @@ -794,6 +797,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) {
return CmdSetup<&Game_Interpreter::CommandEasyRpgCloneMapEvent, 10>(com);
case Cmd::EasyRpg_DestroyMapEvent:
return CmdSetup<&Game_Interpreter::CommandEasyRpgDestroyMapEvent, 2>(com);
case static_cast<Cmd>(5000):
return CmdSetup<&Game_Interpreter::CommandYnoAsyncRpc, 4>(com);
default:
return true;
}
Expand Down Expand Up @@ -5406,6 +5411,11 @@ bool Game_Interpreter::CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand c
Player::game_config.patch_rpg2k3_commands.Set(flag_value);
if (flag_name == "rpg2k-battle")
lcf::Data::system.easyrpg_use_rpg2k_battle_system = flag_value;
if (flag_name == "yno") {
Player::game_config.patch_yno.Set(flag_value);
if (flag_value)
Player::game_config.patch_maniac.Set(flag_value);
}

return true;
}
Expand Down Expand Up @@ -5685,6 +5695,88 @@ bool Game_Interpreter::CommandEasyRpgDestroyMapEvent(lcf::rpg::EventCommand cons
return true;
}

bool Game_Interpreter::CommandYnoAsyncRpc(lcf::rpg::EventCommand const& com) {
// sends a string and two integers to the remote server
//
// @0, @1: RPC parameter 1
// @2, @3: RPC parameter 2
// @4, @5 (optional): variable ID to receive server-defined status code
// @6, @7 (optional): RPC return behavior
// 0: do nothing
// 1: assign to switch ID at parameter 3 return value as boolean (0 or non=0)
// 2: variable " integer
// 3: string " string
// 4: repeat this command until response received (block-in-place), applied as bitflag
// @8, @9 (optional): string/variable/switch ID to receive the response
// @10, @11 (optional): get string from string var
// @10 = 0: get from string ID at @11
// @10 = 1: get from string ID at V[@11]

if (pending_request) {
// each Interpreter is unique to an event page, so it can afford to block here.
if (auto entry = GMI().rpc_requests.find(pending_request); entry != GMI().rpc_requests.end()) {
auto [_1, _2, rpc_mode, target_var_id, rpc_status_var_id] = entry->second.params;
bool rpc_blocking = (rpc_mode & 4) >> 2;
if (entry->second.response.has_value()) {
if (rpc_status_var_id)
Main_Data::game_variables->Set(rpc_status_var_id, entry->second.code);

std::string_view response(entry->second.response.value());
int target_type = rpc_mode & 3;
switch (target_type) {
case 0: break;
case 1: // switch
Main_Data::game_switches->Set(target_var_id, atoi(response.data()) != 0);
break;
case 2: // Variable
Main_Data::game_variables->Set(target_var_id, atoi(response.data()));
break;
case 3: // String
Main_Data::game_strings->Asg({ target_var_id }, response);
break;
}

GMI().rpc_requests.erase(entry);
pending_request = 0;
return true;
}

// if (rpc_blocking) {
// Output::Warning("Blocking on request {}", pending_request);
// _async_op = AsyncOp::MakeYieldRepeat();
// }
return !rpc_blocking;
}
}

int param1 = ValueOrVariable(com.parameters[0], com.parameters[1]);
int param2 = ValueOrVariable(com.parameters[2], com.parameters[3]);

int rpc_mode, rpc_var_id, rpc_status_var_id;
std::string payload;

if (com.parameters.size() >= 6)
rpc_status_var_id = ValueOrVariable(com.parameters[4], com.parameters[5]);
if (com.parameters.size() >= 8)
rpc_mode = ValueOrVariable(com.parameters[6], com.parameters[7]);
if (com.parameters.size() >= 10)
rpc_var_id = ValueOrVariable(com.parameters[8], com.parameters[9]);
if (com.parameters.size() >= 12) {
payload = ToString(CommandStringOrVariable(com, 10, 11));
} else {
payload = com.string;
}

// if (rpc_blocking) {
// _async_op = AsyncOp::MakeYieldRepeat();
// }

pending_request = GMI().MakeRpcRequest(payload, param1, param2, rpc_mode, rpc_var_id, rpc_status_var_id);

bool rpc_blocking = (rpc_mode & 4) >> 2;
return !rpc_blocking;
}

Game_Interpreter& Game_Interpreter::GetForegroundInterpreter() {
return Game_Battle::IsBattleRunning()
? Game_Battle::GetInterpreter()
Expand Down
5 changes: 5 additions & 0 deletions src/game_interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <lcf/dbarray.h>
#include <lcf/rpg/fwd.h>
#include <lcf/rpg/eventcommand.h>
#include "multiplayer/game_multiplayer.h"
#include "system.h"
#include <lcf/rpg/saveeventexecstate.h>
#include <lcf/flag_set.h>
Expand Down Expand Up @@ -303,6 +304,10 @@ class Game_Interpreter : public Game_BaseInterpreterContext
bool CommandEasyRpgDestroyMapEvent(lcf::rpg::EventCommand const& com);
bool CommandManiacGetGameInfo(lcf::rpg::EventCommand const& com);

// Online extensions
bool CommandYnoAsyncRpc(lcf::rpg::EventCommand const& com);
Game_Multiplayer::RequestId pending_request {0};

void SetSubcommandIndex(int indent, int idx);
uint8_t& ReserveSubcommandIndex(int indent);
int GetSubcommandIndex(int indent) const;
Expand Down
22 changes: 20 additions & 2 deletions src/multiplayer/game_multiplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <lcf/data.h>
#include <lcf/reader_util.h>
#include <emscripten/emscripten.h>

#include "game_multiplayer.h"
#include "../output.h"
Expand Down Expand Up @@ -346,7 +347,7 @@ void Game_Multiplayer::InitConnection() {

int rx;
int ry;

if (Game_Map::LoopHorizontal() && px - ox >= hmw) {
rx = Game_Map::GetTilesX() - (px - ox);
} else if (Game_Map::LoopHorizontal() && px - ox < hmw * -1) {
Expand Down Expand Up @@ -441,6 +442,12 @@ void Game_Multiplayer::InitConnection() {

Web_API::OnPlayerNameUpdated(p.name, p.id);
});
connection.RegisterHandler<RpcResponsePacket>("rpc", [this] (RpcResponsePacket& p) {
if (auto req = rpc_requests.find(p.id); req != rpc_requests.end()) {
req->second.response = p.payload;
req->second.code = p.code;
}
});
}

using namespace Messages::C2S;
Expand Down Expand Up @@ -816,7 +823,7 @@ void Game_Multiplayer::Update() {

auto old_list = &DrawableMgr::GetLocalList();
DrawableMgr::SetLocalList(&scene_map->GetDrawableList());

for (auto dcpi = dc_players.rbegin(); dcpi != dc_players.rend(); ++dcpi) {
auto& ch = dcpi->ch;
if (ch->GetBaseOpacity() > 0) {
Expand All @@ -835,3 +842,14 @@ void Game_Multiplayer::Update() {
if (session_connected)
connection.FlushQueue();
}

extern "C" EMSCRIPTEN_KEEPALIVE
void update_rpc_requests(int id) {
// Output::Warning("update_rpc_requests id={}", id);
if (auto entry = GMI().rpc_requests.find(id); entry != GMI().rpc_requests.end()) {
entry->second.response = "123";
entry->second.code = 123;
} else {
// Output::Warning("Request {} already cancelled", id);
}
}
40 changes: 40 additions & 0 deletions src/multiplayer/game_multiplayer.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#ifndef EP_GAME_MULTIPLAYER_H
#define EP_GAME_MULTIPLAYER_H

#include <optional>
#include <string>
#include <bitset>
#include "../string_view.h"
#include "../game_pictures.h"
#include "../tone.h"
#include <lcf/rpg/sound.h>
#include "yno_connection.h"
#include <emscripten/emscripten.h>

Check failure on line 12 in src/multiplayer/game_multiplayer.h

View workflow job for this annotation

GitHub Actions / ubuntu:22.04

emscripten/emscripten.h: No such file or directory

Check failure on line 12 in src/multiplayer/game_multiplayer.h

View workflow job for this annotation

GitHub Actions / debian:12

emscripten/emscripten.h: No such file or directory
#include <utility>
#include "messages.h"

class PlayerOther;

Expand Down Expand Up @@ -48,6 +52,12 @@
void SwitchSet(int switch_id, int value);
void VariableSet(int var_id, int value);

using RequestId = uint32_t;

// Allows up to five ints
template<typename... Args, typename = std::enable_if_t<(std::is_convertible_v<Args, int> && ...)>>
RequestId MakeRpcRequest(std::string method, Args... args);

struct {
bool enable_sounds{ true };
bool mute_audio{ false };
Expand Down Expand Up @@ -92,11 +102,41 @@
std::unique_ptr<std::array<int, 5>> last_frame_flash;
std::map<int, std::array<int, 5>> repeating_flashes;

struct RequestContext {
std::array<int, 5> params;
std::string method;
// if code is not 0, response is error message
std::optional<std::string> response;
int code = 0;
explicit RequestContext(std::array<int, 5> _params, std::string _method)
: params(_params), method(std::move(_method)) {}
};
std::map<RequestId, RequestContext> rpc_requests;

void SpawnOtherPlayer(int id);
void ResetRepeatingFlash();
void InitConnection();
};

inline Game_Multiplayer& GMI() { return Game_Multiplayer::Instance(); }

template<typename... Args, typename>
inline auto Game_Multiplayer::MakeRpcRequest(std::string method, Args... args) -> Game_Multiplayer::RequestId {
using Messages::C2S::RpcRequestPacket;
std::vector<std::string> params {std::to_string(args)...};
connection.SendPacketAsync<RpcRequestPacket>(method, std::move(params));

unsigned id = RpcRequestPacket::LastId();
// rpc_requests.insert_or_assign(id, (RequestContext){{args...}, method});
rpc_requests.emplace(std::make_pair(id, RequestContext{{args...}, method}));

// EM_ASM({
// setTimeout(() => {
// Module.ccall("update_rpc_requests", null, ["number"], [$0]);
// }, 1000);
// }, id);

return id;
}

#endif
29 changes: 27 additions & 2 deletions src/multiplayer/messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "connection.h"
#include "packet.h"
#include <memory>
#include <utility>
#include <lcf/rpg/sound.h>
#include "../game_pictures.h"

Expand Down Expand Up @@ -119,7 +120,7 @@ namespace S2C {
facing(Decode<int>(v.at(1))) {}
const int facing;
};

class SpeedPacket : public PlayerPacket {
public:
SpeedPacket(const PL& v)
Expand Down Expand Up @@ -149,7 +150,7 @@ namespace S2C {
const int p;
const int f;
};

class RepeatingFlashPacket : public FlashPacket {
public:
RepeatingFlashPacket(const PL& v)
Expand Down Expand Up @@ -357,6 +358,17 @@ namespace S2C {
public:
BadgeUpdatePacket(const PL& v) {}
};

class RpcResponsePacket : public S2CPacket {
public:
RpcResponsePacket(const PL& v)
: id(stoi((std::string) v.at(0))),
code(stoi((std::string) v.at(1))),
payload(v.at(2)) {}
unsigned const id;
int code;
std::string payload;
};
}
namespace C2S {
using C2SPacket = Multiplayer::C2SPacket;
Expand Down Expand Up @@ -589,6 +601,19 @@ namespace C2S {
int action_bin;
};

class RpcRequestPacket : public C2SPacket {
public:
RpcRequestPacket(std::string _method, std::vector<std::string> _params) : C2SPacket("rpc"),
id(++idseq), method(std::move(_method)), params(std::move(_params)) {}
std::string ToBytes() const override { return Build((int)id, method, params); }
unsigned const id; // to properly identify server responses
inline static unsigned LastId() noexcept { return idseq; }
protected:
inline static unsigned idseq = 0;
std::string method;
std::vector<std::string> params;
};

}
}

Expand Down
Loading
Loading