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() speed = speed or 1.0 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 get_dir() return 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(flight and 0.0 or props.gravity_scale) 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