This commit is contained in:
Xertis
2025-09-30 23:45:25 +03:00
181 changed files with 4659 additions and 1546 deletions
+118
View File
@@ -74,6 +74,38 @@ local _tcp_client_callbacks = {}
local _udp_server_callbacks = {}
local _udp_client_datagram_callbacks = {}
local _udp_client_open_callbacks = {}
local _http_response_callbacks = {}
local _http_error_callbacks = {}
network.get = function(url, callback, errorCallback, headers)
local id = network.__get(url, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.get_binary = function(url, callback, errorCallback, headers)
local id = network.__get_binary(url, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.post = function(url, data, callback, errorCallback, headers)
local id = network.__post(url, data, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.tcp_open = function (port, handler)
local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket)
@@ -131,10 +163,80 @@ local function clean(iterable, checkFun, ...)
end
end
local updating_blocks = {}
local TYPE_REGISTER = 0
local TYPE_UNREGISTER = 1
block.__perform_ticks = function(delta)
for id, entry in pairs(updating_blocks) do
entry.timer = entry.timer + delta
local steps = math.floor(entry.timer / entry.delta * #entry / 3)
if steps == 0 then
goto continue
end
entry.timer = 0.0
local event = entry.event
local tps = entry.tps
for i=1, steps do
local x = entry[entry.pointer + 1]
local y = entry[entry.pointer + 2]
local z = entry[entry.pointer + 3]
entry.pointer = (entry.pointer + 3) % #entry
events.emit(event, x, y, z, tps)
end
::continue::
end
end
block.__process_register_events = function()
local register_events = block.__pull_register_events()
if not register_events then
return
end
for i=1, #register_events, 4 do
local header = register_events[i]
local type = bit.band(header, 0xFFFF)
local id = bit.rshift(header, 16)
local x = register_events[i + 1]
local y = register_events[i + 2]
local z = register_events[i + 3]
local list = updating_blocks[id]
if type == TYPE_REGISTER then
if not list then
list = {}
list.event = block.name(id) .. ".blocktick"
list.tps = 20 / (block.properties[id]["tick-interval"] or 1)
list.delta = 1.0 / list.tps
list.timer = 0.0
list.pointer = 0
updating_blocks[id] = list
end
table.insert(list, x)
table.insert(list, y)
table.insert(list, z)
elseif type == TYPE_UNREGISTER then
if list then
for j=1, #list, 3 do
if list[j] == x and list[j + 1] == y and list[j + 2] == z then
for k=1,3 do
table.remove(list, j)
end
j = j - 3
end
end
end
end
print(type, id, x, y, z)
end
end
network.__process_events = function()
local CLIENT_CONNECTED = 1
local CONNECTED_TO_SERVER = 2
local DATAGRAM = 3
local RESPONSE = 4
local ON_SERVER = 1
local ON_CLIENT = 2
@@ -160,6 +262,22 @@ network.__process_events = function()
elseif side == ON_SERVER then
_udp_server_callbacks[sid](addr, port, data)
end
elseif etype == RESPONSE then
if event[2] / 100 == 2 then
local callback = _http_response_callbacks[event[3]]
_http_response_callbacks[event[3]] = nil
_http_error_callbacks[event[3]] = nil
if callback then
callback(event[4])
end
else
local callback = _http_error_callbacks[event[3]]
_http_response_callbacks[event[3]] = nil
_http_error_callbacks[event[3]] = nil
if callback then
callback(event[2], event[4])
end
end
end
-- remove dead servers
+177
View File
@@ -0,0 +1,177 @@
local body = entity.rigidbody
local tsf = entity.transform
local rig = entity.skeleton
local props = {}
local function def_prop(name, def_value)
props[name] = SAVED_DATA[name] or ARGS[name] or def_value
this["get_"..name] = function() return props[name] end
this["set_"..name] = function(value)
props[name] = value
if math.abs(value - def_value) < 1e-7 then
SAVED_DATA[name] = nil
else
SAVED_DATA[name] = value
end
end
end
def_prop("jump_force", 0.0)
def_prop("air_damping", 1.0)
def_prop("ground_damping", 1.0)
def_prop("movement_speed", 3.0)
def_prop("run_speed_mul", 1.5)
def_prop("crouch_speed_mul", 0.35)
def_prop("flight_speed_mul", 2.0)
def_prop("gravity_scale", 1.0)
local function normalize_angle(angle)
while angle > 180 do
angle = angle - 360
end
while angle <= -180 do
angle = angle + 360
end
return angle
end
local function angle_delta(a, b)
return normalize_angle(a - b)
end
local dir = mat4.mul(tsf:get_rot(), {0, 0, -1})
local flight = false
function jump(multiplier)
local vel = body:get_vel()
body:set_vel(
vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel))
end
function move_vertical(speed, vel)
vel = vel or body:get_vel()
vel[2] = vel[2] * 0.2 + props.movement_speed * speed * 0.8
body:set_vel(vel)
end
local function move_horizontal(speed, dir, vel)
vel = vel or body:get_vel()
if vec2.length(dir) > 0.0 then
vec2.normalize(dir, dir)
local magnitude = vec2.length({vel[1], vel[3]})
if magnitude <= 1e-4 or (magnitude < speed or vec2.dot(
{vel[1] / magnitude, vel[3] / magnitude}, dir) < 0.9)
then
vel[1] = vel[1] * 0.2 + dir[1] * speed * 0.8
vel[3] = vel[3] * 0.2 + dir[2] * speed * 0.8
end
magnitude = vec3.length({vel[1], 0, vel[3]})
if vec2.dot({vel[1] / magnitude, vel[3] / magnitude}, dir) > 0.5 then
vel[1] = vel[1] / magnitude * speed
vel[3] = vel[3] / magnitude * speed
end
end
body:set_vel(vel)
end
function go(dir, speed_multiplier, sprint, crouch, vel)
local speed = props.movement_speed * speed_multiplier
if flight then
speed = speed * props.flight_speed_mul
end
if sprint then
speed = speed * props.run_speed_mul
elseif crouch then
speed = speed * props.crouch_speed_mul
end
move_horizontal(speed, dir, vel)
end
local headIndex = rig:index("head")
function look_at(point, change_dir)
local pos = tsf:get_pos()
local viewdir = vec3.normalize(vec3.sub(point, pos))
local dot = vec3.dot(viewdir, dir)
if dot < 0.0 and not change_dir then
viewdir = mat4.mul(tsf:get_rot(), {0, 0, -1})
else
dir[1] = dir[1] * 0.8 + viewdir[1] * 0.13
dir[3] = dir[3] * 0.8 + viewdir[3] * 0.13
end
if not headIndex then
return
end
local headrot = mat4.idt()
local curdir = mat4.mul(mat4.mul(tsf:get_rot(),
rig:get_matrix(headIndex)), {0, 0, -1})
vec3.mix(curdir, viewdir, 0.2, viewdir)
headrot = mat4.inverse(mat4.look_at({0,0,0}, viewdir, {0, 1, 0}))
headrot = mat4.mul(mat4.inverse(tsf:get_rot()), headrot)
rig:set_matrix(headIndex, headrot)
end
function follow_waypoints(pathfinding)
pathfinding = pathfinding or entity:require_component("core:pathfinding")
local pos = tsf:get_pos()
local waypoint = pathfinding.next_waypoint()
if not waypoint then
return
end
local speed = props.movement_speed
local vel = body:get_vel()
dir = vec3.sub(
vec3.add(waypoint, {0.5, 0, 0.5}),
{pos[1], math.floor(pos[2]), pos[3]}
)
local upper = dir[2] > 0
dir[2] = 0.0
vec3.normalize(dir, dir)
move_horizontal(speed, {dir[1], dir[3]}, vel)
if upper and body:is_grounded() then
jump(1.0)
end
end
function set_dir(new_dir)
dir = new_dir
end
function is_flight() return flight end
function set_flight(flag) flight = flag end
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
function on_physics_update(delta)
local grounded = body:is_grounded()
body:set_vdamping(flight)
body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0})
body:set_linear_damping(
(flight or not grounded) and props.air_damping or props.ground_damping
)
local new_angle = (vec2.angle({dir[3], dir[1]})) % 360
local angle = prev_angle
local adelta = angle_delta(
normalize_angle(new_angle),
normalize_angle(prev_angle)
)
local rotate_speed = entity:get_player() == -1 and 200 or 400
if math.abs(adelta) > 5 then
angle = angle + delta * rotate_speed * (adelta > 0 and 1 or -1)
end
tsf:set_rot(mat4.rotate({0, 1, 0}, angle + 180))
prev_angle = angle
end
+71
View File
@@ -0,0 +1,71 @@
local target
local route
local started
local tsf = entity.transform
local body = entity.rigidbody
agent = pathfinding.create_agent()
pathfinding.set_max_visited(agent, 1e3)
pathfinding.avoid_tag(agent, "core:liquid", 8)
function set_target(new_target)
target = new_target
end
function set_jump_height(height)
pathfinding.set_jump_height(agent, height)
end
function get_target()
return target
end
function get_route()
return route
end
function next_waypoint()
if not route or #route == 0 then
return
end
local waypoint = route[#route]
local pos = tsf:get_pos()
local dst = vec2.length({
math.floor(waypoint[1] - math.floor(pos[1])),
math.floor(waypoint[3] - math.floor(pos[3]))
})
if dst < 1.0 then
table.remove(route, #route)
end
return route[#route]
end
local refresh_internal = 100
local frameid = math.random(0, refresh_internal)
function set_refresh_interval(interval)
refresh_internal = interval
end
function on_update()
if not started then
frameid = frameid + 1
if body:is_grounded() then
if target and (frameid % refresh_internal == 1 or not route) then
pathfinding.make_route_async(agent, tsf:get_pos(), target)
started = true
end
end
else
local new_route = pathfinding.pull_route(agent)
if new_route then
route = new_route
started = false
end
end
end
function on_despawn()
pathfinding.remove_agent(agent)
end
+62
View File
@@ -0,0 +1,62 @@
local tsf = entity.transform
local body = entity.rigidbody
local mob = entity:require_component("core:mob")
local cheat_speed_mul = 10.0
local function process_player_inputs(pid, delta)
if not hud or hud.is_inventory_open() or menu.page ~= "" then
return
end
local cam = cameras.get("core:first-person")
local front = cam:get_front()
local right = cam:get_right()
front[2] = 0.0
vec3.normalize(front, front)
local isjump = input.is_active('movement.jump')
local issprint = input.is_active('movement.sprint')
local iscrouch = input.is_active('movement.crouch')
local isforward = input.is_active('movement.forward')
local ischeat = input.is_active('movement.cheat')
local isback = input.is_active('movement.back')
local isleft = input.is_active('movement.left')
local isright = input.is_active('movement.right')
mob.set_flight(player.is_flight(pid))
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
body:set_crouching(iscrouch)
local vel = body:get_vel()
local speed = ischeat and cheat_speed_mul or 1.0
local dir = {0, 0, 0}
if isforward then vec3.add(dir, front, dir) end
if isback then vec3.sub(dir, front, dir) end
if isright then vec3.add(dir, right, dir) end
if isleft then vec3.sub(dir, right, dir) end
if vec3.length(dir) > 0.0 then
mob.go({dir[1], dir[3]}, speed, issprint, iscrouch, vel)
end
if mob.is_flight() then
if isjump then
mob.move_vertical(speed * 3)
elseif iscrouch then
mob.move_vertical(-speed * 3)
end
elseif body:is_grounded() and isjump then
mob.jump()
end
end
function on_physics_update(delta)
local pid = entity:get_player()
if pid ~= -1 then
local pos = tsf:get_pos()
local cam = cameras.get("core:first-person")
process_player_inputs(pid, delta)
mob.look_at(vec3.add(pos, cam:get_front()))
end
end
+35 -33
View File
@@ -22,6 +22,39 @@ local function configure_SSAO()
-- for test purposes
end
local function update_hand()
local skeleton = gfx.skeletons
local pid = hud.get_player()
local invid, slot = player.get_inventory(pid)
local itemid = inventory.get(invid, slot)
local cam = cameras.get("core:first-person")
local bone = skeleton.index("hand", "item")
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
local rotation = cam:get_rot()
local angle = player.get_rot(pid) - 90
local cos = math.cos(angle / (180 / math.pi))
local sin = math.sin(angle / (180 / math.pi))
local newX = offset[1] * cos - offset[3] * sin
local newZ = offset[1] * sin + offset[3] * cos
offset[1] = newX
offset[3] = newZ
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
mat4.mul(rotation, mat, mat)
mat4.rotate(mat, {0, 1, 0}, -90, mat)
mat4.translate(mat, offset, mat)
skeleton.set_matrix("hand", bone, mat)
skeleton.set_model("hand", bone, item.model_name(itemid))
end
function on_hud_open()
input.add_callback("player.pick", function ()
if hud.is_paused() or hud.is_inventory_open() then
@@ -63,7 +96,7 @@ function on_hud_open()
player.set_noclip(pid, true)
end
end)
input.add_callback("player.flight", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
@@ -81,39 +114,8 @@ function on_hud_open()
end)
configure_SSAO()
end
local function update_hand()
local skeleton = gfx.skeletons
local pid = hud.get_player()
local invid, slot = player.get_inventory(pid)
local itemid = inventory.get(invid, slot)
local cam = cameras.get("core:first-person")
local bone = skeleton.index("hand", "item")
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
local rotation = cam:get_rot()
local angle = player.get_rot() - 90
local cos = math.cos(angle / (180 / math.pi))
local sin = math.sin(angle / (180 / math.pi))
local newX = offset[1] * cos - offset[3] * sin
local newZ = offset[1] * sin + offset[3] * cos
offset[1] = newX
offset[3] = newZ
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
mat4.mul(rotation, mat, mat)
mat4.rotate(mat, {0, 1, 0}, -90, mat)
mat4.translate(mat, offset, mat)
skeleton.set_matrix("hand", bone, mat)
skeleton.set_model("hand", bone, item.model_name(itemid))
hud.default_hand_controller = update_hand
end
function on_hud_render()
+27 -13
View File
@@ -6,28 +6,29 @@ local names = {
"shadeless", "ambient-occlusion", "breakable", "selectable", "grounded",
"hidden", "draw-group", "picking-item", "surface-replacement", "script-name",
"ui-layout", "inventory-size", "tick-interval", "overlay-texture",
"translucent", "fields", "particles", "icon-type", "icon", "placing-block",
"translucent", "fields", "particles", "icon-type", "icon", "placing-block",
"stack-size", "name", "script-file", "culling"
}
for name, _ in pairs(user_props) do
table.insert(names, name)
end
-- remove undefined properties
for id, blockprops in pairs(block.properties) do
for propname, value in pairs(blockprops) do
if not table.has(names, propname) then
blockprops[propname] = nil
end
end
end
for id, itemprops in pairs(item.properties) do
for propname, value in pairs(itemprops) do
if not table.has(names, propname) then
itemprops[propname] = nil
-- remove undefined properties and build tags set
local function process_properties(lib)
for id, props in pairs(lib.properties) do
for propname, _ in pairs(props) do
if not table.has(names, propname) then
props[propname] = nil
end
end
props.tags_set = lib.__get_tags(id)
end
end
process_properties(block)
process_properties(item)
local function make_read_only(t)
setmetatable(t, {
__newindex = function()
@@ -57,6 +58,19 @@ local function cache_names(library)
function library.index(name)
return indices[name]
end
function library.has_tag(id, tag)
if id == nil then
error("id is nil")
end
local props = library.properties[id]
local tags_set = props.tags_set
if tags_set then
return tags_set[tag]
else
return false
end
end
end
cache_names(block)
+10
View File
@@ -157,6 +157,16 @@ console.add_command(
end
)
console.add_command(
"entity.spawn name:str x:num~pos.x y:num~pos.y z:num~pos.z",
"Spawn entity with default parameters",
function(args, kwargs)
local eid = entities.spawn(args[1], {args[2], args[3], args[4]})
return string.format("spawned %s at %s, %s, %s", unpack(args))
end
)
console.add_command(
"entity.despawn entity:sel=$entity.selected",
"Despawn entity",
+29 -12
View File
@@ -79,13 +79,20 @@ local function complete_app_lib(app)
coroutine.yield()
end
function app.sleep_until(predicate, max_ticks)
function app.sleep_until(predicate, max_ticks, max_time)
max_ticks = max_ticks or 1e9
max_time = max_time or 1e9
local ticks = 0
while ticks < max_ticks and not predicate() do
local start_time = os.clock()
while ticks < max_ticks and
os.clock() - start_time < max_time
and not predicate() do
app.tick()
ticks = ticks + 1
end
if os.clock() - start_time >= max_time then
error("timeout")
end
if ticks == max_ticks then
error("max ticks exceed")
end
@@ -174,6 +181,7 @@ if enable_experimental then
require "core:internal/maths_inline"
end
asserts = require "core:internal/asserts"
events = require "core:internal/events"
function pack.unload(prefix)
@@ -429,6 +437,8 @@ function __vc_on_hud_open()
hud.open_permanent("core:ingame_chat")
end
local Schedule = require "core:schedule"
local ScheduleGroup_mt = {
__index = {
publish = function(self, schedule)
@@ -440,10 +450,11 @@ local ScheduleGroup_mt = {
for id, schedule in pairs(self._schedules) do
schedule:tick(dt)
end
self.common:tick(dt)
end,
remove = function(self, id)
self._schedules[id] = nil
end
end,
}
}
@@ -451,6 +462,7 @@ local function ScheduleGroup()
return setmetatable({
_next_schedule = 1,
_schedules = {},
common = Schedule()
}, ScheduleGroup_mt)
end
@@ -527,15 +539,18 @@ function start_coroutine(chunk, name)
local co = coroutine.create(function()
local status, error = xpcall(chunk, function(err)
local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback()
gui.alert(fullmsg, function()
if world.is_open() then
__vc_app.close_world()
else
__vc_app.reset_content()
menu:reset()
menu.page = "main"
end
end)
if hud then
gui.alert(fullmsg, function()
if world.is_open() then
__vc_app.close_world()
else
__vc_app.reset_content()
menu:reset()
menu.page = "main"
end
end)
end
return fullmsg
end)
if not status then
@@ -573,6 +588,8 @@ function __process_post_runnables()
end
network.__process_events()
block.__process_register_events()
block.__perform_ticks(time.delta())
end
function time.post_runnable(runnable)
+2
View File
@@ -668,3 +668,5 @@ end
bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor"
random.Random = require "core:internal/random_generator"