diff --git a/CHANGELOG.md b/CHANGELOG.md index f6684d32..ff0b1258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.19.1] - 2026-07-05 + +### Added + +- Added packet APIs: + `Packet::sendTo`, `Packet::sendToClients`, `Packet::sendToServer`, + `Packet` / `LXL_Packet` global aliases @zimuya4153 +- Added `BinaryStream` APIs: + `getReadPointer`, `setReadPointer`, `setData`, `writeBytes`, `readBytes`, + `writeUuid` @zimuya4153 +- Added server APIs: + `mc.getMotd`, `mc.getOnlinePlayerNum`, `mc.getMaxNumPlayers`, + `mc.getDimensionId`, `mc.getDimensionName` @zimuya4153 +- Added gameplay helpers: + `en.setCustomName`, `mc.summonMob`, `mc.loadMob`, `it.addCount`, + `it.removeCount`, `it.setCount`, `system.randomUuid` @zimuya4153 +- Added player state APIs: + `pl.isSwimming`, `pl.isCrawling` @zimuya4153 +- Added name access APIs: + `be.setCustomName`, `be.getCustomName`, `en.getCustomName`, + `it.getDisplayName` @zimuya4153 +- Added player data API: + `mc.getAllPlayerUuids([isOnlineMode])` for enumerating saved player UUIDs +- Added database API: + `DBSession::backup(path)` for backing up SQL sessions +- Added `VaillanI18n` API for modifying Minecraft vanilla language data + @zimuya4153 @xianyubb +- Added `toSNBT([space[,format]])` to all NBT object types @zimuya4153 + +### Changed + +- Improved `BinaryStream`: + `getData([release])` now returns `ByteBuffer`, does not clear by default, and + optionally releases data after reading; `createPacket(pktid[,raw])` now + supports raw packets; scalar `read*` APIs were added; several numeric + `write*` APIs now accept `String` for BigInt-friendly input + @zimuya4153 +- Improved file and item behavior: + `File.readFrom(path[,isBinary])` now supports binary reads; + `File.writeTo(path, text)` now accepts `ByteBuffer`; + `it.setLore([])` now clears custom lore instead of failing @zimuya4153 +- Improved player NBT APIs: + `mc.getPlayerNbt(uuid)` now returns `Null` when no saved data exists and can + read online players directly; `mc.setPlayerNbt(uuid, nbt[, forceCreate, + isOnlineMode])` now supports optional offline record creation; player NBT + APIs now validate UUID input more strictly @zimuya4153 +- Improved NBT serialization: + `NbtCompound::toSNBT([space[,format]])` now supports explicitly selecting + `SnbtFormat`; one-argument usage still defaults to `SnbtFormat::ForceQuote` + @zimuya4153 + +### Fixed + +- Fixed `NbtCompound::setTag` @zimuya4153 + ## [0.19.0] - 2026-07-04 ### Changed @@ -1263,7 +1318,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#353]: https://github.com/LiteLDev/LegacyScriptEngine/issues/353 [#358]: https://github.com/LiteLDev/LegacyScriptEngine/issues/358 -[Unreleased]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.19.0...HEAD +[Unreleased]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.19.1...HEAD +[0.19.1]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.19.0...v0.19.1 [0.19.0]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.18.2...v0.19.0 [0.18.2]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.18.1...v0.18.2 [0.18.1]: https://github.com/LiteLDev/LegacyScriptEngine/compare/v0.18.0...v0.18.1 diff --git a/docs/apis/DataAPI/DataBase.md b/docs/apis/DataAPI/DataBase.md index 7be76a8c..699613a9 100644 --- a/docs/apis/DataAPI/DataBase.md +++ b/docs/apis/DataAPI/DataBase.md @@ -209,6 +209,22 @@ The `query` method returns: --- +### Backup Database + +`session.backup(path)` + +* **Parameters:** +* `path`: `String` + Destination path of the backup database file. + + +* **Returns:** Whether the backup operation succeeded. +* **Return Type:** `Boolean` + +> Currently this is implemented for SQLite sessions. + +--- + ### Close Database Session `session.close()` @@ -432,4 +448,4 @@ mc.regPlayerCmd("getcoin", "Get a coin!", (pl, args)=>{ dat[pl.realName]++; modified[pl.realName]++; }); -``` \ No newline at end of file +``` diff --git a/docs/apis/DataAPI/DataBase.zh.md b/docs/apis/DataAPI/DataBase.zh.md index 1ea512b8..f488d1f2 100644 --- a/docs/apis/DataAPI/DataBase.zh.md +++ b/docs/apis/DataAPI/DataBase.zh.md @@ -203,6 +203,20 @@ SQL数据库适用于使用SQL语句处理大量的关系型数据。接口底 +### 备份数据库 + +`session.backup(path)` + +- 参数: + - path : `String` + 备份数据库文件的目标路径 +- 返回值:备份操作是否成功 +- 返回值类型:`Boolean` + +> 目前该接口只为 SQLite 会话实现。 + + + ### 关闭数据库会话 `session.close()` diff --git a/docs/apis/GameAPI/BlockEntity.md b/docs/apis/GameAPI/BlockEntity.md index a7058f12..8f00cd14 100644 --- a/docs/apis/GameAPI/BlockEntity.md +++ b/docs/apis/GameAPI/BlockEntity.md @@ -70,4 +70,27 @@ For more usage of NBT objects, please refer to [NBT Interface Documentation](../ - Return type: The block entity's block object. - Return value type: `Block` +#### Set Block Entity Custom Name + +!!! warning +This function is only available in 0.19.1 and later. + +`be.setCustomName(name)` + +- Parameters: + - name : `String` + New block entity custom name. +- Return type: Whether setting was successful. +- Return value type: `Boolean` + +#### Get Block Entity Custom Name + +!!! warning +This function is only available in 0.19.1 and later. + +`be.getCustomName()` + +- Return type: The block entity's custom name. +- Return value type: `String` + diff --git a/docs/apis/GameAPI/BlockEntity.zh.md b/docs/apis/GameAPI/BlockEntity.zh.md index e6fa07c1..8bd8f453 100644 --- a/docs/apis/GameAPI/BlockEntity.zh.md +++ b/docs/apis/GameAPI/BlockEntity.zh.md @@ -70,4 +70,27 @@ - 返回值:方块实体对应的方块对象 - 返回值类型:`Block` +#### 设置方块实体自定义名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`be.setCustomName(name)` + +- 参数: + - name : `String` + 新的方块实体自定义名称 +- 返回值:是否设置成功 +- 返回值类型:`Boolean` + +#### 获取方块实体自定义名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`be.getCustomName()` + +- 返回值:方块实体自定义名称 +- 返回值类型:`String` + diff --git a/docs/apis/GameAPI/Entity.md b/docs/apis/GameAPI/Entity.md index 3b9f5b99..37ad52de 100644 --- a/docs/apis/GameAPI/Entity.md +++ b/docs/apis/GameAPI/Entity.md @@ -73,6 +73,52 @@ Through this function, generate a new creature at the specified location and get > Therefore, if there is a need to operate an entity for a long time, please obtain the real-time entity object through > the above methods. +#### Summon New Creature and Get Its Entity Object + +!!! warning +This function is only available in 0.19.1 and later. + +Through this function, summon a new creature at the specified location and get its corresponding entity object. + +`mc.summonMob(name,pos[,event])` +`mc.summonMob(name,x,y,z,dimid[,event])` + +- Parameters: + - name : `String` + The namespace name of the creature, such as `minecraft:creeper` + - pos : `IntPos `/ `FloatPos` + A coordinate object of where the mob is spawned (or use x, y, z, dimid to determine where to spawn). + - event : `String` + Optional initialization event name +- Return value: The generated entity object. +- Return value type: `Entity` + - If the return value is `Null` it means that the generation failed + +> Note: Do not save an entity object **long-term**. +> When the entity corresponding to the entity object is destroyed, the corresponding entity object will become invalid. +> Therefore, if there is a need to operate an entity for a long time, please obtain the real-time entity object through +> the above methods. + +#### Load Creature From NBT and Get Its Entity Object + +!!! warning +This function is only available in 0.19.1 and later. + +Through this function, spawn a creature with the specified NBT data at the specified location and get its corresponding +entity object. + +`mc.loadMob(nbt,pos)` +`mc.loadMob(nbt,x,y,z,dimid)` + +- Parameters: + - nbt : `NbtCompound` + Creature NBT object + - pos : `IntPos `/ `FloatPos` + A coordinate object of where the mob is spawned (or use x, y, z, dimid to determine where to spawn). +- Return value: The generated entity object. +- Return value type: `Entity` + - If the return value is `Null` it means that the generation failed + #### Clone A Creature and Get Its Entity Object Through this function, generate a new creature at the specified location and get its corresponding entity object. @@ -393,6 +439,29 @@ Note that the damage dealt here is real damage and cannot be reduced by protecti - Return value: Whether the entity was scaled. - Return value type: `Boolean` +#### Set Entity Custom Name + +!!! warning +This function is only available in 0.19.1 and later. + +`en.setCustomName(name)` + +- Parameters: + - name : `String` + The entity's new custom name +- Return value: Whether setting was successful +- Return value type: `Boolean` + +#### Get Entity Custom Name + +!!! warning +This function is only available in 0.19.1 and later. + +`en.getCustomName()` + +- Return value: The entity's custom name +- Return value type: `String` + #### Get Entity Distance To Pos `en.distanceTo(pos)` diff --git a/docs/apis/GameAPI/Entity.zh.md b/docs/apis/GameAPI/Entity.zh.md index c905ffb6..6c8facd6 100644 --- a/docs/apis/GameAPI/Entity.zh.md +++ b/docs/apis/GameAPI/Entity.zh.md @@ -69,6 +69,49 @@ > 注意:不要**长期保存**一个实体对象 > 当实体对象对应的实体被销毁时,对应的实体对象将同时释放。因此,如果有长期操作某个实体的需要,请通过上述途径获取实时的实体对象 +#### 召唤新生物并获取 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +通过此函数,在指定的位置召唤一个新生物,并获取它对应的实体对象 + +`mc.summonMob(name,pos[,event])` +`mc.summonMob(name,x,y,z,dimid[,event])` + +- 参数: + - name : `String` + 生物的命名空间名称,如 `minecraft:creeper` + - pos : `IntPos `/ `FloatPos` + 生成生物的位置的坐标对象(或者使用x, y, z, dimid来确定生成位置) + - event : `String` + 可选的初始化事件名称 +- 返回值:生成的实体对象 +- 返回值类型:`Entity` + - 如返回值为 `Null` 则表示生成失败 + +> 注意:不要**长期保存**一个实体对象 +> 当实体对象对应的实体被销毁时,对应的实体对象将同时释放。因此,如果有长期操作某个实体的需要,请通过上述途径获取实时的实体对象 + +#### 通过NBT加载生物并获取 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +通过此函数,使用指定的NBT数据在指定位置生成一个生物,并获取它对应的实体对象 + +`mc.loadMob(nbt,pos)` +`mc.loadMob(nbt,x,y,z,dimid)` + +- 参数: + - nbt : `NbtCompound` + 生物的NBT对象 + - pos : `IntPos `/ `FloatPos` + 生成生物的位置的坐标对象(或者使用x, y, z, dimid来确定生成位置) +- 返回值:生成的实体对象 +- 返回值类型:`Entity` + - 如返回值为 `Null` 则表示生成失败 + #### 复制生物并获取 通过此函数,在指定的位置复制另一个实体,并获取它对应的实体对象 @@ -383,6 +426,29 @@ - 返回值: 实体是否成功地被缩放 - 返回值类型: `Boolean` +#### 设置实体自定义名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`en.setCustomName(name)` + +- 参数: + - name : `String` + 实体新的自定义名称 +- 返回值: 是否设置成功 +- 返回值类型: `Boolean` + +#### 获取实体自定义名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`en.getCustomName()` + +- 返回值: 实体自定义名称 +- 返回值类型: `String` + #### 获取实体到坐标的距离 `en.distanceTo(pos)` diff --git a/docs/apis/GameAPI/Item.md b/docs/apis/GameAPI/Item.md index c47b5288..4ea49807 100644 --- a/docs/apis/GameAPI/Item.md +++ b/docs/apis/GameAPI/Item.md @@ -205,6 +205,9 @@ For more usage of NBT objects, please refer to [NBT Interface Documentation](../ - Return value: Whether setting the lore was successful. - Return value type: `Boolean` +!!! warning +In 0.19.1 and later, passing an empty array will clear the item's lore. + #### Set Custom Item Name @@ -217,6 +220,16 @@ For more usage of NBT objects, please refer to [NBT Interface Documentation](../ - Return value: Whether setting the name was successful. - Return value type: `Boolean` +#### Get Custom Item Name + +!!! warning +This function is only available in 0.19.1 and later. + +`it.getDisplayName()` + +- Return value: Custom item name +- Return value type: `String` + #### Determine if it is the same kind of item @@ -229,4 +242,43 @@ For more usage of NBT objects, please refer to [NBT Interface Documentation](../ - Return value: is the same kind of item - Return value type: `Boolean` +#### Increase Item Count + +!!! warning +This function is only available in 0.19.1 and later. + +`it.addCount(count)` + +- Parameters: + - count : `Integer` + Amount to add +- Return value: Whether setting was successful +- Return value type: `Boolean` + +#### Decrease Item Count + +!!! warning +This function is only available in 0.19.1 and later. + +`it.removeCount(count)` + +- Parameters: + - count : `Integer` + Amount to remove +- Return value: Whether setting was successful +- Return value type: `Boolean` + +#### Set Item Count + +!!! warning +This function is only available in 0.19.1 and later. + +`it.setCount(count)` + +- Parameters: + - count : `Integer` + Amount to set +- Return value: Whether setting was successful +- Return value type: `Boolean` + diff --git a/docs/apis/GameAPI/Item.zh.md b/docs/apis/GameAPI/Item.zh.md index 457a28bd..10b676c1 100644 --- a/docs/apis/GameAPI/Item.zh.md +++ b/docs/apis/GameAPI/Item.zh.md @@ -206,6 +206,9 @@ - 返回值:是否设置成功 - 返回值类型: `Boolean` +!!! warning + 在0.19.1及以后版本中,传入空数组会清空物品Lore。 + #### 设置自定义物品名称 @@ -218,6 +221,16 @@ - 返回值: 设置物品名称是否成功 - 返回值类型: `Boolean` +#### 获取自定义物品名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`it.getDisplayName()` + +- 返回值: 自定义物品名称 +- 返回值类型: `String` + #### 判断是否为同类物品 @@ -230,4 +243,43 @@ - 返回值: 是否为同类物品 - 返回值类型: `Boolean` +#### 增加物品数量 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`it.addCount(count)` + +- 参数: + - count : `Integer` + 要增加的数量 +- 返回值: 是否设置成功 +- 返回值类型: `Boolean` + +#### 减少物品数量 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`it.removeCount(count)` + +- 参数: + - count : `Integer` + 要减少的数量 +- 返回值: 是否设置成功 +- 返回值类型: `Boolean` + +#### 设置物品数量 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`it.setCount(count)` + +- 参数: + - count : `Integer` + 要设置的数量 +- 返回值: 是否设置成功 +- 返回值类型: `Boolean` + diff --git a/docs/apis/GameAPI/Packet.md b/docs/apis/GameAPI/Packet.md index fb9ab5a0..46e04dc5 100644 --- a/docs/apis/GameAPI/Packet.md +++ b/docs/apis/GameAPI/Packet.md @@ -1,18 +1,16 @@ -# 🎓 Packet API +# 🎓 Packet API -The following objects and APIs provide the basic BDS packet interface for scripts. +The following objects and APIs provide the basic BDS packet interface for scripts. -(Please refer to Nukkit, PokcetMine, BDS Reverse to know the packet structure) If the client crashes, it is a packet structure error, not a bug. +(Please refer to Nukkit, PokcetMine, BDS Reverse to know the packet structure) If the client crashes, it is a packet structure error, not a bug. The documentation does not list the packet ID and its structure, please check it yourself. - ## 目录 + - 🔉 [Packet Object API](#-packet-object-api) - 🔌 [Binary stream object API](#-binary-stream-object-api) - - ## 🔉 Packet Object API In LLSE, 「Packet Object」 is used to get information about packets. @@ -21,10 +19,23 @@ In LLSE, 「Packet Object」 is used to get information about packets. #### Get from API -Call some **return packet object** function to get to the packet object given by BDS -See [Binary Stream Objects](#-binary-stream-object-api) for details +Call some **return packet object** function to get to the packet object given by BDS +See [Binary Stream Objects](#-binary-stream-object-api) for details + +#### Create from packet ID +!!! warning +The global aliases of this class are only available in 0.19.1 and later. +`Packet.createPacket(pktid[,raw])` + +- Parameters: + - pktid : `Integer` + Packet ID + - raw : `Boolean` (Optional parameter) + Create raw network packet. The default value is `false` +- Return value:Packet object +- Return value type: `Packet` ### Packet Objects - Functions @@ -35,9 +46,7 @@ Every packet object contains some member functions (member methods) that can be `pkt.getName()` - Return value:packet name -- Return value type: `String` - - +- Return value type: `String` #### Get packet ID @@ -46,15 +55,75 @@ Every packet object contains some member functions (member methods) that can be - Return value:packet id - Return value type: `Integer` +#### Read binary stream data into packet + +`pkt.read(bs)` + +- Parameters: + - bs : `BinaryStream` + Source binary stream +- Return value:success or not +- Return value type: `Boolean` + +#### Write packet data into binary stream + +`pkt.write(bs)` + +- Parameters: + - bs : `BinaryStream` + Target binary stream +- Return value:success or not +- Return value type: `Boolean` + +#### Send packet to specified target + +!!! warning +This function is only available in 0.19.1 and later. + +`pkt.sendTo(pos)` +`pkt.sendTo(x,y,z,dimid)` +`pkt.sendTo(target)` + +- Parameters: + + - pos : `IntPos` / `FloatPos` + The coordinates of the packet send target (or use x, y, z, dimid to determine the target position) + - target : `Player` / `Entity` + Packet send target + +- Return value:success or not +- Return value type: `Boolean` + +If `target` is `Player`, the packet will be sent to the specified player. +If `target` is `Entity`, the packet will be sent to players around the specified entity. + +#### Send packet to all clients + +!!! warning +This function is only available in 0.19.1 and later. + +`pkt.sendToClients()` + +- Return value:success or not +- Return value type: `Boolean` + +#### Send packet to server + +!!! warning +This function is only available in 0.19.1 and later. +`pkt.sendToServer()` + +- Return value:success or not +- Return value type: `Boolean` ## 🔌 Binary Stream Object API ### Create a binary stream object -[JavaScript] ```new BinaryStream()``` +[JavaScript] `new BinaryStream()` -[Lua] ```BinaryStream()``` +[Lua] `BinaryStream()` - Return value: Binary stream object - Return value type: `BinaryStream` @@ -70,72 +139,181 @@ Every binary stream object contains some member functions (member methods) that - Return value: success or not - Return value type: `Boolean` +#### Get binary stream read pointer + +!!! warning +This function is only available in 0.19.1 and later. + +`bs.getReadPointer()` + +- Return value: current read pointer +- Return value type: `Integer` + +#### Set binary stream read pointer + +!!! warning +This function is only available in 0.19.1 and later. + +`bs.setReadPointer(pos)` + +- Parameters: + + - pos : `Integer` + New read pointer + +- Return value: success or not +- Return value type: `Boolean` + +#### Get binary stream data + +!!! warning +The optional parameter and `ByteBuffer` return value of this function are only available in 0.19.1 and later. + +`bs.getData([release])` + +- Parameters: + + - release : `Boolean` (Optional parameter) + Whether to clear stream data after reading. The default value is `false` + +- Return value: binary stream data +- Return value type: `ByteBuffer` + +Before 0.19.1, this function can only be used as `bs.getData()`. +At that time, it always cleared the stream data after reading, and the return value type was `String`. + +Because the old return value was `String`, in JavaScript it may be forcibly encoded as UTF-8, causing binary data corruption and incorrect data content. + +#### Set binary stream data + +!!! warning +This function is only available in 0.19.1 and later. + +`bs.setData(data)` + +- Parameters: + - data : `ByteBuffer` + Binary stream data + +- Return value: success or not +- Return value type: `Boolean` #### Write to binary stream -`bs.writexxxx(value)` +`bs.writexxxx(value)` - Parameters: + - value : `NULL` Refer to the table below + For some numeric write functions, `String` is also accepted - Return value:success or not - Return value type: `Boolean` -| Available functions | Parameter Type | -| ------------------------------ | -------------- | -| writeBool | `Boolean` | -| writeByte | `Integer` | -| writeDouble | `Number` | -| writeFloat | `Float` | -| writeSignedBigEndianInt | `Number` | -| writeSignedInt | `Number` | -| writeSignedInt64 | `Number` | -| writeSignedShort | `Integer` | -| writeString | `String` | -| writeUnsignedChar | `Integer` | -| writeUnsignedInt | `Number` | -| writeUnsignedInt64 | `Number` | -| writeUnsignedShort | `Integer` | -| writeUnsignedVarInt | `Number` | -| writeUnsignedVarInt64 | `Number` | -| writeVarInt | `Number` | -| writeVarInt64 | `Number` | -| writeVec3 | `FloatPos` | -| writeBlockPos (Added in 0.9.5) | `BlockPos` | -| writeCompoundTag | `NbtCompound` | -| writeItem (Added in 0.9.5) | `Item` | - - +| Available functions | Parameter Type | +| ------------------------------ | -------------------- | +| writeBool | `Boolean`/ `String` | +| writeByte | `Integer` / `String` | +| writeBytes (Added in 0.19.1) | `ByteBuffer` | +| writeDouble | `Number` / `String` | +| writeFloat | `Float` / `String` | +| writeSignedBigEndianInt | `Number` / `String` | +| writeSignedInt | `Number` / `String` | +| writeSignedInt64 | `Number` / `String` | +| writeSignedShort | `Integer` / `String` | +| writeString | `String` | +| writeUnsignedChar | `Integer` / `String` | +| writeUnsignedInt | `Number` / `String` | +| writeUnsignedInt64 | `Number` / `String` | +| writeUnsignedShort | `Integer` / `String` | +| writeUnsignedVarInt | `Number` / `String` | +| writeUnsignedVarInt64 | `Number` / `String` | +| writeVarInt | `Number` / `String` | +| writeVarInt64 | `Number` / `String` | +| writeVec3 | `FloatPos` | +| writeBlockPos (Added in 0.9.5) | `BlockPos` | +| writeCompoundTag | `NbtCompound` | +| writeItem (Added in 0.9.5) | `Item` | +| writeUuid (Added in 0.19.1) | `String` | + +#### Read from binary stream + +!!! warning +This function group is only available in 0.19.1 and later. + +`bs.readxxxx([asString])` +`bs.readBytes(length)` + +- Parameters: + + - asString : `Boolean` (Optional parameter) + Only available for numeric read functions. When set to `true`, the return value is converted to `String` + - length : `Integer` + Only available for `readBytes`. Number of bytes to read. Must be greater than `0` + +- Return value: + - Read result + +| Available functions | Parameter Type | Return Type | +| ---------------------- | -------------- | ------------------- | +| readBool | `Boolean` | `Boolean` / `String` | +| readByte | `Boolean` | `Number` / `String` | +| readBytes | `Integer` | `ByteBuffer` | +| readUnsignedChar | `Boolean` | `Number` / `String` | +| readDouble | `Boolean` | `Number` / `String` | +| readFloat | `Boolean` | `Number` / `String` | +| readSignedBigEndianInt | `Boolean` | `Number` / `String` | +| readSignedInt | `Boolean` | `Number` / `String` | +| readSignedInt64 | `Boolean` | `Number` / `String` | +| readSignedShort | `Boolean` | `Number` / `String` | +| readString | None | `String` | +| readUnsignedInt | `Boolean` | `Number` / `String` | +| readUnsignedInt64 | `Boolean` | `Number` / `String` | +| readUnsignedShort | `Boolean` | `Number` / `String` | +| readUnsignedVarInt | `Boolean` | `Number` / `String` | +| readUnsignedVarInt64 | `Boolean` | `Number` / `String` | +| readVarInt | `Boolean` | `Number` / `String` | +| readVarInt64 | `Boolean` | `Number` / `String` | + +When `asString` is `true`, scalar `read*` functions return `String`. #### Building packet from binary stream -`bs.createPacket(pktid)` +!!! warning +The optional parameter of this function is only available in 0.19.1 and later. + +`bs.createPacket(pktid[,raw])` - Parameters: + - pktid : `Integer` Packet ID + - raw : `Boolean` (Optional parameter) + Create raw network packet from current binary stream data. The default value is `false` - Return value:Packet object - Return value type: `Packet` - +Before 0.19.1, this function can only be used as `bs.createPacket(pktid)`. ### Dome Code Send TextPacket packets to a player + ```js -mc.listen("onChat",function(pl,msg){ - const bs = new BinaryStream() - var text = "LLSE Packet Test" - bs.reserve(text.length + 8) - bs.writeUnsignedChar(0) - bs.writeBool(true) - bs.writeString(text) - bs.writeString("") - bs.writeString("") - var pkt = bs.createPacket(9) - pl.sendPacket(pkt) -}) +mc.listen("onChat", (player, message) => { + const text = "LLSE Packet Test"; + const bs = new BinaryStream(); + bs.reserve(text.length + 8); + bs.writeBool(false); + bs.writeByte(/* TextPacketPayload::mBody::MessageOnly (Variant Index) */0); + bs.writeByte(/* TextPacketType::Raw (Enum) */0); + bs.writeString(text); + bs.writeString(""); // xuid + bs.writeString(""); // platformId + bs.writeString(""); // filtered message + bs.createPacket(9).sendTo(player); +}); ``` diff --git a/docs/apis/GameAPI/Packet.zh.md b/docs/apis/GameAPI/Packet.zh.md index dac8b728..b521d9e0 100644 --- a/docs/apis/GameAPI/Packet.zh.md +++ b/docs/apis/GameAPI/Packet.zh.md @@ -1,30 +1,41 @@ -# 🎓 数据包 API +# 🎓 数据包 API -下面这些对象与API为脚本提供了基本的BDS数据包接口。 +下面这些对象与 API 为脚本提供了基本的 BDS 数据包接口。 -温馨提示:此类API需要部分逆向基础,了解数据包结构(可通过参考Nukkit,PokcetMine,BDS逆向得知数据包结构)如出现客户端崩溃,为数据包结构错误,并非BUG。 - -文档不列出数据包ID与其结构,请自行查询。 +温馨提示:此类 API 需要部分逆向基础,了解数据包结构(可通过参考 Nukkit,PokcetMine,BDS 逆向得知数据包结构)如出现客户端崩溃,为数据包结构错误,并非 BUG。 +文档不列出数据包 ID 与其结构,请自行查询。 ## 目录 + - 🔉 [数据包对象 API](#-数据包对象-api) - 🔌 [二进制流对象 API](#-二进制流对象-api) - - ## 🔉 数据包对象 API 在脚本引擎中,使用「数据包对象」来获取数据包的相关信息。 ### 获取一个数据包对象 -#### 从API获取 +#### 从 API 获取 + +调用某些**返回数据包对象**的函数,来获取到 BDS 给出的数据包对象 +详见 [二进制流对象](#-二进制流对象-api) -调用某些**返回数据包对象**的函数,来获取到BDS给出的数据包对象 -详见 [二进制流对象](#-二进制流对象-api) +#### 通过数据包 ID 创建 +!!! warning +此类的全局别名仅在 0.19.1 及以后版本可用 +`Packet.createPacket(pktid[,raw])` + +- 参数: + - pktid : `Integer` + 数据包 ID + - raw : `Boolean`(可选参数) + 是否创建原始网络数据包。默认值为 `false` +- 返回值:数据包对象 +- 返回值类型: `Packet` ### 数据包对象 - 函数 @@ -37,24 +48,82 @@ - 返回值:数据包名称 - 返回值类型: `String` - - -#### 获取数据包ID +#### 获取数据包 ID `pkt.getId()` -- 返回值:数据包ID +- 返回值:数据包 ID - 返回值类型: `Integer` +#### 将二进制流数据读取到数据包 + +`pkt.read(bs)` + +- 参数: + - bs : `BinaryStream` + 源二进制流 +- 返回值:是否成功 +- 返回值类型: `Boolean` + +#### 将数据包数据写入二进制流 + +`pkt.write(bs)` + +- 参数: + - bs : `BinaryStream` + 目标二进制流 +- 返回值:是否成功 +- 返回值类型: `Boolean` + +#### 发送数据包到指定目标 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 + +`pkt.sendTo(pos)` +`pkt.sendTo(x,y,z,dimid)` +`pkt.sendTo(target)` + +- 参数: + + - pos : `IntPos` / `FloatPos` + 数据包发送目标所在坐标(或者使用 x, y, z, dimid 来确定目标位置) + - target : `Player` / `Entity` + 数据包发送目标 + +- 返回值:是否成功 +- 返回值类型: `Boolean` + +如果 `target` 是 `Player`,则数据包会发送到指定玩家。 +如果 `target` 是 `Entity`,则数据包会发送到指定实体周围的玩家。 + +#### 发送数据包到所有客户端 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 + +`pkt.sendToClients()` + +- 返回值:是否成功 +- 返回值类型: `Boolean` + +#### 发送数据包到服务端 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 +`pkt.sendToServer()` + +- 返回值:是否成功 +- 返回值类型: `Boolean` ## 🔌 二进制流对象 API ### 创建一个二进制流对象 -[JavaScript] ```new BinaryStream()``` +[JavaScript] `new BinaryStream()` -[Lua] ```BinaryStream()``` +[Lua] `BinaryStream()` - 返回值:二进制流对象 - 返回值类型: `BinaryStream` @@ -70,72 +139,181 @@ - 返回值:是否成功 - 返回值类型: `Boolean` +#### 获取二进制流读指针 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 + +`bs.getReadPointer()` + +- 返回值:当前读指针 +- 返回值类型: `Integer` + +#### 设置二进制流读指针 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 + +`bs.setReadPointer(pos)` + +- 参数: + + - pos : `Integer` + 新的读指针 + +- 返回值:是否成功 +- 返回值类型: `Boolean` + +#### 获取二进制流数据 + +!!! warning +此函数的可选参数和 `ByteBuffer` 返回值仅在 0.19.1 及以后版本可用 +`bs.getData([release])` + +- 参数: + + - release : `Boolean`(可选参数) + 获取后是否清空流数据。默认值为 `false` + +- 返回值:二进制流数据 +- 返回值类型: `ByteBuffer` + +在 0.19.1 之前,此函数只能使用 `bs.getData()` 形式调用。 +当时它在获取后总是会清空流数据,且返回值类型为 `String`。 + +由于旧版返回的是 `String`,在 JavaScript 中可能会被强制按 UTF-8 编码处理,导致二进制数据损坏,拿到的数据不正确。 + +#### 设置二进制流数据 + +!!! warning +此函数仅在 0.19.1 及以后版本可用 + +`bs.setData(data)` + +- 参数: + + - data : `ByteBuffer` + 二进制流数据 + +- 返回值:是否成功 +- 返回值类型: `Boolean` #### 写入二进制流 -`bs.writexxxx(value)` +`bs.writexxxx(value)` - 参数: + - value : `NULL` 参考下面表格 + 部分数值写入函数也允许传入 `String` - 返回值:是否成功 - 返回值类型: `Boolean` -| 可用函数 | 参数类型 | -| ---------------------------- | ------------- | -| writeBool | `Boolean` | -| writeByte | `Integer` | -| writeDouble | `Number` | -| writeFloat | `Float` | -| writeSignedBigEndianInt | `Number` | -| writeSignedInt | `Number` | -| writeSignedInt64 | `Number` | -| writeSignedShort | `Integer` | -| writeString | `String` | -| writeUnsignedChar | `Integer` | -| writeUnsignedInt | `Number` | -| writeUnsignedInt64 | `Number` | -| writeUnsignedShort | `Integer` | -| writeUnsignedVarInt | `Number` | -| writeUnsignedVarInt64 | `Number` | -| writeVarInt | `Number` | -| writeVarInt64 | `Number` | -| writeVec3 | `FloatPos` | -| writeBlockPos (0.9.5 时加入) | `BlockPos` | -| writeCompoundTag | `NbtCompound` | -| writeItem (0.9.5 时加入) | `Item` | +| 可用函数 | 参数类型 | +| ---------------------------- | -------------------- | +| writeBool | `Boolean` | +| writeByte | `Integer` / `String` | +| writeBytes (0.19.1 时加入) | `ByteBuffer` | +| writeDouble | `Number` / `String` | +| writeFloat | `Float` / `String` | +| writeSignedBigEndianInt | `Number` / `String` | +| writeSignedInt | `Number` / `String` | +| writeSignedInt64 | `Number` / `String` | +| writeSignedShort | `Integer` / `String` | +| writeString | `String` | +| writeUnsignedChar | `Integer` | +| writeUnsignedInt | `Number` / `String` | +| writeUnsignedInt64 | `Number` / `String` | +| writeUnsignedShort | `Integer` / `String` | +| writeUnsignedVarInt | `Number` / `String` | +| writeUnsignedVarInt64 | `Number` / `String` | +| writeVarInt | `Number` / `String` | +| writeVarInt64 | `Number` / `String` | +| writeVec3 | `FloatPos` | +| writeBlockPos (0.9.5 时加入) | `BlockPos` | +| writeCompoundTag | `NbtCompound` | +| writeItem (0.9.5 时加入) | `Item` | +| writeUuid (0.19.1 时加入) | `String` | + +#### 从二进制流读取 + +!!! warning +此类函数仅在 0.19.1 及以后版本可用。 + +`bs.readxxxx([asString])` +`bs.readBytes(length)` + +其中 `length` 为 `Integer` 类型,仅 `readBytes` 可用,表示要读取的字节数,必须大于 `0`。 +- 参数: + - asString : `Boolean`(可选参数) + 仅数字读取函数可用。传入 `true` 时,返回值会转换为 `String` + +- 返回值: + - 读取结果 + +| 可用函数 | 参数类型 | 返回值类型 | +| ---------------------- | --------- | ------------------- | +| readBool | `Boolean` | `Boolean`/ `String` | +| readByte | `Boolean` | `Number` / `String` | +| readBytes | `Integer` | `ByteBuffer` | +| readUnsignedChar | `Boolean` | `Number` / `String` | +| readDouble | `Boolean` | `Number` / `String` | +| readFloat | `Boolean` | `Number` / `String` | +| readSignedBigEndianInt | `Boolean` | `Number` / `String` | +| readSignedInt | `Boolean` | `Number` / `String` | +| readSignedInt64 | `Boolean` | `Number` / `String` | +| readSignedShort | `Boolean` | `Number` / `String` | +| readString | 无 | `String` | +| readUnsignedInt | `Boolean` | `Number` / `String` | +| readUnsignedInt64 | `Boolean` | `Number` / `String` | +| readUnsignedShort | `Boolean` | `Number` / `String` | +| readUnsignedVarInt | `Boolean` | `Number` / `String` | +| readUnsignedVarInt64 | `Boolean` | `Number` / `String` | +| readVarInt | `Boolean` | `Number` / `String` | +| readVarInt64 | `Boolean` | `Number` / `String` | + +当 `asString` 为 `true` 时,数字 `read*` 函数返回 `String`;否则返回 `Number`。 #### 通过二进制流构建数据包 -`bs.createPacket(pktid)` +!!! warning +此函数的可选参数仅在 0.19.1 及以后版本可用 + +`bs.createPacket(pktid[,raw])` - 参数: + - pktid : `Integer` - 数据包ID + 数据包 ID + - raw : `Boolean`(可选参数) + 是否从当前二进制流数据创建原始网络数据包。默认值为 `false` - 返回值:数据包对象 - 返回值类型: `Packet` - +在 0.19.1 之前,此函数只能使用 `bs.createPacket(pktid)` 形式调用。 ### 演示代码 -向一个玩家发送TextPacket数据包 +向一个玩家发送 TextPacket 数据包 + ```js -mc.listen("onChat",function(pl,msg){ - const bs = new BinaryStream() - var text = "LLSE Packet Test" - bs.reserve(text.length + 8) - bs.writeUnsignedChar(0) - bs.writeBool(true) - bs.writeString(text) - bs.writeString("") - bs.writeString("") - var pkt = bs.createPacket(9) - pl.sendPacket(pkt) +mc.listen("onChat", (player, message) => { + const text = "LLSE Packet Test"; + const bs = new BinaryStream(); + bs.reserve(text.length + 8); + bs.writeBool(false); + bs.writeByte(/* TextPacketPayload::mBody::MessageOnly (Variant Index) */0); + bs.writeByte(/* TextPacketType::Raw (Enum) */0); + bs.writeString(text); + bs.writeString(""); // xuid + bs.writeString(""); // platformId + bs.writeString(""); // filtered message + bs.createPacket(9).sendTo(player); }); ``` diff --git a/docs/apis/GameAPI/Player.md b/docs/apis/GameAPI/Player.md index 168ca9d6..9073e6d3 100644 --- a/docs/apis/GameAPI/Player.md +++ b/docs/apis/GameAPI/Player.md @@ -103,6 +103,8 @@ properties. | pl.isSleeping | Player is sleeping | `Boolean` | | pl.isMoving | Player is moving | `Boolean` | | pl.isSneaking | Player is sneaking | `Boolean` | +| pl.isSwimming | Whether the player is swimming (Added in 0.19.1) | `Boolean` | +| pl.isCrawling | Whether the player is crawling (Added in 0.19.1) | `Boolean` | These object properties are read-only and cannot be modified. in: @@ -1109,23 +1111,24 @@ For more usage of NBT objects, please refer to [NBT Interface Documentation](../ Player`s UUID - Return value: Player's NBT object. - Return value type: `NbtCompound` - -Using this API, you can operate offline player`s nbt. + - If the player does not exist or no saved data is found, the return value is `Null`. #### Write to an Player's NBT Object -`mc.setPlayerNbt(uuid,nbt)` +`mc.setPlayerNbt(uuid,nbt[,forceCreate,isOnlineMode])` - Parameters: - uuid : `String` Player`s UUID - nbt : `NbtCompound` NBT objects + - forceCreate : `Boolean` (Optional parameter) + Whether to create offline player data when the UUID has no saved record. The default value is `false`. + - isOnlineMode : `Boolean` (Optional parameter) + Only used when `forceCreate` is `true`. Whether the created player data uses online mode. The default value is `true`. - Return value: Whether the write was successful or not. - Return value type: `Boolean` -Using this API, you can operate offline player`s nbt. - #### Write Data to Some Special Tags of an Player's NBT Object `mc.setPlayerNbtTags(uuid,nbt,tags)` @@ -1135,13 +1138,11 @@ Using this API, you can operate offline player`s nbt. Player`s UUID - nbt : `NbtCompound` NBT objects - - tags : `Array` + - tags : `Array` Tags need to write - Return value: Whether the write was successful or not. - Return value type: `Boolean` -Using this API, you can operate offline player`s nbt. - #### Delete an Player's NBT Object `mc.deletePlayerNbt(uuid)` @@ -1152,7 +1153,17 @@ Using this API, you can operate offline player`s nbt. - Return value: Whether the delete was successful or not. - Return value type: `Boolean` -Using this API, you can operate offline player`s nbt. +Using this API, you can delete saved offline player nbt data by UUID. + +#### Get All Saved Player UUIDs + +`mc.getAllPlayerUuids([isOnlineMode])` + +- Parameters: + - isOnlineMode : `Boolean` (Optional parameter) + `true` returns UUIDs of players who logged in with a Microsoft account; `false` returns UUIDs of offline mode players, default is `false`. +- Return value: List of saved player UUID strings. +- Return value type: `Array` #### Add a Tag for the Player diff --git a/docs/apis/GameAPI/Player.zh.md b/docs/apis/GameAPI/Player.zh.md index 3fb52113..c9ff542b 100644 --- a/docs/apis/GameAPI/Player.zh.md +++ b/docs/apis/GameAPI/Player.zh.md @@ -101,6 +101,8 @@ | pl.isSleeping | 玩家是否正在睡觉 | `Boolean` | | pl.isMoving | 玩家是否正在移动 | `Boolean` | | pl.isSneaking | 玩家是否正在潜行 | `Boolean` | +| pl.isSwimming | 玩家是否正在游泳(在 0.19.1 时被加入) | `Boolean` | +| pl.isCrawling | 玩家是否正在爬行(在 0.19.1 时被加入) | `Boolean` | 这些对象属性都是只读的,无法被修改。其中: @@ -1464,23 +1466,24 @@ 玩家的UUID - 返回值:玩家的NBT对象 - 返回值类型:`NbtCompound` - -此API的好处是可以获取到离线玩家NBT,无需玩家在线,无需玩家对象。 + - 如果玩家不存在或没有存档数据,则返回值为 `Null` #### 写入玩家对应的NBT对象 -`mc.setPlayerNbt(uuid,nbt)` +`mc.setPlayerNbt(uuid,nbt[,forceCreate,isOnlineMode])` - 参数: - uuid : `String` 玩家的UUID - nbt : `NbtCompound` NBT对象 + - forceCreate : `Boolean` 可选参数 + 当该 UUID 没有对应存档记录时,是否创建离线玩家数据。默认值为 `false` + - isOnlineMode : `Boolean` 可选参数 + 仅在 `forceCreate` 为 `true` 时生效。决定新建玩家数据是否使用在线模式。默认值为 `true` - 返回值:是否成功写入 - 返回值类型:`Boolean` -此API的好处是可以操作离线玩家NBT,无需玩家在线,无需玩家对象。 - #### 覆盖玩家对应的NBT对象的特定NbtTag `mc.setPlayerNbtTags(uuid,nbt,tags)` @@ -1490,13 +1493,11 @@ 玩家的UUID - nbt : `NbtCompound` NBT对象 - - tags : `Array` - 需要覆盖的NbtTag (String) + - tags : `Array` + 需要覆盖的NbtTag - 返回值:是否成功覆盖对应的Tag - 返回值类型:`Boolean` -此API的好处是可以操作离线玩家NBT,无需玩家在线,无需玩家对象。 - #### 从存档中删除玩家对应的NBT对象的全部内容 `mc.deletePlayerNbt(uuid)` @@ -1507,7 +1508,15 @@ - 返回值:是否删除成功 - 返回值类型:`Boolean` -此API的好处是可以操作离线玩家NBT,无需玩家在线,无需玩家对象。 +#### 获取全部已保存玩家 UUID + +`mc.getAllPlayerUuids([isOnlineMode])` + +- 参数: + - isOnlineMode : `Boolean` 可选参数 + 为 `true` 时返回登录了微软账号玩家的 UUID;为 `false` 时返回离线模式玩家的 UUID,默认为 `false` +- 返回值:已保存玩家 UUID 字符串列表 +- 返回值类型:`Array` #### 为玩家增加一个Tag diff --git a/docs/apis/GameAPI/Server.md b/docs/apis/GameAPI/Server.md index aea886fe..463906fd 100644 --- a/docs/apis/GameAPI/Server.md +++ b/docs/apis/GameAPI/Server.md @@ -20,6 +20,18 @@ The following APIs provide interfaces for customizing some server settings: +### Get Server Motd String + +!!! warning +This function is only available in 0.19.1 and later. + +`mc.getMotd()` + +- Return value: Current server Motd string +- Return value type: `String` + + + ### Set Server Motd String `mc.setMotd(motd)` @@ -44,6 +56,33 @@ The following APIs provide interfaces for customizing some server settings: +### Get Online Player Count + +!!! warning +This function is only available in 0.19.1 and later. + +`mc.getOnlinePlayerNum([ignoreSimulatedPlayer])` + +- Parameters: + - ignoreSimulatedPlayer : `Boolean` = `false` + Whether to ignore simulated players +- Return value: Current number of online players +- Return value type: `Number` + + + +### Get Server Maximum Player Count + +!!! warning +This function is only available in 0.19.1 and later. + +`mc.getMaxNumPlayers()` + +- Return value: Current maximum number of players on the server +- Return value type: `Number` + + + ### Get Sever time `mc.getTime(TimeID)` @@ -90,3 +129,33 @@ Among them, daytime is the number of game ticks since dawn, gametime is the age - Return value type: `Boolean` + +### Get Dimension ID + +!!! warning +This function is only available in 0.19.1 and later. + +`mc.getDimensionId(name)` + +- Parameters: + - name : `String` + Dimension name +- Return value: Dimension ID corresponding to the dimension name, or `null` if the dimension name is invalid +- Return value type: `Number` + + + +### Get Dimension Name + +!!! warning +This function is only available in 0.19.1 and later. + +`mc.getDimensionName(dimid)` + +- Parameters: + - dimid : `Integer` + Dimension ID +- Return value: Dimension name corresponding to the dimension ID, or `null` if the dimension ID is invalid +- Return value type: `String` + + diff --git a/docs/apis/GameAPI/Server.zh.md b/docs/apis/GameAPI/Server.zh.md index 4f0a1b49..430d66f6 100644 --- a/docs/apis/GameAPI/Server.zh.md +++ b/docs/apis/GameAPI/Server.zh.md @@ -20,6 +20,18 @@ +### 获取服务器MOTD字符串 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`mc.getMotd()` + +- 返回值:当前服务器MOTD字符串 +- 返回值类型:`String` + + + ### 设置服务器MOTD字符串 `mc.setMotd(motd)` @@ -44,6 +56,33 @@ +### 获取在线玩家数量 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`mc.getOnlinePlayerNum([ignoreSimulatedPlayer])` + +- 参数: + - ignoreSimulatedPlayer : `Boolean` = `false` + 是否忽略模拟玩家 +- 返回值:当前在线玩家数量 +- 返回值类型:`Number` + + + +### 获取服务器最大玩家数 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`mc.getMaxNumPlayers()` + +- 返回值:当前服务器最大玩家数 +- 返回值类型:`Number` + + + ### 获取服务器游戏时间 `mc.getTime(TimeID)` @@ -90,3 +129,33 @@ - 返回值类型:`Boolean` + +### 获取维度ID + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`mc.getDimensionId(name)` + +- 参数: + - name : `String` + 维度名称 +- 返回值:对应维度名称的维度ID,若维度名称无效则返回`null` +- 返回值类型:`Number` + + + +### 获取维度名称 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`mc.getDimensionName(dimid)` + +- 参数: + - dimid : `Integer` + 维度ID +- 返回值:对应维度ID的维度名称,若维度ID无效则返回`null` +- 返回值类型:`String` + + diff --git a/docs/apis/NbtAPI/NBT.md b/docs/apis/NbtAPI/NBT.md index 4ef88c8f..8a0fc75d 100644 --- a/docs/apis/NbtAPI/NBT.md +++ b/docs/apis/NbtAPI/NBT.md @@ -66,3 +66,46 @@ If this NBT object stores the `ByteArray` Type, the output byte string will be b > If there is a need for deserialization, please use the **SNBT** interface provided by the NBT tag class. +#### Convert NBT Object to SNBT String + +`nbt.toSNBT([space[,format]])` + +- Parameter: + - space : `Integer` + (Optional parameter) Indent width. Pass `-1` or omit it to disable pretty formatting. + - format : `Enum` + (Optional parameter) SNBT output format, using a value from the `SnbtFormat` enum. +- Return value: The SNBT form of the NBT object. +- Return value type: `String` + +Calling `nbt.toSNBT()` or `nbt.toSNBT(space)` uses `SnbtFormat.ForceQuote` by default. +If you need to specify another SNBT format explicitly, use `nbt.toSNBT(space, format)`. + + +## 🎨 SnbtFormat Enum + +`SnbtFormat` is the enum used by `NbtCompound::toSNBT([space[,format]])`. + +The following values are available: + +| Enum Value | Numeric Value | Description | +|----------------------------|---------------|-------------| +| `SnbtFormat.Minimize` | `0` | Minimal output. | +| `SnbtFormat.CompoundLineFeed` | `1` | Insert line feeds for compound members. | +| `SnbtFormat.ArrayLineFeed` | `2` | Insert line feeds for arrays / lists. | +| `SnbtFormat.Colored` | `4` | Add color formatting to output. | +| `SnbtFormat.Console` | `8` | Use console-oriented formatting. | +| `SnbtFormat.ForceAscii` | `16` | Force ASCII output. | +| `SnbtFormat.ForceQuote` | `32` | Force quoted string output. | +| `SnbtFormat.CommentMarks` | `64` | Add comment-style marks. | +| `SnbtFormat.Jsonify` | `96` | Preset: `ForceQuote | CommentMarks`. | +| `SnbtFormat.PartialLineFeed` | `1` | Alias of `CompoundLineFeed`. | +| `SnbtFormat.AlwaysLineFeed` | `3` | Preset: `CompoundLineFeed | ArrayLineFeed`. | +| `SnbtFormat.PrettyFilePrint` | `1` | Alias of `PartialLineFeed`. | +| `SnbtFormat.PrettyChatPrint` | `5` | Preset: `PrettyFilePrint | Colored`. | +| `SnbtFormat.PrettyConsolePrint` | `13` | Preset: `PrettyFilePrint | Colored | Console`. | + +`CompoundLineFeed`, `ArrayLineFeed`, `Colored`, `Console`, `ForceAscii`, `ForceQuote`, and `CommentMarks` are bit flags. +The other entries above are predefined combinations or aliases. + + diff --git a/docs/apis/NbtAPI/NBT.zh.md b/docs/apis/NbtAPI/NBT.zh.md index 138608e2..0d058b6d 100644 --- a/docs/apis/NbtAPI/NBT.zh.md +++ b/docs/apis/NbtAPI/NBT.zh.md @@ -66,3 +66,46 @@ > 如果有反序列化的需求,请使用 NBT标签类 提供的的 **SNBT** 接口 +#### 将 NBT 对象转换为 SNBT 字符串 + +`nbt.toSNBT([space[,format]])` + +- 参数 + - space : `Integer` + (可选参数)缩进空格数。传入 `-1` 或省略时不进行格式化输出。 + - format : `Enum` + (可选参数)SNBT 输出格式,使用 `SnbtFormat` 枚举中的值。 +- 返回值:对应的 SNBT 字符串 +- 返回值类型:`String` + +调用 `nbt.toSNBT()` 或 `nbt.toSNBT(space)` 时,默认使用 `SnbtFormat.ForceQuote`。 +如果需要显式指定其他 SNBT 格式,请使用 `nbt.toSNBT(space, format)`。 + + +## 🎨 SnbtFormat 枚举 + +`SnbtFormat` 是 `NbtCompound::toSNBT([space[,format]])` 使用的枚举。 + +可用的枚举值如下: + +| 枚举值 | 数值 | 说明 | +| ------ | ---- | ---- | +| `SnbtFormat.Minimize` | `0` | 最小化输出。 | +| `SnbtFormat.CompoundLineFeed` | `1` | 对 Compound 成员进行换行。 | +| `SnbtFormat.ArrayLineFeed` | `2` | 对数组 / 列表内容进行换行。 | +| `SnbtFormat.Colored` | `4` | 为输出添加颜色格式。 | +| `SnbtFormat.Console` | `8` | 使用面向控制台的格式。 | +| `SnbtFormat.ForceAscii` | `16` | 强制使用 ASCII 输出。 | +| `SnbtFormat.ForceQuote` | `32` | 强制字符串使用引号输出。 | +| `SnbtFormat.CommentMarks` | `64` | 添加注释风格标记。 | +| `SnbtFormat.Jsonify` | `96` | 预设组合:`ForceQuote | CommentMarks`。 | +| `SnbtFormat.PartialLineFeed` | `1` | `CompoundLineFeed` 的别名。 | +| `SnbtFormat.AlwaysLineFeed` | `3` | 预设组合:`CompoundLineFeed | ArrayLineFeed`。 | +| `SnbtFormat.PrettyFilePrint` | `1` | `PartialLineFeed` 的别名。 | +| `SnbtFormat.PrettyChatPrint` | `5` | 预设组合:`PrettyFilePrint | Colored`。 | +| `SnbtFormat.PrettyConsolePrint` | `13` | 预设组合:`PrettyFilePrint | Colored | Console`。 | + +其中 `CompoundLineFeed`、`ArrayLineFeed`、`Colored`、`Console`、`ForceAscii`、`ForceQuote`、`CommentMarks` 是位标记。 +其余项是预设组合或别名。 + + diff --git a/docs/apis/NbtAPI/NBTCompound.md b/docs/apis/NbtAPI/NBTCompound.md index 2867c938..6e292981 100644 --- a/docs/apis/NbtAPI/NBTCompound.md +++ b/docs/apis/NbtAPI/NBTCompound.md @@ -115,7 +115,7 @@ Possible return values ​​are: `NBT.End` `NBT.Byte` `NBT.Short` `NBT.Int` `NB The name of the key to operate on. - tag: `NBT Object` NBT object to be written (it carries specific NBT data). - The write data type must be the same as the data type stored in the value corresponding to the key, and the key name may not exist. + If the key already exists, the original tag will be replaced. If the key does not exist, a new key-value pair will be created. - Return value: CompoundTag itself. - Return value type: `NBTCompound` @@ -201,16 +201,21 @@ If an item of Compound stores a `List` or `Compound` type NBT, it will recursive #### Serialize NBT Tag Object to SNBT -`nbt.toSNBT([space])` +`comp.toSNBT([space[,format]])` - Parameter: - space : `Integer` (Optional parameter) If you want to format the output string, pass this parameter. Represents the number of spaces per indent, so the resulting string is more human-readable. This parameter defaults to `-1`, i.e. the output string is not formatted. + - format : `Enum` + (Optional parameter) SNBT output format, using a value from the `SnbtFormat` enum. - Return value: The converted SNBT string. - Return value type: `String` +Calling `comp.toSNBT()` or `comp.toSNBT(space)` uses `SnbtFormat.ForceQuote` by default. +If you need to specify another SNBT format explicitly, use `comp.toSNBT(space, format)`. + > In addition to the normal binary NBT, another type of NBT that players are more familiar with is plain text, usually used in [commands](https://minecraft.wiki/w/Commands). This format is often referred to as **SNBT** (**Binary Named Tag,**, **S**tringified **NBT**) > > --- Minecraft Wiki diff --git a/docs/apis/NbtAPI/NBTCompound.zh.md b/docs/apis/NbtAPI/NBTCompound.zh.md index c095bfc2..7d76f736 100644 --- a/docs/apis/NbtAPI/NBTCompound.zh.md +++ b/docs/apis/NbtAPI/NBTCompound.zh.md @@ -114,8 +114,8 @@ local nbt = NbtCompound({ - key:`String` 要操作的键名 - tag: `NBT Object` - 要写入的 NBT 对象(它承载着具体的NBT数据) - 写入数据类型必须和键对应的值储存的数据类型一致,键名可以不存在 + 要写入的 NBT 对象(它承载着具体的 NBT 数据) + 如果键已存在,会直接替换原有标签;如果键不存在,则会新建这个键值对 - 返回值:CompoundTag 自身 - 返回值类型:`NBTCompound` @@ -201,16 +201,21 @@ local nbt = NbtCompound({ #### 将 NBT 标签对象序列化为 SNBT -`nbt.toSNBT([space])` +`comp.toSNBT([space[,format]])` - 参数 - space : `Integer` (可选参数)如果要格式化输出的字符串,则传入此参数 代表每个缩进的空格数量,这样生成的字符串更适合人阅读 此参数默认为 -1,即不对输出字符串进行格式化 + - format : `Enum` + (可选参数)SNBT 输出格式,使用 `SnbtFormat` 枚举中的值 - 返回值:对应的 SNBT 字符串 - 返回值类型:`String` +调用 `comp.toSNBT()` 或 `comp.toSNBT(space)` 时,默认使用 `SnbtFormat.ForceQuote`。 +如果需要显式指定其他 SNBT 格式,请使用 `comp.toSNBT(space, format)`。 + > 除了普通的二进制 NBT 之外,另一种玩家更熟悉的 NBT 是纯文本形式的,通常在[命令](https://zh.minecraft.wiki/w/命令)里使用。这种格式常被称为**SNBT**(**字符串化的二进制命名标签**,**S**tringified **NBT**) > > --- Minecraft Wiki diff --git a/docs/apis/README.md b/docs/apis/README.md index 68b0649d..f4b51ef5 100644 --- a/docs/apis/README.md +++ b/docs/apis/README.md @@ -47,6 +47,7 @@ In addition to the above standard types, there are also some engine-defined obje - `Device` - Player equipment information object (see player for details) - `Container` - container object (see container for details) - `Objective` - Scored item object (see Scoreboard for details) +- `Packet` - Packet object (see Packet for details) - `NBT` - NBT Tag object (see NBT for details) - `SimpleForm` - Normal form objects (see Form Builder for details) - `CustomForm` - Custom form objects (see Form Builder for details) diff --git a/docs/apis/README.zh.md b/docs/apis/README.zh.md index cc90f7ba..7b974f75 100644 --- a/docs/apis/README.zh.md +++ b/docs/apis/README.zh.md @@ -47,6 +47,7 @@ - `Device` - 玩家设备信息对象(详见 玩家)- 在脚本引擎内实际注册的类名:`LLSE_Device` - `Container` - 容器对象(详见 容器)- 在脚本引擎内实际注册的类名:`LLSE_Container` - `Objective` - 计分项对象(详见 计分板)- 在脚本引擎内实际注册的类名:`LLSE_Objective` +- `Packet` - 数据包对象(详见 数据包)- 在脚本引擎内实际注册的类名:`LLSE_Packet` - `NBT` - NBT Tag对象(详见 NBT) - `SimpleForm` - 普通表单对象(详见 表单构建器)- 在脚本引擎内实际注册的类名:`LLSE_SimpleForm` - `CustomForm` - 自定义表单对象(详见 表单构建器)- 在脚本引擎内实际注册的类名:`LLSE_CustomForm` diff --git a/docs/apis/ScriptAPI/VanillaI18n.md b/docs/apis/ScriptAPI/VanillaI18n.md new file mode 100644 index 00000000..f81c33e5 --- /dev/null +++ b/docs/apis/ScriptAPI/VanillaI18n.md @@ -0,0 +1,83 @@ +# VanillaI18n API + +This API is used to modify Minecraft vanilla language data. + +!!! warning +This API is only available in 0.19.1 and later. + +The actual registered class name in the script engine is `VaillanI18n`. + +## Get current language + +`VaillanI18n.getCurrentLanguage()` + +- Return value: current language code +- Return value type: `String` + +## Set current language + +`VaillanI18n.setCurrentLanguage(language)` + +- Parameters: + - language: `String` + Language code, such as `en_US` or `zh_CN` +- Return value: success or not +- Return value type: `Boolean` + +## Get supported languages + +`VaillanI18n.getSupportedLanguages()` + +- Return value: supported language code list +- Return value type: `Array` + +## Translate vanilla text + +`VaillanI18n.translate(key[, params][, language])` + +- Parameters: + - key: `String` + Translation key + - params: `Array` (Optional) + Translation parameters + - language: `String` (Optional) + Target language. If not provided, the current language is used +- Return value: translated text +- Return value type: `String` + +## Load language data from object + +`VaillanI18n.loadLanguage(language, data)` + +- Parameters: + - language: `String` + Language code + - data: `Object` + Translation data. Each key and value must be `String` +- Return value: success or not +- Return value type: `Boolean` + +## Load language data from file + +`VaillanI18n.loadLanguageFromFile(language, path)` + +- Parameters: + - language: `String` + Language code + - path: `String` + Language file path +- Return value: success or not +- Return value type: `Boolean` + +## Load language data from directory + +`VaillanI18n.loadLanguagesFromDirectory(path)` + +- Parameters: + - path: `String` + Directory path +- Return value: success or not +- Return value type: `Boolean` + +This function loads all `.lang` files in the directory. +The file name without the `.lang` extension is used as the language code. diff --git a/docs/apis/ScriptAPI/VanillaI18n.zh.md b/docs/apis/ScriptAPI/VanillaI18n.zh.md new file mode 100644 index 00000000..8c8a963a --- /dev/null +++ b/docs/apis/ScriptAPI/VanillaI18n.zh.md @@ -0,0 +1,83 @@ +# VanillaI18n API + +此 API 用于修改 Minecraft 原版语言数据。 + +!!! warning + 此 API 仅在0.19.1及以后版本可用 + +此对象在脚本引擎内实际注册的类名为 `VaillanI18n`。 + +## 获取当前语言 + +`VaillanI18n.getCurrentLanguage()` + +- 返回值:当前语言代码 +- 返回值类型:`String` + +## 设置当前语言 + +`VaillanI18n.setCurrentLanguage(language)` + +- 参数: + - language: `String` + 语言代码,如 `en_US`、`zh_CN` +- 返回值:是否成功 +- 返回值类型:`Boolean` + +## 获取支持的语言列表 + +`VaillanI18n.getSupportedLanguages()` + +- 返回值:支持的语言代码列表 +- 返回值类型:`Array` + +## 翻译原版文本 + +`VaillanI18n.translate(key[, params][, language])` + +- 参数: + - key: `String` + 翻译键名 + - params: `Array`(可选参数) + 翻译参数 + - language: `String`(可选参数) + 目标语言。不传时使用当前语言 +- 返回值:翻译后的文本 +- 返回值类型:`String` + +## 从对象加载语言数据 + +`VaillanI18n.loadLanguage(language, data)` + +- 参数: + - language: `String` + 语言代码 + - data: `Object` + 语言数据,其中每个键和值都必须为 `String` +- 返回值:是否成功 +- 返回值类型:`Boolean` + +## 从文件加载语言数据 + +`VaillanI18n.loadLanguageFromFile(language, path)` + +- 参数: + - language: `String` + 语言代码 + - path: `String` + 语言文件路径 +- 返回值:是否成功 +- 返回值类型:`Boolean` + +## 从目录加载语言数据 + +`VaillanI18n.loadLanguagesFromDirectory(path)` + +- 参数: + - path: `String` + 目录路径 +- 返回值:是否成功 +- 返回值类型:`Boolean` + +此函数会加载目录下所有 `.lang` 文件。 +文件名去掉 `.lang` 后缀后会作为语言代码使用。 diff --git a/docs/apis/SystemAPI/File.md b/docs/apis/SystemAPI/File.md index 934b1b3a..eb624302 100644 --- a/docs/apis/SystemAPI/File.md +++ b/docs/apis/SystemAPI/File.md @@ -14,15 +14,24 @@ If you need to manipulate files frequently, use the file classes below to improv ### Read in All the Contents of the File -`File.readFrom(path)` +!!! warning +The optional parameter and `ByteBuffer` return value of this function are only available in 0.19.1 and later. + +`File.readFrom(path[,isBinary])` - Parameter: - path : `String` The path of the target file, the relative path is based on the BDS root directory. + - isBinary : `Boolean` + Whether to read in binary mode. The default value is `false` - Return value: All data in the file -- Return value type: `String` +- Return value type: `String` / `ByteBuffer` - If the return value is `Null`, the read failed. +If `isBinary` is `true`, this function returns `ByteBuffer`; otherwise it returns `String`. + +Before 0.19.1, this function could only be used as `File.readFrom(path)`, and the return value type was always `String`. + ### Write Content to the Specified File @@ -33,7 +42,7 @@ If you need to manipulate files frequently, use the file classes below to improv - path : `String` The path of the target file, the relative path is based on the BDS root directory. - - text : `String` + - text : `String` / `ByteBuffer` What will be written to the file. - Return value: Whether the write is successful or not. @@ -42,6 +51,9 @@ If you need to manipulate files frequently, use the file classes below to improv > Note: If the file does not exist, it will be created automatically. If it exists, it will be **emptied** before writing. +!!! warning +Passing `ByteBuffer` is only available in 0.19.1 and later. + ### Append a Line to the Specified File diff --git a/docs/apis/SystemAPI/File.zh.md b/docs/apis/SystemAPI/File.zh.md index 10100a20..f7c59701 100644 --- a/docs/apis/SystemAPI/File.zh.md +++ b/docs/apis/SystemAPI/File.zh.md @@ -14,15 +14,24 @@ ### 读入文件的所有内容 -`File.readFrom(path)` +!!! warning + 此函数的可选参数和 `ByteBuffer` 返回值仅在0.19.1及以后版本可用 + +`File.readFrom(path[,isBinary])` - 参数: - path : `String` 目标文件的路径,相对路径以BDS根目录为基准 + - isBinary : `Boolean` + 是否按二进制方式读取,默认为`false` - 返回值:文件的所有数据 -- 返回值类型:`String` +- 返回值类型:`String` / `ByteBuffer` - 如返回值为 `Null` 则表示读取失败 +如果 `isBinary` 为 `true`,则返回 `ByteBuffer`,否则返回 `String`。 + +在0.19.1之前,此函数只能使用 `File.readFrom(path)` 形式调用,返回值类型固定为 `String`。 + ### 向指定文件写入内容 @@ -33,7 +42,7 @@ - path : `String` 目标文件的路径,相对路径以BDS根目录为基准 - - text : `String` + - text : `String` / `ByteBuffer` 要写入的内容 - 返回值:是否写入成功 @@ -42,6 +51,9 @@ > 注:若文件不存在会自动创建,若存在则会先将其**清空**再写入 +!!! warning + 传入 `ByteBuffer` 仅在0.19.1及以后版本可用 + ### 向指定文件追加一行 diff --git a/docs/apis/SystemAPI/SystemInfo.md b/docs/apis/SystemAPI/SystemInfo.md index b0192efb..58f4d237 100644 --- a/docs/apis/SystemAPI/SystemInfo.md +++ b/docs/apis/SystemAPI/SystemInfo.md @@ -42,3 +42,15 @@ The following APIs provide interfaces to obtain necessary system information: - Return value type: `String` + +### Randomly Generate a UUID String + +!!! warning +This function is only available in 0.19.1 and later. + +`system.randomUuid()` + +- Return value: A randomly generated unique identifier UUID. +- Return value type: `String` + + diff --git a/docs/apis/SystemAPI/SystemInfo.zh.md b/docs/apis/SystemAPI/SystemInfo.zh.md index 07a3d1de..55e5c99c 100644 --- a/docs/apis/SystemAPI/SystemInfo.zh.md +++ b/docs/apis/SystemAPI/SystemInfo.zh.md @@ -42,3 +42,15 @@ - 返回值类型: `String` + +### 随机生成一个 UUID 字符串 + +!!! warning + 此函数仅在0.19.1及以后版本可用 + +`system.randomUuid()` + +- 返回值:一个随机生成的唯一标识符UUID +- 返回值类型: `String` + + diff --git a/src/baselib/BaseLib.js b/src/baselib/BaseLib.js index 4387716d..cd122efc 100644 --- a/src/baselib/BaseLib.js +++ b/src/baselib/BaseLib.js @@ -414,5 +414,7 @@ globalThis.LXL_CustomForm = LLSE_CustomForm; globalThis.LXL_Item = LLSE_Item; globalThis.LXL_Player = LLSE_Player; globalThis.LXL_Objective = LLSE_Objective; +globalThis.LXL_Packet = LLSE_Packet; +globalThis.Packet = LLSE_Packet; ll.export = ll.exports; ll.import = ll.imports; diff --git a/src/baselib/BaseLib.lua b/src/baselib/BaseLib.lua index b70720f0..5f1ceac7 100644 --- a/src/baselib/BaseLib.lua +++ b/src/baselib/BaseLib.lua @@ -20,5 +20,7 @@ LXL_CustomForm = LLSE_CustomForm LXL_Item = LLSE_Item LXL_Player = LLSE_Player LXL_Objective = LLSE_Objective +LXL_Packet = LLSE_Packet +Packet = LLSE_Packet ll.export = ll.exports ll.import = ll.imports diff --git a/src/legacy/api/BlockEntityAPI.cpp b/src/legacy/api/BlockEntityAPI.cpp index c61dbcfe..961c2087 100644 --- a/src/legacy/api/BlockEntityAPI.cpp +++ b/src/legacy/api/BlockEntityAPI.cpp @@ -24,6 +24,8 @@ ClassDefine BlockEntityClassBuilder = defineClass BlockEntityClass::getBlock(Arguments const&) const { } CATCH_AND_THROW } + +Local BlockEntityClass::setCustomName(Arguments const& args) const { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + + try { + blockEntity->setCustomName({args[0].asString().toString(), std::nullopt}); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local BlockEntityClass::getCustomName() const { + try { + return String::newString(blockEntity->getCustomName().mUnredactedString); + } + CATCH_AND_THROW +} \ No newline at end of file diff --git a/src/legacy/api/BlockEntityAPI.h b/src/legacy/api/BlockEntityAPI.h index 4eee3912..8d51af97 100644 --- a/src/legacy/api/BlockEntityAPI.h +++ b/src/legacy/api/BlockEntityAPI.h @@ -20,9 +20,11 @@ class BlockEntityClass : public ScriptClass { Local getName() const; Local getPos() const; Local getType() const; + Local getCustomName() const; Local getNbt(Arguments const& args) const; Local setNbt(Arguments const& args) const; Local getBlock(Arguments const& args) const; + Local setCustomName(Arguments const& args) const; }; extern ClassDefine BlockEntityClassBuilder; diff --git a/src/legacy/api/CommandAPI.cpp b/src/legacy/api/CommandAPI.cpp index 35d47326..6b53849f 100644 --- a/src/legacy/api/CommandAPI.cpp +++ b/src/legacy/api/CommandAPI.cpp @@ -169,6 +169,9 @@ Local convertResult(ParamStorageType const& result, CommandOrigin const& if (result.hold(ParamKind::Kind::String)) { return String::newString(std::get(result.value())); } + if (result.hold(ParamKind::Kind::Dimension)) { + return Number::newNumber(std::get(result.value()).mValue); + } return {}; } diff --git a/src/legacy/api/DatabaseAPI.cpp b/src/legacy/api/DatabaseAPI.cpp index 5533ffc7..448d76c3 100644 --- a/src/legacy/api/DatabaseAPI.cpp +++ b/src/legacy/api/DatabaseAPI.cpp @@ -21,6 +21,7 @@ ClassDefine DBSessionClassBuilder = defineClass( .instanceFunction("prepare", &DBSessionClass::prepare) .instanceFunction("close", &DBSessionClass::close) .instanceFunction("isOpen", &DBSessionClass::isOpen) + .instanceFunction("backup", &DBSessionClass::backup) .build(); ClassDefine DBStmtClassBuilder = defineClass("DBStmt") @@ -347,6 +348,14 @@ Local DBSessionClass::isOpen(Arguments const& args) const { } CATCH_AND_THROW } +Local DBSessionClass::backup(Arguments const& args) const { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + try { + return Boolean::newBoolean(session->backup(args[0].asString().toU8string())); + } + CATCH_AND_THROW +} //////////////////// Classes DBStmt //////////////////// diff --git a/src/legacy/api/DatabaseAPI.h b/src/legacy/api/DatabaseAPI.h index 16ea9dfb..9be141eb 100644 --- a/src/legacy/api/DatabaseAPI.h +++ b/src/legacy/api/DatabaseAPI.h @@ -46,6 +46,7 @@ class DBSessionClass : public ScriptClass { Local prepare(Arguments const& args) const; Local close(Arguments const& args) const; Local isOpen(Arguments const& args) const; + Local backup(Arguments const& args) const; }; extern ClassDefine DBSessionClassBuilder; diff --git a/src/legacy/api/EntityAPI.cpp b/src/legacy/api/EntityAPI.cpp index 973df1ae..9de04066 100644 --- a/src/legacy/api/EntityAPI.cpp +++ b/src/legacy/api/EntityAPI.cpp @@ -12,6 +12,7 @@ #include "ll/api/service/Bedrock.h" #include "lse/api/MoreGlobal.h" #include "lse/api/helper/AttributeHelper.h" +#include "mc/common/Globals.h" #include "mc/deps/core/math/Vec2.h" #include "mc/deps/nbt/CompoundTag.h" #include "mc/deps/shared_types/legacy/actor/ActorDamageCause.h" @@ -39,7 +40,7 @@ #include "mc/world/actor/provider/ActorEquipment.h" #include "mc/world/actor/provider/SynchedActorDataAccess.h" #include "mc/world/attribute/Attribute.h" -#include "mc/world/attribute/AttributeInstance.h" // IWYU pragma: keep +#include "mc/world/attribute/AttributeInstance.h" // IWYU pragma: keep #include "mc/world/attribute/AttributeInstanceHandle.h" // IWYU pragma: keep #include "mc/world/attribute/SharedAttributes.h" #include "mc/world/effect/EffectDuration.h" @@ -56,6 +57,7 @@ #include #include +#include using lse::api::AttributeHelper; using magic_enum::enum_integer; @@ -137,6 +139,8 @@ ClassDefine EntityClassBuilder = .instanceFunction("getContainer", &EntityClass::getContainer) .instanceFunction("refreshItems", &EntityClass::refreshItems) .instanceFunction("setScale", &EntityClass::setScale) + .instanceFunction("setCustomName", &EntityClass::setCustomName) + .instanceFunction("getCustomName", &EntityClass::getCustomName) .instanceFunction("setNbt", &EntityClass::setNbt) .instanceFunction("getNbt", &EntityClass::getNbt) .instanceFunction("addTag", &EntityClass::addTag) @@ -1292,6 +1296,30 @@ Local EntityClass::setScale(Arguments const& args) const { CATCH_AND_THROW } +Local EntityClass::setCustomName(Arguments const& args) const { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + + try { + Actor* entity = get(); + if (!entity) return {}; + + entity->setNameTag(args[0].asString().toString()); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local EntityClass::getCustomName() const { + try { + Actor* entity = get(); + if (!entity) return {}; + + return String::newString(entity->getNameTag()); + } + CATCH_AND_THROW +} + Local EntityClass::getNbt(Arguments const&) const { try { Actor const* entity = get(); @@ -1765,6 +1793,103 @@ Local McClass::spawnMob(Arguments const& args) { CATCH_AND_THROW } +Local McClass::summonMob(Arguments const& args) { + CHECK_ARGS_COUNT(args, 2); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + using namespace ll::memory_literals; + + try { + size_t paramIndex{0}; + + ActorDefinitionIdentifier name{args[paramIndex++].asString().toString()}; + if (EntityTypeFromString(name.mFullName) == ActorType::Player) return {}; + + FloatVec4 pos; + if (IsInstanceOf(args[paramIndex])) { + pos = *IntPos::extractPos(args[paramIndex++]); + } else if (IsInstanceOf(args[paramIndex])) { + pos = *FloatPos::extractPos(args[paramIndex++]); + } else if (args.size() >= 5 && std::ranges::all_of(std::views::iota(0ull, 4ull), [&](auto index) { + return args[paramIndex + index].getKind() == ValueKind::kNumber; + })) { + pos = { + args[paramIndex++].asNumber().toFloat(), + args[paramIndex++].asNumber().toFloat(), + args[paramIndex++].asNumber().toFloat(), + args[paramIndex++].asNumber().toInt32() + }; + } else { + throw WrongArgTypeException(__FUNCTION__); + } + + if (args.size() - paramIndex >= 1) { + name.mInitEvent = args[paramIndex++].asString().toString(); + name._initialize(); + } + + static auto func = reinterpret_cast< + Actor* (*)(BlockSource&, Vec3 const&, ActorDefinitionIdentifier const&, ActorUniqueID&, Actor*)>( + "`anonymous namespace'::CommandUtilsAnon::_spawnEntityAt"_sym.resolve() + ); + + auto dimension = ll::service::getLevel()->getDimension(pos.dim); + if (dimension.expired()) return {}; + ActorUniqueID uniqueId{}; + auto* entity = + func(dimension.lock()->getBlockSourceFromMainChunkSource(), pos.getVec3(), name, uniqueId, nullptr); + + if (!entity || entity->mRemoved) return {}; + + if (auto type = static_cast(entity->getEntityTypeId()); + (type & static_cast(ActorType::Mob)) != 0 || type - 10 <= 0x35) { + entity->setPersistent(); + } + + return EntityClass::newEntity(entity); + } + CATCH_AND_THROW +} + +Local McClass::loadMob(Arguments const& args) { + CHECK_ARGS_COUNT(args, 2); + if (!IsInstanceOf(args[0])) { + throw WrongArgTypeException(__FUNCTION__); + } + + try { + FloatVec4 pos; + if (IsInstanceOf(args[1])) { + pos = *IntPos::extractPos(args[1]); + } else if (IsInstanceOf(args[1])) { + pos = *FloatPos::extractPos(args[1]); + } else if (args.size() >= 5 && std::ranges::all_of(std::views::iota(1ull, 5ull), [&](auto index) { + return args[index].getKind() == ValueKind::kNumber; + })) { + pos = { + args[1].asNumber().toFloat(), + args[2].asNumber().toFloat(), + args[3].asNumber().toFloat(), + args[4].asNumber().toInt32() + }; + } else { + throw WrongArgTypeException(__FUNCTION__); + } + + auto dimension = ll::service::getLevel()->getDimension(pos.dim); + if (dimension.expired()) return {}; + + if (auto* nbt = NbtCompoundClass::extract(args[0]); nbt) { + auto backup = *nbt; + backup["Pos"] = ListTag{pos.x, pos.y, pos.z}; + if (auto entity = dimension.lock()->getBlockSourceFromMainChunkSource().spawnActor(backup); entity) { + return EntityClass::newEntity(entity.as_ptr()); + } + } + return {}; + } + CATCH_AND_THROW +} + Local McClass::explode(Arguments const& args) { CHECK_ARGS_COUNT(args, 5); diff --git a/src/legacy/api/EntityAPI.h b/src/legacy/api/EntityAPI.h index 5e3bbbf2..5bfee5e7 100644 --- a/src/legacy/api/EntityAPI.h +++ b/src/legacy/api/EntityAPI.h @@ -31,6 +31,7 @@ class EntityClass : public ScriptClass { Local getCanFreeze() const; Local getCanSeeDaylight() const; Local getCanPickupItems() const; + Local getCustomName() const; Local getInAir() const; Local getInWater() const; Local getInClouds() const; @@ -83,6 +84,7 @@ class EntityClass : public ScriptClass { Local setFire(Arguments const& args) const; Local stopFire(Arguments const& args) const; Local setScale(Arguments const& args) const; + Local setCustomName(Arguments const& args) const; Local distanceTo(Arguments const& args) const; Local distanceToSqr(Arguments const& args) const; diff --git a/src/legacy/api/FileSystemAPI.cpp b/src/legacy/api/FileSystemAPI.cpp index c79d9ec2..bc758a4f 100644 --- a/src/legacy/api/FileSystemAPI.cpp +++ b/src/legacy/api/FileSystemAPI.cpp @@ -624,11 +624,15 @@ Local GetFilesList(Arguments const& args) { Local FileReadFrom(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); + if (args.size() >= 2) { + CHECK_ARG_TYPE(args[1], ValueKind::kBoolean); + } try { - auto content = ll::file_utils::readFile(args[0].asString().toU8string()); + auto isBinary = args.size() >= 2 && args[1].asBoolean().value(); + auto content = ll::file_utils::readFile(args[0].asString().toU8string(), isBinary); if (!content) return {}; // Null - return String::newString(content.value()); + return isBinary ? ByteBuffer::newByteBuffer(content->data(), content->size()).asValue() : String::newString(content.value()); } CATCH_AND_THROW } @@ -636,7 +640,7 @@ Local FileReadFrom(Arguments const& args) { Local FileWriteTo(Arguments const& args) { CHECK_ARGS_COUNT(args, 2); CHECK_ARG_TYPE(args[0], ValueKind::kString); - CHECK_ARG_TYPE(args[1], ValueKind::kString); + CHECK_ARG_TYPE(args[1], ValueKind::kString && args[1].getKind() != ValueKind::kByteBuffer); try { std::filesystem::path path(args[0].asString().toU8string()); @@ -655,7 +659,15 @@ Local FileWriteTo(Arguments const& args) { "Fail to create directory of " + args[0].asString().toString() + "!" ); } - return Boolean::newBoolean(ll::file_utils::writeFile(path, args[1].asString().toString(), false)); + std::string content; + if (args[1].isString()) { + content = args[1].asString().toString(); + } else { + auto data = args[1].asByteBuffer(); + content = std::string_view{reinterpret_cast(data.getRawBytes()), data.byteLength()}; + } + + return Boolean::newBoolean(ll::file_utils::writeFile(path, content, false)); } CATCH_AND_THROW } diff --git a/src/legacy/api/ItemAPI.cpp b/src/legacy/api/ItemAPI.cpp index e99ea312..f8e99815 100644 --- a/src/legacy/api/ItemAPI.cpp +++ b/src/legacy/api/ItemAPI.cpp @@ -61,11 +61,15 @@ ClassDefine ItemClassBuilder = defineClass("LLSE_Item") .instanceFunction("setAux", &ItemClass::setAux) .instanceFunction("setLore", &ItemClass::setLore) .instanceFunction("setDisplayName", &ItemClass::setDisplayName) + .instanceFunction("getDisplayName", &ItemClass::getDisplayName) .instanceFunction("setDamage", &ItemClass::setDamage) .instanceFunction("setNbt", &ItemClass::setNbt) .instanceFunction("getNbt", &ItemClass::getNbt) .instanceFunction("match", &ItemClass::match) + .instanceFunction("addCount", &ItemClass::addCount) + .instanceFunction("removeCount", &ItemClass::removeCount) + .instanceFunction("setCount", &ItemClass::setCount) // For Compatibility .instanceFunction("setTag", &ItemClass::setNbt) @@ -394,9 +398,8 @@ Local ItemClass::setLore(Arguments const& args) const { auto value = arr.get(i); if (value.getKind() == ValueKind::kString) lores.push_back(value.asString().toString()); } - if (lores.empty()) return Boolean::newBoolean(false); - get()->setCustomLore(lores); + lores.empty() ? get()->clearCustomLore() : get()->setCustomLore(lores); return Boolean::newBoolean(true); } CATCH_AND_THROW @@ -417,6 +420,13 @@ Local ItemClass::setDisplayName(Arguments const& args) const { CATCH_AND_THROW } +Local ItemClass::getDisplayName() const { + try { + return String::newString(get()->getCustomName()); + } + CATCH_AND_THROW +} + Local ItemClass::setDamage(Arguments const& args) const { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -545,3 +555,42 @@ Local ItemClass::match(Arguments const& args) const { } CATCH_AND_THROW } + +Local ItemClass::addCount(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + + try { + get()->add(args[0].asNumber().toInt32()); + // update Pre Data + preloadData(); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local ItemClass::removeCount(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + + try { + get()->remove(args[0].asNumber().toInt32()); + // update Pre Data + preloadData(); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local ItemClass::setCount(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + + try { + get()->set(args[0].asNumber().toInt32()); + // update Pre Data + preloadData(); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} \ No newline at end of file diff --git a/src/legacy/api/ItemAPI.h b/src/legacy/api/ItemAPI.h index 105275bf..dbbfcbee 100644 --- a/src/legacy/api/ItemAPI.h +++ b/src/legacy/api/ItemAPI.h @@ -43,6 +43,7 @@ class ItemClass : public ScriptClass { Local getMaxDamage() const; Local getMaxStackSize() const; Local getLore() const; + Local getDisplayName() const; Local isArmorItem() const; Local isBlock() const; @@ -73,6 +74,9 @@ class ItemClass : public ScriptClass { Local setNbt(Arguments const& args); Local match(Arguments const& args) const; + Local addCount(Arguments const& args); + Local removeCount(Arguments const& args); + Local setCount(Arguments const& args); }; extern ClassDefine ItemClassBuilder; diff --git a/src/legacy/api/McAPI.cpp b/src/legacy/api/McAPI.cpp index edb03ce4..dee9f43a 100644 --- a/src/legacy/api/McAPI.cpp +++ b/src/legacy/api/McAPI.cpp @@ -16,6 +16,8 @@ ClassDefine McClassBuilder = defineClass("mc") .function("getEntity", McClass::getEntity) .function("newItem", &McClass::newItem) .function("spawnMob", &McClass::spawnMob) + .function("summonMob", &McClass::summonMob) + .function("loadMob", &McClass::loadMob) .function("cloneMob", &McClass::cloneMob) .function("spawnItem", &McClass::spawnItem) .function("spawnSimulatedPlayer", &McClass::spawnSimulatedPlayer) @@ -26,6 +28,7 @@ ClassDefine McClassBuilder = defineClass("mc") .function("newSimpleForm", &McClass::newSimpleForm) .function("newCustomForm", &McClass::newCustomForm) .function("regConsoleCmd", &McClass::regConsoleCmd) + .function("getMotd", &McClass::getMotd) .function("setMotd", &McClass::setMotd) .function("sendCmdOutput", &McClass::sendCmdOutput) .function("newIntPos", &McClass::newIntPos) @@ -43,6 +46,7 @@ ClassDefine McClassBuilder = defineClass("mc") .function("setPlayerNbt", &McClass::setPlayerNbt) .function("setPlayerNbtTags", &McClass::setPlayerNbtTags) .function("deletePlayerNbt", &McClass::deletePlayerNbt) + .function("getAllPlayerUuids", &McClass::getAllPlayerUuids) .function("getPlayerScore", &McClass::getPlayerScore) .function("setPlayerScore", &McClass::setPlayerScore) .function("addPlayerScore", &McClass::addPlayerScore) @@ -52,6 +56,10 @@ ClassDefine McClassBuilder = defineClass("mc") .function("setTime", &McClass::setTime) .function("getWeather", &McClass::getWeather) .function("setWeather", &McClass::setWeather) + .function("getDimensionId", &McClass::getDimensionId) + .function("getDimensionName", &McClass::getDimensionName) + .function("getOnlinePlayerNum", &McClass::getOnlinePlayerNum) + .function("getMaxNumPlayers", &McClass::getMaxNumPlayers) // For Compatibility .function("getAllScoreObjective", &McClass::getAllScoreObjectives) .function("getDisplayObjectives", &McClass::getDisplayObjective) diff --git a/src/legacy/api/McAPI.h b/src/legacy/api/McAPI.h index db54356a..f7ca46a2 100644 --- a/src/legacy/api/McAPI.h +++ b/src/legacy/api/McAPI.h @@ -22,6 +22,8 @@ class McClass { static Local newItem(Arguments const& args); static Local spawnMob(Arguments const& args); + static Local summonMob(Arguments const& args); + static Local loadMob(Arguments const& args); static Local spawnItem(Arguments const& args); static Local spawnSimulatedPlayer(Arguments const& args); static Local explode(Arguments const& args); @@ -35,10 +37,13 @@ class McClass { static Local newCustomForm(Arguments const& args); static Local regConsoleCmd(Arguments const& args); + static Local getMotd(Arguments const& args); static Local setMotd(Arguments const& args); static Local sendCmdOutput(Arguments const& args); static Local crashBDS(Arguments const& args); + static Local getOnlinePlayerNum(Arguments const& args); + static Local getMaxNumPlayers(Arguments const& args); static Local setMaxNumPlayers(Arguments const& args); static Local newIntPos(Arguments const& args); @@ -59,6 +64,7 @@ class McClass { static Local setPlayerNbt(Arguments const& args); static Local setPlayerNbtTags(Arguments const& args); static Local deletePlayerNbt(Arguments const& args); + static Local getAllPlayerUuids(Arguments const& args); static Local getPlayerScore(Arguments const& args); static Local setPlayerScore(Arguments const& args); static Local addPlayerScore(Arguments const& args); @@ -69,5 +75,8 @@ class McClass { static Local setTime(Arguments const& args); static Local getWeather(Arguments const& args); static Local setWeather(Arguments const& args); + + static Local getDimensionId(Arguments const& args); + static Local getDimensionName(Arguments const& args); }; extern ClassDefine<> McClassBuilder; diff --git a/src/legacy/api/NbtAPI.cpp b/src/legacy/api/NbtAPI.cpp index a889d559..99809985 100644 --- a/src/legacy/api/NbtAPI.cpp +++ b/src/legacy/api/NbtAPI.cpp @@ -26,26 +26,45 @@ using magic_enum::enum_cast; ClassDefine NbtStaticBuilder = defineClass("NBT") .function("parseSNBT", &NbtStatic::parseSNBT) .function("parseBinaryNBT", &NbtStatic::parseBinaryNBT) - .property("End", &NbtStatic::getType) - .property("Byte", &NbtStatic::getType) - .property("Short", &NbtStatic::getType) - .property("Int", &NbtStatic::getType) - .property("Long", &NbtStatic::getType) - .property("Float", &NbtStatic::getType) - .property("Double", &NbtStatic::getType) - .property("ByteArray", &NbtStatic::getType) - .property("String", &NbtStatic::getType) - .property("List", &NbtStatic::getType) - .property("Compound", &NbtStatic::getType) + .property("End", &NbtStatic::getValue) + .property("Byte", &NbtStatic::getValue) + .property("Short", &NbtStatic::getValue) + .property("Int", &NbtStatic::getValue) + .property("Long", &NbtStatic::getValue) + .property("Float", &NbtStatic::getValue) + .property("Double", &NbtStatic::getValue) + .property("ByteArray", &NbtStatic::getValue) + .property("String", &NbtStatic::getValue) + .property("List", &NbtStatic::getValue) + .property("Compound", &NbtStatic::getValue) // For Compatibility .function("createTag", &NbtStatic::newTag) .function("newTag", &NbtStatic::newTag) .build(); +ClassDefine SnbtFormatEnumBuilder = + defineClass("SnbtFormatEnumBuilder") + .property("Minimize", &NbtStatic::getValue) + .property("CompoundLineFeed", &NbtStatic::getValue) + .property("ArrayLineFeed", &NbtStatic::getValue) + .property("Colored", &NbtStatic::getValue) + .property("Console", &NbtStatic::getValue) + .property("ForceAscii", &NbtStatic::getValue) + .property("ForceQuote", &NbtStatic::getValue) + .property("CommentMarks", &NbtStatic::getValue) + .property("Jsonify", &NbtStatic::getValue) + .property("PartialLineFeed", &NbtStatic::getValue) + .property("AlwaysLineFeed", &NbtStatic::getValue) + .property("PrettyFilePrint", &NbtStatic::getValue) + .property("PrettyChatPrint", &NbtStatic::getValue) + .property("PrettyConsolePrint", &NbtStatic::getValue) + .build(); + ClassDefine NbtByteClassBuilder = defineClass("NbtByte") .constructor(&NbtByteClass::constructor) .instanceFunction("getType", &NbtByteClass::getType) + .instanceFunction("toSNBT", &NbtByteClass::toSNBT) .instanceFunction("toString", &NbtByteClass::toString) .instanceFunction("set", &NbtByteClass::set) .instanceFunction("get", &NbtByteClass::get) @@ -54,6 +73,7 @@ ClassDefine NbtByteClassBuilder = defineClass("NbtBy ClassDefine NbtShortClassBuilder = defineClass("NbtShort") .constructor(&NbtShortClass::constructor) .instanceFunction("getType", &NbtShortClass::getType) + .instanceFunction("toSNBT", &NbtShortClass::toSNBT) .instanceFunction("toString", &NbtShortClass::toString) .instanceFunction("set", &NbtShortClass::set) .instanceFunction("get", &NbtShortClass::get) @@ -62,6 +82,7 @@ ClassDefine NbtShortClassBuilder = defineClass("Nb ClassDefine NbtIntClassBuilder = defineClass("NbtInt") .constructor(&NbtIntClass::constructor) .instanceFunction("getType", &NbtIntClass::getType) + .instanceFunction("toSNBT", &NbtIntClass::toSNBT) .instanceFunction("toString", &NbtIntClass::toString) .instanceFunction("set", &NbtIntClass::set) .instanceFunction("get", &NbtIntClass::get) @@ -70,6 +91,7 @@ ClassDefine NbtIntClassBuilder = defineClass("NbtInt") ClassDefine NbtLongClassBuilder = defineClass("NbtLong") .constructor(&NbtLongClass::constructor) .instanceFunction("getType", &NbtLongClass::getType) + .instanceFunction("toSNBT", &NbtLongClass::toSNBT) .instanceFunction("toString", &NbtLongClass::toString) .instanceFunction("set", &NbtLongClass::set) .instanceFunction("get", &NbtLongClass::get) @@ -78,6 +100,7 @@ ClassDefine NbtLongClassBuilder = defineClass("NbtLo ClassDefine NbtFloatClassBuilder = defineClass("NbtFloat") .constructor(&NbtFloatClass::constructor) .instanceFunction("getType", &NbtFloatClass::getType) + .instanceFunction("toSNBT", &NbtFloatClass::toSNBT) .instanceFunction("toString", &NbtFloatClass::toString) .instanceFunction("set", &NbtFloatClass::set) .instanceFunction("get", &NbtFloatClass::get) @@ -86,6 +109,7 @@ ClassDefine NbtFloatClassBuilder = defineClass("Nb ClassDefine NbtDoubleClassBuilder = defineClass("NbtDouble") .constructor(&NbtDoubleClass::constructor) .instanceFunction("getType", &NbtDoubleClass::getType) + .instanceFunction("toSNBT", &NbtDoubleClass::toSNBT) .instanceFunction("toString", &NbtDoubleClass::toString) .instanceFunction("set", &NbtDoubleClass::set) .instanceFunction("get", &NbtDoubleClass::get) @@ -94,6 +118,7 @@ ClassDefine NbtDoubleClassBuilder = defineClass( ClassDefine NbtStringClassBuilder = defineClass("NbtString") .constructor(&NbtStringClass::constructor) .instanceFunction("getType", &NbtStringClass::getType) + .instanceFunction("toSNBT", &NbtStringClass::toSNBT) .instanceFunction("toString", &NbtStringClass::toString) .instanceFunction("set", &NbtStringClass::set) .instanceFunction("get", &NbtStringClass::get) @@ -103,6 +128,7 @@ ClassDefine NbtByteArrayClassBuilder = defineClass("NbtByteArray") .constructor(&NbtByteArrayClass::constructor) .instanceFunction("getType", &NbtByteArrayClass::getType) + .instanceFunction("toSNBT", &NbtByteArrayClass::toSNBT) .instanceFunction("toString", &NbtByteArrayClass::toString) .instanceFunction("set", &NbtByteArrayClass::set) .instanceFunction("get", &NbtByteArrayClass::get) @@ -111,6 +137,7 @@ ClassDefine NbtByteArrayClassBuilder = ClassDefine NbtListClassBuilder = defineClass("NbtList") .constructor(&NbtListClass::constructor) .instanceFunction("getType", &NbtListClass::getType) + .instanceFunction("toSNBT", &NbtListClass::toSNBT) .instanceFunction("toString", &NbtListClass::toString) .instanceFunction("getSize", &NbtListClass::getSize) .instanceFunction("getTypeOf", &NbtListClass::getTypeOf) @@ -159,6 +186,23 @@ ClassDefine NbtCompoundClassBuilder = void TagToJson_Compound_Helper(ordered_json& res, CompoundTag* nbt); +template +Local TagToSNBT(TagT* tag, Arguments const& args) { + if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + if (args.size() >= 2) CHECK_ARG_TYPE(args[1], ValueKind::kNumber); + + if (args.size() <= 1) { + auto indent = args.size() >= 1 ? args[0].asNumber().toInt32() : -1; + return String::newString( + indent == -1 ? tag->toSnbt(SnbtFormat::ForceQuote, 0) : tag->toSnbt(SnbtFormat::ForceQuote, indent) + ); + } + + return String::newString( + tag->toSnbt(static_cast(args[1].asNumber().toInt32()), args[0].asNumber().toInt32()) + ); +} + void TagToJson_List_Helper(ordered_json& res, ListTag const* nbt) { for (auto& tag : *nbt) { switch (tag->getId()) { @@ -378,6 +422,13 @@ Local NbtByteClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtByteClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtByteClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -448,6 +499,13 @@ Local NbtIntClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtIntClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtIntClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -518,6 +576,13 @@ Local NbtShortClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtShortClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtShortClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -588,6 +653,13 @@ Local NbtLongClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtLongClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtLongClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -659,6 +731,13 @@ Local NbtFloatClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtFloatClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtFloatClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -731,6 +810,13 @@ Local NbtDoubleClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtDoubleClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtDoubleClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -803,6 +889,13 @@ Local NbtStringClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtStringClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtStringClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -886,6 +979,13 @@ Local NbtByteArrayClass::get(Arguments const&) const { CATCH_AND_THROW } +Local NbtByteArrayClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtByteArrayClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -1398,6 +1498,13 @@ Local NbtListClass::toArray(Arguments const&) const { CATCH_AND_THROW } +Local NbtListClass::toSNBT(Arguments const& args) const { + try { + return TagToSNBT(getPtr(), args); + } + CATCH_AND_THROW +} + Local NbtListClass::toString(Arguments const& args) const { if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber); @@ -1678,25 +1785,25 @@ Local NbtCompoundClass::setTag(Arguments const& args) const { if (IsInstanceOf( args[1] )) { // Assignment refers to the rvalue, so the Tag is copied before assignment - getPtr()->at(key) = NbtByteClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtByteClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtShortClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtShortClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtIntClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtIntClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtLongClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtLongClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtFloatClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtFloatClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtDoubleClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtDoubleClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtStringClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtStringClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = NbtByteArrayClass::extract(args[1])->copy()->as(); + (*getPtr())[key] = NbtByteArrayClass::extract(args[1])->copy()->as(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = *NbtListClass::extract(args[1])->copyList(); + (*getPtr())[key] = *NbtListClass::extract(args[1])->copyList(); } else if (IsInstanceOf(args[1])) { - getPtr()->at(key) = *NbtCompoundClass::extract(args[1])->clone(); + (*getPtr())[key] = *NbtCompoundClass::extract(args[1])->clone(); } else { throw CreateExceptionWithInfo(__FUNCTION__, "Unknown type! Cannot set Tag into Compound"); } @@ -1808,9 +1915,7 @@ Local NbtCompoundClass::toObject(Arguments const&) const { Local NbtCompoundClass::toSNBT(Arguments const& args) const { try { - int indent = args.size() >= 1 ? args[0].asNumber().toInt32() : -1; - if (indent == -1) return String::newString(getPtr()->toSnbt(SnbtFormat::ForceQuote, 0)); - return String::newString(getPtr()->toSnbt(SnbtFormat::PartialLineFeed, indent)); + return TagToSNBT(getPtr(), args); } CATCH_AND_THROW } diff --git a/src/legacy/api/NbtAPI.h b/src/legacy/api/NbtAPI.h index 09a10e3d..da3b6a1e 100644 --- a/src/legacy/api/NbtAPI.h +++ b/src/legacy/api/NbtAPI.h @@ -20,12 +20,13 @@ class NbtStatic : public ScriptClass { static Local parseSNBT(Arguments const& args); static Local parseBinaryNBT(Arguments const& args); - template - static Local getType() { + template + static Local getValue() { return Number::newNumber(static_cast(T)); } }; extern ClassDefine<> NbtStaticBuilder; +extern ClassDefine<> SnbtFormatEnumBuilder; // NBT Byte class NbtByteClass : public ScriptClass { @@ -52,6 +53,7 @@ class NbtByteClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtByteClassBuilder; @@ -82,6 +84,7 @@ class NbtShortClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtShortClassBuilder; @@ -111,6 +114,7 @@ class NbtIntClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtIntClassBuilder; @@ -141,6 +145,7 @@ class NbtLongClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtLongClassBuilder; @@ -171,6 +176,7 @@ class NbtFloatClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtFloatClassBuilder; @@ -201,6 +207,7 @@ class NbtDoubleClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtDoubleClassBuilder; @@ -231,6 +238,7 @@ class NbtStringClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtStringClassBuilder; @@ -261,6 +269,7 @@ class NbtByteArrayClass : public ScriptClass { Local getType(Arguments const& args); Local set(Arguments const& args) const; Local get(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtByteArrayClassBuilder; @@ -308,6 +317,7 @@ class NbtListClass : public ScriptClass { Local getTag(Arguments const& args) const; Local toArray(Arguments const& args) const; + Local toSNBT(Arguments const& args) const; Local toString(Arguments const& args) const; }; extern ClassDefine NbtListClassBuilder; diff --git a/src/legacy/api/PacketAPI.cpp b/src/legacy/api/PacketAPI.cpp index 9380143f..9489c7cd 100644 --- a/src/legacy/api/PacketAPI.cpp +++ b/src/legacy/api/PacketAPI.cpp @@ -1,32 +1,50 @@ #include "legacy/api/PacketAPI.h" +#include "PacketAPI.h" #include "legacy/api/APIHelp.h" #include "legacy/api/BaseAPI.h" +#include "legacy/api/EntityAPI.h" #include "legacy/api/ItemAPI.h" #include "legacy/api/NbtAPI.h" +#include "legacy/api/PlayerAPI.h" +#include "lse/api/NetworkPacket.h" #include "lse/api/helper/ItemStackSerializerHelpers.h" #include "mc/deps/core/utility/BinaryStream.h" #include "mc/network/MinecraftPackets.h" #include "mc/network/Packet.h" #include "mc/world/item/NetworkItemStackDescriptor.h" +#include +#include + //////////////////// Class Definition //////////////////// ClassDefine PacketClassBuilder = defineClass("LLSE_Packet") .constructor(nullptr) .instanceFunction("getName", &PacketClass::getName) .instanceFunction("getId", &PacketClass::getId) + .instanceFunction("read", &PacketClass::read) + .instanceFunction("write", &PacketClass::write) + .instanceFunction("sendTo", &PacketClass::sendTo) + .instanceFunction("sendToClients", &PacketClass::sendToClients) + .instanceFunction("sendToServer", &PacketClass::sendToServer) + + .function("createPacket", &PacketClass::createPacket) .build(); ClassDefine BinaryStreamClassBuilder = defineClass("BinaryStream") .constructor(&BinaryStreamClass::constructor) - .instanceFunction("getData", &BinaryStreamClass::getAndReleaseData) + .instanceFunction("getReadPointer", &BinaryStreamClass::getReadPointer) + .instanceFunction("setReadPointer", &BinaryStreamClass::setReadPointer) + .instanceFunction("getData", &BinaryStreamClass::getData) + .instanceFunction("setData", &BinaryStreamClass::setData) .instanceFunction("reset", &BinaryStreamClass::reset) .instanceFunction("reserve", &BinaryStreamClass::reserve) .instanceFunction("writeBool", &BinaryStreamClass::writeBool) .instanceFunction("writeByte", &BinaryStreamClass::writeByte) + .instanceFunction("writeBytes", &BinaryStreamClass::writeBytes) .instanceFunction("writeDouble", &BinaryStreamClass::writeDouble) .instanceFunction("writeFloat", &BinaryStreamClass::writeFloat) .instanceFunction("writeSignedBigEndianInt", &BinaryStreamClass::writeSignedBigEndianInt) @@ -34,7 +52,7 @@ ClassDefine BinaryStreamClassBuilder = .instanceFunction("writeSignedInt64", &BinaryStreamClass::writeSignedInt64) .instanceFunction("writeSignedShort", &BinaryStreamClass::writeSignedShort) .instanceFunction("writeString", &BinaryStreamClass::writeString) - .instanceFunction("writeUnsignedChar", &BinaryStreamClass::writeUnsignedChar) + .instanceFunction("writeUnsignedChar", &BinaryStreamClass::writeByte) .instanceFunction("writeUnsignedInt", &BinaryStreamClass::writeUnsignedInt) .instanceFunction("writeUnsignedInt64", &BinaryStreamClass::writeUnsignedInt64) .instanceFunction("writeUnsignedShort", &BinaryStreamClass::writeUnsignedShort) @@ -46,6 +64,25 @@ ClassDefine BinaryStreamClassBuilder = .instanceFunction("writeBlockPos", &BinaryStreamClass::writeBlockPos) .instanceFunction("writeCompoundTag", &BinaryStreamClass::writeCompoundTag) .instanceFunction("writeItem", &BinaryStreamClass::writeItem) + .instanceFunction("writeUuid", &BinaryStreamClass::writeUuid) + .instanceFunction("readBool", &BinaryStreamClass::readBool) + .instanceFunction("readByte", &BinaryStreamClass::readByte) + .instanceFunction("readBytes", &BinaryStreamClass::readBytes) + .instanceFunction("readUnsignedChar", &BinaryStreamClass::readByte) + .instanceFunction("readDouble", &BinaryStreamClass::readDouble) + .instanceFunction("readFloat", &BinaryStreamClass::readFloat) + .instanceFunction("readSignedBigEndianInt", &BinaryStreamClass::readSignedBigEndianInt) + .instanceFunction("readSignedInt", &BinaryStreamClass::readSignedInt) + .instanceFunction("readSignedInt64", &BinaryStreamClass::readSignedInt64) + .instanceFunction("readSignedShort", &BinaryStreamClass::readSignedShort) + .instanceFunction("readString", &BinaryStreamClass::readString) + .instanceFunction("readUnsignedInt", &BinaryStreamClass::readUnsignedInt) + .instanceFunction("readUnsignedInt64", &BinaryStreamClass::readUnsignedInt64) + .instanceFunction("readUnsignedShort", &BinaryStreamClass::readUnsignedShort) + .instanceFunction("readUnsignedVarInt", &BinaryStreamClass::readUnsignedVarInt) + .instanceFunction("readUnsignedVarInt64", &BinaryStreamClass::readUnsignedVarInt64) + .instanceFunction("readVarInt", &BinaryStreamClass::readVarInt) + .instanceFunction("readVarInt64", &BinaryStreamClass::readVarInt64) .instanceFunction("createPacket", &BinaryStreamClass::createPacket) .build(); @@ -89,231 +126,282 @@ Local PacketClass::getId() { CATCH_AND_THROW } -//////////////////// BinaryStream Classes //////////////////// - -BinaryStreamClass::BinaryStreamClass(std::shared_ptr const& bs) -: ScriptClass(ConstructFromCpp{}) { - set(bs); -} - -// generating function -Local BinaryStreamClass::newBinaryStream() { - auto out = new BinaryStreamClass(std::make_shared()); - return out->getScriptObject(); -} - -// member function - -Local BinaryStreamClass::getAndReleaseData() { - try { - auto stream = get(); - if (!stream) { - return {}; - } - std::string data; - stream->mBuffer.swap(data); - return String::newString(data); +Local PacketClass::read(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + if (!IsInstanceOf(args[0])) { + throw WrongArgTypeException(__FUNCTION__); } - CATCH_AND_THROW -} -BinaryStreamClass* BinaryStreamClass::constructor(Arguments const& args) { try { - return new BinaryStreamClass(args.thiz()); - } - CATCH_AND_THROW -} + auto pkt = get(); + if (!pkt) { + return Boolean::newBoolean(false); + } -Local BinaryStreamClass::reset() { - try { - auto stream = get(); + auto stream = BinaryStreamClass::extract(args[0]); if (!stream) { - return {}; + return Boolean::newBoolean(false); + } + + if (auto res = pkt->read(*stream); !res) { + throw Exception(fmt::format("{}\nfunction: {}", res.error().code().message(), __FUNCTION__)); } - stream->mBuffer.clear(); return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::reserve(Arguments const& args) { +Local PacketClass::write(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); - try { - return Boolean::newBoolean(true); + if (!IsInstanceOf(args[0])) { + throw WrongArgTypeException(__FUNCTION__); } - CATCH_AND_THROW -} -Local BinaryStreamClass::writeBool(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kBoolean); try { - auto stream = get(); - if (!stream) { - return {}; + auto pkt = get(); + if (!pkt) { + return Boolean::newBoolean(false); } - stream->writeBool(args[0].asBoolean().value(), nullptr, nullptr); - return Boolean::newBoolean(true); - } - CATCH_AND_THROW -} -Local BinaryStreamClass::writeByte(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); - try { - auto stream = get(); + auto stream = BinaryStreamClass::extract(args[0]); if (!stream) { - return {}; + return Boolean::newBoolean(false); } - stream->writeByte(args[0].asNumber().toInt32(), nullptr, nullptr); + + pkt->write(*stream); return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeDouble(Arguments const& args) { +Local PacketClass::createPacket(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + if (args.size() >= 2) { + CHECK_ARG_TYPE(args[1], ValueKind::kBoolean); + } try { - auto stream = get(); - if (!stream) { - return {}; - } - stream->writeDouble(args[0].asNumber().toDouble(), nullptr, nullptr); - return Boolean::newBoolean(true); + auto out = new PacketClass( + args.size() < 2 || !args[1].asBoolean().value() + ? MinecraftPackets::createPacket(static_cast(args[0].asNumber().toInt32())) + : std::make_shared( + static_cast(args[0].asNumber().toInt32()), + "" + ) + ); + return out->getScriptObject(); } CATCH_AND_THROW } -Local BinaryStreamClass::writeFloat(Arguments const& args) { +Local PacketClass::sendTo(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); try { - auto stream = get(); - if (!stream) { + std::shared_ptr pkt = get(); + if (!pkt) { return {}; } - stream->writeFloat(args[0].asNumber().toFloat(), nullptr, nullptr); + + if (IsInstanceOf(args[0])) { + auto* player = PlayerClass::extract(args[0]); + pkt->sendTo(*player); + } else if (IsInstanceOf(args[0])) { + auto* entity = EntityClass::extract(args[0]); + pkt->sendTo(*entity); + } else if (IsInstanceOf(args[0])) { + auto* pos = IntPos::extractPos(args[0]); + pkt->sendTo(pos->getBlockPos(), pos->getDimensionId()); + } else if (IsInstanceOf(args[0])) { + auto* pos = FloatPos::extractPos(args[0]); + pkt->sendTo(pos->getVec3(), pos->getDimensionId()); + } else if (args.size() >= 4 && std::ranges::all_of(std::views::iota(0ull, 4ull), [&](auto index) { + return args[index].getKind() == ValueKind::kNumber; + })) { + pkt->sendTo( + BlockPos{args[0].asNumber().toInt32(), args[1].asNumber().toInt32(), args[2].asNumber().toInt32()}, + args[3].asNumber().toInt32() + ); + } else { + throw WrongArgTypeException(__FUNCTION__); + } return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeSignedBigEndianInt(Arguments const& args) { +Local PacketClass::sendToClients(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); try { - auto stream = get(); - if (!stream) { + std::shared_ptr pkt = get(); + if (!pkt) { return {}; } - stream->writeSignedBigEndianInt(args[0].asNumber().toInt32(), nullptr, nullptr); + + pkt->sendToClients(); + return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeSignedInt(Arguments const& args) { +Local PacketClass::sendToServer(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); try { - auto stream = get(); - if (!stream) { + std::shared_ptr pkt = get(); + if (!pkt) { return {}; } - stream->writeSignedInt(args[0].asNumber().toInt32(), nullptr, nullptr); + + pkt->sendToServer(); + return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeSignedInt64(Arguments const& args) { +//////////////////// BinaryStream Classes //////////////////// + +template +auto parseScalarArg(Arguments const& args, std::string_view func) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); - try { - auto stream = get(); - if (!stream) { - return {}; + + auto const& value = args[0]; + if constexpr (ll::traits::is_string_v) { + if (value.isString()) { + return value.asString().toString(); + } + } else { + if (value.isBoolean()) { + return static_cast(value.asBoolean().value()); + } + if (value.isNumber()) { + if (auto num = value.asNumber(); num.isInteger()) { + return static_cast(value.asNumber().toInt64()); + } else { + return static_cast(value.asNumber().toDouble()); + } + } + if (value.isString()) { + if constexpr (std::is_same_v) { + if (auto res = ll::string_utils::svtobool(value.asString().toString()); res) { + return res.value(); + } + } else if constexpr (std::is_floating_point_v) { + if (auto res = + ll::string_utils::svtonum(value.asString().toString(), nullptr, std::chars_format::general); + res) { + return res.value(); + } + } else { + if (auto res = ll::string_utils::svtonum(value.asString().toString(), nullptr, 10); res) { + return res.value(); + } + } } - stream->writeSignedInt64(args[0].asNumber().toInt64(), nullptr, nullptr); - return Boolean::newBoolean(true); } - CATCH_AND_THROW + throw WrongArgTypeException(std::string{func}); } -Local BinaryStreamClass::writeSignedShort(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); - try { - auto stream = get(); - if (!stream) { - return {}; +template +Local makeScalarResult(Bedrock::Result&& res, char const* func, bool asString) { + if (!res) { + throw Exception(fmt::format("{}\nfunction: {}", res.error().code().message(), func)); + } + auto& value = res.value(); + if (asString) return String::newString(fmt::to_string(value)); + + if constexpr (std::is_same_v) { + return Boolean::newBoolean(res.value()); + } else if constexpr (std::is_same_v) { + return String::newString(res.value()); + } else if constexpr (std::is_floating_point_v) { + return Number::newNumber(static_cast(value)); + } else if constexpr (std::is_unsigned_v) { + if (value <= static_cast(std::numeric_limits::max())) { + return Number::newNumber(static_cast(value)); } - stream->writeSignedShort(args[0].asNumber().toInt32(), nullptr, nullptr); - return Boolean::newBoolean(true); + return Number::newNumber(static_cast(value)); + } else { + return Number::newNumber(static_cast(value)); } - CATCH_AND_THROW } -Local BinaryStreamClass::writeString(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kString); +BinaryStreamClass::BinaryStreamClass(std::shared_ptr const& bs) +: ScriptClass(ConstructFromCpp{}) { + set(bs); +} + +// generating function +Local BinaryStreamClass::newBinaryStream() { + auto out = new BinaryStreamClass(std::make_shared()); + return out->getScriptObject(); +} + +std::shared_ptr BinaryStreamClass::extract(Local const& v) { + if (EngineScope::currentEngine()->isInstanceOf(v)) + return EngineScope::currentEngine()->getNativeInstance(v)->get(); + return nullptr; +} + +// member function + +Local BinaryStreamClass::getData(Arguments const& args) { + if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kBoolean); try { + auto stream = get(); if (!stream) { return {}; } - stream->writeString(args[0].asString().toString(), nullptr, nullptr); - return Boolean::newBoolean(true); + + auto result = ByteBuffer::newByteBuffer(stream->mBuffer.data(), stream->mBuffer.size()); + if (args.size() >= 1 && args[0].asBoolean().value()) { + stream->mBuffer.clear(); + stream->mView = stream->mBuffer; + stream->mReadPointer = 0; + } + return result; } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedChar(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); +Local BinaryStreamClass::setData(Arguments const& args) { + if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kByteBuffer); try { + auto stream = get(); if (!stream) { return {}; } - stream->writeByte(args[0].asNumber().toInt32(), nullptr, nullptr); + + auto buffer = args[0].asByteBuffer(); + stream->mBuffer = std::string_view{reinterpret_cast(buffer.getRawBytes()), buffer.byteLength()}; + stream->mView = stream->mBuffer; + stream->mReadPointer = 0; + return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedInt(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); +BinaryStreamClass* BinaryStreamClass::constructor(Arguments const& args) { try { - auto stream = get(); - if (!stream) { - return {}; - } - stream->writeUnsignedInt(static_cast(args[0].asNumber().toInt32()), nullptr, nullptr); - return Boolean::newBoolean(true); + return new BinaryStreamClass(args.thiz()); } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedInt64(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); +Local BinaryStreamClass::getReadPointer(Arguments const& args) { try { auto stream = get(); if (!stream) { return {}; } - stream->writeUnsignedInt64(static_cast(args[0].asNumber().toInt64()), nullptr, nullptr); - return Boolean::newBoolean(true); + return Number::newNumber(static_cast(stream->mReadPointer)); } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedShort(Arguments const& args) { +Local BinaryStreamClass::setReadPointer(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); try { @@ -321,27 +409,27 @@ Local BinaryStreamClass::writeUnsignedShort(Arguments const& args) { if (!stream) { return {}; } - stream->writeUnsignedShort(static_cast(args[0].asNumber().toInt32()), nullptr, nullptr); + stream->mReadPointer = args[0].asNumber().toInt64(); return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedVarInt(Arguments const& args) { - CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); +Local BinaryStreamClass::reset() { try { auto stream = get(); if (!stream) { return {}; } - stream->writeUnsignedVarInt(static_cast(args[0].asNumber().toInt32()), nullptr, nullptr); + stream->mBuffer.clear(); + stream->mView = stream->mBuffer; + stream->mReadPointer = 0; return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeUnsignedVarInt64(Arguments const& args) { +Local BinaryStreamClass::reserve(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); try { @@ -349,40 +437,100 @@ Local BinaryStreamClass::writeUnsignedVarInt64(Arguments const& args) { if (!stream) { return {}; } - stream->writeUnsignedVarInt64(static_cast(args[0].asNumber().toInt64()), nullptr, nullptr); + stream->mBuffer.reserve(args[0].asNumber().toInt32()); return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeVarInt(Arguments const& args) { +Local BinaryStreamClass::writeBytes(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); - CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + CHECK_ARG_TYPE(args[0], ValueKind::kByteBuffer); try { auto stream = get(); if (!stream) { return {}; } - stream->writeVarInt(args[0].asNumber().toInt32(), nullptr, nullptr); + auto buffer = args[0].asByteBuffer(); + stream->mBuffer.append(reinterpret_cast(buffer.getRawBytes()), buffer.byteLength()); + stream->mView = stream->mBuffer; return Boolean::newBoolean(true); } CATCH_AND_THROW } -Local BinaryStreamClass::writeVarInt64(Arguments const& args) { +Local BinaryStreamClass::readBytes(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); + + auto length = args[0].asNumber().toInt32(); + if (length <= 0) throw WrongArgTypeException(__FUNCTION__); try { auto stream = get(); if (!stream) { return {}; } - stream->writeVarInt64(args[0].asNumber().toInt64(), nullptr, nullptr); - return Boolean::newBoolean(true); + + std::string buffer(length, '\0'); + if (auto res = stream->read(buffer.data(), length); !res) { + throw Exception(fmt::format("{}\nfunction: {}", res.error().code().message(), __func__)); + } + return ByteBuffer::newByteBuffer(buffer.data(), length); } CATCH_AND_THROW } +#define SCALAR_STREAM_MACRO(NAME, ...) \ + Local BinaryStreamClass::write##NAME(Arguments const& args) { \ + try { \ + auto stream = get(); \ + if (!stream) { \ + return {}; \ + } \ + auto value = parseScalarArg::arg<0>>( \ + args, \ + __FUNCTION__ \ + ); \ + stream->write##NAME(value, nullptr, nullptr); \ + return Boolean::newBoolean(true); \ + } \ + CATCH_AND_THROW \ + } \ + Local BinaryStreamClass::read##NAME(Arguments const& args) { \ + if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kBoolean); \ + try { \ + auto stream = get(); \ + if (!stream) { \ + return {}; \ + } \ + return makeScalarResult( \ + stream->get##NAME(__VA_ARGS__), \ + __FUNCTION__, \ + args.size() >= 1 ? args[0].asBoolean().value() : false \ + ); \ + } \ + CATCH_AND_THROW \ + } + +SCALAR_STREAM_MACRO(Bool); +SCALAR_STREAM_MACRO(Byte); +SCALAR_STREAM_MACRO(Double); +SCALAR_STREAM_MACRO(Float); +SCALAR_STREAM_MACRO(SignedBigEndianInt); +SCALAR_STREAM_MACRO(SignedInt); +SCALAR_STREAM_MACRO(SignedInt64); +SCALAR_STREAM_MACRO(SignedShort); +SCALAR_STREAM_MACRO(String, std::numeric_limits::max()); +SCALAR_STREAM_MACRO(UnsignedInt); +SCALAR_STREAM_MACRO(UnsignedInt64); +SCALAR_STREAM_MACRO(UnsignedShort); +SCALAR_STREAM_MACRO(UnsignedVarInt); +SCALAR_STREAM_MACRO(UnsignedVarInt64); +SCALAR_STREAM_MACRO(VarInt); +SCALAR_STREAM_MACRO(VarInt64); + +#undef SCALAR_STREAM_MACRO + Local BinaryStreamClass::writeVec3(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); try { @@ -414,7 +562,7 @@ Local BinaryStreamClass::writeBlockPos(Arguments const& args) { } IntPos* posObj = IntPos::extractPos(args[0]); stream->writeVarInt(posObj->getBlockPos().x, nullptr, nullptr); - stream->writeUnsignedVarInt(posObj->getBlockPos().y, nullptr, nullptr); + stream->writeVarInt(posObj->getBlockPos().y, nullptr, nullptr); stream->writeVarInt(posObj->getBlockPos().z, nullptr, nullptr); return Boolean::newBoolean(true); } @@ -455,15 +603,49 @@ Local BinaryStreamClass::writeItem(Arguments const& args) { CATCH_AND_THROW } +Local BinaryStreamClass::writeUuid(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + auto uuidStr = args[0].asString().toString(); + if (!mce::UUID::canParse(uuidStr)) { + throw Exception(fmt ::format("Invalid UUID: {}", uuidStr)); + } + + try { + auto stream = get(); + if (!stream) { + return {}; + } + + auto uuid = mce::UUID::fromString(uuidStr); + stream->writeUnsignedInt64(uuid.a, nullptr, nullptr); + stream->writeUnsignedInt64(uuid.b, nullptr, nullptr); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + Local BinaryStreamClass::createPacket(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); + if (args.size() >= 2) { + CHECK_ARG_TYPE(args[1], ValueKind::kBoolean); + } try { auto stream = get(); if (!stream) { return {}; } - auto pkt = MinecraftPackets::createPacket(static_cast(args[0].asNumber().toInt32())); - pkt->read(*stream); + std::shared_ptr pkt = + args.size() < 2 || !args[1].asBoolean().value() + ? MinecraftPackets::createPacket(static_cast(args[0].asNumber().toInt32())) + : std::make_shared( + static_cast(args[0].asNumber().toInt32()), + "" + ); + + if (auto res = pkt->read(*stream); !res) { + throw Exception(fmt::format("{}\nfunction: {}", res.error().code().message(), __func__)); + } return PacketClass::newPacket(pkt); } CATCH_AND_THROW diff --git a/src/legacy/api/PacketAPI.h b/src/legacy/api/PacketAPI.h index 3b711cb7..2757359e 100644 --- a/src/legacy/api/PacketAPI.h +++ b/src/legacy/api/PacketAPI.h @@ -12,16 +12,22 @@ class PacketClass : public ScriptClass { public: explicit PacketClass(std::shared_ptr const& p); + static std::shared_ptr extract(Local const& v); + static Local newPacket(std::shared_ptr const& pkt); + static Local createPacket(Arguments const& args); std::shared_ptr get() { return packet; } - - void set(std::shared_ptr const& pkt) { packet = pkt; }; - - static Local newPacket(std::shared_ptr const& pkt); + void set(std::shared_ptr const& pkt) { packet = pkt; }; Local getId(); Local getName(); + Local read(Arguments const& args); + Local write(Arguments const& args); + + Local sendTo(Arguments const& args); + Local sendToClients(Arguments const& args); + Local sendToServer(Arguments const& args); }; extern ClassDefine PacketClassBuilder; @@ -39,15 +45,20 @@ class BinaryStreamClass : public ScriptClass { std::shared_ptr get() { return binaryStream; } void set(std::shared_ptr const& bs) { binaryStream = bs; }; - static Local newBinaryStream(); - static BinaryStreamClass* constructor(Arguments const& args); + static Local newBinaryStream(); + static BinaryStreamClass* constructor(Arguments const& args); + static std::shared_ptr extract(Local const& v); - Local getAndReleaseData(); + Local getReadPointer(Arguments const& args); + Local setReadPointer(Arguments const& args); + Local getData(Arguments const& args); + Local setData(Arguments const& args); + Local reserve(Arguments const& args); Local reset(); - Local reserve(Arguments const& args); Local writeBool(Arguments const& args); Local writeByte(Arguments const& args); + Local writeBytes(Arguments const& args); Local writeDouble(Arguments const& args); Local writeFloat(Arguments const& args); Local writeSignedBigEndianInt(Arguments const& args); @@ -55,7 +66,6 @@ class BinaryStreamClass : public ScriptClass { Local writeSignedInt64(Arguments const& args); Local writeSignedShort(Arguments const& args); Local writeString(Arguments const& args); - Local writeUnsignedChar(Arguments const& args); Local writeUnsignedInt(Arguments const& args); Local writeUnsignedInt64(Arguments const& args); Local writeUnsignedShort(Arguments const& args); @@ -67,6 +77,25 @@ class BinaryStreamClass : public ScriptClass { Local writeBlockPos(Arguments const& args); Local writeCompoundTag(Arguments const& args); Local writeItem(Arguments const& args); + Local writeUuid(Arguments const& args); + + Local readBool(Arguments const& args); + Local readByte(Arguments const& args); + Local readBytes(Arguments const& args); + Local readDouble(Arguments const& args); + Local readFloat(Arguments const& args); + Local readSignedBigEndianInt(Arguments const& args); + Local readSignedInt(Arguments const& args); + Local readSignedInt64(Arguments const& args); + Local readSignedShort(Arguments const& args); + Local readString(Arguments const& args); + Local readUnsignedInt(Arguments const& args); + Local readUnsignedInt64(Arguments const& args); + Local readUnsignedShort(Arguments const& args); + Local readUnsignedVarInt(Arguments const& args); + Local readUnsignedVarInt64(Arguments const& args); + Local readVarInt(Arguments const& args); + Local readVarInt64(Arguments const& args); Local createPacket(Arguments const& args); }; diff --git a/src/legacy/api/PlayerAPI.cpp b/src/legacy/api/PlayerAPI.cpp index 6122b6b9..99484867 100644 --- a/src/legacy/api/PlayerAPI.cpp +++ b/src/legacy/api/PlayerAPI.cpp @@ -1,5 +1,6 @@ #include "legacy/api/PlayerAPI.h" +#include "NbtAPI.h" #include "legacy/api/APIHelp.h" #include "legacy/api/BaseAPI.h" #include "legacy/api/BlockAPI.h" @@ -33,6 +34,7 @@ #include "lse/api/helper/ScoreboardHelper.h" #include "mc/deps/core/math/Vec2.h" #include "mc/deps/core/utility/MCRESULT.h" +#include "mc/deps/core/utility/optional_ref.h" #include "mc/deps/nbt/CompoundTag.h" #include "mc/deps/nbt/ListTag.h" #include "mc/deps/nbt/StringTag.h" @@ -177,6 +179,8 @@ ClassDefine PlayerClassBuilder = .instanceProperty("isSleeping", &PlayerClass::isSleeping) .instanceProperty("isMoving", &PlayerClass::isMoving) .instanceProperty("isSneaking", &PlayerClass::isSneaking) + .instanceProperty("isSwimming", &PlayerClass::isSwimming) + .instanceProperty("isCrawling", &PlayerClass::isCrawling) .instanceFunction("isOP", &PlayerClass::isOP) .instanceFunction("setPermLevel", &PlayerClass::setPermLevel) @@ -356,19 +360,39 @@ using namespace lse::api; Local McClass::getPlayerNbt(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); + if (!mce::UUID::canParse(args[0].asString().toString())) { + throw CreateExceptionWithInfo( + __FUNCTION__, + fmt::format("{0} is not a valid UUID", args[0].asString().toString()) + ); + } try { auto uuid = mce::UUID::fromString(args[0].asString().toString()); - auto db = ll::service::getDBStorage(); - if (db && db->hasKey("player_" + uuid.asString(), DBHelpers::Category::Player)) { - std::unique_ptr playerTag = - db->getCompoundTag("player_" + uuid.asString(), DBHelpers::Category::Player); - if (playerTag) { - std::string serverId = playerTag->at("ServerId"); - if (!serverId.empty() && db->hasKey(serverId, DBHelpers::Category::Player)) { - return NbtCompoundClass::pack(db->getCompoundTag(serverId, DBHelpers::Category::Player)); + if (!uuid) return Boolean::newBoolean(false); + + // online + if (auto* player = ll::service::getLevel()->getPlayer(uuid); player) { + auto tag = std::make_unique(); + player->save(*tag); + return NbtCompoundClass::pack(std::move(tag)); + } + + auto storage = ll::service::getDBStorage(); + + // offline + if (auto playerKey = "player_" + uuid.asString(); storage->hasKey(playerKey, DBHelpers::Category::All)) { + if (auto data = storage->getCompoundTag(playerKey, DBHelpers::Category::All); + data && data->contains("ServerId", Tag::String)) { + if (auto serverId = (*data)["ServerId"].get(); !serverId.empty()) { + if (storage->hasKey(serverId, DBHelpers::Category::Player)) { + if (auto nbt = storage->getCompoundTag(serverId, DBHelpers::Category::Player); nbt) { + return NbtCompoundClass::pack(std::move(nbt)); + } + } } } } + return {}; } CATCH_AND_THROW @@ -377,26 +401,73 @@ Local McClass::getPlayerNbt(Arguments const& args) { Local McClass::setPlayerNbt(Arguments const& args) { CHECK_ARGS_COUNT(args, 2); CHECK_ARG_TYPE(args[0], ValueKind::kString); + if (!mce::UUID::canParse(args[0].asString().toString())) { + throw CreateExceptionWithInfo( + __FUNCTION__, + fmt::format("{0} is not a valid UUID", args[0].asString().toString()) + ); + } + if (!IsInstanceOf(args[1])) { + throw WrongArgTypeException(__FUNCTION__); + } + if (args.size() >= 3) CHECK_ARG_TYPE(args[2], ValueKind::kBoolean); // forceCreate (default: false) + if (args.size() >= 4) CHECK_ARG_TYPE(args[3], ValueKind::kBoolean); // isOnlineMode (default: true) try { - mce::UUID uuid = mce::UUID::fromString(args[0].asString().toString()); - auto tag = NbtCompoundClass::extract(args[1]); - Player* player = ll::service::getLevel()->getPlayer(uuid); - if (player && tag) { + auto uuid = mce::UUID::fromString(args[0].asString().toString()); + if (!uuid) return Boolean::newBoolean(false); + + auto* tag = NbtCompoundClass::extract(args[1]); + if (!tag) return Boolean::newBoolean(false); + + // online + if (auto* player = ll::service::getLevel()->getPlayer(uuid); player) { player->load(*tag, MoreGlobal::defaultDataLoadHelper()); - } else if (tag) { - auto db = ll::service::getDBStorage(); - if (db && db->hasKey("player_" + uuid.asString(), DBHelpers::Category::Player)) { - std::unique_ptr playerTag = - db->getCompoundTag("player_" + uuid.asString(), DBHelpers::Category::Player); - if (playerTag) { - std::string serverId = playerTag->at("ServerId"); - if (!serverId.empty()) { - db->saveData(serverId, tag->toBinaryNbt(), DBHelpers::Category::Player); - return Boolean::newBoolean(true); + return Boolean::newBoolean(true); + } + + auto storage = ll::service::getDBStorage(); + + // offline + if (auto playerKey = "player_" + uuid.asString(); storage->hasKey(playerKey, DBHelpers::Category::All)) { + if (auto data = storage->getCompoundTag(playerKey, DBHelpers::Category::All); + data && data->contains("ServerId", Tag::String)) { + if (auto serverId = (*data)["ServerId"].get(); !serverId.empty()) { + storage->saveData(serverId, tag->toBinaryNbt(), DBHelpers::Category::All); + return Boolean::newBoolean(true); + } + } + } + + // not found + if (args.size() >= 3 && args[2].asBoolean().value()) { + auto playerKey = "player_" + uuid.asString(); + auto serverId = fmt::format("player_server_{0}", mce::UUID::random().asString()); + { + // 这里要么 playerKey 不存在,要么 ServerId 不存在/为空,不然 offline 就能正常处理了 + auto playerTag = storage->hasKey(playerKey, DBHelpers::Category::All) + ? storage->getCompoundTag(playerKey, DBHelpers::Category::All) + : nullptr; + if (!playerTag) { + playerTag = std::make_unique(std::initializer_list{ + {"ServerId", serverId} + }); + + if (args.size() < 4 || args[3].asBoolean().value()) { // isOnlineMode + (*playerTag)["MsaId"] = uuid.asString(); + (*playerTag)["SelfSignedId"] = mce::UUID::random().asString(); + } else { + (*playerTag)["MsaId"] = ""; + (*playerTag)["SelfSignedId"] = uuid.asString(); } + } else { + (*playerTag)["ServerId"] = serverId; } + storage->saveData(playerKey, playerTag->toBinaryNbt(), DBHelpers::Category::All); } + storage->saveData(serverId, tag->toBinaryNbt(), DBHelpers::Category::All); + return Boolean::newBoolean(true); } + return Boolean::newBoolean(false); } CATCH_AND_THROW @@ -405,51 +476,62 @@ Local McClass::setPlayerNbt(Arguments const& args) { Local McClass::setPlayerNbtTags(Arguments const& args) { CHECK_ARGS_COUNT(args, 3); CHECK_ARG_TYPE(args[0], ValueKind::kString); + if (!mce::UUID::canParse(args[0].asString().toString())) { + throw CreateExceptionWithInfo( + __FUNCTION__, + fmt::format("{0} is not a valid UUID", args[0].asString().toString()) + ); + } + if (!IsInstanceOf(args[1])) { + throw WrongArgTypeException(__FUNCTION__); + } CHECK_ARG_TYPE(args[2], ValueKind::kArray); try { - mce::UUID uuid = mce::UUID::fromString(args[0].asString().toString()); - auto tag = NbtCompoundClass::extract(args[1]); - Local arr = args[2].asArray(); - Player* player = ll::service::getLevel()->getPlayer(uuid); - if (player && tag) { - CompoundTag loadedTag; - player->save(loadedTag); + auto uuid = mce::UUID::fromString(args[0].asString().toString()); + if (!uuid) return Boolean::newBoolean(false); + + auto* tag = NbtCompoundClass::extract(args[1]); + if (!tag) return Boolean::newBoolean(false); + + Local arr = args[2].asArray(); + + auto copyTags = [&](CompoundTag& target) { for (size_t i = 0; i < arr.size(); ++i) { auto value = arr.get(i); - if (value.getKind() == ValueKind::kString) { - std::string tagName = value.asString().toString(); - if (!tag->at(tagName).is_null()) { - loadedTag.at(tagName) = tag->at(tagName); - } + if (value.getKind() != ValueKind::kString) continue; + std::string tagName = value.asString().toString(); + if (tag->contains(tagName)) { + target[tagName] = tag->at(tagName); } } + }; + + // online + if (auto* player = ll::service::getLevel()->getPlayer(uuid)) { + CompoundTag loadedTag; + player->save(loadedTag); + copyTags(loadedTag); player->load(loadedTag, MoreGlobal::defaultDataLoadHelper()); - } else if (tag) { - auto db = ll::service::getDBStorage(); - if (db && db->hasKey("player_" + uuid.asString(), DBHelpers::Category::Player)) { - std::unique_ptr playerTag = - db->getCompoundTag("player_" + uuid.asString(), DBHelpers::Category::Player); - if (playerTag) { - std::string serverId = playerTag->at("ServerId"); - if (!serverId.empty() && db->hasKey(serverId, DBHelpers::Category::Player)) { - if (auto loadedTag = db->getCompoundTag(serverId, DBHelpers::Category::Player)) { - for (size_t i = 0; i < arr.size(); ++i) { - auto value = arr.get(i); - if (value.getKind() == ValueKind::kString) { - std::string tagName = value.asString().toString(); - if (!tag->at(tagName).is_null()) { - loadedTag->at(tagName) = tag->at(tagName); - } - } - } - db->saveData(serverId, loadedTag->toBinaryNbt(), DBHelpers::Category::Player); - return Boolean::newBoolean(true); - } + return Boolean::newBoolean(true); + } + + // offline + auto storage = ll::service::getDBStorage(); + + if (auto playerKey = "player_" + uuid.asString(); storage->hasKey(playerKey, DBHelpers::Category::All)) { + auto data = storage->getCompoundTag(playerKey, DBHelpers::Category::All); + if (data && data->contains("ServerId", Tag::String)) { + if (auto serverId = (*data)["ServerId"].get(); + !serverId.empty() && storage->hasKey(serverId, DBHelpers::Category::All)) { + if (auto loadedTag = storage->getCompoundTag(serverId, DBHelpers::Category::All); loadedTag) { + copyTags(*loadedTag); + storage->saveData(serverId, loadedTag->toBinaryNbt(), DBHelpers::Category::All); return Boolean::newBoolean(true); } } } } + return Boolean::newBoolean(false); } CATCH_AND_THROW @@ -458,31 +540,59 @@ Local McClass::setPlayerNbtTags(Arguments const& args) { Local McClass::deletePlayerNbt(Arguments const& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); + if (!mce::UUID::canParse(args[0].asString().toString())) { + throw CreateExceptionWithInfo( + __FUNCTION__, + fmt::format("{0} is not a valid UUID", args[0].asString().toString()) + ); + } try { - mce::UUID uuid = mce::UUID::fromString(args[0].asString().toString()); - if (uuid == mce::UUID::EMPTY()) { - throw std::invalid_argument(args[0].asString().toString() + " is not a valid UUID"); - } - auto storage = ll::service::getLevel().transform([](auto& level) { return &level.getLevelStorage(); }); - if (!storage) { - return Boolean::newBoolean(false); - } - auto playerIds = storage->getCompoundTag("player_" + uuid.asString(), DBHelpers::Category::Player); - if (!playerIds) { - return Boolean::newBoolean(false); - } - for (auto& [type, id] : *playerIds) { - if (!id.is_string()) { - continue; - } - std::string& key = id.get(); - if (type == "ServerId") { - storage->deleteData(key, ::DBHelpers::Category::Player); - } else { - storage->deleteData("player_" + key, ::DBHelpers::Category::Player); + auto storage = ll::service::getDBStorage(); + + if (auto playerKey = "player_" + args[0].asString().toString(); storage->hasKey(playerKey, DBHelpers::Category::All)) { + if (auto data = storage->getCompoundTag(playerKey, DBHelpers::Category::All); data) { + if (data->contains("ServerId", Tag::String)) { + if (auto serverId = (*data)["ServerId"].get(); !serverId.empty()) { + storage->deleteData(serverId, DBHelpers::Category::All); + } + } + return Boolean::newBoolean(true); } } - return Boolean::newBoolean(true); + return Boolean::newBoolean(false); + } + CATCH_AND_THROW +} + +Local McClass::getAllPlayerUuids(Arguments const& args) { + if (args.size() >= 1) CHECK_ARG_TYPE(args[1], ValueKind::kBoolean); // isOnlineMode (default: true) + + try { + auto isOnlineMode = args.size() >= 2 && args[1].asBoolean().value(); + + auto arr = Array::newArray(); + ll::service::getDBStorage()->forEachKeyWithPrefix( + "player_", + DBHelpers::Category::All, + [&](std::string_view key, std::string_view content) { + if (key.size() != 36) return; + + auto data = CompoundTag::fromBinaryNbt(content); + if (!data) return; + if (!data->contains("ServerId", Tag::String) || (*data)["ServerId"].get().empty()) return; + + auto msaId = data->contains("MsaId", Tag::String) ? (*data)["MsaId"].get() : ""; + auto selfSignedId = + data->contains("SelfSignedId", Tag::String) ? (*data)["SelfSignedId"].get() : ""; + + if (!msaId.empty() && selfSignedId != key && isOnlineMode) { + arr.add(String::newString(msaId)); + } else if (!isOnlineMode && !selfSignedId.empty() && msaId != key) { + arr.add(String::newString(selfSignedId)); + } + } + ); + return arr; } CATCH_AND_THROW } @@ -1023,6 +1133,30 @@ Local PlayerClass::isSneaking() const { CATCH_AND_THROW } +Local PlayerClass::isSwimming() const { + try { + Player* player = get(); + if (!player) return {}; + + return Boolean::newBoolean(player->isSwimming()); + } + CATCH_AND_THROW +} + +Local PlayerClass::isCrawling() const { + try { + Player* player = get(); + if (!player) { + return {}; + } + + return Boolean::newBoolean( + SynchedActorDataAccess::getActorFlag(player->getEntityContext(), ActorFlags::Crawling) + ); + } + CATCH_AND_THROW +} + Local PlayerClass::getSpeed() const { try { Player* player = get(); @@ -2399,7 +2533,7 @@ Local PlayerClass::setBossBar(Arguments const& args) const { bs.writeUnsignedVarInt(0, nullptr, nullptr); // Links bs.writeUnsignedVarInt(0, nullptr, nullptr); - auto addPkt = lse::api::NetworkPacket(std::move(bs.mBuffer)); + auto addPkt = lse::api::NetworkPacket(MinecraftPacketIds::AddActor, std::move(bs.mBuffer)); BossBarColor color = static_cast(args[3].asNumber().toInt32()); auto pkt = diff --git a/src/legacy/api/PlayerAPI.h b/src/legacy/api/PlayerAPI.h index 6d1bfc6a..0f39da14 100644 --- a/src/legacy/api/PlayerAPI.h +++ b/src/legacy/api/PlayerAPI.h @@ -77,6 +77,8 @@ class PlayerClass : public ScriptClass { Local isSleeping() const; Local isMoving() const; Local isSneaking() const; + Local isSwimming() const; + Local isCrawling() const; Local isOP(Arguments const& args) const; Local setPermLevel(Arguments const& args) const; diff --git a/src/legacy/api/ServerAPI.cpp b/src/legacy/api/ServerAPI.cpp index 2c416e1d..221b31e1 100644 --- a/src/legacy/api/ServerAPI.cpp +++ b/src/legacy/api/ServerAPI.cpp @@ -8,10 +8,18 @@ #include "mc/common/SharedConstants.h" #include "mc/network/ServerNetworkHandler.h" #include "mc/network/packet/SetTimePacket.h" +#include "mc/world/level/dimension/VanillaDimensions.h" #include "mc/world/level/storage/LevelData.h" #include +Local McClass::getMotd(Arguments const& args) { + try { + return String::newString(ll::service::getServerNetworkHandler().and_then(&ServerNetworkHandler::mServerName)); + } + CATCH_AND_THROW +} + Local McClass::setMotd(Arguments const& args) { CHECK_ARGS_COUNT(args, 1) CHECK_ARG_TYPE(args[0], ValueKind::kString) @@ -24,6 +32,30 @@ Local McClass::setMotd(Arguments const& args) { Local McClass::crashBDS(Arguments const&) { return Boolean::newBoolean(false); } +Local McClass::getOnlinePlayerNum(Arguments const& args) { + if (args.size() >= 1) CHECK_ARG_TYPE(args[0], ValueKind::kBoolean); + + try { + if (args.size() >= 1 && args[0].asBoolean().value()) { + return Number::newNumber(ll::service::getServerNetworkHandler().and_then([](auto& handler) { + return static_cast(handler.mClients->size()); + })); + } else { + return Number::newNumber(ll::service::getLevel().and_then(&Level::getUserCount)); + } + } + CATCH_AND_THROW +} + +Local McClass::getMaxNumPlayers(Arguments const& args) { + try { + return Number::newNumber( + ll::service::getServerNetworkHandler().and_then(&ServerNetworkHandler::mMaxNumPlayers) + ); + } + CATCH_AND_THROW +} + Local McClass::setMaxNumPlayers(Arguments const& args) { CHECK_ARGS_COUNT(args, 1) CHECK_ARG_TYPE(args[0], ValueKind::kNumber) @@ -128,3 +160,35 @@ Local McClass::setWeather(Arguments const& args) { return Boolean::newBoolean(true); } + +Local McClass::getDimensionId(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1) + CHECK_ARG_TYPE(args[0], ValueKind::kString) + + try { + if (auto dimid = VanillaDimensions::fromString(args[0].asString().toString()); + dimid != VanillaDimensions::Undefined()) { + return Number::newNumber(dimid); + } + return {}; + } + CATCH_AND_THROW + + return Boolean::newBoolean(true); +} + +Local McClass::getDimensionName(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1) + CHECK_ARG_TYPE(args[0], ValueKind::kNumber) + + try { + auto& map = VanillaDimensions::DimensionMap().mLeft; + if (auto it = map.find(args[0].asNumber().toInt32()); it != map.end()) { + return String::newString(it->second); + } + return {}; + } + CATCH_AND_THROW + + return Boolean::newBoolean(true); +} diff --git a/src/legacy/api/SystemAPI.cpp b/src/legacy/api/SystemAPI.cpp index 8e5190ce..ebf6373b 100644 --- a/src/legacy/api/SystemAPI.cpp +++ b/src/legacy/api/SystemAPI.cpp @@ -19,6 +19,7 @@ ClassDefine SystemClassBuilder = defineClass("system") .function("getTimeStr", &SystemClass::getTimeStr) .function("getTimeObj", &SystemClass::getTimeObj) .function("randomGuid", &SystemClass::randomGuid) + .function("randomUuid", &SystemClass::randomUuid) .function("cmd", &SystemClass::cmd) .function("newProcess", &SystemClass::newProcess) .build(); @@ -192,3 +193,5 @@ Local SystemClass::getTimeObj(Arguments const&) { } Local SystemClass::randomGuid(Arguments const&) { return String::newString(Raw_RandomGuid()); } + +Local SystemClass::randomUuid(Arguments const&) { return String::newString(mce::UUID::random().asString()); } diff --git a/src/legacy/api/SystemAPI.h b/src/legacy/api/SystemAPI.h index 360d73e7..85a9fb64 100644 --- a/src/legacy/api/SystemAPI.h +++ b/src/legacy/api/SystemAPI.h @@ -8,6 +8,7 @@ class SystemClass { static Local getTimeStr(Arguments const& args); static Local getTimeObj(Arguments const& args); static Local randomGuid(Arguments const& args); + static Local randomUuid(Arguments const& args); static Local cmd(Arguments const& args); static Local newProcess(Arguments const& args); diff --git a/src/legacy/api/VaillanI18n.cpp b/src/legacy/api/VaillanI18n.cpp new file mode 100644 index 00000000..1ebb7420 --- /dev/null +++ b/src/legacy/api/VaillanI18n.cpp @@ -0,0 +1,163 @@ +#include "legacy/api/VaillanI18n.h" + +#include +#include + +ClassDefine VaillanI18nClassBuilder = + defineClass("VaillanI18n") + .function("setCurrentLanguage", &VaillanI18nClass::setCurrentLanguage) + .function("getCurrentLanguage", &VaillanI18nClass::getCurrentLanguage) + .function("getSupportedLanguages", &VaillanI18nClass::getSupportedLanguages) + .function("translate", &VaillanI18nClass::translate) + .function("loadLanguage", &VaillanI18nClass::loadLanguage) + .function("loadLanguageFromFile", &VaillanI18nClass::loadLanguageFromFile) + .function("loadLanguagesFromDirectory", &VaillanI18nClass::loadLanguagesFromDirectory) + .build(); + +Local VaillanI18nClass::setCurrentLanguage(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + std::string lang = args[0].asString().toString(); + try { + getI18n().chooseLanguage(lang); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::getCurrentLanguage(Arguments const& args) { + CHECK_ARGS_COUNT(args, 0); + try { + return String::newString(*getI18n().getCurrentLanguage()->mCode); + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::getSupportedLanguages(Arguments const& args) { + CHECK_ARGS_COUNT(args, 0); + try { + auto arr = Array::newArray(); + for (auto& lang : getI18n().getSupportedLanguageCodes()) { + arr.add(String::newString(lang)); + } + return arr; + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::translate(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + + auto key = args[0].asString().toString(); + auto language = getI18n().getCurrentLanguage().get(); + + std::vector params; + if (args.size() >= 2) { + CHECK_ARG_TYPE(args[1], ValueKind::kArray); + auto arr = args[1].asArray(); + for (size_t i = 0; i < arr.size(); i++) { + CHECK_ARG_TYPE(arr.get(i), ValueKind::kString); + params.push_back(arr.get(i).asString().toString()); + } + } + + if (args.size() >= 3) { + CHECK_ARG_TYPE(args[2], ValueKind::kString); + if (auto res = getI18n().getLocaleFor(args[2].asString().toString()); res) { + language = res; + } + } + + try { + return String::newString(getI18n().get(key, params, language)); + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::loadLanguage(Arguments const& args) { + CHECK_ARGS_COUNT(args, 2); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + CHECK_ARG_TYPE(args[1], ValueKind::kObject); + std::string lang = args[0].asString().toString(); + auto obj = args[1].asObject(); + + try { + std::unordered_map map; + for (auto& prop : obj.getKeys()) { + CHECK_ARG_TYPE(obj.get(prop), ValueKind::kString); + map[prop.toString()] = obj.get(prop).asString().toString(); + } + auto loc = std::const_pointer_cast(getI18n().getLocaleFor(lang)); + if (!loc) { + getI18n().appendAdditionalTranslations(map, lang); + } else { + for (auto& [key, value] : map) { + loc->mStrings->insert_or_assign(key, std::move(value)); + } + } + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::loadLanguageFromFile(Arguments const& args) { + CHECK_ARGS_COUNT(args, 2); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + CHECK_ARG_TYPE(args[1], ValueKind::kString); + + try { + auto path = std::filesystem::path{args[1].asString().toU8string()}; + if (!std::filesystem::is_regular_file(path)) return Boolean::newBoolean(false); + loadLanguageFile(args[0].asString().toString(), path); + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +Local VaillanI18nClass::loadLanguagesFromDirectory(Arguments const& args) { + CHECK_ARGS_COUNT(args, 1); + CHECK_ARG_TYPE(args[0], ValueKind::kString); + + try { + for (auto& entry : std::filesystem::directory_iterator(args[0].asString().toU8string())) { + if (!entry.is_regular_file()) continue; + auto file = ll::string_utils::u8str2str(entry.path().filename().u8string()); + if (!file.ends_with(".lang")) continue; + loadLanguageFile(file.substr(0, file.size() - 5), entry.path()); + } + return Boolean::newBoolean(true); + } + CATCH_AND_THROW +} + +void VaillanI18nClass::loadLanguageFile(std::string const& language, std::filesystem::path const& path) { + std::ifstream file(path); + if (!file.is_open()) return; + std::unordered_map map; + + for (std::string line; std::getline(file, line); ) { + if (line.empty()) continue; + ll::string_utils::replaceAll(line, "\t", ""); + ll::string_utils::replaceAll(line, "\\n", "\n"); + if (auto equalPos = line.find('='); equalPos != std::string::npos) { + auto key = line.substr(0, equalPos); + ll::string_utils::replaceAll(key, " ", ""); + + auto value = line.substr(equalPos + 1, line.find('#', equalPos + 1)); + while (value.ends_with(' ')) value.erase(value.size() - 1); + + if (!key.empty() && !value.empty()) map.insert_or_assign(key, value); + } + } + + file.close(); + auto loc = std::const_pointer_cast(getI18n().getLocaleFor(language)); + if (!loc) { + getI18n().appendAdditionalTranslations(map, language); + } else { + for (auto& [key, value] : map) { + loc->mStrings->insert_or_assign(key, std::move(value)); + } + } +} \ No newline at end of file diff --git a/src/legacy/api/VaillanI18n.h b/src/legacy/api/VaillanI18n.h new file mode 100644 index 00000000..f3bdf6d1 --- /dev/null +++ b/src/legacy/api/VaillanI18n.h @@ -0,0 +1,17 @@ +#pragma once +#include "legacy/api/APIHelp.h" + +class VaillanI18nClass : public ScriptClass { +public: + static Local setCurrentLanguage(Arguments const& args); + static Local getCurrentLanguage(Arguments const& args); + static Local getSupportedLanguages(Arguments const& args); + static Local translate(Arguments const& args); + static Local loadLanguage(Arguments const& args); + static Local loadLanguageFromFile(Arguments const& args); + static Local loadLanguagesFromDirectory(Arguments const& args); + + static void loadLanguageFile(std::string const& language, std::filesystem::path const& path); +}; + +extern ClassDefine VaillanI18nClassBuilder; diff --git a/src/legacy/db/Session.cpp b/src/legacy/db/Session.cpp index 0fc5858d..53a9a367 100644 --- a/src/legacy/db/Session.cpp +++ b/src/legacy/db/Session.cpp @@ -40,6 +40,8 @@ ResultSet Session::query(std::string const& query) { std::string Session::getLastError() const { throw std::runtime_error("Session::getLastError: Not implemented"); } +bool Session::backup(std::filesystem::path const& path) { return false; } + std::weak_ptr Session::getOrSetSelf() { if (self.expired()) { IF_ENDBG lse::LegacyScriptEngine::getLogger().debug("Session::getOrSetSelf: `self` expired, trying fetching"); diff --git a/src/legacy/db/Session.h b/src/legacy/db/Session.h index db0d306a..e7da6a40 100644 --- a/src/legacy/db/Session.h +++ b/src/legacy/db/Session.h @@ -122,6 +122,13 @@ class Session { * @return DBType The database type */ virtual DBType getType() = 0; + /** + * @brief Backup the database to a file. + * + * @param backupPath Path to the backup file + * @return bool Success or not + */ + virtual bool backup(std::filesystem::path const& backupPath); /** * @brief Get or set the self pointer * diff --git a/src/legacy/db/impl/sqlite/Session.cpp b/src/legacy/db/impl/sqlite/Session.cpp index bd7e5871..96554ffe 100644 --- a/src/legacy/db/impl/sqlite/Session.cpp +++ b/src/legacy/db/impl/sqlite/Session.cpp @@ -127,6 +127,69 @@ bool SQLiteSession::isOpen() { return conn != nullptr; } DBType SQLiteSession::getType() { return DBType::SQLite; } +bool SQLiteSession::backup(std::filesystem::path const& backupPath) { + if (!conn) { + IF_ENDBG lse::LegacyScriptEngine::getLogger().error("SQLiteSession::backup: No open database connection."); + return false; + } + + struct DestDb { + sqlite3* mDb{nullptr}; + explicit DestDb(std::filesystem::path const& path) { + { + std::error_code ec; + std::filesystem::create_directories(path.parent_path(), ec); + } + if (sqlite3_open_v2( + ll::string_utils::u8str2str(path.u8string()).c_str(), + &mDb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr + ) + != SQLITE_OK) { + if (mDb) sqlite3_close(mDb); + mDb = nullptr; + return; + } + } + ~DestDb() { + if (mDb) sqlite3_close(mDb); + } + } destDb{backupPath}; + if (!destDb.mDb) { + IF_ENDBG lse::LegacyScriptEngine::getLogger().error( + "SQLiteSession::backup: Failed to open destination database '{}'", + backupPath.string() + ); + return false; + } + + sqlite3_exec(conn, "ROLLBACK", nullptr, nullptr, nullptr); + struct Backup { + sqlite3_backup* mBackup{nullptr}; + Backup(sqlite3* srcDb, sqlite3* destDb) : mBackup(sqlite3_backup_init(destDb, "main", srcDb, "main")) {} + ~Backup() { + if (mBackup) sqlite3_backup_finish(mBackup); + } + } backup{conn, destDb.mDb}; + if (!backup.mBackup) { + IF_ENDBG lse::LegacyScriptEngine::getLogger().error("SQLiteSession::backup: Failed to initialize backup."); + return false; + } + + if (auto rc = sqlite3_backup_step(backup.mBackup, -1); rc != SQLITE_DONE) { + IF_ENDBG lse::LegacyScriptEngine::getLogger() + .error("SQLiteSession::backup: Backup step failed with code {} ({})", rc, sqlite3_errstr(rc)); + return false; + } + + IF_ENDBG lse::LegacyScriptEngine::getLogger().info( + "SQLiteSession::backup: Database successfully backed up to '{}'", + backupPath.string() + ); + return true; +} + SharedPointer SQLiteSession::operator<<(std::string const& query) { return prepare(query, true); } } // namespace DB diff --git a/src/legacy/db/impl/sqlite/Session.h b/src/legacy/db/impl/sqlite/Session.h index 7d57994d..326e0264 100644 --- a/src/legacy/db/impl/sqlite/Session.h +++ b/src/legacy/db/impl/sqlite/Session.h @@ -22,6 +22,7 @@ class SQLiteSession : public Session { void close() override; bool isOpen() override; DBType getType() override; + bool backup(std::filesystem::path const& backupPath) override; SharedPointer operator<<(std::string const& query) override; diff --git a/src/legacy/main/BindAPIs.cpp b/src/legacy/main/BindAPIs.cpp index cd9cca12..0189a178 100644 --- a/src/legacy/main/BindAPIs.cpp +++ b/src/legacy/main/BindAPIs.cpp @@ -26,6 +26,7 @@ #include "legacy/api/ScoreboardAPI.h" #include "legacy/api/ScriptAPI.h" #include "legacy/api/SystemAPI.h" +#include "legacy/api/VaillanI18n.h" // #include "legacy/api/PermissionAPI.h" #include "legacy/api/InternationalAPI.h" @@ -56,6 +57,7 @@ void BindAPIs(std::shared_ptr const& engine) { engine->registerNativeClass(LlClassBuilder); engine->registerNativeClass(VersionClassBuilder); engine->registerNativeClass(NbtStaticBuilder); + engine->registerNativeClass(SnbtFormatEnumBuilder); engine->registerNativeClass(TextClassBuilder); engine->registerNativeClass(ParticleColorBuilder); engine->registerNativeClass(DirectionBuilder); @@ -110,4 +112,5 @@ void BindAPIs(std::shared_ptr const& engine) { engine->registerNativeClass(HttpResponseClassBuilder); engine->registerNativeClass(BinaryStreamClassBuilder); engine->registerNativeClass(ParticleSpawnerBuilder); + engine->registerNativeClass(VaillanI18nClassBuilder); } diff --git a/src/legacy/utils/IniHelper.cpp b/src/legacy/utils/IniHelper.cpp index 3c56c20f..e9b58a40 100644 --- a/src/legacy/utils/IniHelper.cpp +++ b/src/legacy/utils/IniHelper.cpp @@ -1,5 +1,6 @@ #include "legacy/utils/IniHelper.h" +#include "ll/api/base/Macro.h" #include "ll/api/io/Logger.h" #include "lse/Entry.h" diff --git a/src/lse/api/NetworkPacket.h b/src/lse/api/NetworkPacket.h index ed0e7bce..f1719671 100644 --- a/src/lse/api/NetworkPacket.h +++ b/src/lse/api/NetworkPacket.h @@ -10,10 +10,9 @@ namespace lse::api { -template class NetworkPacket final : public Packet { public: - NetworkPacket(std::string data) : mData(std::move(data)) {} + NetworkPacket(MinecraftPacketIds id, std::string data) : Packet(), mPacketId(id), mData(std::move(data)) {} NetworkPacket() = default; NetworkPacket(NetworkPacket&&) = default; @@ -23,16 +22,20 @@ class NetworkPacket final : public Packet { NetworkPacket(NetworkPacket const&) = delete; NetworkPacket& operator=(NetworkPacket const&) = delete; - [[nodiscard]] MinecraftPacketIds getId() const override { return packetId; } + [[nodiscard]] MinecraftPacketIds getId() const override { return mPacketId; } [[nodiscard]] std::string_view getName() const override { return "NetworkPacket"; } void write(BinaryStream& stream) const override { stream.mBuffer.append(mData); } - Bedrock::Result _read(class ReadOnlyBinaryStream& /*stream*/) override { return Bedrock::Result{}; } + Bedrock::Result _read(ReadOnlyBinaryStream& stream) override { + mData = stream.mView.substr(stream.mReadPointer); + return {}; + } private: - std::string mData; + MinecraftPacketIds mPacketId; + std::string mData; }; } // namespace lse::api diff --git a/tooth.json b/tooth.json index fd8acf6b..ca795796 100644 --- a/tooth.json +++ b/tooth.json @@ -2,7 +2,7 @@ "format_version": 3, "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", "tooth": "github.com/LiteLDev/LegacyScriptEngine", - "version": "0.19.0", + "version": "0.19.1", "info": { "name": "LegacyScriptEngine", "description": "A plugin engine for running LLSE plugins on LeviLamina", diff --git a/xmake.lua b/xmake.lua index cba7cd43..87596966 100644 --- a/xmake.lua +++ b/xmake.lua @@ -224,4 +224,4 @@ target("LegacyScriptEngine") os.mkdir(outputPath) os.cp(langPath, outputPath) end) - end + end \ No newline at end of file