Merge branch 'MihailRis:main' into main

This commit is contained in:
Astazium 2026-07-19 11:47:43 +03:00 • committed by GitHub
commit 8bf5a63a36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 258 additions and 59 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,51 @@
local W, H = 128, 128
local canvas = Canvas({W, H})
canvas:clear(0xff202020)
local WHITE = 0xffffffff
local RED = 0xffff4040
local GREEN = 0xff40ff40
local BLUE = 0xff4080ff
local YELLOW = 0xffffff40
-- border
canvas:line(0, 0, W - 1, 0, WHITE)
canvas:line(W - 1, 0, W - 1, H - 1, WHITE)
canvas:line(W - 1, H - 1, 0, H - 1, WHITE)
canvas:line(0, H - 1, 0, 0, WHITE)
-- horizontal
canvas:line(-40, 16, W + 40, 16, RED)
canvas:line(-40, H / 2, W + 40, H / 2, RED)
canvas:line(-40, H - 17, W + 40, H - 17, RED)
-- vertical
canvas:line(16, -40, 16, H + 40, GREEN)
canvas:line(W / 2, -40, W / 2, H + 40, GREEN)
canvas:line(W - 17, -40, W - 17, H + 40, GREEN)
-- diagonals
canvas:line(-40, -40, W + 40, H + 40, BLUE)
canvas:line(-40, H + 40, W + 40, -40, BLUE)
canvas:line(W / 2, -40, W + 40, H / 2, BLUE)
canvas:line(-40, H / 2, W / 2, H + 40, BLUE)
-- outer
canvas:line(-30, -30, 20, 20, YELLOW)
canvas:line(W - 20, H - 20, W + 30, H + 30, YELLOW)
canvas:line(W + 30, -30, W - 20, 20, YELLOW)
canvas:line(-30, H + 30, 20, H - 20, YELLOW)
-- completely outside
canvas:line(-50, -20, -10, H + 20, 0xffff00ff)
canvas:line(W + 10, -20, W + 50, H + 20, 0xffff00ff)
canvas:line(-20, -20, W + 20, -10, 0xffff00ff)
canvas:line(-20, H + 10, W + 20, H + 30, 0xffff00ff)
local expected = Canvas.decode(
file.read_bytes("script:attachments/line_clipping_test.png"), "png"
)
asserts.equals(expected, canvas)

View file

@ -264,3 +264,9 @@ function on_used(playerid: int)
``` ```
Called when an entity is used (RMB by entity). The player ID is passed as an argument. Called when an entity is used (RMB by entity). The player ID is passed as an argument.
```lua
function on_player_set(playerid: int)
```
Called when an entity is attached to a player.

View file

@ -265,3 +265,8 @@ function on_used(playerid: int)
Вызывается при использовании сущности (ПКМ по сущности). ID игрока передается в качестве аргумента. Вызывается при использовании сущности (ПКМ по сущности). ID игрока передается в качестве аргумента.
```lua
function on_player_set(playerid: int)
```
Вызывается при прикреплении сущности к игроку.

View file

@ -1,8 +1,77 @@
local this = {} local this = {}
local app = __vc_app
function this.equals(expected, fact) local function diff_canvas(expected, fact)
this.equals(expected.width, fact.width, "image width")
this.equals(expected.width, fact.width, "image height")
local diff = Canvas({expected.width, expected.height})
local mismatches = 0
for y = 0, expected.height - 1 do
for x = 0, expected.width - 1 do
local ca = expected:at(x, y)
local cb = fact:at(x, y)
if ca == cb then
diff:set(x, y, ca)
else
diff:set(x, y, 0xff0000ff)
mismatches = mismatches + 1
end
end
end
return diff, mismatches
end
local TEST_ARTIFACTS_PATH = "export:test-results"
local function get_test_artifact_path(name)
local path = TEST_ARTIFACTS_PATH
if not file.isdir(path) then
file.mkdirs(path)
end
return file.join(path, name)
end
function this.equals(expected, fact, prefix)
local mt = getmetatable(expected)
if mt == Canvas then
local diff, mismatches = diff_canvas(expected, fact)
if mismatches > 0 then
prefix = prefix or app.script or base64.encode_urlsafe(random.bytes(6))
local path_diff = get_test_artifact_path(
string.format("%s.diff.png", prefix)
)
local path_fact = get_test_artifact_path(
string.format("%s.fact.png", prefix)
)
local path_expected = get_test_artifact_path(
string.format("%s.expected.png", prefix)
)
file.write_bytes(path_diff, diff:encode('png'))
file.write_bytes(path_fact, fact:encode('png'))
file.write_bytes(path_expected, expected:encode('png'))
local total_pixels = expected.width * expected.height
assert(mismatches == 0, string.format(
"%s pixels of %s (%s%%) are different, diff saved as %s",
mismatches,
total_pixels,
math.round(mismatches / total_pixels * 100, 2),
string.escape(path_diff))
)
end
return
end
if prefix then
prefix = prefix .. " "
else
prefix = ""
end
assert(fact == expected, string.format( assert(fact == expected, string.format(
"(fact == expected) assertion failed\n Expected: %s\n Fact: %s", prefix .. "(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
expected, fact expected, fact
)) ))
end end

View file

@ -142,14 +142,16 @@ end
function is_flight() return flight end function is_flight() return flight end
function set_flight(flag) flight = flag end function set_flight(flag)
flight = flag
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
end
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360 local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
function on_physics_update(delta) function on_physics_update(delta)
local grounded = body:is_grounded() local grounded = body:is_grounded()
body:set_vdamping(flight) body:set_vdamping(flight)
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
body:set_linear_damping( body:set_linear_damping(
(flight or not grounded) and props.air_damping or props.ground_damping (flight or not grounded) and props.air_damping or props.ground_damping
) )

View file

@ -58,16 +58,12 @@ local function process_player_inputs(pid, rot, delta)
end end
end end
local prepared = false function on_player_set(pid)
body:set_enabled(false) mob.set_flight(player.is_flight(pid))
mob.set_flight(true) body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
end
function on_physics_update(delta) function on_physics_update(delta)
if not prepared then
prepared = true
body:set_enabled(true)
end
local pid = entity:get_player() local pid = entity:get_player()
if pid == -1 then if pid == -1 then
return return

View file

@ -49,9 +49,9 @@ static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte*
png_write_info(png_ptr, info_ptr); png_write_info(png_ptr, info_ptr);
auto row = std::make_unique<png_byte[]>(pixsize * width); auto row = std::make_unique<png_byte[]>(pixsize * width);
for (uint y = 0; y < height; y++) { for (int y = height-1; y >= 0; y--) {
for (uint x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
for (uint i = 0; i < pixsize; i++) { for (int i = 0; i < pixsize; i++) {
row[x * pixsize + i] = row[x * pixsize + i] =
(png_byte)data[(y * width + x) * pixsize + i]; (png_byte)data[(y * width + x) * pixsize + i];
} }

View file

@ -1,5 +1,7 @@
#include "ImageData.hpp" #include "ImageData.hpp"
#include "debug/Logger.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <assert.h> #include <assert.h>
#include <stdexcept> #include <stdexcept>
@ -7,6 +9,8 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
static debug::Logger logger("image-data");
ImageData::ImageData(ImageFormat format, uint width, uint height) ImageData::ImageData(ImageFormat format, uint width, uint height)
: format(format), width(width), height(height) { : format(format), width(width), height(height) {
size_t pixsize; size_t pixsize;
@ -106,47 +110,83 @@ std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int heigh
return subImage; return subImage;
} }
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { static bool clip_line(
const int left = 0; int& x0, int& y0, int& x1, int& y1, int width, int height
const int right = width; ) {
const int bottom = 0; if (width <= 0 || height <= 0) {
const int top = height; return false;
}
constexpr int INSIDE = 0;
constexpr int LEFT = 1;
constexpr int RIGHT = 2;
constexpr int BOTTOM = 4;
constexpr int TOP = 8;
int dx = x2 - x1; const int xmin = 0;
int dy = y2 - y1; const int ymin = 0;
const int xmax = width - 1;
const int ymax = height - 1;
float t0 = 0.0f; auto outcode = [&](int x, int y) {
float t1 = 1.0f; int code = INSIDE;
auto clip = [](int p, int q, float& t0, float& t1) { if (x < xmin)
if (p == 0) { code |= LEFT;
return q >= 0; else if (x > xmax)
} code |= RIGHT;
float t = static_cast<float>(q) / p;
if (p < 0) { if (y < ymin)
if (t > t1) return false; code |= BOTTOM;
if (t > t0) t0 = t; else if (y > ymax)
} else { code |= TOP;
if (t < t0) return false;
if (t < t1) t1 = t; return code;
}
return true;
}; };
if (!clip(-dx, x1 - left, t0, t1)) return false; int c0 = outcode(x0, y0);
if (!clip( dx, right - x1, t0, t1)) return false; int c1 = outcode(x1, y1);
if (!clip(-dy, y1 - bottom, t0, t1)) return false;
if (!clip( dy, top - y1, t0, t1)) return false;
if (t1 < 1.0f) { while (true) {
x2 = x1 + static_cast<int>(std::round(t1 * dx)); if (!(c0 | c1)) {
y2 = y1 + static_cast<int>(std::round(t1 * dy)); return true;
}
if (c0 & c1) {
return false;
}
const int out = c0 ? c0 : c1;
double x = 0.0;
double y = 0.0;
if (out & TOP) {
y = ymax;
x = x0 + (x1 - x0) * static_cast<double>(ymax - y0) / (y1 - y0);
} else if (out & BOTTOM) {
y = ymin;
x = x0 + (x1 - x0) * static_cast<double>(ymin - y0) / (y1 - y0);
} else if (out & RIGHT) {
x = xmax;
y = y0 + (y1 - y0) * static_cast<double>(xmax - x0) / (x1 - x0);
} else { // LEFT
x = xmin;
y = y0 + (y1 - y0) * static_cast<double>(xmin - x0) / (x1 - x0);
}
if (out == c0) {
x0 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y0 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c0 = outcode(x0, y0);
} else {
x1 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y1 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c1 = outcode(x1, y1);
}
} }
if (t0 > 0.0f) { }
x1 = x1 + static_cast<int>(std::round(t0 * dx));
y1 = y1 + static_cast<int>(std::round(t0 * dy)); static bool is_point_outside(int x, int y, int width, int height) {
} return x < 0 || y < 0 || x >= width || y >= height;
return true;
} }
template<uint channels> template<uint channels>
@ -155,11 +195,24 @@ static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const gl
uint width = image.getWidth(); uint width = image.getWidth();
uint height = image.getHeight(); uint height = image.getHeight();
glm::ivec4 init {x1, y1, x2, y2};
if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width || if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width ||
y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) && y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) &&
!clip_line(x1, y1, x2, y2, width, height)) { !clip_line(x1, y1, x2, y2, width, height)) {
return; return;
} }
// spam info for bug report
if (is_point_outside(x1, y1, width, height) || is_point_outside(x2, y2, width, height)) {
logger.warning() << "clip_line fault: [" << init.x << ", " << init.y
<< "] - [" << init.z << ", " << init.w << "] --> ["
<< x1 << ", " << y1 << "] - [" << x2 << ", " << y2
<< "]";
x1 = glm::clamp(x1, 0, static_cast<int>(width) - 1);
y1 = glm::clamp(y1, 0, static_cast<int>(height) - 1);
x2 = glm::clamp(x2, 0, static_cast<int>(width) - 1);
y2 = glm::clamp(y2, 0, static_cast<int>(height) - 1);
}
int dx = std::abs(x2 - x1); int dx = std::abs(x2 - x1);
int dy = -std::abs(y2 - y1); int dy = -std::abs(y2 - y1);

View file

@ -82,7 +82,6 @@ void LevelController::update(float delta, bool pause) {
continue; continue;
} }
player->rotationInterpolation.updateTimer(delta); player->rotationInterpolation.updateTimer(delta);
player->updateEntity();
glm::vec3 position = player->getPosition(); glm::vec3 position = player->getPosition();
player->chunks->configure( player->chunks->configure(
glm::floor(position.x), glm::floor(position.x),
@ -96,9 +95,9 @@ void LevelController::update(float delta, bool pause) {
*player, *player,
player.get() == clientPlayer player.get() == clientPlayer
); );
player->updateEntity();
} }
if (!pause) { if (!pause) {
// update all objects that needed
blocks->update(delta, settings.chunks.padding.get()); blocks->update(delta, settings.chunks.padding.get());
level->entities->update(delta); level->entities->update(delta);
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {

View file

@ -81,16 +81,17 @@ union RGBA {
uint8_t arr[4]; uint8_t arr[4];
uint32_t rgba; uint32_t rgba;
}; };
static_assert(sizeof(RGBA) == 4);
static RGBA* get_at(const ImageData& data, uint index) { static RGBA* get_at(const ImageData& data, size_t index) {
if (index >= data.getWidth() * data.getHeight()) {
return nullptr;
}
return reinterpret_cast<RGBA*>(data.getData() + index * sizeof(RGBA)); return reinterpret_cast<RGBA*>(data.getData() + index * sizeof(RGBA));
} }
static RGBA* get_at(const ImageData& data, uint x, uint y) { static RGBA* get_at(const ImageData& data, uint x, uint y) {
return get_at(data, y * data.getWidth() + x); if (x >= data.getWidth() || y >= data.getHeight()) {
return nullptr;
}
return get_at(data, static_cast<size_t>(y) * data.getWidth() + x);
} }
static RGBA* get_at(State* L, uint x, uint y) { static RGBA* get_at(State* L, uint x, uint y) {

View file

@ -142,6 +142,7 @@ namespace scripting {
void on_entity_grounded(const Entity& entity, float force); void on_entity_grounded(const Entity& entity, float force);
void on_entity_fall(const Entity& entity); void on_entity_fall(const Entity& entity);
void on_entity_save(const Entity& entity); void on_entity_save(const Entity& entity);
void on_entity_player_set(const Entity& entity, int64_t pid);
void on_entities_update(int tps, int parts, int part); void on_entities_update(int tps, int parts, int part);
void on_entities_physics_update(float delta); void on_entities_physics_update(float delta);
void on_entities_render(float delta); void on_entities_render(float delta);

View file

@ -71,6 +71,7 @@ static void init_component_events(
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
funcsset.on_attacked = lua::hasfield(L, "on_attacked"); funcsset.on_attacked = lua::hasfield(L, "on_attacked");
funcsset.on_used = lua::hasfield(L, "on_used"); funcsset.on_used = lua::hasfield(L, "on_used");
funcsset.on_player_set = lua::hasfield(L, "on_player_set");
lua::pop(L); lua::pop(L);
} }
@ -238,6 +239,15 @@ void scripting::on_entity_save(const Entity& entity) {
); );
} }
void scripting::on_entity_player_set(const Entity& entity, int64_t pid) {
process_entity_callback(
entity,
"on_player_set",
&EntityFuncsSet::on_player_set,
[pid](auto L) { return lua::pushinteger(L, pid); }
);
}
void scripting::on_sensor_enter( void scripting::on_sensor_enter(
const Entity& entity, size_t index, entityid_t oid const Entity& entity, size_t index, entityid_t oid
) { ) {

View file

@ -113,6 +113,11 @@ int64_t Entity::getPlayer() const {
} }
void Entity::setPlayer(int64_t id) { void Entity::setPlayer(int64_t id) {
registry.get<EntityId>(entity).player = id; auto& eid = registry.get<EntityId>(entity);
if (eid.player == id) {
return;
}
eid.player = id;
scripting::on_entity_player_set(*this, id);
} }

View file

@ -81,7 +81,7 @@ void Player::updateEntity() {
} }
} }
Hitbox* Player::getHitbox() { Hitbox* Player::getHitbox() const {
if (auto entity = level.entities->get(eid)) { if (auto entity = level.entities->get(eid)) {
return &entity->getRigidbody().hitbox; return &entity->getRigidbody().hitbox;
} }

View file

@ -135,7 +135,7 @@ public:
bool isCurrentCameraBuiltin() const; bool isCurrentCameraBuiltin() const;
Hitbox* getHitbox(); Hitbox* getHitbox() const;
void setSpawnPoint(glm::vec3 point); void setSpawnPoint(glm::vec3 point);
glm::vec3 getSpawnPoint() const; glm::vec3 getSpawnPoint() const;

View file

@ -17,6 +17,7 @@ struct EntityFuncsSet {
bool on_aim_off; bool on_aim_off;
bool on_attacked; bool on_attacked;
bool on_used; bool on_used;
bool on_player_set;
}; };
struct UserComponent { struct UserComponent {