Per-player and per-entity events
So much for "MCPI limitations"!
This commit is contained in:
parent
2ea637581d
commit
245cc4dea5
@ -30,6 +30,7 @@ void misc_run_on_swap_buffers(const std::function<void()> &function);
|
||||
std::string misc_get_player_username(Player *player);
|
||||
std::map<int, std::string> &misc_get_entity_names();
|
||||
std::string misc_get_entity_name(Entity *entity);
|
||||
std::string misc_get_entity_name_upper(Entity *entity);
|
||||
Entity *misc_make_entity_from_id(Level *level, int id);
|
||||
|
||||
std::string misc_base64_encode(const std::string &data);
|
||||
|
@ -19,14 +19,14 @@ This includes:
|
||||
- Gets a list of chat messages, see the limitations section down below to see why it has a zero
|
||||
- When compatibility mode is enabled, `|` in the chat messages is replaced with `\`
|
||||
- When compatibility mode is disabled, `|` in the chat messages is replaced with `\|`
|
||||
- When compatibility mode is disabled, the first "0" is removed, so the return value is just the message
|
||||
- [x] `player.events.chat.posts() -> {"0", message}[]`
|
||||
- This is *exactly* the same as `events.chat.posts`, however it only returns chat messages sent by the player
|
||||
- [x] `events.projectile.hits() -> {x: int, y: int, z: int, "1", owner: string, target: int}`
|
||||
- [x] `events.projectile.hits() -> {x: int, y: int, z: int, "1", owner: string, target: int}` (and `player.events.projectile.hits()` and `entity.events.projectile.hits(id: int)`)
|
||||
- Returns a list of projectile hit events, the list is cleared each time it is read by this call
|
||||
- When target id is not 0, it means the projectile hit an entity and (x, y, z) is the entity's position
|
||||
- When target id is 0, it means the projectile hit a block and (x, y, z) is the block's position
|
||||
- When compatibility mode is disabled, the constant "1" is removed in the return value, leaving just the useful information
|
||||
- When compatibility mode is disabled, the owner's name is the owner's id instead, as players can have duplicate names
|
||||
- Unlike RaspberryJuice, the last argument is the hit entity id, not the entity's name. This shouldn't break anything, and indeed should be more useful.
|
||||
- [x] `player.setDirection(x: float, y: float, z: float)`
|
||||
- sets rotation as if the player was at 0, 0, 0 and looking towards (x, y, z)
|
||||
@ -83,12 +83,16 @@ This includes:
|
||||
- Gets the absolute position of the player, aka, without passing it through `OffsetPosTranslator`
|
||||
- [x] `player.setAbsPos(x: int, y: int, z: int)`
|
||||
- Sets the absolute position of the player, aka, without passing it through `OffsetPosTranslator`
|
||||
- [x] `player.events.block.hits()` and `entity.events.block.hits(id: int)`, both return `{x: int, y: int, z: int, face: int, hitter: int}`
|
||||
- Same as `events.block.hits`, but for only the player or entity with the id. Only block hits in creative with an iron sword are counted
|
||||
- [x] `player.events.clear()` and `entity.events.clear(id: int)`
|
||||
- These clear projectile, block hit, and chat events for the player or entity
|
||||
- [x] `events.clear` has been changed
|
||||
- It will clear block hit events and chat events, and when compat mode is disabled; projectile events
|
||||
|
||||
|
||||
It does not implement (due to MCPI limitations):
|
||||
- Per-entity events
|
||||
- Per-player events besides `player.events.chat.posts` for the main player
|
||||
- `events.chat.posts`'s id field is always set to 0
|
||||
Due to MCPI limitations:
|
||||
- `events.chat.posts`'s id field is set to 0 for messages that did not come from the main player
|
||||
- `events.block.hits` (and more specialized calls), only work in creative or creator mode
|
||||
|
||||
Egdecases:
|
||||
- `world.removeEntities`/`player.removeEntities`/`entity.removeEntities` will not remove players when given 0 or -1 for type.
|
||||
@ -99,6 +103,7 @@ Egdecases:
|
||||
|
||||
Extras:
|
||||
- Compatibility mode, this is enabled by default, and can be changed with `reborn.disableCompatMode` or `reborn.enableCompatMode`.
|
||||
- `events.clear`
|
||||
|
||||
## How does it work?
|
||||
|
||||
|
@ -67,7 +67,7 @@ static std::string getEntityData(CommandServer *commandserver, Entity *entity) {
|
||||
// type
|
||||
std::to_string(entity->getEntityTypeId()) + "," +
|
||||
// name
|
||||
misc_get_entity_name(entity) + "," +
|
||||
misc_get_entity_name_upper(entity) + "," +
|
||||
// x
|
||||
std::to_string(x) + "," +
|
||||
// y
|
||||
@ -87,7 +87,9 @@ static float distance_between(Entity *e1, Entity *e2) {
|
||||
struct ProjectileHitEvent {
|
||||
int x, y, z;
|
||||
std::string owner = "";
|
||||
int owner_id = 0;
|
||||
int targetId = 0;
|
||||
bool invalid = false;
|
||||
|
||||
std::string toString(CommandServer *commandserver) {
|
||||
float nx = x, ny = y, nz = z;
|
||||
@ -96,13 +98,14 @@ struct ProjectileHitEvent {
|
||||
+ std::to_string((int) ny) + ","
|
||||
+ std::to_string((int) nz) + ","
|
||||
+ (compat_mode ? "1," : "")
|
||||
+ owner + "," + std::to_string(targetId);
|
||||
+ (compat_mode ? owner : std::to_string(owner_id)) + "," + std::to_string(targetId);
|
||||
}
|
||||
};
|
||||
|
||||
struct ChatEvent {
|
||||
std::string message = "";
|
||||
bool from_player = false;
|
||||
int from_player = 0;
|
||||
bool invalid = false;
|
||||
|
||||
std::string toString() {
|
||||
return message;
|
||||
@ -118,12 +121,8 @@ struct CircularQueue {
|
||||
|
||||
void push(T event) {
|
||||
buf[at] = event;
|
||||
at++;
|
||||
at %= SIZE;
|
||||
if (at == start) {
|
||||
start++;
|
||||
start %= SIZE;
|
||||
}
|
||||
advance_counter(at);
|
||||
if (at == start) advance_counter(start);
|
||||
}
|
||||
|
||||
T pop() {
|
||||
@ -131,14 +130,104 @@ struct CircularQueue {
|
||||
ERR("Over popped projectile hit events!");
|
||||
}
|
||||
T ret = buf[start];
|
||||
start++;
|
||||
start %= EVENT_SIZE;
|
||||
advance_counter(start);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void advance_counter(int &i) {
|
||||
i += 1;
|
||||
i %= EVENT_SIZE;
|
||||
}
|
||||
};
|
||||
CircularQueue<ProjectileHitEvent> hitEvents{};
|
||||
CircularQueue<ChatEvent> chatEvents{};
|
||||
|
||||
void clearEvents(CommandServer *commandserver, int id) {
|
||||
// Clear block hit events
|
||||
if (commandserver->minecraft->isCreativeMode()) {
|
||||
EventList_TileEvent &tileEvents = commandserver->minecraft->getCreator()->getTileEvents();
|
||||
for (int i = tileEvents.at + 1, j = 0; j < tileEvents.len; i++, j++) {
|
||||
if (i == tileEvents.len) i = 0;
|
||||
if (tileEvents.events[i].item.entityId == id)
|
||||
// This will make the API ignore it
|
||||
tileEvents.events[i].tick = -1;
|
||||
}
|
||||
}
|
||||
// Clear chat events
|
||||
int i = chatEvents.start;
|
||||
while (chatEvents.at != i) {
|
||||
if (chatEvents.buf[i].from_player == id) {
|
||||
chatEvents.buf[i].invalid = true;
|
||||
}
|
||||
}
|
||||
// Clear projectile events
|
||||
i = hitEvents.start;
|
||||
while (hitEvents.at != i) {
|
||||
if (hitEvents.buf[i].owner_id == id) {
|
||||
hitEvents.buf[i].invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string getBlockHits(CommandServer *commandserver, ConnectedClient &client, int id) {
|
||||
std::string ret = "";
|
||||
if (commandserver->minecraft->isCreativeMode()) {
|
||||
EventList_TileEvent &tileEvents = commandserver->minecraft->getCreator()->getTileEvents();
|
||||
for (int i = tileEvents.at + 1, j = 0; j < tileEvents.len; i++, j++) {
|
||||
if (i == tileEvents.len) i = 0;
|
||||
if (tileEvents.events[i].item.entityId == id && tileEvents.events[i].tick > client.lastBlockHitPoll) {
|
||||
tileEvents.events[i].tick = -1;
|
||||
float x = tileEvents.events[i].item.x;
|
||||
float y = tileEvents.events[i].item.y;
|
||||
float z = tileEvents.events[i].item.z;
|
||||
commandserver->pos_translator.to(x, y, z);
|
||||
ret +=
|
||||
std::to_string((int) x) + "," +
|
||||
std::to_string((int) y) + "," +
|
||||
std::to_string((int) z) + "," +
|
||||
std::to_string(tileEvents.events[i].item.face) + "," +
|
||||
std::to_string(tileEvents.events[i].item.entityId) + "|";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.size() > 1) ret.pop_back();
|
||||
return ret + "\n";
|
||||
}
|
||||
|
||||
std::string getChatPosts(int id) {
|
||||
std::string ret = "";
|
||||
int i = chatEvents.start;
|
||||
while (chatEvents.at != i) {
|
||||
ChatEvent ce = chatEvents.buf[i];
|
||||
if (ce.from_player == id && !ce.invalid) {
|
||||
chatEvents.buf[i].invalid = true;
|
||||
std::string message = ce.toString();
|
||||
if (compat_mode) std::replace(message.begin(), message.end(), '|', '\\');
|
||||
else message = misc_base64_encode(message);
|
||||
ret += std::to_string(ce.from_player) + "," + message + "|";
|
||||
}
|
||||
chatEvents.advance_counter(i);
|
||||
}
|
||||
if (ret.size() > 1) ret.pop_back();
|
||||
return ret + "\n";
|
||||
}
|
||||
|
||||
std::string getProjectileHits(CommandServer *commandserver, int id) {
|
||||
std::string ret = "";
|
||||
int i = hitEvents.start;
|
||||
while (hitEvents.at != i) {
|
||||
ProjectileHitEvent he = hitEvents.buf[i];
|
||||
if (he.owner_id == id && !he.invalid) {
|
||||
hitEvents.buf[i].invalid = true;
|
||||
std::string message = he.toString(commandserver);
|
||||
ret += message + "|";
|
||||
}
|
||||
hitEvents.advance_counter(i);
|
||||
}
|
||||
if (ret.size() > 1) ret.pop_back();
|
||||
return ret + "\n";
|
||||
}
|
||||
|
||||
static const std::string fail = "Fail\n";
|
||||
std::unordered_map<int, int> modern_entity_id_mapping = {
|
||||
{93, 10}, /* Chicken */
|
||||
@ -192,7 +281,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ
|
||||
if (entity == NULL) {
|
||||
WARN("Player (or Entity) [%i] not found in entity.getName.", id);
|
||||
}
|
||||
return misc_get_entity_name(entity) + "\n";
|
||||
return misc_get_entity_name_upper(entity) + "\n";
|
||||
} else if (cmd == "world.getEntities") {
|
||||
int type;
|
||||
int ret = sscanf(args.c_str(), "%d", &type);
|
||||
@ -236,33 +325,45 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ
|
||||
std::string ret = "";
|
||||
while (chatEvents.at != chatEvents.start) {
|
||||
ChatEvent ce = chatEvents.pop();
|
||||
std::string message = ce.toString();
|
||||
if (compat_mode) std::replace(message.begin(), message.end(), '|', '\\');
|
||||
else message = misc_base64_encode(message);
|
||||
ret += (compat_mode ? "0," : "") + message + "|";
|
||||
}
|
||||
if (ret.size() > 1) ret.pop_back();
|
||||
return ret + "\n";
|
||||
} else if (cmd == "player.events.chat.posts") {
|
||||
std::string ret = "";
|
||||
while (chatEvents.at != chatEvents.start) {
|
||||
ChatEvent ce = chatEvents.pop();
|
||||
if (ce.from_player) {
|
||||
if (!ce.invalid) {
|
||||
std::string message = ce.toString();
|
||||
if (compat_mode) std::replace(message.begin(), message.end(), '|', '\\');
|
||||
else message = misc_base64_encode(message);
|
||||
ret += (compat_mode ? "0," : "") + message + "|";
|
||||
ret += std::to_string(ce.from_player) + "," + message + "|";
|
||||
}
|
||||
}
|
||||
if (ret.size() > 1) ret.pop_back();
|
||||
return ret + "\n";
|
||||
} else if (cmd == "player.events.chat.posts") {
|
||||
return getChatPosts(commandserver->minecraft->player->id);
|
||||
} else if (cmd == "entity.events.chat.posts") {
|
||||
int id;
|
||||
int ret = sscanf(args.c_str(), "%d", &id);
|
||||
if (ret != 1) return fail;
|
||||
return getChatPosts(id);
|
||||
} else if (cmd == "player.events.block.hits") {
|
||||
return getBlockHits(commandserver, client, commandserver->minecraft->player->id);
|
||||
} else if (cmd == "entity.events.block.hits") {
|
||||
int id;
|
||||
int ret = sscanf(args.c_str(), "%d", &id);
|
||||
if (ret != 1) return fail;
|
||||
return getBlockHits(commandserver, client, id);
|
||||
} else if (cmd == "events.projectile.hits") {
|
||||
std::string result = "";
|
||||
while (hitEvents.at != hitEvents.start) {
|
||||
result += hitEvents.pop().toString(commandserver) + "|";
|
||||
ProjectileHitEvent he = hitEvents.pop();
|
||||
if (!he.invalid)
|
||||
result += he.toString(commandserver) + "|";
|
||||
}
|
||||
if (result.size() > 1) result.pop_back();
|
||||
return result + "\n";
|
||||
} else if (cmd == "player.events.projectile.hits") {
|
||||
return getProjectileHits(commandserver, commandserver->minecraft->player->id);
|
||||
} else if (cmd == "entity.events.projectile.hits") {
|
||||
int id;
|
||||
int ret = sscanf(args.c_str(), "%d", &id);
|
||||
if (ret != 1) return fail;
|
||||
return getProjectileHits(commandserver, id);
|
||||
} else if (cmd == "player.setDirection" && commandserver->minecraft->player) {
|
||||
float x, y, z;
|
||||
int ret = sscanf(args.c_str(), "%f,%f,%f", &x, &y, &z);
|
||||
@ -452,6 +553,7 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ
|
||||
result += "," + i.second + "|";
|
||||
}
|
||||
if (result.size() > 1) result.pop_back();
|
||||
for (auto &c : result) c = toupper(c);
|
||||
return result + "\n";
|
||||
} else if (cmd == "player.setAbsPos" && commandserver->minecraft->player) {
|
||||
float x, y, z;
|
||||
@ -460,8 +562,22 @@ std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServ
|
||||
commandserver->minecraft->player->moveTo(x, y, z, commandserver->minecraft->player->yaw, commandserver->minecraft->player->pitch);
|
||||
} else if (cmd == "player.getAbsPos" && commandserver->minecraft->player) {
|
||||
return std::to_string(commandserver->minecraft->player->x) + ","
|
||||
+ std::to_string(commandserver->minecraft->player->y) + ","
|
||||
+ std::to_string(commandserver->minecraft->player->z) + "\n";
|
||||
+ std::to_string(commandserver->minecraft->player->y) + ","
|
||||
+ std::to_string(commandserver->minecraft->player->z) + "\n";
|
||||
} else if (cmd == "player.events.clear") {
|
||||
clearEvents(commandserver, commandserver->minecraft->player->id);
|
||||
} else if (cmd == "entity.events.clear") {
|
||||
int id;
|
||||
int ret = sscanf(args.c_str(), "%d", &id);
|
||||
if (ret != 1) return fail;
|
||||
clearEvents(commandserver, id);
|
||||
} else if (cmd == "events.clear") {
|
||||
// Clear chat events
|
||||
chatEvents.at = chatEvents.start;
|
||||
// Clear projectile events
|
||||
if (!compat_mode) hitEvents.at = hitEvents.start;
|
||||
// Extended the original, so now call the original
|
||||
return old(commandserver, client, command);
|
||||
} else if (cmd == "reborn.disableCompatMode") {
|
||||
compat_mode = false;
|
||||
} else if (cmd == "reborn.enableCompatMode") {
|
||||
@ -486,6 +602,7 @@ static HitResult *Arrow_tick_HitResult_constructor_injection(HitResult *self, En
|
||||
.y = (int) target->y,
|
||||
.z = (int) target->z,
|
||||
.owner = misc_get_player_username((Player *) shooter),
|
||||
.owner_id = shooter->id,
|
||||
.targetId = target->id
|
||||
});
|
||||
}
|
||||
@ -505,6 +622,7 @@ static void Arrow_tick_injection(Arrow_tick_t old, Arrow *self) {
|
||||
.y = self->hit_y,
|
||||
.z = self->hit_z,
|
||||
.owner = misc_get_player_username((Player *) shooter),
|
||||
.owner_id = shooter->id,
|
||||
.targetId = 0
|
||||
});
|
||||
}
|
||||
@ -524,6 +642,7 @@ static void Throwable_tick_Throwable_onHit_injection(Throwable *self, HitResult
|
||||
.y = (int) res->exact.y,
|
||||
.z = (int) res->exact.z,
|
||||
.owner = misc_get_player_username((Player *) thrower),
|
||||
.owner_id = thrower->id,
|
||||
.targetId = res->entity->id
|
||||
};
|
||||
} else {
|
||||
@ -533,6 +652,7 @@ static void Throwable_tick_Throwable_onHit_injection(Throwable *self, HitResult
|
||||
.y = res->y,
|
||||
.z = res->z,
|
||||
.owner = misc_get_player_username((Player *) thrower),
|
||||
.owner_id = thrower->id,
|
||||
.targetId = 0
|
||||
};
|
||||
}
|
||||
@ -546,8 +666,8 @@ static void Gui_addMessage_injection(Gui_addMessage_t original, Gui *gui, const
|
||||
original(gui, text);
|
||||
} else {
|
||||
chatEvents.push(ChatEvent{
|
||||
text,
|
||||
chat_is_sending()
|
||||
text.substr(gui->minecraft->player->username.size() + 3),
|
||||
chat_is_sending() ? gui->minecraft->player->id : 0
|
||||
});
|
||||
recursing = true;
|
||||
original(gui, text);
|
||||
|
@ -15,7 +15,7 @@ std::string chat_send_api_command(const Minecraft *minecraft, const std::string
|
||||
ConnectedClient client;
|
||||
client.sock = -1;
|
||||
client.str = "";
|
||||
client.time = 0;
|
||||
client.lastBlockHitPoll = 0;
|
||||
CommandServer *command_server = minecraft->command_server;
|
||||
if (command_server != nullptr) {
|
||||
return command_server->parse(client, str);
|
||||
|
@ -38,7 +38,7 @@ static void Minecraft_setIsCreativeMode_injection(Minecraft_setIsCreativeMode_t
|
||||
}
|
||||
|
||||
// Disable CreatorMode-Specific API Features (Polling Block Hits) In SurvivalMode, This Is Preferable To Crashing
|
||||
static unsigned char *Minecraft_getCreator_injection(Minecraft_getCreator_t original, Minecraft *minecraft) {
|
||||
static ICreator *Minecraft_getCreator_injection(Minecraft_getCreator_t original, Minecraft *minecraft) {
|
||||
if (is_survival) {
|
||||
// SurvivalMode, Return nullptr
|
||||
return nullptr;
|
||||
|
@ -173,6 +173,14 @@ std::string misc_get_entity_name(Entity *entity) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string misc_get_entity_name_upper(Entity *entity) {
|
||||
std::string ret = misc_get_entity_name(entity);
|
||||
if (entity != NULL && !entity->isPlayer()) {
|
||||
for (auto &c : ret) c = toupper(c);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Entity *misc_make_entity_from_id(Level *level, int id) {
|
||||
if (id < 0x40) {
|
||||
return (Entity *) MobFactory::CreateMob(id, level);
|
||||
|
@ -8,6 +8,11 @@ set(SRC
|
||||
src/game/Minecraft.def
|
||||
src/game/mode/GameMode.def
|
||||
src/game/mode/CreatorMode.def
|
||||
src/game/mode/ICreator.def
|
||||
src/game/mode/Creator.def
|
||||
src/game/mode/EventList_Item.def
|
||||
src/game/mode/EventList_TileEvent.def
|
||||
src/game/mode/TileEvent.def
|
||||
src/game/mode/SurvivalMode.def
|
||||
src/game/NinecraftApp.def
|
||||
src/game/GameRenderer.def
|
||||
|
@ -2,6 +2,6 @@ size 0xc;
|
||||
|
||||
property uint sock = 0x0;
|
||||
property std::string str = 0x4;
|
||||
property int time = 0x8;
|
||||
property int lastBlockHitPoll = 0x8;
|
||||
|
||||
mark-as-simple;
|
@ -12,7 +12,7 @@ method bool isCreativeMode() = 0x17270;
|
||||
method void releaseMouse() = 0x15d30;
|
||||
method void grabMouse() = 0x15d10;
|
||||
method void leaveGame(bool save_remote_level) = 0x15ea0;
|
||||
method uchar *getCreator() = 0x17538;
|
||||
method ICreator *getCreator() = 0x17538;
|
||||
method LevelStorageSource *getLevelSource() = 0x16e84;
|
||||
method void handleMouseDown(int param_1, bool can_destroy) = 0x1584c;
|
||||
method void handleBuildAction(uint *build_action_intention) = 0x15920;
|
||||
|
7
symbols/src/game/mode/Creator.def
Normal file
7
symbols/src/game/mode/Creator.def
Normal file
@ -0,0 +1,7 @@
|
||||
size 0x20;
|
||||
|
||||
extends ICreator;
|
||||
vtable 0x1a0dc;
|
||||
|
||||
property EventList_TileEvent tileEvents = 0x4;
|
||||
property int tick = 0x1c;
|
@ -1,5 +1,8 @@
|
||||
extends GameMode;
|
||||
|
||||
size 0x1c;
|
||||
vtable 0x102d00;
|
||||
|
||||
constructor (Minecraft *minecraft) = 0x1a044;
|
||||
|
||||
property Creator *creator = 0x4;
|
||||
|
6
symbols/src/game/mode/EventList_Item.def
Normal file
6
symbols/src/game/mode/EventList_Item.def
Normal file
@ -0,0 +1,6 @@
|
||||
size 0x18;
|
||||
|
||||
property int tick = 0x0;
|
||||
property TileEvent item = 0x4;
|
||||
|
||||
mark-as-simple;
|
8
symbols/src/game/mode/EventList_TileEvent.def
Normal file
8
symbols/src/game/mode/EventList_TileEvent.def
Normal file
@ -0,0 +1,8 @@
|
||||
size 0x18;
|
||||
|
||||
property int at = 0x0;
|
||||
property int len = 0x4;
|
||||
property int cap = 0x8;
|
||||
property std::vector<EventList_Item> events = 0xc;
|
||||
|
||||
mark-as-simple;
|
3
symbols/src/game/mode/ICreator.def
Normal file
3
symbols/src/game/mode/ICreator.def
Normal file
@ -0,0 +1,3 @@
|
||||
vtable 0x102ca8;
|
||||
|
||||
virtual-method EventList_TileEvent &getTileEvents() = 0x8;
|
9
symbols/src/game/mode/TileEvent.def
Normal file
9
symbols/src/game/mode/TileEvent.def
Normal file
@ -0,0 +1,9 @@
|
||||
size 0x14;
|
||||
|
||||
property int entityId = 0x0;
|
||||
property int x = 0x4;
|
||||
property int y = 0x8;
|
||||
property int z = 0xc;
|
||||
property int face = 0x10;
|
||||
|
||||
mark-as-simple;
|
Loading…
Reference in New Issue
Block a user