Initial commit

This commit is contained in:
mid 2025-01-19 17:29:52 +02:00
commit 49c44fe87c
31 changed files with 38962 additions and 0 deletions

27
.gitea/workflows/k4.yaml Normal file
View File

@ -0,0 +1,27 @@
name: k4 Build Test
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on:
push:
tags:
- 'v*'
jobs:
do_the_build:
runs-on: debian_amd64
steps:
- name: Check out repository code
uses: actions/checkout@v3
with:
submodules: 'true'
- run: mkdir build build/k3 bin bin/assets
- run: CC="i686-w64-mingw32-gcc" CFLAGS="-I/usr/i686-w64-mingw32/include -I/usr/i686-w64-mingw32/include/lua5.3 -L/usr/i686-w64-mingw32/lib -Wno-error" make -B
- run: CC="i686-linux-gnu-gcc" CFLAGS="-I/usr/i686-linux-gnu/include -I/usr/i686-linux-gnu/include/lua5.3 -L/usr/i686-linux-gnu/lib -Wno-error" make -B
- run: cp /usr/lib/gcc/i686-w64-mingw32/10-win32/libgcc_s_dw2-1.dll /usr/lib/gcc/i686-w64-mingw32/10-win32/libstdc++-6.dll /usr/i686-w64-mingw32/lib/libportaudio-2.dll /usr/i686-w64-mingw32/lib/libwinpthread-1.dll bin/
- run: cp -r /home/git/k4templateassets/* bin/assets/
- run: zip -9r "k4${{ github.ref_name }}.zip" bin/
- name: Create package
uses: akkuman/gitea-release-action@v1
with:
files: |-
k4${{ github.ref_name }}.zip
- run: echo "🍏 This job's status is ${{ job.status }}."

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "k3"]
path = k3
url = https://mid.net.ua/git/mid/k3

36
Makefile Normal file
View File

@ -0,0 +1,36 @@
k4_SRCS := $(wildcard src/*.c)
k4_HDRS := $(wildcard src/*.h)
k4_OBJS := $(patsubst src/%.c, build/%.o, $(k4_SRCS))
k4_DEPS := $(patsubst src/%.c, build/%.d, $(k4_SRCS))
k3_SRCS = $(wildcard k3/src/*.c)
k3_HDRS = $(wildcard k3/src/*.h)
k3_OBJS := $(patsubst k3/src/%.c, build/k3/%.o, $(k3_SRCS))
k3_DEPS := $(patsubst k3/src/%.c, build/k3/%.d, $(k3_SRCS))
CFLAGS := $(CFLAGS) -Ik3/src
ifneq (,$(findstring mingw,$(CC)))
CFLAGS := -static-libgcc -static-libstdc++ -std=gnu99 -march=pentium4 -D_WIN32_WINNT=0x600 -DENET_FEATURE_ADDRESS_MAPPING -fno-pic -no-pie -fms-extensions -fno-pie -O0 -g -gdwarf-2 -Isrc $(CFLAGS)
LIBS := -l:libglfw3.a -lopengl32 -pthread -lm -l:libode.a -l:libvorbisfile.a -l:libvorbis.a -l:libogg.a -lportaudio -lgdi32 -lws2_32 -lwinmm -lstdc++ -lole32 -lsetupapi -lhid -l:liblua5.3.a $(LIBS)
else
CFLAGS := -march=opteron $(SAN) -std=gnu99 -DENET_FEATURE_ADDRESS_MAPPING -fms-extensions -fno-pic -no-pie -fno-pie -O0 -g -Isrc $(CFLAGS)
LIBS := -lglfw3 -pthread -ldl -lm -lode -lstdc++ -llua5.3 -lvorbis -lvorbisfile -lportaudio $(LIBS)
endif
CFLAGS := $(CFLAGS) -DLOCALHOST_ONLY -Dk3_IRREGULAR_SHADOWS
build/k3/%.o: k3/src/%.c
$(CC) $(CFLAGS) -MMD -o $@ -c $<
build/%.o: src/%.c
$(CC) $(CFLAGS) -MMD -o $@ -c $<
k4: $(k4_OBJS) $(k3_OBJS)
$(CC) $(CFLAGS) -o bin/k4 $(k4_OBJS) $(k3_OBJS) $(LIBS)
include $(k4_DEPS)
include $(k3_DEPS)
$(k4_DEPS): ;
$(k3_DEPS): ;

247
k3ex.py Normal file
View File

@ -0,0 +1,247 @@
import bpy, bmesh, struct, collections, bpy_extras, re
from collections import namedtuple
AxisChange = bpy_extras.io_utils.axis_conversion(to_forward = "-Z", to_up = "Y").to_4x4()
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def normalize_weights(items):
items = list(items)
items.sort(key = lambda x: x[1], reverse = True)
items = items[:4]
while len(items) < 4: items.append((0, 0))
normFac = sum([w[1] for w in items])
return tuple([(w[0], w[1] / normFac) for w in items])
V = namedtuple("V", ["pos", "nrm", "uv", "weights", "col"])
def vert2tup(bm, loop):
return V(
pos = (loop.vert.co.x, loop.vert.co.y, loop.vert.co.z),
nrm = (loop.vert.normal.x, loop.vert.normal.y, loop.vert.normal.z),
uv = (loop[bm.loops.layers.uv.active].uv[0], 1 - loop[bm.loops.layers.uv.active].uv[1]) if bm.loops.layers.uv.active is not None else (0, 0),
weights = normalize_weights(loop.vert[bm.verts.layers.deform.active].items()) if bm.verts.layers.deform.active is not None else ((0, 0), (0, 0), (0, 0), (0, 0)),
col = tuple(list(loop[bm.loops.layers.color.active]) + [1.0]) if bm.loops.layers.color.active is not None else None
)
patrn = re.compile(r"^\s*(\d+)\s*\(\s*(\d+)\s*\@\s*(\d+)\s*\)\s*$")
def write_some_data(context, filepath, doExportEverything):
if doExportEverything:
bpy.ops.object.select_all(action = "DESELECT")
objs = [o for o in context.scene.objects if o.layers[0] and o.type == "MESH"]
for o in objs:
o.select = True
bpy.ops.object.duplicate()
objs = [o for o in context.scene.objects if o.select]
bpy.ops.object.select_all(action = "DESELECT")
for o in objs:
o.select = True
context.scene.objects.active = o
for m in o.modifiers:
bpy.ops.object.modifier_apply(modifier=m.name)
o.select = False
for o in objs:
o.select = True
bpy.ops.object.join()
context.scene.objects.active = [o for o in context.scene.objects if o.select][0]
bpy.ops.object.transform_apply(location = True, scale = True, rotation = True)
arm = None
obj = None
if context.active_object.type == "ARMATURE":
arm = context.active_object
obj = arm.children[0]
elif context.active_object.type == "MESH":
obj = context.active_object
arm = obj.parent
if not doExportEverything:
bpy.ops.object.select_all(action = "DESELECT")
obj.select = True
bpy.ops.object.duplicate()
obj = context.scene.objects.active = [o for o in context.scene.objects if o.select][0]
for m in obj.modifiers:
if m.type != "ARMATURE":
bpy.ops.object.modifier_apply(modifier=m.name)
bm = bmesh.new()
bm.from_mesh(obj.data)
bmesh.ops.triangulate(bm, faces = bm.faces)
vertdict = collections.OrderedDict()
for face in bm.faces:
for loop in face.loops:
vertdict.setdefault(vert2tup(bm, loop), None)
vertkeys = list(vertdict.keys())
tris = [[] for i in range(len(obj.data.materials))]
for face in bm.faces:
for loop in face.loops:
tris[face.material_index].append(vertkeys.index(vert2tup(bm, loop)))
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
print("Beginning to write..")
with open(filepath, "wb") as out:
out.write(b"K3M ")
out.write(struct.pack("I", len(vertdict)))
out.write(struct.pack("I", 3 * len(bm.faces)))
out.write(struct.pack("B", len(arm.data.bones) if arm else 0))
print("A")
colorsEnabled = any([v.col is not None for v in vertdict.keys()])
print("B", colorsEnabled)
out.write(struct.pack("B", 1 if colorsEnabled else 0))
out.write(struct.pack("H", len([a for a in bpy.data.actions if patrn.match(a.name)]) if arm else 0))
print("Wrote header.")
if arm:
dabones = list(arm.data.bones)
for b in dabones:
m = (AxisChange * b.matrix_local).inverted()
for c in range(4):
out.write(struct.pack("4f", m.col[c][0], m.col[c][1], m.col[c][2], m.col[c][3]))
for b in dabones:
out.write(struct.pack("B", dabones.index(b.parent) if b.parent else 255))
for v in vertdict.keys():
out.write(struct.pack("3f", v[0][0], v[0][2], -v[0][1]))
for v in vertdict.keys():
invFactor = max(abs(v[1][0]), max(abs(v[1][1]), abs(v[1][2])))
out.write(struct.pack("3b", round(v[1][0] * 127 / invFactor), round(v[1][2] * 127 / invFactor), round(-v[1][1] * 127 / invFactor)))
for v in vertdict.keys():
out.write(struct.pack("2f", v[2][0], v[2][1]))
for v in vertdict.keys():
out.write(struct.pack("4B", v[3][0][0], v[3][1][0], v[3][2][0], v[3][3][0]))
for v in vertdict.keys():
out.write(struct.pack("4H", round(65535 * v[3][0][1]), round(65535 * v[3][1][1]), round(65535 * v[3][2][1]), round(65535 * v[3][3][1])))
if colorsEnabled:
for v in vertdict.keys():
out.write(struct.pack("4B", int(255 * v.col[0]), int(255 * v.col[1]), int(255 * v.col[2]), int(255 * v.col[3])))
for mat in tris:
for idx in mat:
out.write(struct.pack("H", idx))
out.write(struct.pack("H", len(tris)))
offset = 0
for i, mat in enumerate(tris):
out.write(struct.pack("2H", offset, len(mat)))
out.write(obj.material_slots[i].name.encode("UTF-8"))
out.write(b"\0")
offset += len(mat)
if arm:
actions = [a for a in bpy.data.actions if patrn.match(a.name)]
actions.sort(key = lambda a: int(patrn.match(a.name).group(1)))
for action in actions:
matched = patrn.match(action.name)
arm.animation_data.action = action
id, framecount, fps = int(matched.group(1)), int(matched.group(2)), int(matched.group(3))
out.write(struct.pack("4H", id, framecount, fps, 0))
for f in range(framecount):
bpy.context.scene.frame_set(1 + int(f * context.scene.render.fps / fps))
bpy.context.scene.update()
for bone in arm.pose.bones:
matrix = bone.matrix
if bone.parent:
matrix = bone.parent.matrix.inverted() * matrix
else:
matrix = AxisChange * matrix
translation, rotation, scale = matrix.decompose()
if rotation.w <= 0: rotation = -rotation
out.write(struct.pack("4f", translation.x, translation.y, translation.z, 1))
out.write(struct.pack("4f", rotation.x, rotation.y, rotation.z, rotation.w))
bpy.ops.object.select_all(action = "DESELECT")
obj.select = True
context.scene.objects.active = obj
bpy.ops.object.delete()
return {'FINISHED'}
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
class ExportSomeData(Operator, ExportHelper):
"""This appears in the tooltip of the operator and in the generated docs"""
bl_idname = "export_k3m.some_data" # important since its how bpy.ops.import_test.some_data is constructed
bl_label = "Export"
# ExportHelper mixin class uses this
filename_ext = ".k3m"
filter_glob = StringProperty(
default="*.k3m",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
use_setting = BoolProperty(
name="Export Everything in Layer",
description="",
default=False,
)
type = EnumProperty(
name="Example Enum",
description="Choose between two items",
items=(('OPT_A', "First Option", "Description one"),
('OPT_B', "Second Option", "Description two")),
default='OPT_A',
)
def execute(self, context):
return write_some_data(context, self.filepath, self.use_setting)
# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
self.layout.operator(ExportSomeData.bl_idname, text="k3 Model")
def register():
bpy.utils.register_class(ExportSomeData)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(ExportSomeData)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
# test call
#bpy.ops.export_test.some_data('INVOKE_DEFAULT')

77
physex.py Normal file
View File

@ -0,0 +1,77 @@
import bpy, bmesh, struct
def write_some_data(context, filepath, use_some_setting):
f = open(filepath, "wb")
bm = bmesh.new()
bm.from_object(context.active_object, context.scene)
bmesh.ops.triangulate(bm, faces = bm.faces)
for face in bm.faces:
for loop in face.loops:
f.write(struct.pack("3f", loop.vert.co.x, loop.vert.co.z, -loop.vert.co.y))
f.close()
return {'FINISHED'}
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
class ExportSomeData(Operator, ExportHelper):
"""This appears in the tooltip of the operator and in the generated docs"""
bl_idname = "export_trimesh.some_data" # important since its how bpy.ops.import_test.some_data is constructed
bl_label = "Export Triangle Mesh"
# ExportHelper mixin class uses this
filename_ext = ".phys"
filter_glob = StringProperty(
default="*.phys",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
use_setting = BoolProperty(
name="Example Boolean",
description="Example Tooltip",
default=True,
)
type = EnumProperty(
name="Example Enum",
description="Choose between two items",
items=(('OPT_A', "First Option", "Description one"),
('OPT_B', "Second Option", "Description two")),
default='OPT_A',
)
def execute(self, context):
return write_some_data(context, self.filepath, self.use_setting)
# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
self.layout.operator(ExportSomeData.bl_idname, text="Export Raw Triangle Mesh")
def register():
bpy.utils.register_class(ExportSomeData)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(ExportSomeData)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()

1479
src/cwalk.c Normal file

File diff suppressed because it is too large Load Diff

498
src/cwalk.h Normal file
View File

@ -0,0 +1,498 @@
#pragma once
#ifndef CWK_LIBRARY_H
#define CWK_LIBRARY_H
#include <stdbool.h>
#include <stddef.h>
#if defined(_WIN32) || defined(__CYGWIN__)
#define CWK_EXPORT __declspec(dllexport)
#define CWK_IMPORT __declspec(dllimport)
#elif __GNUC__ >= 4
#define CWK_EXPORT __attribute__((visibility("default")))
#define CWK_IMPORT __attribute__((visibility("default")))
#else
#define CWK_EXPORT
#define CWK_IMPORT
#endif
#if defined(CWK_SHARED)
#if defined(CWK_EXPORTS)
#define CWK_PUBLIC CWK_EXPORT
#else
#define CWK_PUBLIC CWK_IMPORT
#endif
#else
#define CWK_PUBLIC
#endif
#ifdef __cplusplus
extern "C"
{
#endif
/**
* A segment represents a single component of a path. For instance, on linux a
* path might look like this "/var/log/", which consists of two segments "var"
* and "log".
*/
struct cwk_segment
{
const char *path;
const char *segments;
const char *begin;
const char *end;
size_t size;
};
/**
* The segment type can be used to identify whether a segment is a special
* segment or not.
*
* CWK_NORMAL - normal folder or file segment
* CWK_CURRENT - "./" current folder segment
* CWK_BACK - "../" relative back navigation segment
*/
enum cwk_segment_type
{
CWK_NORMAL,
CWK_CURRENT,
CWK_BACK
};
/**
* @brief Determines the style which is used for the path parsing and
* generation.
*/
enum cwk_path_style
{
CWK_STYLE_WINDOWS,
CWK_STYLE_UNIX
};
/**
* @brief Generates an absolute path based on a base.
*
* This function generates an absolute path based on a base path and another
* path. It is guaranteed to return an absolute path. If the second submitted
* path is absolute, it will override the base path. The result will be
* written to a buffer, which might be truncated if the buffer is not large
* enough to hold the full path. However, the truncated result will always be
* null-terminated. The returned value is the amount of characters which the
* resulting path would take if it was not truncated (excluding the
* null-terminating character).
*
* @param base The absolute base path on which the relative path will be
* applied.
* @param path The relative path which will be applied on the base path.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the new absolute path.
*/
CWK_PUBLIC size_t cwk_path_get_absolute(const char *base, const char *path,
char *buffer, size_t buffer_size);
/**
* @brief Generates a relative path based on a base.
*
* This function generates a relative path based on a base path and another
* path. It determines how to get to the submitted path, starting from the
* base directory. The result will be written to a buffer, which might be
* truncated if the buffer is not large enough to hold the full path. However,
* the truncated result will always be null-terminated. The returned value is
* the amount of characters which the resulting path would take if it was not
* truncated (excluding the null-terminating character).
*
* @param base_directory The base path from which the relative path will
* start.
* @param path The target path where the relative path will point to.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full path.
*/
CWK_PUBLIC size_t cwk_path_get_relative(const char *base_directory,
const char *path, char *buffer, size_t buffer_size);
/**
* @brief Joins two paths together.
*
* This function generates a new path by combining the two submitted paths. It
* will remove double separators, and unlike cwk_path_get_absolute it permits
* the use of two relative paths to combine. The result will be written to a
* buffer, which might be truncated if the buffer is not large enough to hold
* the full path. However, the truncated result will always be
* null-terminated. The returned value is the amount of characters which the
* resulting path would take if it was not truncated (excluding the
* null-terminating character).
*
* @param path_a The first path which comes first.
* @param path_b The second path which comes after the first.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full, combined path.
*/
CWK_PUBLIC size_t cwk_path_join(const char *path_a, const char *path_b,
char *buffer, size_t buffer_size);
/**
* @brief Joins multiple paths together.
*
* This function generates a new path by joining multiple paths together. It
* will remove double separators, and unlike cwk_path_get_absolute it permits
* the use of multiple relative paths to combine. The last path of the
* submitted string array must be set to NULL. The result will be written to a
* buffer, which might be truncated if the buffer is not large enough to hold
* the full path. However, the truncated result will always be
* null-terminated. The returned value is the amount of characters which the
* resulting path would take if it was not truncated (excluding the
* null-terminating character).
*
* @param paths An array of paths which will be joined.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full, combined path.
*/
CWK_PUBLIC size_t cwk_path_join_multiple(const char **paths, char *buffer,
size_t buffer_size);
/**
* @brief Determines the root of a path.
*
* This function determines the root of a path by finding its length. The
* root always starts at the submitted path. If the path has no root, the
* length will be set to zero.
*
* @param path The path which will be inspected.
* @param length The output of the root length.
*/
CWK_PUBLIC void cwk_path_get_root(const char *path, size_t *length);
/**
* @brief Changes the root of a path.
*
* This function changes the root of a path. It does not normalize the result.
* The result will be written to a buffer, which might be truncated if the
* buffer is not large enough to hold the full path. However, the truncated
* result will always be null-terminated. The returned value is the amount of
* characters which the resulting path would take if it was not truncated
* (excluding the null-terminating character).
*
* @param path The original path which will get a new root.
* @param new_root The new root which will be placed in the path.
* @param buffer The output buffer where the result is written to.
* @param buffer_size The size of the output buffer where the result is
* written to.
* @return Returns the total amount of characters of the new path.
*/
CWK_PUBLIC size_t cwk_path_change_root(const char *path, const char *new_root,
char *buffer, size_t buffer_size);
/**
* @brief Determine whether the path is absolute or not.
*
* This function checks whether the path is an absolute path or not. A path is
* considered to be absolute if the root ends with a separator.
*
* @param path The path which will be checked.
* @return Returns true if the path is absolute or false otherwise.
*/
CWK_PUBLIC bool cwk_path_is_absolute(const char *path);
/**
* @brief Determine whether the path is relative or not.
*
* This function checks whether the path is a relative path or not. A path is
* considered to be relative if the root does not end with a separator.
*
* @param path The path which will be checked.
* @return Returns true if the path is relative or false otherwise.
*/
CWK_PUBLIC bool cwk_path_is_relative(const char *path);
/**
* @brief Gets the basename of a file path.
*
* This function gets the basename of a file path. A pointer to the beginning
* of the basename will be returned through the basename parameter. This
* pointer will be positioned on the first letter after the separator. The
* length of the file path will be returned through the length parameter. The
* length will be set to zero and the basename to NULL if there is no basename
* available.
*
* @param path The path which will be inspected.
* @param basename The output of the basename pointer.
* @param length The output of the length of the basename. This may be
* null if not required.
*/
CWK_PUBLIC void cwk_path_get_basename(const char *path, const char **basename,
size_t *length);
/**
* @brief Changes the basename of a file path.
*
* This function changes the basename of a file path. This function will not
* write out more than the specified buffer can contain. However, the
* generated string is always null-terminated - even if not the whole path is
* written out. The function returns the total number of characters the
* complete buffer would have, even if it was not written out completely. The
* path may be the same memory address as the buffer.
*
* @param path The original path which will be used for the modified path.
* @param new_basename The new basename which will replace the old one.
* @param buffer The buffer where the changed path will be written to.
* @param buffer_size The size of the result buffer where the changed path is
* written to.
* @return Returns the size which the complete new path would have if it was
* not truncated.
*/
CWK_PUBLIC size_t cwk_path_change_basename(const char *path,
const char *new_basename, char *buffer, size_t buffer_size);
/**
* @brief Gets the dirname of a file path.
*
* This function determines the dirname of a file path and returns the length
* up to which character is considered to be part of it. If no dirname is
* found, the length will be set to zero. The beginning of the dirname is
* always equal to the submitted path pointer.
*
* @param path The path which will be inspected.
* @param length The length of the dirname.
*/
CWK_PUBLIC void cwk_path_get_dirname(const char *path, size_t *length);
/**
* @brief Gets the extension of a file path.
*
* This function extracts the extension portion of a file path. A pointer to
* the beginning of the extension will be returned through the extension
* parameter if an extension is found and true is returned. This pointer will
* be positioned on the dot. The length of the extension name will be returned
* through the length parameter. If no extension is found both parameters
* won't be touched and false will be returned.
*
* @param path The path which will be inspected.
* @param extension The output of the extension pointer.
* @param length The output of the length of the extension.
* @return Returns true if an extension is found or false otherwise.
*/
CWK_PUBLIC bool cwk_path_get_extension(const char *path, const char **extension,
size_t *length);
/**
* @brief Determines whether the file path has an extension.
*
* This function determines whether the submitted file path has an extension.
* This will evaluate to true if the last segment of the path contains a dot.
*
* @param path The path which will be inspected.
* @return Returns true if the path has an extension or false otherwise.
*/
CWK_PUBLIC bool cwk_path_has_extension(const char *path);
/**
* @brief Changes the extension of a file path.
*
* This function changes the extension of a file name. The function will
* append an extension if the basename does not have an extension, or use the
* extension as a basename if the path does not have a basename. This function
* will not write out more than the specified buffer can contain. However, the
* generated string is always null-terminated - even if not the whole path is
* written out. The function returns the total number of characters the
* complete buffer would have, even if it was not written out completely. The
* path may be the same memory address as the buffer.
*
* @param path The path which will be used to make the change.
* @param new_extension The extension which will be placed within the new
* path.
* @param buffer The output buffer where the result will be written to.
* @param buffer_size The size of the output buffer where the result will be
* written to.
* @return Returns the total size which the output would have if it was not
* truncated.
*/
CWK_PUBLIC size_t cwk_path_change_extension(const char *path,
const char *new_extension, char *buffer, size_t buffer_size);
/**
* @brief Creates a normalized version of the path.
*
* This function creates a normalized version of the path within the specified
* buffer. This function will not write out more than the specified buffer can
* contain. However, the generated string is always null-terminated - even if
* not the whole path is written out. The function returns the total number of
* characters the complete buffer would have, even if it was not written out
* completely. The path may be the same memory address as the buffer.
*
* The following will be true for the normalized path:
* 1) "../" will be resolved.
* 2) "./" will be removed.
* 3) double separators will be fixed with a single separator.
* 4) separator suffixes will be removed.
*
* @param path The path which will be normalized.
* @param buffer The buffer where the new path is written to.
* @param buffer_size The size of the buffer.
* @return The size which the complete normalized path has if it was not
* truncated.
*/
CWK_PUBLIC size_t cwk_path_normalize(const char *path, char *buffer,
size_t buffer_size);
/**
* @brief Finds common portions in two paths.
*
* This function finds common portions in two paths and returns the number
* characters from the beginning of the base path which are equal to the other
* path.
*
* @param path_base The base path which will be compared with the other path.
* @param path_other The other path which will compared with the base path.
* @return Returns the number of characters which are common in the base path.
*/
CWK_PUBLIC size_t cwk_path_get_intersection(const char *path_base,
const char *path_other);
/**
* @brief Gets the first segment of a path.
*
* This function finds the first segment of a path. The position of the
* segment is set to the first character after the separator, and the length
* counts all characters until the next separator (excluding the separator).
*
* @param path The path which will be inspected.
* @param segment The segment which will be extracted.
* @return Returns true if there is a segment or false if there is none.
*/
CWK_PUBLIC bool cwk_path_get_first_segment(const char *path,
struct cwk_segment *segment);
/**
* @brief Gets the last segment of the path.
*
* This function gets the last segment of a path. This function may return
* false if the path doesn't contain any segments, in which case the submitted
* segment parameter is not modified. The position of the segment is set to
* the first character after the separator, and the length counts all
* characters until the end of the path (excluding the separator).
*
* @param path The path which will be inspected.
* @param segment The segment which will be extracted.
* @return Returns true if there is a segment or false if there is none.
*/
CWK_PUBLIC bool cwk_path_get_last_segment(const char *path,
struct cwk_segment *segment);
/**
* @brief Advances to the next segment.
*
* This function advances the current segment to the next segment. If there
* are no more segments left, the submitted segment structure will stay
* unchanged and false is returned.
*
* @param segment The current segment which will be advanced to the next one.
* @return Returns true if another segment was found or false otherwise.
*/
CWK_PUBLIC bool cwk_path_get_next_segment(struct cwk_segment *segment);
/**
* @brief Moves to the previous segment.
*
* This function moves the current segment to the previous segment. If the
* current segment is the first one, the submitted segment structure will stay
* unchanged and false is returned.
*
* @param segment The current segment which will be moved to the previous one.
* @return Returns true if there is a segment before this one or false
* otherwise.
*/
CWK_PUBLIC bool cwk_path_get_previous_segment(struct cwk_segment *segment);
/**
* @brief Gets the type of the submitted path segment.
*
* This function inspects the contents of the segment and determines the type
* of it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and
* CWK_BACK. A CWK_NORMAL segment is a normal folder or file entry. A
* CWK_CURRENT is a "./" and a CWK_BACK a "../" segment.
*
* @param segment The segment which will be inspected.
* @return Returns the type of the segment.
*/
CWK_PUBLIC enum cwk_segment_type cwk_path_get_segment_type(
const struct cwk_segment *segment);
/**
* @brief Changes the content of a segment.
*
* This function overrides the content of a segment to the submitted value and
* outputs the whole new path to the submitted buffer. The result might
* require less or more space than before if the new value length differs from
* the original length. The output is truncated if the new path is larger than
* the submitted buffer size, but it is always null-terminated. The source of
* the segment and the submitted buffer may be the same.
*
* @param segment The segment which will be modifier.
* @param value The new content of the segment.
* @param buffer The buffer where the modified path will be written to.
* @param buffer_size The size of the output buffer.
* @return Returns the total size which would have been written if the output
* was not truncated.
*/
CWK_PUBLIC size_t cwk_path_change_segment(struct cwk_segment *segment,
const char *value, char *buffer, size_t buffer_size);
/**
* @brief Checks whether the submitted pointer points to a separator.
*
* This function simply checks whether the submitted pointer points to a
* separator, which has to be null-terminated (but not necessarily after the
* separator). The function will return true if it is a separator, or false
* otherwise.
*
* @param symbol A pointer to a string.
* @return Returns true if it is a separator, or false otherwise.
*/
CWK_PUBLIC bool cwk_path_is_separator(const char *str);
/**
* @brief Guesses the path style.
*
* This function guesses the path style based on a submitted path-string. The
* guessing will look at the root and the type of slashes contained in the
* path and return the style which is more likely used in the path.
*
* @param path The path which will be inspected.
* @return Returns the style which is most likely used for the path.
*/
CWK_PUBLIC enum cwk_path_style cwk_path_guess_style(const char *path);
/**
* @brief Configures which path style is used.
*
* This function configures which path style is used. The following styles are
* currently supported.
*
* CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root.
* CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root.
*
* @param style The style which will be used from now on.
*/
CWK_PUBLIC void cwk_path_set_style(enum cwk_path_style style);
/**
* @brief Gets the path style configuration.
*
* This function gets the style configuration which is currently used for the
* paths. This configuration determines how paths are parsed and generated.
*
* @return Returns the current path style configuration.
*/
CWK_PUBLIC enum cwk_path_style cwk_path_get_style(void);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

6069
src/enet.h Normal file

File diff suppressed because it is too large Load Diff

898
src/game.c Normal file
View File

@ -0,0 +1,898 @@
#include"game.h"
#include"k4.h"
#include<assert.h>
#include"resman.h"
#include<cglm/vec3.h>
struct Game Game;
void game_init() {
static int initode = 0;
if(initode == 0) {
dInitODE();
initode = 1;
}
k3Log(k3_INFO, "ODE config: %s", dGetConfiguration());
memset(&Game, 0, sizeof(Game));
Game.spectated = ENT_ID_INVALID;
Game.controlled = ENT_ID_INVALID;
// Assume singleplayer first
Game.isMultiplayer = 0;
Game.isAuthority = 1;
Game.reconciliate = 1;
game_cleanup();
}
struct CollisionPair {
dGeomID g1; // greater
dGeomID g2; // lesser
uint8_t x;
} __attribute__((packed));
static size_t activeCollisionCount, activeCollisionCapacity;
static struct CollisionPair *activeCollisions;
int pair_comparator(const void *a_, const void *b_) {
const struct CollisionPair *a = a_;
const struct CollisionPair *b = b_;
if(a->g1 == b->g1) {
return (uintptr_t) a->g2 - (uintptr_t) b->g2;
} else {
return (uintptr_t) a->g1 - (uintptr_t) b->g1;
}
}
static int activate_pair(dGeomID g1, dGeomID g2) {
struct CollisionPair p = {
.g1 = g1 > g2 ? g1 : g2,
.g2 = g1 > g2 ? g2 : g1,
};
struct CollisionPair *peepee = bsearch(&p, activeCollisions, activeCollisionCount, sizeof(struct CollisionPair), pair_comparator);
if(peepee) {
peepee->x++;
return TRIGGER_EV_CONTINUOUS;
}
if(activeCollisionCount == activeCollisionCapacity) {
activeCollisions = realloc(activeCollisions, sizeof(*activeCollisions) * (activeCollisionCapacity += 32));
}
p.x = 2;
memcpy(&activeCollisions[activeCollisionCount++], &p, sizeof(p));
qsort(activeCollisions, activeCollisionCount, sizeof(struct CollisionPair), pair_comparator);
return TRIGGER_EV_IN;
}
static void tick_pairs() {
for(size_t i = 0; i < activeCollisionCount;) {
if(--activeCollisions[i].x == 0) {
uint16_t e1 = (uintptr_t) dGeomGetData(activeCollisions[i].g1);
uint16_t e2 = (uintptr_t) dGeomGetData(activeCollisions[i].g2);
if(e1 != ENT_ID_INVALID) {
struct CPhysics *p1 = game_getcomponent(e1, physics);
if(p1 && p1->trigger != TRIGGER_INVALID) {
Game.triggers[p1->trigger - 1](p1->trigger, e1, e2, TRIGGER_EV_OUT);
}
}
if(e2 != ENT_ID_INVALID) {
struct CPhysics *p2 = game_getcomponent(e2, physics);
if(p2 && p2->trigger != TRIGGER_INVALID) {
Game.triggers[p2->trigger - 1](p2->trigger, e2, e1, TRIGGER_EV_OUT);
}
}
memmove(&activeCollisions[i], &activeCollisions[i + 1], sizeof(*activeCollisions) * (activeCollisionCount - i - 1));
activeCollisionCount--;
} else {
i++;
}
}
}
static void contact_callback(void *data, dGeomID g1, dGeomID g2) {
uint16_t e1 = (uintptr_t) dGeomGetData(g1);
uint16_t e2 = (uintptr_t) dGeomGetData(g2);
dBodyID b1 = dGeomGetBody(g1);
dBodyID b2 = dGeomGetBody(g2);
int movingPlatform = 0;
if(b1 && dBodyIsKinematic(b1)) movingPlatform = 1;
else if(b2 && dBodyIsKinematic(b2)) movingPlatform = 2;
#define MAX_CONTACTS 8
dContact contact[MAX_CONTACTS];
memset(contact, 0, sizeof(contact));
int numc = dCollide(g1, g2, MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
for(int i = 0; i < numc; i++) {
contact[i].surface.mode = dContactRolling;
contact[i].surface.mu = 2;
contact[i].surface.mu2 = 0;
contact[i].surface.bounce = 0.01;
contact[i].surface.bounce_vel = 0.1;
if(movingPlatform) {
const dReal *platvel = dBodyGetLinearVel(movingPlatform == 1 ? b1 : b2);
//if(i == 0)printf("get %f %f %f\n", platvel[0], platvel[1], platvel[2]);
contact[i].surface.mode |= dContactMotion1 | dContactMotion2 | dContactMotionN | dContactFDir1;
contact[i].surface.mode |= dContactSoftERP;
contact[i].surface.soft_erp = 0.9;
const dReal *normal = contact[i].geom.normal;
dVector3 fdir1, fdir2;
dPlaneSpace(normal, fdir1, fdir2);
glm_vec3_copy(contact[i].fdir1, fdir1);
dReal inv = movingPlatform == 1 ? -1 : 1;
contact[i].surface.motion1 = dDOT(platvel, fdir1);
contact[i].surface.motion2 = inv * dDOT(platvel, fdir2);
contact[i].surface.motionN = -fabs(dDOT(platvel, normal)); //This makes no physical sense but makes objects not bounce on moving platforms going up
}
}
int ghost = 0;
if(numc) {
int triggerType = activate_pair(g1, g2);
if(e1 != ENT_ID_INVALID) {
if(game_getcomponent(e1, physics)->dynamics & CPHYSICS_GHOST) {
ghost = 1;
}
}
if(e2 != ENT_ID_INVALID) {
if(game_getcomponent(e2, physics)->dynamics & CPHYSICS_GHOST) {
ghost = 1;
}
}
float friction = 1;
if(e1 != ENT_ID_INVALID) {
struct CPhysics *c = game_getcomponent(e1, physics);
friction *= c->friction;
vec4 q;
if(b1) {
memcpy(q, dBodyGetQuaternion(b1), sizeof(q));
} else {
dGeomGetQuaternion(c->geom, q);
}
{
float temp = q[0];
q[0] = q[1];
q[1] = q[2];
q[2] = q[3];
q[3] = temp;
}
vec3 n = {0, 1, 0};
glm_quat_rotatev(q, n, n);
if(!ghost) for(int i = 0; i < numc; i++) {
if(glm_vec3_dot(contact[i].geom.normal, n) >= 0.7) {
struct CMovement *m = game_getcomponent(e1, movement);
if(m) {
if(m->holding != ENT_ID_INVALID && m->holding == e2) {
ghost = 1;
}
m->canJump = 1;
break;
}
}
}
if(c->trigger != TRIGGER_INVALID) {
Game.triggers[c->trigger - 1](c->trigger, e1, e2, triggerType);
}
}
if(e2 != ENT_ID_INVALID) {
struct CPhysics *c = game_getcomponent(e2, physics);
friction *= c->friction;
vec4 q;
if(b2) {
memcpy(q, dBodyGetQuaternion(b2), sizeof(q));
} else {
dGeomGetQuaternion(c->geom, q);
}
{
float temp = q[0];
q[0] = q[1];
q[1] = q[2];
q[2] = q[3];
q[3] = temp;
}
vec3 n = {0, -1, 0};
glm_quat_rotatev(q, n, n);
if(!ghost) for(int i = 0; i < numc; i++) {
if(glm_vec3_dot(contact[i].geom.normal, n) >= 0.7) {
struct CMovement *m = game_getcomponent(e2, movement);
if(m) {
if(m->holding != ENT_ID_INVALID && m->holding == e1) {
ghost = 1;
}
m->canJump = 1;
break;
}
}
}
if(c->trigger != TRIGGER_INVALID) {
Game.triggers[c->trigger - 1](c->trigger, e2, e1, triggerType);
}
}
for(int i = 0; i < numc; i++) {
contact[i].surface.mu = friction;
}
}
if(!ghost) for(int i = 0; i < numc; i++) {
dJointID c = dJointCreateContact(Game.phys, Game.contactgroup, contact + i);
dJointAttach(c, dGeomGetBody(g1), dGeomGetBody(g2));
}
}
struct RaycastCtx {
struct LocalRay *rays;
size_t count;
};
static void raycast_handler(void *data, dGeomID g1, dGeomID g2) {
struct RaycastCtx *ctx = data;
if(dGeomGetClass(g1) != dRayClass && dGeomGetClass(g2) != dRayClass) {
return;
}
dContact contact[1];
memset(contact, 0, sizeof(contact));
int numc = dCollide(g1, g2, 1, &contact[0].geom, sizeof(dContact));
uint16_t e1 = (uintptr_t) dGeomGetData(g1);
uint16_t e2 = (uintptr_t) dGeomGetData(g2);
if(numc) {
if(dGeomGetClass(g1) == dRayClass) {
struct LocalRay *r = &ctx->rays[(uintptr_t) dGeomGetData(g1)];
if(r->ignore != e2 && contact->geom.depth < r->depth) {
struct CPhysics *asdf = game_getcomponent(e2, physics);
if(!asdf || !(asdf->dynamics & CPHYSICS_GHOST)) {
glm_vec3_copy(contact[0].geom.pos, r->out);
r->hit = e2;
r->depth = contact->geom.depth;
}
}
return;
} else if(dGeomGetClass(g2) == dRayClass) {
struct LocalRay *r = &ctx->rays[(uintptr_t) dGeomGetData(g2)];
if(r->ignore != e1 && contact->geom.depth < r->depth) {
struct CPhysics *asdf = game_getcomponent(e1, physics);
if(!asdf || !(asdf->dynamics & CPHYSICS_GHOST)) {
glm_vec3_copy(contact[0].geom.pos, r->out);
r->hit = e1;
r->depth = contact->geom.depth;
}
}
return;
}
}
}
void game_raycast(struct LocalRay *rays, size_t count) {
dGeomID rayGeoms[count];
for(int i = 0; i < count; i++) {
const float len = rays[i].maxlen;
glm_vec3_copy(rays[i].pos, rays[i].out);
glm_vec3_muladds(rays[i].dir, len, rays[i].out);
rayGeoms[i] = dCreateRay(Game.space, len);
dGeomSetData(rayGeoms[i], (void*) (uintptr_t) i);
dGeomSetCategoryBits(rayGeoms[i], CATEGORY_RAY);
dGeomSetCollideBits(rayGeoms[i], CATEGORY_STATIC | CATEGORY_ENTITY);
dGeomRaySet(rayGeoms[i], rays[i].pos[0], rays[i].pos[1], rays[i].pos[2], rays[i].dir[0], rays[i].dir[1], rays[i].dir[2]);
// Broken. We manually find closest hit with contact depths.
//dGeomRaySetClosestHit(rayGeoms[i], 1);
rays[i].hit = ENT_ID_INVALID;
rays[i].depth = HUGE_VALF;
}
dSpaceCollide(Game.space, &(struct RaycastCtx) {
.rays = rays,
.count = count,
}, raycast_handler);
for(int i = 0; i < count; i++) {
dGeomDestroy(rayGeoms[i]);
}
}
void game_update() {
for(size_t i = 0; i < Game.entities.playerctrlCount; i++) {
struct CPlayerCtrl *cc = &Game.entities.playerctrl[i];
struct CMovement *cm = game_getcomponent(cc->entity, movement);
struct CPhysics *cp = game_getcomponent(cc->entity, physics);
if(cm && cp) {
vec3 shift = {0, 0, 0};
if(cc->keys & (1 << k4_control_get_id_by_name("forward"))) shift[2] -= 1;
if(cc->keys & (1 << k4_control_get_id_by_name("back"))) shift[2] += 1;
if(cc->keys & (1 << k4_control_get_id_by_name("left"))) shift[0] -= 1;
if(cc->keys & (1 << k4_control_get_id_by_name("right"))) shift[0] += 1;
glm_vec3_rotate(shift, cc->yaw, (vec3) {0, 1, 0});
if(Game.isAuthority || Game.reconciliate) {
glm_vec3_copy(shift, cm->dir);
}
cm->pointing = cc->yaw;
cm->jump = !!(cc->keys & (1 << k4_control_get_id_by_name("jump")));
if(cm->holding == ENT_ID_INVALID && (cc->keys & (1 << k4_control_get_id_by_name("grab")))) {
vec3 d = {0, 0, -1};
glm_vec3_rotate(d, cc->pitch, (vec3) {1, 0, 0});
glm_vec3_rotate(d, cc->yaw, (vec3) {0, 1, 0});
glm_vec3_copy(dGeomGetPosition(cp->geom), cc->holdray.pos);
glm_vec3_copy(d, cc->holdray.dir);
cc->holdray.ignore = cc->entity;
cc->holdray.maxlen = 3;
game_raycast(&cc->holdray, 1);
} else if(!(cc->keys & (1 << k4_control_get_id_by_name("grab")))) {
cm->holding = ENT_ID_INVALID;
}
}
}
for(size_t i = 0; i < Game.entities.movementCount; i++) {
struct CPhysics *cp = game_getcomponent(Game.entities.movement[i].entity, physics);
if(cp && cp->geom) {
dBodyID bid = dGeomGetBody(cp->geom);
if(Game.entities.movement[i].canJump) {
dBodySetLinearVel(bid, 0, 0, 0);
}
dBodySetGravityMode(bid, !Game.entities.movement[i].canJump);
}
}
size_t ci = 0, mi = 0;
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
if(Game.entities.physics[i].geom == NULL) {
/* Create */
dMass mass;
dMassSetZero(&mass);
switch(Game.entities.physics[i].type) {
default:
if(!Game.entities.physics[i].trimesh.cache) {
Game.entities.physics[i].trimesh.cache = resman_ref(RESMAN_PHYSICS, Game.entities.physics[i].trimesh.name);
}
Game.entities.physics[i].geom = dCreateTriMesh(Game.space, Game.entities.physics[i].trimesh.cache->trid, NULL, NULL, NULL);
dMassSetTrimesh(&mass, 1, Game.entities.physics[i].geom);
break;
case CPHYSICS_BOX:
Game.entities.physics[i].geom = dCreateBox(Game.space,
Game.entities.physics[i].box.w,
Game.entities.physics[i].box.h,
Game.entities.physics[i].box.l);
dMassSetBoxTotal(&mass, Game.entities.physics[i].mass,
Game.entities.physics[i].box.w,
Game.entities.physics[i].box.h,
Game.entities.physics[i].box.l);
break;
case CPHYSICS_CAPSULE:
Game.entities.physics[i].geom = dCreateCapsule(Game.space,
Game.entities.physics[i].capsule.radius,
Game.entities.physics[i].capsule.length);
dMassSetCapsuleTotal(&mass, Game.entities.physics[i].mass, 2, Game.entities.physics[i].capsule.radius, Game.entities.physics[i].capsule.length);
break;
case CPHYSICS_SPHERE:
Game.entities.physics[i].geom = dCreateSphere(Game.space,
Game.entities.physics[i].sphere.radius);
dMassSetSphereTotal(&mass, Game.entities.physics[i].mass, Game.entities.physics[i].sphere.radius);
break;
}
dGeomSetCategoryBits(Game.entities.physics[i].geom, CATEGORY_ENTITY);
dGeomSetCollideBits(Game.entities.physics[i].geom, Game.entities.physics[i].collide);
dGeomSetData(Game.entities.physics[i].geom, (void*) Game.entities.physics[i].entity);
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) != CPHYSICS_STATIC) {
dBodyID body = dBodyCreate(Game.phys);
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) == CPHYSICS_KINEMATIC) {
dBodySetKinematic(body);
} else {
dBodySetMass(body, &mass);
}
dGeomSetBody(Game.entities.physics[i].geom, body);
if(Game.entities.physics[i].type == CPHYSICS_CAPSULE) {
dBodySetMaxAngularSpeed(body, 0);
// Rotate to Y-up
dQuaternion q;
dQFromAxisAndAngle(q, 1, 0, 0, M_PI * 0.5);
dGeomSetOffsetQuaternion(Game.entities.physics[i].geom, q);
}
}
dGeomSetPosition(Game.entities.physics[i].geom,
Game.entities.physics[i].start[0],
Game.entities.physics[i].start[1],
Game.entities.physics[i].start[2]);
dGeomSetQuaternion(Game.entities.physics[i].geom, (dQuaternion) {
Game.entities.physics[i].rot[3],
Game.entities.physics[i].rot[0],
Game.entities.physics[i].rot[1],
Game.entities.physics[i].rot[2]
});
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
dBodySetLinearVel(dGeomGetBody(Game.entities.physics[i].geom),
Game.entities.physics[i].vel[0],
Game.entities.physics[i].vel[1],
Game.entities.physics[i].vel[2]);
}
}
dGeomID gid = Game.entities.physics[i].geom;
dBodyID bid = dGeomGetBody(gid);
// Find corresponding movement component
while(mi < Game.entities.movementCount && Game.entities.movement[mi].entity < Game.entities.physics[i].entity) {
mi++;
}
if(mi < Game.entities.movementCount && Game.entities.movement[mi].entity == Game.entities.physics[i].entity) {
if(Game.entities.movement[mi].holding != ENT_ID_INVALID) {
struct CPhysics *hp = game_getcomponent(Game.entities.movement[mi].holding, physics);
const float *p = dGeomGetPosition(gid);
dGeomSetPosition(hp->geom, p[0] - sinf(Game.entities.movement[mi].pointing) * 1.4, p[1], p[2] - cosf(Game.entities.movement[mi].pointing) * 1.4);
dBodyID bid = dGeomGetBody(hp->geom);
if(bid) {
dBodySetLinearVel(bid, 0, 0, 0);
}
}
vec3 wishvel;
glm_vec3_scale(Game.entities.movement[mi].dir, Game.entities.physics[i].speed, wishvel);
#ifdef RETARDED_MOVEMENT
dBodySetLinearVel(bid, wishvel[0], dBodyGetLinearVel(bid)[1], wishvel[2]);
#else
int inAir = !Game.entities.movement[mi].canJump;
float factor = (inAir ? 8 : 20) * (GAME_TPS / 25);
if(inAir) {
if(glm_vec3_norm(wishvel) > 3) {
glm_vec3_scale_as(wishvel, 3, wishvel);
}
}
float currentspeed = glm_vec3_dot(wishvel, dBodyGetLinearVel(bid));
float addspeed = glm_vec3_norm(wishvel) - currentspeed;
if(addspeed > 0) {
dMass massiveness;
dBodyGetMass(bid, &massiveness);
vec3 accel;
glm_vec3_scale_as(wishvel, addspeed, accel);
vec3 force;
glm_vec3_scale(accel, massiveness.mass * factor, force);
dBodyAddForce(bid, force[0], force[1], force[2]);
}
#endif
dQuaternion q;
dQFromAxisAndAngle(q, 0, 1, 0, Game.entities.movement[mi].pointing + M_PI);
dBodySetQuaternion(bid, q);
if(Game.entities.movement[mi].jump && (0||Game.entities.movement[mi].canJump)) {
Game.entities.movement[mi].canJump = 0;
dVector3 force;
dWorldImpulseToForce(Game.phys, 1.f / GAME_TPS, 0, 5, 0, force);
if(bid) {
dBodyAddForce(bid, force[0], force[1], force[2]);
}
}
}
}
for(size_t i = 0; i < Game.entities.movementCount; i++) {
Game.entities.movement[i].canJump = 0;
}
dSpaceCollide(Game.space, 0, &contact_callback);
tick_pairs();
dWorldSetQuickStepNumIterations(Game.phys, 60);
dWorldSetQuickStepW(Game.phys, 1);
dWorldQuickStep(Game.phys, 1.0f / GAME_TPS);
dJointGroupEmpty(Game.contactgroup);
for(size_t i = 0; i < Game.entities.playerctrlCount; i++) {
struct CPlayerCtrl *cc = &Game.entities.playerctrl[i];
struct CMovement *cm = game_getcomponent(cc->entity, movement);
if(cc->holdray.hit != ENT_ID_INVALID && (cc->keys & (1 << k4_control_get_id_by_name("grab"))) && cm->holding == ENT_ID_INVALID) {
dGeomID hd = game_getcomponent(cc->holdray.hit, physics)->geom;
dBodyID hb = dGeomGetBody(hd);
if(hb && !dBodyIsKinematic(hb)) {
cm->holding = cc->holdray.hit;
}
}
}
for(size_t ci = 0; ci < Game.entities.renderCount; ci++) {
struct CPhysics *cp = game_getcomponent(Game.entities.render[ci].entity, physics);
if(!Game.entities.render[ci].cache && strlen(Game.entities.render[ci].mdl)) {
struct k3Mdl *srccache = resman_ref(RESMAN_MODEL, Game.entities.render[ci].mdl);
Game.entities.render[ci].cache = k3MdlCopySubs(srccache);
}
size_t boneCount = Game.entities.render[ci].cache ? k3MdlGetBoneCount(Game.entities.render[ci].cache) : 0;
if(boneCount) {
struct CBoned *cb = game_getcomponent(Game.entities.render[ci].entity, boned);
if(!cb) {
cb = game_ensurecomponent(Game.entities.render[ci].entity, boned);
}
if(cb->size < boneCount) {
size_t start;
if(cb->bones) {
start = cb->size;
struct k3AnimationBone *nu = _mm_malloc(sizeof(*cb->bones) * boneCount, 16);
memcpy(nu, cb->bones, sizeof(*cb->bones) * cb->size);
struct k3AnimationBone *ol = cb->bones;
cb->bones = nu;
_mm_free(ol);
} else {
start = 0;
cb->bones = _mm_malloc(sizeof(*cb->bones) * boneCount, 16);
}
cb->size = boneCount;
for(size_t asdf = start; asdf < boneCount; asdf++) {
cb->bones[asdf].rotation[3] = cb->bones[asdf].translation[3] = 1;
}
}
if(cb->anim.id) {
if(!cb->anim.cache.base) {
memset(&cb->anim.cache, 0, sizeof(cb->anim.cache));
cb->anim.cache.base = k3MdlGetAnim(Game.entities.render[ci].cache, cb->anim.id);
cb->anim.cache.loop = true;
k3AnimatorSet(&cb->anim.cache, 0);
} else {
float spd = 1.f;
if(cb->anim.standard && cb->anim.id != 1 && cp && cp->geom) {
dBodyID bid = dGeomGetBody(cp->geom);
if(bid) {
spd = glm_vec3_norm(dBodyGetLinearVel(bid)) * 0.3;
}
}
k3AnimatorStep(&cb->anim.cache, spd / GAME_TPS);
}
for(size_t m = 0; m < boneCount; m++) {
vec3 scaleignore;
mat4 rot;
glm_decompose(cb->anim.cache.inter[m], cb->bones[m].translation, rot, scaleignore);
glm_mat4_quat(cb->anim.cache.inter[m], cb->bones[m].rotation);
}
}
}
dGeomID gid = cp->geom;
if(!gid) continue;
dBodyID bid = dGeomGetBody(gid);
if(bid) {
const float *q = dBodyGetQuaternion(bid);
Game.entities.render[ci].rot[0] = q[1];
Game.entities.render[ci].rot[1] = q[2];
Game.entities.render[ci].rot[2] = q[3];
Game.entities.render[ci].rot[3] = q[0];
} else {
dQuaternion q;
dGeomGetQuaternion(gid, q);
Game.entities.render[ci].rot[0] = q[1];
Game.entities.render[ci].rot[1] = q[2];
Game.entities.render[ci].rot[2] = q[3];
Game.entities.render[ci].rot[3] = q[0];
}
const float *src = bid ? dBodyGetPosition(bid) : dGeomGetPosition(gid);
Game.entities.render[ci].pos[0] = src[0];
Game.entities.render[ci].pos[1] = src[1];
Game.entities.render[ci].pos[2] = src[2];
Game.entities.render[ci].pos[3] = 1;
}
for(size_t cm = 0; cm < Game.entities.movementCount; cm++) {
struct CBoned *cb = game_getcomponent(Game.entities.movement[cm].entity, boned);
if(cb && cb->anim.standard) {
struct CPhysics *cp = game_getcomponent(Game.entities.movement[cm].entity, physics);
if(cp && cp->geom) {
dBodyID bid = dGeomGetBody(cp->geom);
if(bid) {
uint16_t newID = glm_vec3_norm(dBodyGetLinearVel(bid)) > 1 ? 2 : 1;
if(cb->anim.id != newID) {
cb->anim.id = newID;
cb->anim.cache.base = NULL;
}
}
}
}
}
for(size_t i = 0; i < Game.conveyorCount; i++) {
struct Conveyor *conveyor = Game.conveyors[i];
struct CPhysics *cp = conveyor->entity == ENT_ID_INVALID ? NULL : game_getcomponent(conveyor->entity, physics);
dGeomID gid = cp ? cp->geom : NULL;
conveyor->effectivelyActiveLast = conveyor->effectivelyActive;
if(conveyor->type == CONVEYOR_TYPE_LOOP) {
if(!conveyor->active) {
conveyor->effectivelyActive = 0;
continue;
}
conveyor->effectivelyActive = 1;
// Teleport to first point
if(conveyor->position >= conveyor->pointCount - 1) {
conveyor->position -= conveyor->pointCount - 1;
}
size_t currentPoint = floorf(conveyor->position);
size_t nextPoint = currentPoint + 1;
float alpha = fmodf(conveyor->position, 1.f);
if(gid) {
vec3 lerped;
glm_vec3_lerp(conveyor->points[currentPoint], conveyor->points[nextPoint], alpha, lerped);
dGeomSetPosition(gid, lerped[0], lerped[1], lerped[2]);
dBodyID bid = dGeomGetBody(gid);
if(bid) {
vec3 dif;
glm_vec3_sub(conveyor->points[nextPoint], conveyor->points[currentPoint], dif);
glm_vec3_scale_as(dif, conveyor->speed, dif);
dBodySetLinearVel(bid, dif[0], dif[1], dif[2]); // doesn't affect movement, but necessary for proper collision response
}
}
conveyor->position += conveyor->speed / glm_vec3_distance(conveyor->points[currentPoint], conveyor->points[nextPoint]) / GAME_TPS;
} else if(conveyor->type == CONVEYOR_TYPE_BINARY) {
int stopped = 0;
if(conveyor->active > 0 && conveyor->position >= conveyor->pointCount - 1) {
conveyor->position = conveyor->pointCount - 1;
stopped = 1;
} else if(conveyor->active <= 0 && conveyor->position <= 0) {
conveyor->position = 0;
stopped = 1;
}
conveyor->effectivelyActive = !stopped;
size_t currentPoint, nextPoint;
if(!stopped) {
currentPoint = (size_t) floorf(conveyor->position) % conveyor->pointCount;
nextPoint = (currentPoint + 1) % conveyor->pointCount;
float alpha = fmodf(conveyor->position, 1.f);
if(gid) {
vec3 lerped;
glm_vec3_lerp(conveyor->points[currentPoint], conveyor->points[nextPoint], alpha, lerped);
dGeomSetPosition(gid, lerped[0], lerped[1], lerped[2]);
}
float offset = conveyor->speed / glm_vec3_distance(conveyor->points[currentPoint], conveyor->points[nextPoint]) / GAME_TPS;
if(conveyor->active > 0) {
conveyor->position += offset;
} else {
conveyor->position -= offset;
}
}
if(gid) {
dBodyID bid = dGeomGetBody(gid);
if(bid) {
if(stopped) {
dBodySetLinearVel(bid, 0, 0, 0);
} else {
vec3 dif;
glm_vec3_sub(conveyor->points[nextPoint], conveyor->points[currentPoint], dif);
glm_vec3_scale_as(dif, conveyor->speed * (conveyor->active <= 0 ? -1 : 1), dif);
//printf("set %f %f %f\n", dif[0], dif[1], dif[2]);
dBodySetLinearVel(bid, dif[0], dif[1], dif[2]); // doesn't affect movement, but necessary for proper collision response
}
}
}
}
if(conveyor->trigger != TRIGGER_INVALID) {
if(conveyor->effectivelyActive && !conveyor->effectivelyActiveLast) {
Game.triggers[conveyor->trigger - 1](conveyor->trigger, ENT_ID_INVALID, ENT_ID_INVALID, 0);
} else if(conveyor->effectivelyActive && conveyor->effectivelyActiveLast) {
Game.triggers[conveyor->trigger - 1](conveyor->trigger, ENT_ID_INVALID, ENT_ID_INVALID, 1);
} else if(!conveyor->effectivelyActive && conveyor->effectivelyActiveLast) {
Game.triggers[conveyor->trigger - 1](conveyor->trigger, ENT_ID_INVALID, ENT_ID_INVALID, 2);
}
}
}
Game.frame++;
}
void game_setphys(struct TrimeshData *phys) {
if(Game.statikRes) {
resman_unref_ptr(RESMAN_PHYSICS, Game.statikRes);
}
if(Game.statik) {
dGeomDestroy(Game.statik);
}
Game.statikRes = phys;
Game.statik = dCreateTriMesh(Game.space, phys->trid, NULL, NULL, NULL);
dGeomSetCategoryBits(Game.statik, CATEGORY_STATIC);
dGeomSetCollideBits(Game.statik, 0);
dGeomSetData(Game.statik, (void*) ENT_ID_INVALID);
}
void game_synccphysics() {
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
if(Game.entities.physics[i].geom) {
glm_vec3_copy(dGeomGetPosition(Game.entities.physics[i].geom), Game.entities.physics[i].start);
}
}
}
void game_cleanup() {
// XXX: Go over ALL component types!
memset(Game.entities.freeIDs, 0, sizeof(Game.entities.freeIDs));
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
if(Game.entities.physics[i].type == CPHYSICS_TRIMESH) {
resman_unref_ptr(RESMAN_PHYSICS, Game.entities.physics[i].trimesh.cache);
}
dGeomID gid = Game.entities.physics[i].geom;
if(gid) {
dBodyID bid = dGeomGetBody(gid);
if(bid) {
dBodyDestroy(bid);
}
dGeomDestroy(gid);
}
}
Game.entities.physicsCount = 0;
for(size_t i = 0; i < Game.entities.renderCount; i++) {
if(Game.entities.render[i].cache) {
resman_unref(RESMAN_MODEL, Game.entities.render[i].mdl);
}
}
Game.entities.renderCount = 0;
for(size_t i = 0; i < Game.entities.bonedCount; i++) {
free(Game.entities.boned[i].bones);
}
Game.entities.bonedCount = 0;
Game.entities.movementCount = 0;
Game.entities.playerctrlCount = 0;
Game.entityCount = 0;
for(size_t i = 0; i < Game.conveyorCount; i++) {
free(Game.conveyors[i]->points);
free(Game.conveyors[i]);
}
free(Game.conveyors);
Game.conveyors = NULL;
Game.conveyorCount = 0;
free(Game.triggers);
Game.triggers = NULL;
Game.triggerCount = 0;
Game.spectated = ENT_ID_INVALID;
Game.controlled = ENT_ID_INVALID;
free(activeCollisions);
activeCollisions = NULL;
activeCollisionCount = 0;
activeCollisionCapacity = 0;
if(Game.phys) {
dWorldDestroy(Game.phys);
}
Game.phys = dWorldCreate();
dWorldSetGravity(Game.phys, 0, -15, 0);
dWorldSetCFM(Game.phys, 0.0001);
dWorldSetERP(Game.phys, 0.5);
Game.space = dHashSpaceCreate(NULL);
Game.contactgroup = dJointGroupCreate(0);
}

254
src/game.h Normal file
View File

@ -0,0 +1,254 @@
#pragma once
#include<stddef.h>
#include<stdint.h>
#include<cglm/mat4.h>
#include<string.h>
#include<ode/ode.h>
#include<cglm/quat.h>
#include"k3.h"
#define GAME_TPS 50
#define CATEGORY_STATIC 1
#define CATEGORY_RAY 2
#define CATEGORY_ENTITY 4
#define TRIGGER_INVALID 0
#define MODELNAME_LENGTH 12
struct CRender {
uint16_t entity;
char mdl[MODELNAME_LENGTH];
struct k3Mdl *cache;
vec4 pos;
versor rot;
vec4 posLast;
versor rotLast;
};
struct TrimeshData {
void *vdata;
void *idata;
dTriMeshDataID trid;
};
#define CPHYSICS_TRIMESH 0
#define CPHYSICS_CAPSULE 1
#define CPHYSICS_BOX 2
#define CPHYSICS_SPHERE 3
#define CPHYSICS_STATIC 0
#define CPHYSICS_KINEMATIC 1
#define CPHYSICS_DYNAMIC 2
#define CPHYSICS_GHOST 128
struct CPhysics {
uint16_t entity;
dGeomID geom;
uint16_t trigger;
uint8_t type;
uint8_t dynamics;
union {
struct {
char name[MODELNAME_LENGTH];
struct TrimeshData *cache;
} trimesh;
struct {
float length;
float radius;
} capsule;
struct {
float w;
float h;
float l;
} box;
struct {
float radius;
} sphere;
};
vec3 start;
vec3 vel;
vec4 rot;
float speed;
float mass;
float friction;
uint32_t collide;
};
struct CMovement {
uint16_t entity;
vec3 dir;
float pointing;
char jump, canJump;
uint16_t holding;
};
struct LocalRay {
vec3 pos;
vec3 dir;
uint16_t ignore;
vec3 out;
uint16_t hit;
float depth;
float maxlen;
};
struct CPlayerCtrl {
uint16_t entity;
float yaw;
float pitch;
uint32_t keys;
struct LocalRay holdray;
};
struct CBoned {
uint16_t entity;
size_t size;
struct k3AnimationBone *bones;
struct {
uint16_t id;
struct k3Animator cache;
bool standard;
} anim;
};
#define CONVEYOR_TYPE_LOOP 0
#define CONVEYOR_TYPE_BINARY 1
struct Conveyor {
uint16_t entity;
int16_t active;
uint8_t type;
float speed;
float position;
size_t pointCount;
vec3 *points;
uint16_t trigger;
uint8_t effectivelyActive;
uint8_t effectivelyActiveLast;
};
#define TRIGGER_EV_IN 0
#define TRIGGER_EV_CONTINUOUS 1
#define TRIGGER_EV_OUT 2
typedef int(*TriggerFunc)(size_t id, uint16_t ethis, uint16_t ethat, uint8_t event);
#define MAX_ENTS 128
extern struct Game {
size_t entityCount;
struct {
struct CRender render[MAX_ENTS];
size_t renderCount;
struct CPhysics physics[MAX_ENTS];
size_t physicsCount;
struct CMovement movement[MAX_ENTS];
size_t movementCount;
struct CPlayerCtrl playerctrl[MAX_ENTS];
size_t playerctrlCount;
struct CBoned boned[MAX_ENTS];
size_t bonedCount;
uint64_t freeIDs[(MAX_ENTS + 63) / 64];
} entities;
int isMultiplayer;
int isAuthority;
int reconciliate;
size_t frame;
#define MAX_CONVEYORS 4096
size_t conveyorCount;
struct Conveyor **conveyors;
#define MAX_TRIGGERS 4096
size_t triggerCount;
TriggerFunc *triggers;
dWorldID phys;
dSpaceID space;
dJointGroupID contactgroup;
struct TrimeshData *statikRes;
dGeomID statik;
uint16_t spectated;
uint16_t controlled;
} Game;
void game_init();
void game_raycast(struct LocalRay *rays, size_t count);
void game_update();
void game_setphys(struct TrimeshData*);
#define ENT_ID_INVALID 65535
static inline size_t game_nextid() {
for(size_t i = 0; i < (MAX_ENTS + 63) / 64; i++) {
for(uint8_t b = 0; b < 64; b++) {
if((Game.entities.freeIDs[i] & (1ULL << b)) == 0) {
return i * 8 + b;
}
}
}
return ENT_ID_INVALID;
}
static inline void game_claimentid(size_t i) {
Game.entities.freeIDs[i / 64] |= 1 << (i % 64);
}
static inline int game_componentcomparator(const void *a, const void *b) {
return *(const uint16_t*) a - *(const uint16_t*) b;
}
#define game_addcomponent(a, c) game_addcomponent_((uint8_t*) (Game.entities.a), (uint8_t*) (c), sizeof(*(Game.entities.a)), &(Game.entities.a##Count))
static inline void game_addcomponent_(uint8_t *array, uint8_t *component, size_t componentSize, size_t *componentCount) {
memcpy(&array[*componentCount * componentSize], component, componentSize);
(*componentCount)++;
qsort(array, *componentCount, componentSize, game_componentcomparator);
}
#define game_getcomponent(eid, arr) ((typeof(&Game.entities.arr[0]))game_getcomponent_((eid), (Game.entities.arr), sizeof(*(Game.entities.arr)), (Game.entities.arr##Count)))
static inline void *game_getcomponent_(uint16_t eid, void *array, size_t componentSize, size_t componentCount) {
return bsearch(&eid, array, componentCount, componentSize, game_componentcomparator);
}
#define game_ensurecomponent(eid, arr) ((typeof(&Game.entities.arr[0]))game_ensurecomponent_((eid), (Game.entities.arr), sizeof(*(Game.entities.arr)), &(Game.entities.arr##Count)))
static inline void *game_ensurecomponent_(uint16_t eid, void *array, size_t componentSize, size_t *componentCount) {
void *ptr = game_getcomponent_(eid, array, componentSize, *componentCount);
if(!ptr) {
void *data = alloca(componentSize);
memset(data, 0, componentSize);
*(uint16_t*) data = eid;
game_addcomponent_(array, data, componentSize, componentCount);
ptr = game_getcomponent_(eid, array, componentSize, *componentCount);
}
return ptr;
}
// An artifact of using a third-party physics engine
void game_synccphysics();
void game_cleanup();

10961
src/gl.h Normal file

File diff suppressed because it is too large Load Diff

395
src/k3mix.c Normal file
View File

@ -0,0 +1,395 @@
#include"k3mix.h"
#include<vorbis/vorbisfile.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<pthread.h>
#include<assert.h>
static uint32_t FinalSampleRate;
static uint8_t FinalChannels;
void k3MixInit(uint32_t sampleRate, uint8_t channels) {
FinalSampleRate = sampleRate;
FinalChannels = channels;
}
struct k3MixSource {
char *filepath;
OggVorbis_File vf;
int bitstream;
};
struct k3MixSource *k3MixSourceFile(const char *path) {
struct k3MixSource *ret = calloc(1, sizeof(*ret));
ret->filepath = strdup(path);
ov_fopen(path, &ret->vf);
ret->bitstream = 0;
return ret;
}
void k3MixSourceClose(struct k3MixSource *src) {
ov_clear(&src->vf);
free(src->filepath);
free(src);
}
__attribute__((optimize("Ofast"))) static intmax_t ogg_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct k3MixSource *od = this->data;
float **ni;
long totalRead = 0;
do {
long lastRead = ov_read_float(&od->vf, &ni, sampleCount, &od->bitstream);
if(this->loop && lastRead == 0) {
ov_pcm_seek(&od->vf, 0);
continue;
} else if(lastRead <= 0) {
this->end = true;
return totalRead;
}
for(long s = 0; s < lastRead; s++) {
for(long c = 0; c < this->channels; c++) {
*(data++) += ni[c][s] * this->volume;
}
}
totalRead += lastRead;
sampleCount -= lastRead;
} while(sampleCount);
return totalRead;
}
static void ogg_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
k3MixSourceClose(this->data);
}
}
static void ogg_clone(struct k3MixWave *this, struct k3MixWave *new) {
new->refs = 1;
new->sampleRate = this->sampleRate;
new->channels = this->channels;
new->data = k3MixSourceFile(((struct k3MixSource*) this->data)->filepath);
new->read = ogg_read;
new->clone = ogg_clone;
new->close = ogg_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
struct k3MixWave *k3MixSimple(struct k3MixSource *src) {
vorbis_info *vi = ov_info(&src->vf, -1);
struct k3MixWave *ret = calloc(1, sizeof(*ret));
ret->refs = 1;
ret->sampleRate = vi->rate;
ret->channels = vi->channels;
ret->data = k3MixSourceFile(src->filepath);
ret->read = ogg_read;
ret->clone = ogg_clone;
ret->close = ogg_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
void k3MixSimpleSeek(struct k3MixWave *this, double t) {
struct k3MixSource *od = this->data;
ov_time_seek(&od->vf, t);
}
struct qdata {
size_t ref;
size_t count;
struct k3MixWave *children;
pthread_mutex_t damootecks;
};
static intmax_t queue_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
size_t totalRead = 0;
while(sampleCount && qdat->count > 0) {
intmax_t i = qdat->children[0].read(&qdat->children[0], sampleCount, data);
if(i == 0) {
qdat->children[0].close(&qdat->children[0]);
memmove(&qdat->children[0], &qdat->children[1], sizeof(*qdat->children) * (qdat->count - 1));
qdat->count--;
} else {
data += this->channels * i;
totalRead += i;
sampleCount -= i;
}
}
pthread_mutex_unlock(&qdat->damootecks);
return totalRead;
}
static void queue_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
struct qdata *qdat = this->data;
if(--qdat->ref == 0) {
for(size_t i = 0; i < qdat->count; i++) {
qdat->children[i].close(&qdat->children[i]);
}
free(qdat);
}
}
}
static void queue_clone(struct k3MixWave *this, struct k3MixWave *new) {
struct qdata *data = this->data;
data->ref++;
new->refs = 1;
new->sampleRate = this->sampleRate;
new->channels = this->channels;
new->data = data;
new->read = queue_read;
new->clone = queue_clone;
new->close = queue_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
struct k3MixWave *k3MixQueue() {
struct k3MixWave *ret = malloc(sizeof(*ret));
ret->refs = 1;
ret->sampleRate = FinalSampleRate;
ret->channels = FinalChannels;
struct qdata *data = ret->data = calloc(1, sizeof(struct qdata));
data->ref = 1;
pthread_mutex_init(&data->damootecks, NULL);
ret->read = queue_read;
ret->clone = queue_clone;
ret->close = queue_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
void k3MixQueueAdd(struct k3MixWave *this, struct k3MixWave *child) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
qdat->children = realloc(qdat->children, sizeof(*qdat->children) * (qdat->count + 1));
child->clone(child, &qdat->children[qdat->count]);
qdat->count++;
pthread_mutex_unlock(&qdat->damootecks);
}
void k3MixQueueClear(struct k3MixWave *this) {
struct qdata *qdat = this->data;
pthread_mutex_lock(&qdat->damootecks);
for(size_t i = 0; i < qdat->count; i++) {
qdat->children[i].close(&qdat->children[i]);
}
qdat->count = 0;
free(qdat->children);
qdat->children = NULL;
pthread_mutex_unlock(&qdat->damootecks);
}
struct powermeasurementdata {
struct k3MixWave *child;
size_t historySize;
float *history;
};
static intmax_t power_measurement_read(struct k3MixWave *this, size_t sampleCount, float *data) {
struct powermeasurementdata *dat = this->data;
sampleCount = dat->child->read(dat->child, sampleCount, data);
if(sampleCount * FinalChannels < dat->historySize) {
memmove(dat->history, dat->history + sampleCount * FinalChannels, (dat->historySize - sampleCount * FinalChannels) * sizeof(*dat->history));
memcpy(dat->history + (dat->historySize - sampleCount * FinalChannels), data, sampleCount * FinalChannels * sizeof(*dat->history));
} else {
memcpy(dat->history, data + sampleCount * FinalChannels - dat->historySize, dat->historySize * sizeof(*dat->history));
}
this->end = dat->child->end;
return sampleCount;
}
static void power_measurement_close(struct k3MixWave *this) {
if(this->refs-- == 1) {
struct powermeasurementdata *dat = this->data;
dat->child->close(dat->child);
free(dat);
}
}
static void power_measurement_clone(struct k3MixWave *this, struct k3MixWave *new) {
struct powermeasurementdata *src = this->data;
struct powermeasurementdata *dat = malloc(sizeof(*dat));
dat->child = calloc(1, sizeof(*dat->child));
src->child->clone(src->child, dat->child);
dat->history = calloc(dat->historySize = src->historySize, sizeof(*dat->history));
memcpy(dat->history, src->history, sizeof(*dat->history) * dat->historySize);
new->refs = 1;
new->sampleRate = FinalSampleRate;
new->channels = FinalChannels;
new->data = dat;
new->read = power_measurement_read;
new->clone = power_measurement_clone;
new->close = power_measurement_close;
new->loop = this->loop;
new->dam = this->dam;
new->volume = this->volume;
}
float k3MixPowerMeasurementGetRMS(struct k3MixWave *this) {
struct powermeasurementdata *dat = this->data;
float sum = 0;
for(int i = 0; i < dat->historySize; i++) {
sum += pow(dat->history[i], 2);
}
return sqrtf(sum / dat->historySize);
}
struct k3MixWave *k3MixPowerMeasurement(struct k3MixWave *child) {
child->refs++;
struct powermeasurementdata *dat = malloc(sizeof(*dat));
dat->child = child;
dat->historySize = 8820;
dat->history = calloc(dat->historySize, sizeof(*dat->history));
struct k3MixWave *ret = malloc(sizeof(*ret));
ret->refs = 1;
ret->sampleRate = FinalSampleRate;
ret->channels = FinalChannels;
ret->data = dat;
ret->read = power_measurement_read;
ret->clone = power_measurement_clone;
ret->close = power_measurement_close;
ret->loop = 0;
ret->dam = 0;
ret->volume = 1;
return ret;
}
static size_t playingCount, playingCapacity;
static struct k3MixWave **playings;
void k3MixStop(struct k3MixWave *wav) {
for(size_t i = 0; i < playingCount; i++) {
if(playings[i] == wav) {
playings[i]->close(playings[i]);
memmove(&playings[i], &playings[i + 1], sizeof(*playings) * (playingCount - i - 1));
playingCount--;
}
}
}
struct k3MixWave *k3MixPlay(struct k3MixWave *wav) {
for(size_t i = 0; i < playingCount; i++) {
if(playings[i] == wav) {
return NULL;
}
}
k3MixPlayDirect(wav);
return wav;
}
void k3MixPlayDirect(struct k3MixWave *w) {
w->refs++;
if(playingCount == playingCapacity) {
playings = realloc(playings, sizeof(*playings) * (playingCapacity += 8));
}
playings[playingCount++] = w;
}
#include<xmmintrin.h>
__attribute__((optimize("Ofast"))) static void k3MixDoYourThang(size_t sampleCount, float *FinalData) {
memset(FinalData, 0, sampleCount * FinalChannels * sizeof(*FinalData));
for(size_t i = 0; i < playingCount;) {
intmax_t read = playings[i]->read(playings[i], sampleCount, FinalData);
if(playings[i]->end) {
k3MixStop(playings[i]);
} else i++;
}
for(size_t i = 0; i < sampleCount * FinalChannels; i += 4) {
// Compute tanh approximation x*(27+x*x)/(27+9*x*x)
float *ptr = FinalData + i;
__m128 x = _mm_loadu_ps(ptr);
__m128 xx = _mm_mul_ps(x, x);
__m128 numer = _mm_mul_ps(x, _mm_add_ps(_mm_set1_ps(27), xx));
__m128 denom = _mm_add_ps(_mm_set1_ps(27), _mm_mul_ps(_mm_set1_ps(9), xx));
_mm_storeu_ps(ptr, _mm_div_ps(numer, denom));
}
// The accuracy isn't worth the function call per sample
/*for(size_t i = 0; i < sampleCount * FinalChannels; i++) {
*FinalData = tanhf(*FinalData);
FinalData++;
}*/
}
#include<portaudio.h>
#include<xmmintrin.h>
static PaStream *paStream;
static int pa_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *ud) {
float *out = output;
static int done = 0;
if(done == 0) {
done = 1;
_mm_setcsr(_mm_getcsr() | 0x8040);
}
k3MixDoYourThang(frameCount, out);
return paContinue;
}
void k3MixPortAudio() {
Pa_Initialize();
Pa_OpenDefaultStream(&paStream, 0, FinalChannels, paFloat32, FinalSampleRate, 512, pa_callback, NULL);
Pa_StartStream(paStream);
}

50
src/k3mix.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include<stdint.h>
#include<stddef.h>
#include<stdbool.h>
struct k3MixWave;
typedef intmax_t(*k3MixWaveReadFunc)(struct k3MixWave *dis, size_t sampleCount, float *buf);
typedef void(*k3MixWaveCloseFunc)(struct k3MixWave *dis);
typedef void(*k3MixWaveCloneFunc)(struct k3MixWave *dis, struct k3MixWave *destination);
struct k3MixSource;
struct k3MixSource *k3MixSourceFile(const char *path);
void k3MixSourceClose(struct k3MixSource*);
struct k3MixWave {
size_t refs;
uint32_t sampleRate;
uint8_t channels;
void *data;
k3MixWaveReadFunc read;
k3MixWaveCloseFunc close;
k3MixWaveCloneFunc clone;
// All the cool things
bool loop;
bool end;
uint16_t dam;
float volume;
};
void k3MixInit(uint32_t sampleRate, uint8_t channels);
struct k3MixWave *k3MixSimple(struct k3MixSource*);
void k3MixSimpleSeek(struct k3MixWave*, double t);
struct k3MixWave *k3MixQueue();
void k3MixQueueAdd(struct k3MixWave *this, struct k3MixWave *child);
void k3MixQueueClear(struct k3MixWave *this);
struct k3MixWave *k3MixPowerMeasurement(struct k3MixWave *child);
float k3MixPowerMeasurementGetRMS(struct k3MixWave*);
void k3MixStop(struct k3MixWave*);
struct k3MixWave *k3MixPlay(struct k3MixWave*);
void k3MixPlayDirect(struct k3MixWave*);
void k3MixPortAudio();

41
src/k4.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#define K4_MAX_CONTROLS 32
struct k3MScreen;
#include"stoon.h"
struct NetWrap {
struct Stoon stoon;
int stage;
float timeout;
int isHost;
// Used for punching stage
int gotten;
char otherpeer[STOON_CONN_INFO_SIZE];
};
extern struct NetWrap NetWrap;
extern struct k3MScreen *UiActive;
__attribute__((pure)) const char *k4_get_arg(const char *arg);
int k4_control_set(const char *name, int btn);
int k4_control_get_id(int btn);
int k4_control_get_by_name(const char *name);
int k4_control_get_id_by_name(const char *name);
const char *k4_control_get_name_by_ctrl(int btn);
void k4_set_reduction(float);
void k4_set_texture_reduction(int);
void k4_set_clipboard_text(const char *str);
const char *k4_get_clipboard_text();

973
src/komihash.h Normal file
View File

@ -0,0 +1,973 @@
/**
* @file komihash.h
*
* @version 5.10
*
* @brief The inclusion file for the "komihash" 64-bit hash function,
* "komirand" 64-bit PRNG, and streamed "komihash".
*
* This function is named the way it is named is to honor the Komi Republic
* (located in Russia), native to the author.
*
* Description is available at https://github.com/avaneev/komihash
*
* E-mail: aleksey.vaneev@gmail.com or info@voxengo.com
*
* LICENSE:
*
* Copyright (c) 2021-2024 Aleksey Vaneev
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef KOMIHASH_INCLUDED
#define KOMIHASH_INCLUDED
#include <stdint.h>
#include <string.h>
#define KOMIHASH_VER_STR "5.10" ///< KOMIHASH source code version string.
/**
* @def KOMIHASH_LITTLE_ENDIAN
* @brief Endianness definition macro, can be used as a logical constant.
*
* Can be defined externally (e.g., =1, if endianness-correction and
* hash-value portability are unnecessary in any case, to reduce overhead).
*/
#if !defined( KOMIHASH_LITTLE_ENDIAN )
#if defined( __LITTLE_ENDIAN__ ) || defined( __LITTLE_ENDIAN ) || \
defined( _LITTLE_ENDIAN ) || defined( _WIN32 ) || defined( i386 ) || \
defined( __i386 ) || defined( __i386__ ) || defined( __x86_64__ ) || \
( defined( __BYTE_ORDER__ ) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ )
#define KOMIHASH_LITTLE_ENDIAN 1
#elif defined( __BIG_ENDIAN__ ) || defined( __BIG_ENDIAN ) || \
defined( _BIG_ENDIAN ) || defined( __SYSC_ZARCH__ ) || \
defined( __zarch__ ) || defined( __s390x__ ) || defined( __sparc ) || \
defined( __sparc__ ) || ( defined( __BYTE_ORDER__ ) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
#define KOMIHASH_LITTLE_ENDIAN 0
#else // defined( __BIG_ENDIAN__ )
#warning KOMIHASH: cannot determine endianness, assuming little-endian.
#define KOMIHASH_LITTLE_ENDIAN 1
#endif // defined( __BIG_ENDIAN__ )
#endif // !defined( KOMIHASH_LITTLE_ENDIAN )
/**
* @def KOMIHASH_GCC_BUILTINS
* @brief Macro that denotes availability of GCC-style built-in functions.
*/
#if defined( __GNUC__ ) || defined( __clang__ ) || \
defined( __IBMC__ ) || defined( __IBMCPP__ ) || defined( __COMPCERT__ )
#define KOMIHASH_GCC_BUILTINS
#endif // GCC built-ins check
/**
* @def KOMIHASH_EC32( v )
* @brief Macro that appies 32-bit byte-swapping, for endianness-correction.
* @param v Value to byte-swap.
*/
/**
* @def KOMIHASH_EC64( v )
* @brief Macro that appies 64-bit byte-swapping, for endianness-correction.
* @param v Value to byte-swap.
*/
#if KOMIHASH_LITTLE_ENDIAN
#define KOMIHASH_EC32( v ) ( v )
#define KOMIHASH_EC64( v ) ( v )
#else // KOMIHASH_LITTLE_ENDIAN
#if defined( KOMIHASH_GCC_BUILTINS )
#define KOMIHASH_EC32( v ) __builtin_bswap32( v )
#define KOMIHASH_EC64( v ) __builtin_bswap64( v )
#elif defined( _MSC_VER )
#include <intrin.h>
#define KOMIHASH_EC32( v ) _byteswap_ulong( v )
#define KOMIHASH_EC64( v ) _byteswap_uint64( v )
#else // defined( _MSC_VER )
#define KOMIHASH_EC32( v ) ( \
( v & 0xFF000000 ) >> 24 | \
( v & 0x00FF0000 ) >> 8 | \
( v & 0x0000FF00 ) << 8 | \
( v & 0x000000FF ) << 24 )
#define KOMIHASH_EC64( v ) ( \
( v & 0xFF00000000000000 ) >> 56 | \
( v & 0x00FF000000000000 ) >> 40 | \
( v & 0x0000FF0000000000 ) >> 24 | \
( v & 0x000000FF00000000 ) >> 8 | \
( v & 0x00000000FF000000 ) << 8 | \
( v & 0x0000000000FF0000 ) << 24 | \
( v & 0x000000000000FF00 ) << 40 | \
( v & 0x00000000000000FF ) << 56 )
#endif // defined( _MSC_VER )
#endif // KOMIHASH_LITTLE_ENDIAN
/**
* @def KOMIHASH_LIKELY( x )
* @brief Likelihood macro that is used for manually-guided
* micro-optimization.
* @param x Expression that is likely to be evaluated to "true".
*/
/**
* @def KOMIHASH_UNLIKELY( x )
* @brief Unlikelihood macro that is used for manually-guided
* micro-optimization.
* @param x Expression that is unlikely to be evaluated to "true".
*/
#if defined( KOMIHASH_GCC_BUILTINS )
#define KOMIHASH_LIKELY( x ) __builtin_expect( x, 1 )
#define KOMIHASH_UNLIKELY( x ) __builtin_expect( x, 0 )
#else // defined( KOMIHASH_GCC_BUILTINS )
#define KOMIHASH_LIKELY( x ) ( x )
#define KOMIHASH_UNLIKELY( x ) ( x )
#endif // defined( KOMIHASH_GCC_BUILTINS )
/**
* @def KOMIHASH_PREFETCH( a )
* @brief Memory address prefetch macro, to preload some data into CPU cache.
*
* Temporal locality=2, in case a collision resolution would be necessary,
* or for a subsequent disk write.
*
* @param a Prefetch address.
*/
#if defined( KOMIHASH_GCC_BUILTINS ) && !defined( __COMPCERT__ )
#define KOMIHASH_PREFETCH( a ) __builtin_prefetch( a, 0, 2 )
#else // Prefetch macro
#define KOMIHASH_PREFETCH( a )
#endif // Prefetch macro
/**
* @def KOMIHASH_PREFETCH_1
* @brief Compiler-dependent address prefetch macro, ordered position 1.
* @param a Prefetch address.
*/
/**
* @def KOMIHASH_PREFETCH_2
* @brief Compiler-dependent address prefetch macro, ordered position 2.
* @param a Prefetch address.
*/
#if defined( __clang__ )
#define KOMIHASH_PREFETCH_1( a ) KOMIHASH_PREFETCH( a )
#define KOMIHASH_PREFETCH_2( a )
#else // defined( __clang__ )
#define KOMIHASH_PREFETCH_1( a )
#define KOMIHASH_PREFETCH_2( a ) KOMIHASH_PREFETCH( a )
#endif // defined( __clang__ )
/**
* @def KOMIHASH_INLINE
* @brief Macro to force code inlining.
*/
#if defined( KOMIHASH_GCC_BUILTINS )
#define KOMIHASH_INLINE inline __attribute__((always_inline))
#elif defined( _MSC_VER )
#define KOMIHASH_INLINE inline __forceinline
#else // defined( _MSC_VER )
#define KOMIHASH_INLINE inline
#endif // defined( _MSC_VER )
/**
* @brief Load unsigned 32-bit value with endianness-correction.
*
* An auxiliary function that returns an unsigned 32-bit value created out of
* a sequence of bytes in memory. This function is used to convert endianness
* of in-memory 32-bit unsigned values, and to avoid unaligned memory
* accesses.
*
* @param p Pointer to 4 bytes in memory. Alignment is unimportant.
* @return Endianness-corrected 32-bit value from memory.
*/
static KOMIHASH_INLINE uint32_t kh_lu32ec( const uint8_t* const p )
{
uint32_t v;
memcpy( &v, p, 4 );
return( KOMIHASH_EC32( v ));
}
/**
* @brief Load unsigned 64-bit value with endianness-correction.
*
* An auxiliary function that returns an unsigned 64-bit value created out of
* a sequence of bytes in memory. This function is used to convert endianness
* of in-memory 64-bit unsigned values, and to avoid unaligned memory
* accesses.
*
* @param p Pointer to 8 bytes in memory. Alignment is unimportant.
* @return Endianness-corrected 64-bit value from memory.
*/
static KOMIHASH_INLINE uint64_t kh_lu64ec( const uint8_t* const p )
{
uint64_t v;
memcpy( &v, p, 8 );
return( KOMIHASH_EC64( v ));
}
/**
* @brief Load unsigned 64-bit value with padding (Msg-3 reads).
*
* Function builds an unsigned 64-bit value out of remaining bytes in a
* message, and pads it with the "final byte". This function can only be
* called if less than 8 bytes are left to read. The message should be "long",
* permitting `Msg[ -3 ]` reads.
*
* @param Msg Message pointer, alignment is unimportant.
* @param MsgLen Message's remaining length, in bytes; can be 0.
* @return Final byte-padded value from the message.
*/
static KOMIHASH_INLINE uint64_t kh_lpu64ec_l3( const uint8_t* const Msg,
const size_t MsgLen )
{
const int ml8 = (int) ( MsgLen * 8 );
if( MsgLen < 4 )
{
const uint8_t* const Msg3 = Msg + MsgLen - 3;
const uint64_t m = (uint64_t) Msg3[ 0 ] | (uint64_t) Msg3[ 1 ] << 8 |
(uint64_t) Msg3[ 2 ] << 16;
return( (uint64_t) 1 << ml8 | m >> ( 24 - ml8 ));
}
const uint64_t mh = kh_lu32ec( Msg + MsgLen - 4 );
const uint64_t ml = kh_lu32ec( Msg );
return( (uint64_t) 1 << ml8 | ml | ( mh >> ( 64 - ml8 )) << 32 );
}
/**
* @brief Load unsigned 64-bit value with padding (non-zero message length).
*
* Function builds an unsigned 64-bit value out of remaining bytes in a
* message, and pads it with the "final byte". This function can only be
* called if less than 8 bytes are left to read. Can be used on "short"
* messages, but `MsgLen` should be greater than 0.
*
* @param Msg Message pointer, alignment is unimportant.
* @param MsgLen Message's remaining length, in bytes; cannot be 0.
* @return Final byte-padded value from the message.
*/
static KOMIHASH_INLINE uint64_t kh_lpu64ec_nz( const uint8_t* const Msg,
const size_t MsgLen )
{
const int ml8 = (int) ( MsgLen * 8 );
if( MsgLen < 4 )
{
uint64_t m = Msg[ 0 ];
if( MsgLen > 1 )
{
m |= (uint64_t) Msg[ 1 ] << 8;
if( MsgLen > 2 )
{
m |= (uint64_t) Msg[ 2 ] << 16;
}
}
return( (uint64_t) 1 << ml8 | m );
}
const uint64_t mh = kh_lu32ec( Msg + MsgLen - 4 );
const uint64_t ml = kh_lu32ec( Msg );
return( (uint64_t) 1 << ml8 | ml | ( mh >> ( 64 - ml8 )) << 32 );
}
/**
* @brief Load unsigned 64-bit value with padding (Msg-4 reads).
*
* Function builds an unsigned 64-bit value out of remaining bytes in a
* message, and pads it with the "final byte". This function can only be
* called if less than 8 bytes are left to read. The message should be "long",
* permitting `Msg[ -4 ]` reads.
*
* @param Msg Message pointer, alignment is unimportant.
* @param MsgLen Message's remaining length, in bytes; can be 0.
* @return Final byte-padded value from the message.
*/
static KOMIHASH_INLINE uint64_t kh_lpu64ec_l4( const uint8_t* const Msg,
const size_t MsgLen )
{
const int ml8 = (int) ( MsgLen * 8 );
if( MsgLen < 5 )
{
const uint64_t m = kh_lu32ec( Msg + MsgLen - 4 );
return( (uint64_t) 1 << ml8 | m >> ( 32 - ml8 ));
}
const uint64_t m = kh_lu64ec( Msg + MsgLen - 8 );
return( (uint64_t) 1 << ml8 | m >> ( 64 - ml8 ));
}
/**
* @fn void kh_m128( uint64_t u, uint64_t v, uint64_t* rl, uint64_t* rha )
* @brief 64-bit by 64-bit unsigned multiplication with result accumulation.
*
* @param u Multiplier 1.
* @param v Multiplier 2.
* @param[out] rl The lower half of the 128-bit result.
* @param[in,out] rha The accumulator to receive the higher half of the
* 128-bit result.
*/
/**
* @def KOMIHASH_EMULU( u, v )
* @brief Macro for 32-bit by 32-bit unsigned multiplication with 64-bit
* result.
* @param u Multiplier 1.
* @param v Multiplier 2.
*/
#if defined( _MSC_VER ) && ( defined( _M_ARM64 ) || \
( defined( _M_X64 ) && defined( __INTEL_COMPILER )))
#include <intrin.h>
static KOMIHASH_INLINE void kh_m128( const uint64_t u, const uint64_t v,
uint64_t* const rl, uint64_t* const rha )
{
*rl = u * v;
*rha += __umulh( u, v );
}
#elif defined( _MSC_VER ) && ( defined( _M_X64 ) || defined( _M_IA64 ))
#include <intrin.h>
#pragma intrinsic(_umul128)
static KOMIHASH_INLINE void kh_m128( const uint64_t u, const uint64_t v,
uint64_t* const rl, uint64_t* const rha )
{
uint64_t rh;
*rl = _umul128( u, v, &rh );
*rha += rh;
}
#elif defined( __SIZEOF_INT128__ )
static KOMIHASH_INLINE void kh_m128( const uint64_t u, const uint64_t v,
uint64_t* const rl, uint64_t* const rha )
{
const unsigned __int128 r = (unsigned __int128) u * v;
*rha += (uint64_t) ( r >> 64 );
*rl = (uint64_t) r;
}
#elif ( defined( __IBMC__ ) || defined( __IBMCPP__ )) && defined( __LP64__ )
static KOMIHASH_INLINE void kh_m128( const uint64_t u, const uint64_t v,
uint64_t* const rl, uint64_t* const rha )
{
*rl = u * v;
*rha += __mulhdu( u, v );
}
#else // defined( __IBMC__ )
// _umul128() code for 32-bit systems, adapted from Hacker's Delight,
// Henry S. Warren, Jr.
#if defined( _MSC_VER ) && !defined( __INTEL_COMPILER )
#include <intrin.h>
#pragma intrinsic(__emulu)
#define KOMIHASH_EMULU( u, v ) __emulu( u, v )
#else // defined( _MSC_VER ) && !defined( __INTEL_COMPILER )
#define KOMIHASH_EMULU( u, v ) ( (uint64_t) ( u ) * ( v ))
#endif // defined( _MSC_VER ) && !defined( __INTEL_COMPILER )
static inline void kh_m128( const uint64_t u, const uint64_t v,
uint64_t* const rl, uint64_t* const rha )
{
*rl = u * v;
const uint32_t u0 = (uint32_t) u;
const uint32_t v0 = (uint32_t) v;
const uint64_t w0 = KOMIHASH_EMULU( u0, v0 );
const uint32_t u1 = (uint32_t) ( u >> 32 );
const uint32_t v1 = (uint32_t) ( v >> 32 );
const uint64_t t = KOMIHASH_EMULU( u1, v0 ) + (uint32_t) ( w0 >> 32 );
const uint64_t w1 = KOMIHASH_EMULU( u0, v1 ) + (uint32_t) t;
*rha += KOMIHASH_EMULU( u1, v1 ) + (uint32_t) ( w1 >> 32 ) +
(uint32_t) ( t >> 32 );
}
#endif // defined( __IBMC__ )
/**
* @def KOMIHASH_HASHROUND()
* @brief Macro for a common hashing round without input.
*
* The three instructions in this macro (multiply, add, and XOR) represent the
* simplest constantless PRNG, scalable to any even-sized state variables,
* with the `Seed1` being the PRNG output (2^64 PRNG period). It passes
* `PractRand` tests with rare non-systematic "unusual" evaluations.
*
* To make this PRNG reliable, self-starting, and eliminate a risk of
* stopping, the following variant can be used, which adds a "register
* checker-board", a source of raw entropy. The PRNG is available as the
* komirand() function. Not required for hashing (but works for it) since the
* input entropy is usually available in abundance during hashing.
*
* `Seed5 += 0xAAAAAAAAAAAAAAAA;`
*
* (the `0xAAAA...` constant should match register's size; essentially, it is
* a replication of the `10` bit-pair; it is not an arbitrary constant).
*/
#define KOMIHASH_HASHROUND() \
kh_m128( Seed1, Seed5, &Seed1, &Seed5 ); \
Seed1 ^= Seed5;
/**
* @def KOMIHASH_HASH16( m )
* @brief Macro for a common hashing round with 16-byte input.
* @param m Message pointer, alignment is unimportant.
*/
#define KOMIHASH_HASH16( m ) \
kh_m128( Seed1 ^ kh_lu64ec( m ), \
Seed5 ^ kh_lu64ec( m + 8 ), &Seed1, &Seed5 ); \
Seed1 ^= Seed5;
/**
* @def KOMIHASH_HASHFIN()
* @brief Macro for common hashing finalization round.
*
* The final hashing input is expected in the `r1h` and `r2h` temporary
* variables. The macro inserts the function return instruction.
*/
#define KOMIHASH_HASHFIN() \
kh_m128( r1h, r2h, &Seed1, &Seed5 ); \
Seed1 ^= Seed5; \
KOMIHASH_HASHROUND(); \
return( Seed1 );
/**
* @def KOMIHASH_HASHLOOP64()
* @brief Macro for a common 64-byte full-performance hashing loop.
*
* Expects `Msg` and `MsgLen` values (greater than 63), requires initialized
* `Seed1-8` values.
*
* The "shifting" arrangement of `Seed1-4` XORs (below) does not increase
* individual `SeedN` PRNG period beyond 2^64, but reduces a chance of any
* occassional synchronization between PRNG lanes happening. Practically,
* `Seed1-4` together become a single "fused" 256-bit PRNG value, having 2^66
* summary PRNG period.
*/
#define KOMIHASH_HASHLOOP64() \
do \
{ \
KOMIHASH_PREFETCH_1( Msg ); \
\
kh_m128( Seed1 ^ kh_lu64ec( Msg ), \
Seed5 ^ kh_lu64ec( Msg + 32 ), &Seed1, &Seed5 ); \
\
kh_m128( Seed2 ^ kh_lu64ec( Msg + 8 ), \
Seed6 ^ kh_lu64ec( Msg + 40 ), &Seed2, &Seed6 ); \
\
kh_m128( Seed3 ^ kh_lu64ec( Msg + 16 ), \
Seed7 ^ kh_lu64ec( Msg + 48 ), &Seed3, &Seed7 ); \
\
kh_m128( Seed4 ^ kh_lu64ec( Msg + 24 ), \
Seed8 ^ kh_lu64ec( Msg + 56 ), &Seed4, &Seed8 ); \
\
Msg += 64; \
MsgLen -= 64; \
\
KOMIHASH_PREFETCH_2( Msg ); \
\
Seed2 ^= Seed5; \
Seed3 ^= Seed6; \
Seed4 ^= Seed7; \
Seed1 ^= Seed8; \
\
} while( KOMIHASH_LIKELY( MsgLen > 63 ));
/**
* @brief The hashing epilogue function (for internal use).
*
* @param Msg Pointer to the remaining part of the message.
* @param MsgLen Remaining part's length, can be 0.
* @param Seed1 Latest Seed1 value.
* @param Seed5 Latest Seed5 value.
* @return 64-bit hash value.
*/
static KOMIHASH_INLINE uint64_t komihash_epi( const uint8_t* Msg,
size_t MsgLen, uint64_t Seed1, uint64_t Seed5 )
{
uint64_t r1h, r2h;
if( KOMIHASH_LIKELY( MsgLen > 31 ))
{
KOMIHASH_HASH16( Msg );
KOMIHASH_HASH16( Msg + 16 );
Msg += 32;
MsgLen -= 32;
}
if( MsgLen > 15 )
{
KOMIHASH_HASH16( Msg );
Msg += 16;
MsgLen -= 16;
}
if( MsgLen > 7 )
{
r2h = Seed5 ^ kh_lpu64ec_l4( Msg + 8, MsgLen - 8 );
r1h = Seed1 ^ kh_lu64ec( Msg );
}
else
{
r1h = Seed1 ^ kh_lpu64ec_l4( Msg, MsgLen );
r2h = Seed5;
}
KOMIHASH_HASHFIN();
}
/**
* @brief KOMIHASH 64-bit hash function.
*
* Produces and returns a 64-bit hash value of the specified message, string,
* or binary data block. Designed for 64-bit hash-table and hash-map uses.
* Produces identical hashes on both big- and little-endian systems.
*
* @param Msg0 The message to produce a hash from. The alignment of this
* pointer is unimportant. It is valid to pass 0 when `MsgLen` equals 0
* (assuming that compiler's implementation of the address prefetch permits
* the use of zero address).
* @param MsgLen Message's length, in bytes, can be zero.
* @param UseSeed Optional value, to use instead of the default seed. To use
* the default seed, set to 0. The UseSeed value can have any bit length and
* statistical quality, and is used only as an additional entropy source. May
* need endianness-correction via KOMIHASH_EC64(), if this value is shared
* between big- and little-endian systems.
* @return 64-bit hash of the input data. Should be endianness-corrected when
* this value is shared between big- and little-endian systems.
*/
static inline uint64_t komihash( const void* const Msg0, size_t MsgLen,
const uint64_t UseSeed )
{
const uint8_t* Msg = (const uint8_t*) Msg0;
// The seeds are initialized to the first mantissa bits of PI.
uint64_t Seed1 = 0x243F6A8885A308D3 ^ ( UseSeed & 0x5555555555555555 );
uint64_t Seed5 = 0x452821E638D01377 ^ ( UseSeed & 0xAAAAAAAAAAAAAAAA );
uint64_t r1h, r2h;
KOMIHASH_PREFETCH( Msg );
KOMIHASH_HASHROUND(); // Required for Perlin Noise.
if( KOMIHASH_LIKELY( MsgLen < 16 ))
{
r1h = Seed1;
r2h = Seed5;
if( MsgLen > 7 )
{
// The following two XOR instructions are equivalent to mixing a
// message with a cryptographic one-time-pad (bitwise modulo 2
// addition). Message's statistics and distribution are thus
// unimportant.
r2h ^= kh_lpu64ec_l3( Msg + 8, MsgLen - 8 );
r1h ^= kh_lu64ec( Msg );
}
else
if( KOMIHASH_LIKELY( MsgLen != 0 ))
{
r1h ^= kh_lpu64ec_nz( Msg, MsgLen );
}
KOMIHASH_HASHFIN();
}
if( KOMIHASH_LIKELY( MsgLen < 32 ))
{
KOMIHASH_HASH16( Msg );
if( MsgLen > 23 )
{
r2h = Seed5 ^ kh_lpu64ec_l4( Msg + 24, MsgLen - 24 );
r1h = Seed1 ^ kh_lu64ec( Msg + 16 );
KOMIHASH_HASHFIN();
}
else
{
r1h = Seed1 ^ kh_lpu64ec_l4( Msg + 16, MsgLen - 16 );
r2h = Seed5;
KOMIHASH_HASHFIN();
}
}
if( KOMIHASH_LIKELY( MsgLen > 63 ))
{
uint64_t Seed2 = 0x13198A2E03707344 ^ Seed1;
uint64_t Seed3 = 0xA4093822299F31D0 ^ Seed1;
uint64_t Seed4 = 0x082EFA98EC4E6C89 ^ Seed1;
uint64_t Seed6 = 0xBE5466CF34E90C6C ^ Seed5;
uint64_t Seed7 = 0xC0AC29B7C97C50DD ^ Seed5;
uint64_t Seed8 = 0x3F84D5B5B5470917 ^ Seed5;
KOMIHASH_HASHLOOP64();
Seed5 ^= Seed6 ^ Seed7 ^ Seed8;
Seed1 ^= Seed2 ^ Seed3 ^ Seed4;
}
return( komihash_epi( Msg, MsgLen, Seed1, Seed5 ));
}
/**
* @brief KOMIRAND 64-bit pseudo-random number generator.
*
* Simple, reliable, self-starting yet efficient PRNG, with 2^64 period.
* 0.62 cycles/byte performance. Self-starts in 4 iterations, which is a
* suggested "warming up" initialization before using its output.
*
* @param[in,out] Seed1 Seed value 1. Can be initialized to any value
* (even 0). This is the usual "PRNG seed" value.
* @param[in,out] Seed2 Seed value 2, a supporting variable. Best initialized
* to the same value as `Seed1`.
* @return The next uniformly-random 64-bit value.
*/
static KOMIHASH_INLINE uint64_t komirand( uint64_t* const Seed1,
uint64_t* const Seed2 )
{
uint64_t s1 = *Seed1;
uint64_t s2 = *Seed2;
kh_m128( s1, s2, &s1, &s2 );
s2 += 0xAAAAAAAAAAAAAAAA;
s1 ^= s2;
*Seed2 = s2;
*Seed1 = s1;
return( s1 );
}
/**
* @def KOMIHASH_BUFSIZE
* @brief Streamed hashing's buffer size, in bytes.
*
* Must be a multiple of 64, and not less than 128. Can be defined externally.
*/
#if !defined( KOMIHASH_BUFSIZE )
#define KOMIHASH_BUFSIZE 768
#endif // !defined( KOMIHASH_BUFSIZE )
/**
* @brief Context structure for the streamed "komihash" hashing.
*
* The komihash_init() function should be called to initalize the structure
* before hashing. Note that the default buffer size is modest, permitting
* placement of this structure on stack. `Seed[ 0 ]` is used as `UseSeed`
* value storage.
*/
typedef struct {
uint8_t pb[ 8 ]; ///< Buffer's padding bytes, to avoid OOB.
uint8_t Buf[ KOMIHASH_BUFSIZE ]; ///< Buffer.
uint64_t Seed[ 8 ]; ///< Hashing state variables.
size_t BufFill; ///< Buffer fill count (position), in bytes.
size_t IsHashing; ///< 0 or 1, equals 1 if the actual hashing was started.
} komihash_stream_t;
/**
* @brief Function initializes the streamed "komihash" hashing session.
*
* @param[out] ctx Pointer to the context structure.
* @param UseSeed Optional value, to use instead of the default seed. To use
* the default seed, set to 0. The UseSeed value can have any bit length and
* statistical quality, and is used only as an additional entropy source. May
* need endianness-correction via KOMIHASH_EC64(), if this value is shared
* between big- and little-endian systems.
*/
static inline void komihash_stream_init( komihash_stream_t* const ctx,
const uint64_t UseSeed )
{
ctx -> Seed[ 0 ] = UseSeed;
ctx -> BufFill = 0;
ctx -> IsHashing = 0;
}
/**
* @brief Function updates the streamed hashing state with a new input data.
*
* @param[in,out] ctx Pointer to the context structure. The structure must be
* initialized via the komihash_stream_init() function.
* @param Msg0 The next part of the whole message being hashed. The alignment
* of this pointer is unimportant. It is valid to pass 0 when `MsgLen` equals
* 0.
* @param MsgLen Message's length, in bytes, can be zero.
*/
static inline void komihash_stream_update( komihash_stream_t* const ctx,
const void* const Msg0, size_t MsgLen )
{
const uint8_t* Msg = (const uint8_t*) Msg0;
const uint8_t* SwMsg = 0;
size_t SwMsgLen = 0;
size_t BufFill = ctx -> BufFill;
if( BufFill + MsgLen >= KOMIHASH_BUFSIZE && BufFill != 0 )
{
const size_t CopyLen = KOMIHASH_BUFSIZE - BufFill;
memcpy( ctx -> Buf + BufFill, Msg, CopyLen );
BufFill = 0;
SwMsg = Msg + CopyLen;
SwMsgLen = MsgLen - CopyLen;
Msg = ctx -> Buf;
MsgLen = KOMIHASH_BUFSIZE;
}
if( BufFill == 0 )
{
while( MsgLen > 127 )
{
uint64_t Seed1, Seed2, Seed3, Seed4;
uint64_t Seed5, Seed6, Seed7, Seed8;
KOMIHASH_PREFETCH_2( Msg );
if( ctx -> IsHashing )
{
Seed1 = ctx -> Seed[ 0 ];
Seed2 = ctx -> Seed[ 1 ];
Seed3 = ctx -> Seed[ 2 ];
Seed4 = ctx -> Seed[ 3 ];
Seed5 = ctx -> Seed[ 4 ];
Seed6 = ctx -> Seed[ 5 ];
Seed7 = ctx -> Seed[ 6 ];
Seed8 = ctx -> Seed[ 7 ];
}
else
{
ctx -> IsHashing = 1;
const uint64_t UseSeed = ctx -> Seed[ 0 ];
Seed1 = 0x243F6A8885A308D3 ^ ( UseSeed & 0x5555555555555555 );
Seed5 = 0x452821E638D01377 ^ ( UseSeed & 0xAAAAAAAAAAAAAAAA );
KOMIHASH_HASHROUND();
Seed2 = 0x13198A2E03707344 ^ Seed1;
Seed3 = 0xA4093822299F31D0 ^ Seed1;
Seed4 = 0x082EFA98EC4E6C89 ^ Seed1;
Seed6 = 0xBE5466CF34E90C6C ^ Seed5;
Seed7 = 0xC0AC29B7C97C50DD ^ Seed5;
Seed8 = 0x3F84D5B5B5470917 ^ Seed5;
}
KOMIHASH_HASHLOOP64();
ctx -> Seed[ 0 ] = Seed1;
ctx -> Seed[ 1 ] = Seed2;
ctx -> Seed[ 2 ] = Seed3;
ctx -> Seed[ 3 ] = Seed4;
ctx -> Seed[ 4 ] = Seed5;
ctx -> Seed[ 5 ] = Seed6;
ctx -> Seed[ 6 ] = Seed7;
ctx -> Seed[ 7 ] = Seed8;
if( SwMsgLen == 0 )
{
if( MsgLen != 0 )
{
break;
}
ctx -> BufFill = 0;
return;
}
Msg = SwMsg;
MsgLen = SwMsgLen;
SwMsgLen = 0;
}
}
ctx -> BufFill = BufFill + MsgLen;
uint8_t* op = ctx -> Buf + BufFill;
while( MsgLen != 0 )
{
*op = *Msg;
Msg++;
op++;
MsgLen--;
}
}
/**
* @brief Function finalizes the streamed "komihash" hashing session.
*
* Returns the resulting hash value of the previously hashed data. This value
* is equal to the value returned by the komihash() function for the same
* provided data.
*
* Note that since this function is non-destructive to the context structure,
* the function can be used to obtain intermediate hashes of the data stream
* being hashed, and the hashing can then be resumed.
*
* @param[in] ctx Pointer to the context structure. The structure must be
* initialized via the komihash_stream_init() function.
* @return 64-bit hash value. Should be endianness-corrected when this value
* is shared between big- and little-endian systems.
*/
static inline uint64_t komihash_stream_final( komihash_stream_t* const ctx )
{
const uint8_t* Msg = ctx -> Buf;
size_t MsgLen = ctx -> BufFill;
if( ctx -> IsHashing == 0 )
{
return( komihash( Msg, MsgLen, ctx -> Seed[ 0 ]));
}
ctx -> pb[ 4 ] = 0;
ctx -> pb[ 5 ] = 0;
ctx -> pb[ 6 ] = 0;
ctx -> pb[ 7 ] = 0;
uint64_t Seed1 = ctx -> Seed[ 0 ];
uint64_t Seed2 = ctx -> Seed[ 1 ];
uint64_t Seed3 = ctx -> Seed[ 2 ];
uint64_t Seed4 = ctx -> Seed[ 3 ];
uint64_t Seed5 = ctx -> Seed[ 4 ];
uint64_t Seed6 = ctx -> Seed[ 5 ];
uint64_t Seed7 = ctx -> Seed[ 6 ];
uint64_t Seed8 = ctx -> Seed[ 7 ];
if( MsgLen > 63 )
{
KOMIHASH_HASHLOOP64();
}
Seed5 ^= Seed6 ^ Seed7 ^ Seed8;
Seed1 ^= Seed2 ^ Seed3 ^ Seed4;
return( komihash_epi( Msg, MsgLen, Seed1, Seed5 ));
}
/**
* @brief FOR TESTING PURPOSES ONLY - use the komihash() function instead.
*
* @param Msg The message to produce a hash from.
* @param MsgLen Message's length, in bytes.
* @param UseSeed Seed to use.
* @return 64-bit hash value.
*/
static inline uint64_t komihash_stream_oneshot( const void* const Msg,
const size_t MsgLen, const uint64_t UseSeed )
{
komihash_stream_t ctx;
komihash_stream_init( &ctx, UseSeed );
komihash_stream_update( &ctx, Msg, MsgLen );
return( komihash_stream_final( &ctx ));
}
#endif // KOMIHASH_INCLUDED

347
src/loaders.inc Normal file
View File

@ -0,0 +1,347 @@
static int matloader(void *ud, struct ResManRes *res) {
char namebuf[256] = {};
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%s.lua", res->name);
struct k3Mat *mat = _mm_malloc(sizeof(*mat), 16);
luaapi_fillmaterial(namebuf, mat);
res->thing = mat;
return 1;
}
static int mdlloader(void *ud, struct ResManRes *res) {
char buf[256];
snprintf(buf, sizeof(buf), "assets/mdl/%s.k3m", res->name);
FILE *f = fopen(buf, "rb");
struct {
uint32_t magic;
uint32_t vertCount;
uint32_t indCount;
uint8_t boneCount;
uint8_t flags;
uint16_t animCount;
} header;
fread(&header, 4, 4, f);
int colorsEnabled = header.flags & 1;
mat4 *invBind = _mm_malloc(sizeof(*invBind) * header.boneCount, 16);
fread(invBind, sizeof(*invBind), header.boneCount, f);
uint8_t *boneParents = malloc(header.boneCount);
fread(boneParents, 1, header.boneCount, f);
vec3 *pos = malloc(sizeof(*pos) * header.vertCount);
fread(pos, sizeof(vec3), header.vertCount, f);
uint8_t *nrm = malloc(3 * header.vertCount);
fread(nrm, 3, header.vertCount, f);
float *uvs = malloc(8 * header.vertCount);
fread(uvs, 8, header.vertCount, f);
uint8_t *boneIDs = malloc(4 * header.vertCount);
fread(boneIDs, 4, header.vertCount, f);
uint16_t *boneWeights = malloc(8 * header.vertCount);
fread(boneWeights, 8, header.vertCount, f);
uint8_t *cols = NULL;
if(colorsEnabled) {
cols = malloc(4 * header.vertCount);
fread(cols, 4, header.vertCount, f);
}
uint16_t *inds = malloc(sizeof(*inds) * header.indCount);
fread(inds, sizeof(uint16_t), header.indCount, f);
struct k3Mdl *mdl = k3MdlCreate(header.vertCount, header.indCount, header.boneCount, pos, nrm, uvs, cols, boneIDs, boneWeights, inds, invBind, boneParents);
k3MdlSetDebugName(mdl, res->name);
free(pos);
free(nrm);
free(uvs);
free(inds);
uint16_t meshes;
fread(&meshes, sizeof(uint16_t), 1, f);
for(uint32_t i = 0; i < meshes; i++) {
uint16_t startnum[2];
fread(startnum, sizeof(uint16_t), 2, f);
char buf[256] = {};
for(int c = 0; c < sizeof(buf) - 1; c++) {
fread(buf + c, 1, 1, f);
if(buf[c] == 0) {
break;
}
}
k3MdlAddMesh(mdl, resman_ref(RESMAN_MATERIAL, buf), startnum[0], startnum[1]);
}
for(size_t i = 0; i < header.animCount; i++) {
struct {
uint16_t id;
uint16_t frames;
uint16_t fps;
uint16_t zero;
} info;
fread(&info, sizeof(info), 1, f);
struct k3Animation *anim = malloc(sizeof(*anim));
anim->frames = _mm_malloc(sizeof(*anim->frames) * info.frames * header.boneCount, 16);
anim->boneParents = boneParents;
anim->invBind = invBind;
anim->id = info.id;
anim->fps = info.fps;
anim->frameCount = info.frames;
anim->boneCount = header.boneCount;
fread(anim->frames, sizeof(*anim->frames), info.frames * header.boneCount, f);
k3MdlAddAnim(mdl, anim);
}
fclose(f);
res->thing = mdl;
return 1;
}
static int physloader(void *ud, struct ResManRes *res) {
char namebuf[256];
snprintf(namebuf, sizeof(namebuf), "assets/phys/%s.phys", res->name);
FILE *p = fopen(namebuf, "rb");
if(!p) return 0;
dTriMeshDataID trid = dGeomTriMeshDataCreate();
fseek(p, 0, SEEK_END);
size_t len = ftell(p);
assert(len % 36 == 0);
fseek(p, 0, SEEK_SET);
float *data = malloc(len);
fread(data, 1, len, p);
int *idxs = malloc(sizeof(*idxs) * (len / sizeof(vec3)));
for(int i = 0; i < len / sizeof(vec3); i++) idxs[i] = i;
dGeomTriMeshDataBuildSingle(trid, data, sizeof(vec3), len / sizeof(vec3), idxs, len / sizeof(vec3), 3 * sizeof(int));
fclose(p);
struct TrimeshData *ret = malloc(sizeof(*ret));
ret->vdata = data;
ret->idata = idxs;
ret->trid = trid;
res->thing = ret;
return 1;
}
static int binloader(void *ud, struct ResManRes *res) {
char namebuf[256];
snprintf(namebuf, sizeof(namebuf), "assets/%s", res->name);
FILE *f = fopen(namebuf, "rb");
if(!f) return 0;
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
struct ResManBin *data = malloc(sizeof(*data) + len + 1);
fseek(f, 0, SEEK_SET);
fread(data->data, 1, len, f);
data->data[len] = 0;
data->length = len;
fclose(f);
res->thing = data;
return 1;
}
static void binunloader(void *ud, const char *name, void *data) {
free(data);
}
static int streamloader(void *ud, struct ResManRes *res) {
char namebuf[256];
snprintf(namebuf, sizeof(namebuf), "assets/aud/%s", res->name);
res->thing = k3MixSourceFile(namebuf);
return 1;
}
static int refresh_texture(struct ResManRes *res) {
if(strlen(res->name) < 8) return 0;
const char *suffix = res->name + strlen(res->name) - 8;
if(!strcmp(suffix, ".cub.png")) {
char namebuf[256];
int w, h, origN;
unsigned char *data;
struct k3Tex *tex = res->thing;
if(!tex) {
tex = k3TexCreate(k3_CUBEMAP);
}
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.nx.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 1, w, h, data);
stbi_image_free(data);
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.px.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 0, w, h, data);
stbi_image_free(data);
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.py.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 2, w, h, data);
stbi_image_free(data);
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.ny.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 3, w, h, data);
stbi_image_free(data);
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.nz.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 5, w, h, data);
stbi_image_free(data);
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%.*s.pz.png", suffix - res->name, res->name);
data = stbi_load(namebuf, &w, &h, &origN, 4);
k3TexUpdate(tex, k3_DIFFUSE, 4, w, h, data);
stbi_image_free(data);
res->thing = tex;
} else {
enum k3TexType type;
int n, gamma;
if(!strcmp(suffix, ".alp.png")) {
type = k3_ALPHA;
n = 1;
gamma = 0;
} else if(!strcmp(suffix, ".nrm.png")) {
type = k3_NORMAL;
n = 4;
gamma = 1;
} else if(!strcmp(suffix, ".dsp.png")) {
type = k3_DISPLACEMENT;
n = 1;
gamma = 0;
} else if(!strcmp(suffix, ".emt.png")) {
type = k3_EMISSION;
n = 4;
gamma = 1;
} else if(!strcmp(suffix, ".rgh.png")) {
type = k3_ROUGHNESS;
n = 1;
gamma = 0;
} else if(!strcmp(suffix, ".dif.png")) {
type = k3_DIFFUSE;
n = 4;
gamma = 1;
} else return 0;
char namebuf[256];
snprintf(namebuf, sizeof(namebuf), "assets/mdl/%s", res->name);
int w, h, origN;
unsigned char *data = stbi_load(namebuf, &w, &h, &origN, n);
int stbifree = 1;
if(TextureResolutionReduction && data) {
int newW = w >> TextureResolutionReduction;
int newH = h >> TextureResolutionReduction;
if(newW <= 4) newW = 4;
if(newH <= 4) newH = 4;
unsigned char *data2 = malloc(newW * newH * n);
int success = stbir_resize_uint8_generic(data, w, h, 0, data2, newW, newH, 0, n, n == 4 ? 3 : -1, 0, STBIR_EDGE_WRAP, STBIR_FILTER_DEFAULT, gamma ? STBIR_COLORSPACE_SRGB : STBIR_COLORSPACE_LINEAR, NULL);
if(success) {
stbi_image_free(data);
w = newW;
h = newH;
data = data2;
stbifree = 0;
} else {
free(data2);
}
}
if(!res->thing) {
res->thing = k3TexCreate(type);
}
if(data) {
k3TexUpdate(res->thing, type, 0, w, h, data);
if(stbifree) {
stbi_image_free(data);
} else {
free(data);
}
}
}
return 1;
}
static int refresh_textures() {
for(int b = 0; b < RESMAN_BUCKETS; b++) {
for(struct ResManRes *r = ResMan.buckets[b]; r; r = r->next) {
if(r->type == RESMAN_TEXTURE) {
refresh_texture(r);
}
}
}
}
static int texloader(void *ud, struct ResManRes *res) {
refresh_texture(res);
return 1;
}
static int fontloader(void *ud, struct ResManRes *res) {
FILE *fntf = fopen(res->name, "rb");
fseek(fntf, 0, SEEK_END);
size_t fntfsz = ftell(fntf);
fseek(fntf, 0, SEEK_SET);
char *fntbuf = malloc(fntfsz);
fread(fntbuf, 1, fntfsz, fntf);
struct k3Font *fnt = k3FontCreate();
struct k3Tex *fnttexldr(struct k3Font *fnt, const char *name) {
return resman_ref(RESMAN_TEXTURE, name);
}
k3FontLoad(fnt, fntbuf, fntfsz, fnttexldr);
res->thing = fnt;
return 1;
}
static void init_res_handlers() {
ResMan.handlers[RESMAN_MATERIAL].loader = matloader;
ResMan.handlers[RESMAN_MODEL].loader = mdlloader;
ResMan.handlers[RESMAN_PHYSICS].loader = physloader;
ResMan.handlers[RESMAN_BIN].loader = binloader;
ResMan.handlers[RESMAN_BIN].unloader = binunloader;
ResMan.handlers[RESMAN_STREAM].loader = streamloader;
ResMan.handlers[RESMAN_TEXTURE].loader = texloader;
ResMan.handlers[RESMAN_FONT].loader = fontloader;
}

3422
src/luaapi.c Normal file

File diff suppressed because it is too large Load Diff

51
src/luaapi.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include"enet.h"
#include<cglm/vec3.h>
extern int LuaapiCamFocus;
extern vec3 LuaapiCamFocusPos;
extern vec3 LuaapiCamFocusDir;
extern mat4 LuaapiCamMatrix;
extern int LuaapiFirstPerson;
extern double LuaapiStartTime;
extern float LuaapiFov;
extern struct k3Tex *LuaapiSkybox;
extern vec4 LuaapiSkyboxRotation;
extern bool LuaapiSkyboxRotationFunky;
void luaapi_init();
void luaapi_load(const char *name);
void luaapi_render(double dt, double alpha);
void luaapi_render2d();
void luaapi_update();
// peer will be NULL if local player
int luaapi_join(ENetPeer *peer, int oldlua);
void luaapi_leave(int);
struct k3Mat;
void luaapi_fillmaterial_direct(struct k3Mat *mat);
void luaapi_fillmaterial(const char *src, struct k3Mat*);
struct k3Light *luaapi_getlights(size_t *count);
void luaapi_cleanup();
void luaapi_perform(const char *statement);
void luaapi_ctrlev(int ctrl, int action);
struct bstr;
int luaapi_recvmsg(struct bstr *b, int lua);
void luaapi_escape();
void luaapi_peercode_found(const char *peercode);

962
src/main.c Normal file
View File

@ -0,0 +1,962 @@
#include"k4.h"
#ifdef _WIN32
#include<winsock2.h>
#endif
#define ENET_IMPLEMENTATION
#define GLAD_GL_IMPLEMENTATION
#include"gl.h"
#pragma GCC push_options
#pragma GCC optimize("Ofast")
#define STB_IMAGE_IMPLEMENTATION
#include"stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include"stb_image_resize.h"
#pragma GCC pop_options
#include<GLFW/glfw3.h>
#include<cglm/cam.h>
#include<cglm/affine.h>
#include"k3.h"
#include"k3water.h"
#include"k3mix.h"
#include"k3font.h"
#include"k3menu.h"
#include"k3bloom.h"
#include"resman.h"
#include"game.h"
#include"luaapi.h"
#include"net_server.h"
#include"net_client.h"
#include<ctype.h>
struct NetWrap NetWrap;
GLFWwindow *GameWnd;
static int TextureResolutionReduction = 0;
static int IrregularShadows = 0;
static float CamPitch, CamYaw;
static vec3 CamPos;
static int ThirdPerson = 1;
struct k3MScreen *UiActive;
static double CurrentTime;
static double LastTime;
#include"loaders.inc"
void set_ui_mode(int yes) {
glfwSetInputMode(GameWnd, GLFW_CURSOR, yes ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
static int is_ui_mode() {
return glfwGetInputMode(GameWnd, GLFW_CURSOR) == GLFW_CURSOR_NORMAL;
}
static double xposold, yposold;
static void buttoncallback(GLFWwindow *GameWnd, int button, int action, int mods) {
if(button == GLFW_MOUSE_BUTTON_LEFT) {
if(UiActive) {
int ww, wh;
glfwGetWindowSize(GameWnd, &ww, &wh);
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = action == GLFW_PRESS ? k3M_EVENT_MOUSE_PRESS : k3M_EVENT_MOUSE_RELEASE,
.mouse = {
.button = k3M_MOUSE_BUTTON_0,
.x = xposold * UiActive->w / ww,
.y = (wh - yposold - 1) * UiActive->h / wh,
},
};
k3MEventSend(&ev);
}
}
if(!UiActive) {
luaapi_ctrlev(button, action == GLFW_PRESS ? 0 : (action == GLFW_RELEASE ? 2 : 1));
}
}
static void motioncallback(GLFWwindow *GameWnd, double xpos, double ypos) {
static int first = 1;
if(!first && !is_ui_mode()) {
CamPitch -= (ypos - yposold) / 500.f;
CamYaw -= (xpos - xposold) / 500.f;
if(CamPitch > 1.5393) {
CamPitch = 1.5393;
} else if(CamPitch < -1.5393) {
CamPitch = -1.5393;
}
}
xposold = xpos;
yposold = ypos;
first = 0;
if(UiActive) {
int ww, wh;
glfwGetWindowSize(GameWnd, &ww, &wh);
uint16_t uix = xposold * UiActive->w / ww;
uint16_t uiy = (wh - yposold - 1) * UiActive->h / wh;
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = k3M_EVENT_MOUSE_MOTION,
.mouse = {
.x = uix,
.y = uiy,
},
};
k3MEventSend(&ev);
}
}
static void keycallback(GLFWwindow *GameWnd, int key, int scancode, int action, int mods) {
if(action == GLFW_RELEASE && key == GLFW_KEY_ESCAPE) {
luaapi_escape();
set_ui_mode(!!UiActive);
} else {
if(UiActive) {
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = action == GLFW_PRESS ? k3M_EVENT_KEY_PRESS : k3M_EVENT_KEY_RELEASE,
.key = {
.num = key,
},
};
k3MEventSend(&ev);
}
}
luaapi_ctrlev(key, action == GLFW_PRESS ? 0 : (action == GLFW_RELEASE ? 2 : 1));
}
static void charcallback(GLFWwindow *window, unsigned int codepoint) {
if(UiActive) {
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = k3M_EVENT_INPUT,
.input = {
.code = codepoint
},
};
k3MEventSend(&ev);
}
}
static void resizecallback(GLFWwindow *window, int width, int height) {
k3Resize(width, height);
}
static int argc;
static char **argv;
const char *k4_get_arg(const char *name) {
for(int i = 1; i < argc; i++) {
if(strstr(argv[i], name) == argv[i] && strlen(argv[i]) > strlen(name) && argv[i][strlen(name)] == '=') {
return argv[i] + strlen(name) + 1;
}
}
return NULL;
}
static void netwrap_step() {
if(NetWrap.stage && CurrentTime >= NetWrap.timeout) {
if(NetWrap.stage == 1) {
if(stoon_req(&NetWrap.stoon)) {
if(stoon_listen(&NetWrap.stoon)) {
uint8_t conndata[STOON_CONN_INFO_SIZE] = {};
stoon_serialize(&NetWrap.stoon, conndata);
char str[STOON_CONN_INFO_SIZE * 2 + 1] = {};
for(int i = 0; i < sizeof(conndata); i++) {
snprintf(str + i * 2, 3, "%02x", conndata[i]);
}
luaapi_peercode_found(str);
//glfwSetClipboardString(NULL, str);
//screenMain.lblpeerdata->invisible = 0;
//screenMain.btnconnect->invisible = 0;
NetWrap.stage = 2;
} else {
k3Log(k3_INFO, "Stoon listen timeout.");
NetWrap.timeout = CurrentTime + 0.5;
}
} else {
k3Log(k3_INFO, "Stoon request failed.");
NetWrap.timeout = CurrentTime + 0.5;
}
} else if(NetWrap.stage == 2) {
stoon_keepalive(&NetWrap.stoon);
NetWrap.timeout = CurrentTime + 1;
} else if(NetWrap.stage == 3) {
int status = stoon_poonch(&NetWrap.stoon, NetWrap.otherpeer);
//screenMain.lblpeerdata->txt = strdup("Trying to connect...\nMay not work if both are\nbehind symmetric NATs.");
if(status == STOON_POONCH_INCOMPATIBLE_NETFAMS) {
NetWrap.stage = 0;
stoon_kill(&NetWrap.stoon);
//screenMain.lblpeerdata->txt = strdup("Connection cannot be established:\nIncompatible netfams.");
} else if(status == STOON_POONCH_NO_KNOCK && NetWrap.stoon.poonchstage == POONCH_STAGE_ACK) {
if(NetWrap.gotten++ > 5) {
NetWrap.stage = 0;
stoon_kill(&NetWrap.stoon);
if(NetWrap.isHost) {
net_server_init();
} else {
char ip[64];
int port;
if(*(uint16_t*) (NetWrap.otherpeer + 22) == 0) {
inet_ntop(AF_INET, NetWrap.otherpeer, ip, sizeof(ip));
port = ntohs(*(uint16_t*) (NetWrap.otherpeer + 4));
} else {
inet_ntop(AF_INET6, NetWrap.otherpeer + 6, ip, sizeof(ip));
port = ntohs(*(uint16_t*) (NetWrap.otherpeer + 22));
}
printf("Trying [%s]:%u\n", ip, port);
net_client_init();
net_client_connect(ip, port);
}
//screenMain.lblpeerdata->txt = strdup("Connection successful.");
}
} else NetWrap.gotten = 0;
NetWrap.timeout = CurrentTime + 1;
}
}
}
#include<signal.h>
void k4k3LogCallback(enum k3LogLevel lvl, const char *str, size_t len) {
static const char *prefixes[] = {
[k3_DEBUG] = "[\x1B[95mDEBUG\x1B[0m]",
[k3_INFO] = "[\x1B[97mINFO\x1B[0m] ",
[k3_WARN] = "[\x1B[93mWARN\x1B[0m] ",
[k3_ERR] = "[\x1B[91mERROR\x1B[0m]",
};
//if(lvl == k3_ERR) raise(SIGINT);
fprintf(stderr, "%s : %s\n", prefixes[lvl], str);
}
struct k4Control {
const char *name;
int btn;
} Controls[K4_MAX_CONTROLS];
static int eng_init() {
glfwInit();
glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE);
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
#define START_WIDTH 960
#define START_HEIGHT 540
#define START_TITLE "k4"
// Prefer core
if(!k4_get_arg("core") || strcmp(k4_get_arg("core"), "0")) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GameWnd = glfwCreateWindow(START_WIDTH, START_HEIGHT, START_TITLE, NULL, NULL);
}
if(!GameWnd) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
GameWnd = glfwCreateWindow(START_WIDTH, START_HEIGHT, START_TITLE, NULL, NULL);
}
k3MixInit(44100, 2);
k3MixPortAudio();
if(glfwRawMouseMotionSupported()) {
glfwSetInputMode(GameWnd, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
}
glfwSetMouseButtonCallback(GameWnd, buttoncallback);
glfwSetCursorPosCallback(GameWnd, motioncallback);
glfwSetKeyCallback(GameWnd, keycallback);
glfwSetCharCallback(GameWnd, charcallback);
glfwSetFramebufferSizeCallback(GameWnd, resizecallback);
glfwMakeContextCurrent(GameWnd);
gladLoadGL(glfwGetProcAddress);
if(k4_get_arg("shading") && strtol(k4_get_arg("shading"), NULL, 0) == 0) {
GLAD_GL_ARB_shading_language_100 = 0;
GLAD_GL_ARB_vertex_program = 0;
GLAD_GL_ARB_fragment_program = 0;
GLAD_GL_ARB_shader_objects = 0;
}
if(k4_get_arg("srgb") && strtol(k4_get_arg("srgb"), NULL, 0) == 0) {
// Just forget sRGB if the driver doesn't bother with GL_FRAMEBUFFER_SRGB
GLAD_GL_EXT_framebuffer_sRGB = 0;
GLAD_GL_EXT_texture_sRGB = 0;
}
if(k4_get_arg("fbo") && strtol(k4_get_arg("fbo"), NULL, 0) == 0) {
GLAD_GL_ARB_framebuffer_sRGB = 0;
GLAD_GL_EXT_framebuffer_object = 0;
}
glfwSwapInterval(0);
printf("GL version: %s\n", glGetString(GL_VERSION));
printf("GL renderer: %s\n", glGetString(GL_RENDERER));
printf("GL vendor: %s\n", glGetString(GL_VENDOR));
k3SetLogCallback(k4k3LogCallback);
k3Init();
k3BatchInit();
resizecallback(GameWnd, START_WIDTH, START_HEIGHT);
enet_initialize();
init_res_handlers();
luaapi_init();
}
static int gr_lowres(int w, int h);
static int ResolPercentage = 100;
static void fix_resol() {
int w, h;
glfwGetWindowSize(GameWnd, &w, &h);
gr_lowres(roundf(w * ResolPercentage / 100.f / 4) * 4, roundf(h * ResolPercentage / 100.f / 4) * 4);
}
static bool onqualitypress(struct k3MEvent *ev, uint8_t *ud) {
k3GraphicalReduction = (k3GraphicalReduction + 1) % 3;
return true;
}
static bool onresolpress(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextButton *btn = (void*) ev->target;
ResolPercentage -= 10;
if(ResolPercentage <= 0) {
ResolPercentage = 100;
}
const char *txt = NULL;
switch(ResolPercentage) {
case 10:
txt = "10%";
break;
case 20:
txt = "20%";
break;
case 30:
txt = "30%";
break;
case 40:
txt = "40%";
break;
case 50:
txt = "50%";
break;
case 60:
txt = "60%";
break;
case 70:
txt = "70%";
break;
case 80:
txt = "80%";
break;
case 90:
txt = "90%";
break;
case 100:
txt = "Full Resolution";
break;
}
btn->txt = strdup(txt);
fix_resol();
return true;
}
static bool ontexrespress(struct k3MEvent *ev, uint8_t *ud) {
TextureResolutionReduction = (TextureResolutionReduction + 1) % 3;
refresh_textures();
return true;
}
static bool onresumepress(struct k3MEvent *ev, uint8_t *ud) {
UiActive = NULL;
set_ui_mode(0);
return true;
}
#ifdef LOCALHOST_ONLY
static bool onhostpress(struct k3MEvent *ev, uint8_t *ud) {
net_server_init();
return true;
}
static bool onjoinpress(struct k3MEvent *ev, uint8_t *ud) {
net_client_init();
net_client_connect("127.0.0.1", 26656);
return true;
}
static bool onconnectpress(struct k3MEvent *ev, uint8_t *ud) {
return true;
}
#else
static bool onhostpress(struct k3MEvent *ev, uint8_t *ud) {
screenMain.btnhost->disabled = 1;
screenMain.btnjoin->disabled = 1;
NetWrap.stage = 1;
NetWrap.timeout = glfwGetTime() + 0;
NetWrap.isHost = 1;
NetWrap.stoon = stoon_init("stun.easybell.de", 3478, 26656);
return true;
}
static bool onjoinpress(struct k3MEvent *ev, uint8_t *ud) {
screenMain.btnhost->disabled = 1;
screenMain.btnjoin->disabled = 1;
NetWrap.stage = 1;
NetWrap.timeout = glfwGetTime() + 0;
NetWrap.isHost = 0;
NetWrap.stoon = stoon_init("stun.easybell.de", 3478, 26656);
return true;
}
static bool onconnectpress(struct k3MEvent *ev, uint8_t *ud) {
const char *name = glfwGetClipboardString(NULL);
while(isspace(*name)) name++;
if(strlen(name) < STOON_CONN_INFO_SIZE * 2) {
goto bad;
}
for(int i = 0; i < STOON_CONN_INFO_SIZE; i++) {
int b = 0;
if(name[i * 2 + 0] >= '0' && name[i * 2 + 0] <= '9') {
b |= (name[i * 2 + 0] - '0') << 4;
} else if(name[i * 2 + 0] >= 'a' && name[i * 2 + 0] <= 'f') {
b |= (name[i * 2 + 0] - 'a' + 10) << 4;
} else goto bad;
if(name[i * 2 + 1] >= '0' && name[i * 2 + 1] <= '9') {
b |= (name[i * 2 + 1] - '0') << 0;
} else if(name[i * 2 + 1] >= 'a' && name[i * 2 + 1] <= 'f') {
b |= (name[i * 2 + 1] - 'a' + 10) << 0;
} else goto bad;
NetWrap.otherpeer[i] = b;
}
NetWrap.stage = 3;
screenMain.btnconnect->disabled = 1;
return true;
bad:
screenMain.lblpeerdata->txt = strdup("Incorrect peer conndata.");
return true;
}
#endif
static bool onconsoleenter(struct k3MEvent *ev, uint8_t *ud) {
return true;
}
static void eng_ui_init() {
set_ui_mode(1);
}
static void fps_counter_step(double newDT) {
static size_t lastDTidx = 0;
static double lastDTs[30] = {};
lastDTs[lastDTidx] = newDT;
lastDTidx = (lastDTidx + 1) % (sizeof(lastDTs) / sizeof(*lastDTs));
if(fmodf(CurrentTime, 1) < fmodf(LastTime, 1)) {
double dtSum = 0;
for(int i = 0; i < sizeof(lastDTs) / sizeof(*lastDTs); i++) {
dtSum += lastDTs[i];
}
k3Log(k3_DEBUG, "fps=%05.03f", 1 / (dtSum / (sizeof(lastDTs) / sizeof(*lastDTs))));
}
}
static struct k3Tex *shadowmap;
static struct k3Offscreen *shadowmapOffscreen;
static struct k3ARBFP *shadowmapDebugThing;
static int gr_shadowmap_init() {
shadowmap = k3TexCreate(k3_DEPTH);
k3TexUpdate(shadowmap, k3_DEPTH, 0, k3TexSzMax() / 2, k3TexSzMax() / 2, NULL);
shadowmapOffscreen = k3OffscreenCreate(NULL, shadowmap);
if(!shadowmapOffscreen) {
return 0;
}
shadowmapDebugThing = k3ProgramARBFP(
"!!ARBfp1.0\n"
"TEMP col;\n"
"TEX col, fragment.texcoord, texture[0], 2D;\n"
"MAD col.xyz, -col, 29.9, 30.1;\n"
"RCP col.x, col.x;\n"
"RCP col.y, col.y;\n"
"RCP col.z, col.z;\n"
"MUL col.xyz, col, 0.2;\n"
"MOV result.color, col;\n"
"END\n"
);
return 1;
}
static struct k3Tex *bloomTex;
static struct k3Offscreen *bloomOffscr;
static int gr_bloom_init() {
if(!k3BloomInit()) {
return 0;
}
bloomTex = k3TexCreate(k3_RAWCOLOR);
k3TexUpdate(bloomTex, k3_RAWCOLOR, 0, 1024, 512, NULL);
bloomOffscr = k3OffscreenCreate(bloomTex, NULL);
return !!bloomOffscr;
}
static struct k3Tex *lowres, *lowresDepth;
static struct k3Offscreen *lowresOffscreen;
static int gr_lowres(int newW, int newH) {
if(lowresOffscreen) {
k3OffscreenDestroy(lowresOffscreen);
lowresOffscreen = NULL;
}
if(newW && newH) {
if(!lowres) {
lowres = k3TexCreate(k3_RAWCOLOR);
}
k3TexUpdate(lowres, k3_RAWCOLOR, 0, newW, newH, NULL);
if(!lowresDepth) {
lowresDepth = k3TexCreate(k3_DEPTH);
}
k3TexUpdate(lowresDepth, k3_DEPTH, 0, newW, newH, NULL);
lowresOffscreen = k3OffscreenCreate(lowres, lowresDepth);
}
return !!lowresOffscreen;
}
#define MAX_RAYS 64
static struct LocalRay RaysToCast[MAX_RAYS];
static size_t RaysToCastCount = 0;
struct LocalRay *request_ray(struct LocalRay *lr) {
if(RaysToCastCount == MAX_RAYS) {
return NULL;
}
memcpy(RaysToCast + RaysToCastCount, lr, sizeof(*lr));
return &RaysToCast[RaysToCastCount++];
}
int main(int argc_, char **argv_) {
argc = argc_;
argv = argv_;
eng_init();
eng_ui_init();
gr_shadowmap_init();
gr_bloom_init();
if(k4_get_arg("res%")) {
ResolPercentage = strtod(k4_get_arg("res%"), NULL);
fix_resol();
}
IrregularShadows = 0;
if(k4_get_arg("irsh") && strcmp(k4_get_arg("irsh"), "0")) {
IrregularShadows = 1;
}
// INIT LEVEL
game_init();
luaapi_load(k4_get_arg("script") ? k4_get_arg("script") : "lvl1");
//
LuaapiStartTime = glfwGetTime();
LastTime = glfwGetTime();
double accumulator = 0;
while(!glfwWindowShouldClose(GameWnd)) {
CurrentTime = glfwGetTime();
double dt = CurrentTime - LastTime;
accumulator += dt;
fps_counter_step(dt);
LastTime = CurrentTime;
glfwPollEvents();
netwrap_step();
float alpha = fmodf(accumulator * GAME_TPS, 1.f);
vec3 cameraForwardDir = {0, 0, -1};
glm_vec3_rotate(cameraForwardDir, CamPitch, (vec3) {1, 0, 0});
glm_vec3_rotate(cameraForwardDir, CamYaw, (vec3) {0, 1, 0});
/* Control translation from keys to game logic */
struct CPlayerCtrl *cc = Game.controlled == ENT_ID_INVALID ? NULL : game_ensurecomponent(Game.controlled, playerctrl);
if(cc && !is_ui_mode()) {
cc->yaw = CamYaw;
cc->pitch = CamPitch;
cc->keys = 0;
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
struct k4Control *k = &Controls[i];
if(k->name) {
if((k->btn <= GLFW_MOUSE_BUTTON_LAST ? glfwGetMouseButton(GameWnd, k->btn) : glfwGetKey(GameWnd, k->btn)) == GLFW_PRESS) {
cc->keys |= 1 << i;
}
}
}
// This is necessary so the host player would send its own pointing to clients
game_ensurecomponent(Game.controlled, movement)->pointing = cc->yaw;
}
if(Game.isMultiplayer) {
if(Game.isAuthority) {
net_server_receive();
} else {
net_client_receive();
}
}
game_raycast(RaysToCast, RaysToCastCount);
RaysToCastCount = 0;
while(accumulator >= 1. / GAME_TPS) {
for(size_t i = 0; i < Game.entities.renderCount; i++) {
glm_vec4_copy(Game.entities.render[i].pos, Game.entities.render[i].posLast);
glm_quat_copy(Game.entities.render[i].rot, Game.entities.render[i].rotLast);
}
if(Game.isMultiplayer && !Game.isAuthority) {
net_client_dejitter();
net_client_update();
}
accumulator -= 1. / GAME_TPS;
game_update();
luaapi_update();
if(Game.isMultiplayer && Game.isAuthority) {
net_server_update();
}
}
int width, height;
glfwGetFramebufferSize(GameWnd, &width, &height);
mat4 proj;
glm_perspective(glm_rad(LuaapiFov), (float) width / height, 0.01f, 100.f, proj);
/* Third-person camera movement */
struct LocalRay camray;
if(Game.spectated != ENT_ID_INVALID) {
struct CRender *c = game_getcomponent(Game.spectated, render);
if(c) {
if(!LuaapiFirstPerson) {
glm_vec3_lerp(c->posLast, c->pos, alpha, camray.pos);
camray.pos[1] += 1.5;
glm_vec3_negate_to(cameraForwardDir, camray.dir);
camray.ignore = Game.spectated;
camray.maxlen = 3;
camray.depth = 0;
game_raycast(&camray, 1);
}
}
}
mat4 cam;
if(LuaapiFirstPerson) {
struct CRender *c = game_getcomponent(Game.spectated, render);
if(c) {
vec3 p;
glm_vec3_lerp(c->posLast, c->pos, alpha, p);
mat4 view;
glm_look(p, cameraForwardDir, (vec3) {0, 1, 0}, view);
glm_mat4_inv(view, cam);
}
} else if(camray.depth || LuaapiCamFocus) {
glm_mat4_identity(cam);
vec3 almostThere;
if(LuaapiCamFocus) {
glm_vec3_copy(LuaapiCamFocusPos, almostThere);
glm_vec3_lerp(CamPos, almostThere, 0.1, CamPos);
glm_look(CamPos, LuaapiCamFocusDir, (vec3) {0, 1, 0}, cam);
} else {
vec3 dirneg;
glm_vec3_negate_to(camray.dir, dirneg);
glm_vec3_lerp(camray.pos, camray.out, 0.9, almostThere);
glm_vec3_lerp(CamPos, almostThere, 0.2, CamPos);
glm_look(CamPos, dirneg, (vec3) {0, 1, 0}, cam);
}
glm_mat4_inv(cam, cam);
}
glm_mat4_copy(cam, LuaapiCamMatrix);
k3SetTime(glfwGetTime());
size_t lightCount;
struct k3Light *lights = luaapi_getlights(&lightCount);
k3SetLights(lightCount, lights);
luaapi_render(dt, alpha);
for(size_t i = 0; i < Game.entities.renderCount; i++) {
struct CRender *c = &Game.entities.render[i];
versor interrot;
glm_quat_nlerp(c->rotLast, c->rot, alpha, interrot);
vec4 interpos;
glm_vec4_lerp(c->posLast, c->pos, alpha, interpos);
mat4 transform = {};
glm_quat_mat4(interrot, transform);
glm_vec4_copy(interpos, (float*) transform[3]);
if(c->cache) {
struct CBoned *b = game_getcomponent(c->entity, boned);
k3Batch(c->cache, transform, b ? b->bones : NULL);
}
}
if(!IrregularShadows) {
k3PassShadowmap(proj, cam, shadowmapOffscreen);
}
if(lowresOffscreen) {
k3BeginOffscreen(lowresOffscreen);
}
k3Clear();
if(LuaapiSkybox) {
mat4 rotation;
glm_quat_mat4(LuaapiSkyboxRotation, rotation);
mat4 rotated;
glm_mat4_mul(rotation, cam, rotated);
k3CubemapTraditional(LuaapiSkybox, proj, rotated);
}
k3PassDepthOnly(proj, cam, true, true);// TRY COMMENTING THIS LINE
if(IrregularShadows) {
k3PassIrregular(lowresOffscreen, proj, cam);
glEnable(GL_SCISSOR_TEST);
//glScissor(256, 0, 10000, 10000);
}
k3PassForward(proj, cam);
glDisable(GL_SCISSOR_TEST);
k3BatchClear();
if(lowresOffscreen) {
k3EndOffscreen(lowresOffscreen);
k3BlitToScreenEffect(lowresOffscreen, false, k3_GLSL, k3ToneMapper(), NULL);
if(bloomOffscr) {
k3Bloom(lowresOffscreen, bloomOffscr);
}
}
glClear(GL_DEPTH_BUFFER_BIT);
if(!k3IsCore) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 3600, 0, 2025, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
luaapi_render2d();
if(UiActive) {
struct k3MEvent ev = {
.original = UiActive,
.target = UiActive,
.code = k3M_EVENT_DRAW,
};
k3MEventSend(&ev);
}
k3BatchFlush();
// Shadowmap debugging
if(glfwGetKey(GameWnd, GLFW_KEY_F5) == GLFW_PRESS) {
k3BlitToScreen(shadowmapOffscreen, 0);
}
glfwSwapBuffers(GameWnd);
}
}
int k4_control_set(const char *name, int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(!Controls[i].name) {
Controls[i].name = strdup(name);
Controls[i].btn = btn;
return i;
}
}
return -1;
}
int k4_control_get_id(int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && btn == Controls[i].btn) {
return i;
}
}
return -1;
}
int k4_control_get_by_name(const char *name) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && !strcmp(Controls[i].name, name)) {
return Controls[i].btn;
}
}
return -1;
}
int k4_control_get_id_by_name(const char *name) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && !strcmp(Controls[i].name, name)) {
return i;
}
}
return -1;
}
const char *k4_control_get_name_by_ctrl(int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && Controls[i].btn == btn) {
return Controls[i].name;
}
}
return NULL;
}
void k4_set_reduction(float f) {
ResolPercentage = f * 100;
fix_resol();
}
void k4_set_texture_reduction(int i) {
TextureResolutionReduction = i;
refresh_textures();
}
void k4_set_clipboard_text(const char *str) {
glfwSetClipboardString(GameWnd, str);
}
const char *k4_get_clipboard_text() {
return glfwGetClipboardString(GameWnd);
}

206
src/net.h Normal file
View File

@ -0,0 +1,206 @@
#pragma once
// SC - server to client
// CS - client to server
#include<string.h>
#include<stdint.h>
#define PKT_PERFRAME (1<<15)
#define CMD_SC_ASSIGN 0
#define CMD_SC_SINGLE_COMPONENT 1
#define CMD_SC_POSUPDATE 2
#define CMD_SC_PING 3
#define CMD_SC_LOAD_SCRIPT 4
#define CMD_SC_MSG 5
#define CMD_CTYPE_PHYSICS 0
#define CMD_CTYPE_RENDER 1
#define CMD_CTYPE_MOVEMENT 2
#define CMD_CTYPE_PLAYERCTRL 3
#define CMD_CTYPE_BONED 3
#define PEER_DISCONNECT_TOO_MANY_PLAYERS 1
struct bstr {
uint32_t cap;
uint32_t wi;
uint32_t ri;
uint8_t *data;
};
static inline void bstr(struct bstr *dis, size_t cap) {
dis->cap = cap;
dis->wi = dis->ri = 0;
dis->data = malloc(dis->cap);
}
static inline void bwrap(struct bstr *dis, uint8_t *data, size_t dataLength) {
dis->cap = dataLength;
dis->wi = dis->ri = 0;
dis->data = data;
}
static inline void b_inc(struct bstr *b, uint32_t i) {
if(b->wi + i > b->cap) {
b->data = realloc(b->data, b->cap = ((b->cap + i + 63) & ~63));
}
}
static inline void b_wf32(struct bstr *b, float f) {
b_inc(b, sizeof(f));
memcpy(b->data + b->wi, &f, sizeof(f));
b->wi += sizeof(f);
}
static inline void b_wf64(struct bstr *b, double f) {
b_inc(b, sizeof(f));
memcpy(b->data + b->wi, &f, sizeof(f));
b->wi += sizeof(f);
}
static inline void b_wu8(struct bstr *b, uint8_t i) {
b_inc(b, sizeof(i));
b->data[b->wi] = i;
b->wi += sizeof(i);
}
static inline void b_wu16(struct bstr *b, uint16_t i) {
b_inc(b, sizeof(i));
memcpy(b->data + b->wi, &i, sizeof(i));
b->wi += sizeof(i);
}
static inline void b_wu32(struct bstr *b, uint32_t i) {
b_inc(b, sizeof(i));
memcpy(b->data + b->wi, &i, sizeof(i));
b->wi += sizeof(i);
}
static inline void b_wu64(struct bstr *b, uint64_t i) {
b_inc(b, sizeof(i));
memcpy(b->data + b->wi, &i, sizeof(i));
b->wi += sizeof(i);
}
static inline void b_wf16(struct bstr *b, float f) {
uint32_t I = *(uint32_t*) &f;
uint16_t i = (I >> 16) & 0x8000;
int32_t mant = I & 0x7FFFFF;
uint16_t exp = (I >> 23) & 0xFF;
if(exp == 0xFF) {
i |= mant ? 0x7E00 : 0x7C00;
} else if(exp >= 0x8F) {
i |= 0x7C00;
} else if(exp >= 0x71) {
i |= ((exp - 0x70) << 10) | (mant >> 13);
} else if(exp >= 0x67) {
i |= (mant | 0x800000) >> (0x7E - exp);
}
b_wu16(b, i);
}
// TODO: implement varints
static inline void b_wvu(struct bstr *b, size_t i) {
}
static inline void b_wzstr(struct bstr *b, const char *str) {
size_t l = strlen(str);
b_inc(b, l + 1);
strcpy((char*) b->data + b->wi, str);
b->wi += l;
b->data[b->wi++] = 0;
}
static inline void b_wraw(struct bstr *b, const uint8_t *data, size_t len) {
b_inc(b, len);
memcpy(b->data + b->wi, data, len);
b->wi += len;
}
static inline float b_rf32(struct bstr *b) {
float ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline float b_rf64(struct bstr *b) {
double ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline uint16_t b_ru8(struct bstr *b) {
uint8_t ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline uint16_t b_ru16(struct bstr *b) {
uint16_t ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline uint16_t b_ru32(struct bstr *b) {
uint32_t ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline uint16_t b_ru64(struct bstr *b) {
uint64_t ret;
memcpy(&ret, b->data + b->ri, sizeof(ret));
b->ri += sizeof(ret);
return ret;
}
static inline float b_rf16(struct bstr *b) {
uint16_t i = b_ru16(b);
uint32_t I = (i & 0x8000) << 16;
uint32_t mant = i & 0x3FF;
uint32_t exp = (i & 0x7C00) >> 10;
if(exp == 0x1F) {
I |= mant ? 0x7FC00000 : 0x7F800000;
} else if(exp > 0) {
I |= (exp + 0x70) << 23;
if(mant) {
I |= mant << 13;
}
} else if(mant) {
for(int j = 9; j >= 0; j--) {
if(mant & (1 << j)) {
I |= ((0x67 + j) << 23) | ((mant << (23 - j)) & 0x7FFFFF);
break;
}
}
}
return *(float*) &I;
}
static inline const char *b_rzstr(struct bstr *b) {
const char *ret = (char*) b->data + b->ri;
b->ri += strlen(ret) + 1;
return ret;
}
static inline const uint8_t *b_rraw(struct bstr *b, size_t len) {
const uint8_t *ret = b->data + b->ri;
b->ri += len;
return ret;
}
static inline void b_rraw_copy(struct bstr *b, uint8_t *data, size_t len) {
memcpy(data, b_rraw(b, len), len);
}

287
src/net_client.c Normal file
View File

@ -0,0 +1,287 @@
#include"net_server.h"
#include"enet.h"
#include<stdio.h>
#include"luaapi.h"
#include"net.h"
#include"game.h"
#include"resman.h"
#include<cglm/vec3.h>
static ENetHost *host;
static ENetPeer *peer;
static uint16_t serverTick;
#define RECONBUFSZ 64
static size_t stateUpdateLastClientTick;
static ENetPacket *stateUpdates[RECONBUFSZ];
static struct CPlayerCtrl myInputs[RECONBUFSZ];
static int dejitterLeft = 0;
void net_client_init() {
host = enet_host_create(NULL, 1, 1, 0, 0);
Game.isMultiplayer = 1;
Game.isAuthority = 0;
}
void net_client_connect(const char *addr, uint16_t port) {
ENetAddress eaddr;
enet_address_set_host(&eaddr, addr);
eaddr.port = port;
peer = enet_host_connect(host, &eaddr, 1, 0);
}
extern double glfwGetTime();
static void interpret_pkt(ENetPacket *pkt) {
struct bstr b;
bwrap(&b, pkt->data, pkt->dataLength);
uint16_t cmdCount = b_ru16(&b) & ~PKT_PERFRAME;
uint16_t t = b_ru16(&b);
for(size_t i = 0; i < cmdCount; i++) {
uint16_t cmd = b_ru16(&b);
if(cmd == CMD_SC_ASSIGN) {
Game.spectated = Game.controlled = b_ru16(&b);
} else if(cmd == CMD_SC_SINGLE_COMPONENT) {
uint8_t ctype = b_ru8(&b);
uint16_t ent = b_ru16(&b);
if(ctype == CMD_CTYPE_RENDER) {
struct CRender *c = game_ensurecomponent(ent, render);
const char *mdl = b_rzstr(&b);
if(strncmp(c->mdl, mdl, MODELNAME_LENGTH)) {
if(strlen(c->mdl)) {
resman_unref(RESMAN_MODEL, c->mdl);
c->cache = NULL;
}
memcpy(c->mdl, mdl, MODELNAME_LENGTH);
}
} else if(ctype == CMD_CTYPE_PHYSICS) {
struct CPhysics *c = game_ensurecomponent(ent, physics);
c->trigger = b_ru16(&b);
c->type = b_ru8(&b);
c->dynamics = b_ru8(&b);
if(c->type == CPHYSICS_BOX) {
c->box.w = b_rf32(&b);
c->box.h = b_rf32(&b);
c->box.l = b_rf32(&b);
} else if(c->type == CPHYSICS_CAPSULE) {
c->capsule.length = b_rf32(&b);
c->capsule.radius = b_rf32(&b);
} else abort();
c->start[0] = b_rf32(&b);
c->start[1] = b_rf32(&b);
c->start[2] = b_rf32(&b);
if((c->dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
c->vel[0] = b_rf32(&b);
c->vel[1] = b_rf32(&b);
c->vel[2] = b_rf32(&b);
c->speed = b_rf32(&b);
}
c->mass = b_rf32(&b);
c->friction = b_rf32(&b);
c->collide = b_ru32(&b);
if(c->geom) {
dGeomSetPosition(c->geom, c->start[0], c->start[1], c->start[2]);
if((c->dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
dBodySetLinearVel(dGeomGetBody(c->geom), c->vel[0], c->vel[1], c->vel[2]);
}
}
} else if(ctype == CMD_CTYPE_MOVEMENT) {
struct CMovement *c = game_ensurecomponent(ent, movement);
c->dir[0] = b_rf32(&b);
c->dir[1] = b_rf32(&b);
c->dir[2] = b_rf32(&b);
c->pointing = b_rf32(&b);
c->jump = b_ru8(&b);
c->canJump = b_ru8(&b);
c->holding = b_ru16(&b);
} if(ctype == CMD_CTYPE_BONED) {
struct CBoned *c = game_ensurecomponent(ent, boned);
c->anim.standard = b_ru8(&b);
}
} else if(cmd == CMD_SC_POSUPDATE) {
uint16_t lastOurTick = b_ru16(&b);
stateUpdateLastClientTick = stateUpdateLastClientTick < lastOurTick ? lastOurTick : stateUpdateLastClientTick + 1;
uint16_t conveyorCount = b_ru16(&b);
if(conveyorCount > Game.conveyorCount) conveyorCount = Game.conveyorCount;
for(size_t i = 0; i < conveyorCount; i++) {
struct Conveyor *conveyor = Game.conveyors[i];
conveyor->position = b_rf16(&b);
}
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
vec3 p = {b_rf16(&b), b_rf16(&b), b_rf16(&b)};
if(Game.entities.physics[i].geom) {
dGeomSetPosition(Game.entities.physics[i].geom, p[0], p[1], p[2]);
} else {
glm_vec3_copy(p, Game.entities.physics[i].start);
}
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
vec3 v = {b_rf16(&b), b_rf16(&b), b_rf16(&b)};
if(Game.entities.physics[i].geom) {
dBodyID bid = dGeomGetBody(Game.entities.physics[i].geom);
dBodySetLinearVel(bid, v[0], v[1], v[2]);
} else {
glm_vec3_copy(v, Game.entities.physics[i].vel);
}
struct CMovement *cm = game_getcomponent(Game.entities.physics[i].entity, movement);
if(cm) {
glm_vec3_normalize_to(v, cm->dir);
}
if(Game.entities.physics[i].geom) {
vec4 r = {0, b_rf16(&b), b_rf16(&b), b_rf16(&b)};
r[0] = b_rf16(&b);
dGeomSetQuaternion(Game.entities.physics[i].geom, (float*) r);
} else {
Game.entities.physics[i].rot[0] = b_rf16(&b);
Game.entities.physics[i].rot[1] = b_rf16(&b);
Game.entities.physics[i].rot[2] = b_rf16(&b);
Game.entities.physics[i].rot[3] = b_rf16(&b);
}
}
}
for(size_t i = 0; i < Game.entities.movementCount; i++) {
Game.entities.movement[i].pointing = b_rf16(&b);
}
if(Game.reconciliate) {
// printf("Recon %lu -> %lu\n", stateUpdateLastClientTick, Game.frame);
if(Game.frame - stateUpdateLastClientTick < RECONBUFSZ) {
size_t properFrame = Game.frame;
struct CPlayerCtrl *cc = &myInputs[Game.frame - stateUpdateLastClientTick];
struct CPlayerCtrl *w = game_getcomponent(Game.controlled, playerctrl);
size_t keys = w ? w->keys : 0;
// printf("Currently %X\n", keys);
for(; cc > myInputs + 1; cc--) {
w = game_getcomponent(Game.controlled, playerctrl);
if(w) w->keys = cc->keys;
// printf("pred %X\n", cc->keys);
game_update();
}
w = game_getcomponent(Game.controlled, playerctrl);
if(w) w->keys = keys;
Game.frame = properFrame;
}
}
} else if(cmd == CMD_SC_LOAD_SCRIPT) {
luaapi_load(b_rzstr(&b));
} else if(cmd == CMD_SC_MSG) {
if(!luaapi_recvmsg(&b, -2)) {
// Corrupted packet.
break;
}
}
}
}
void net_client_receive() {
ENetEvent ev;
while(enet_host_service(host, &ev, 0) > 0) {
if(ev.type == ENET_EVENT_TYPE_CONNECT) {
luaapi_cleanup();
} else if(ev.type == ENET_EVENT_TYPE_RECEIVE) {
struct bstr b;
bwrap(&b, ev.packet->data, ev.packet->dataLength);
uint16_t cmdCount = b_ru16(&b);
int perframe = cmdCount & PKT_PERFRAME;
cmdCount &= ~PKT_PERFRAME;
uint16_t t = b_ru16(&b);
if(perframe) {
if(stateUpdates[RECONBUFSZ - 1]) {
enet_packet_destroy(stateUpdates[RECONBUFSZ - 1]);
}
memmove(stateUpdates + 1, stateUpdates, sizeof(*stateUpdates) * (RECONBUFSZ - 1));
stateUpdates[0] = ev.packet;
dejitterLeft++;
if(dejitterLeft > RECONBUFSZ) dejitterLeft = RECONBUFSZ;
} else {
interpret_pkt(ev.packet);
enet_packet_destroy(ev.packet);
}
}
}
}
void net_client_dejitter() {
if(dejitterLeft) {
interpret_pkt(stateUpdates[--dejitterLeft]);
}
}
void net_client_update() {
struct CPlayerCtrl *cc = game_getcomponent(Game.controlled, playerctrl);
memmove(myInputs + 1, myInputs, sizeof(*myInputs) * (RECONBUFSZ - 1));
if(cc) {
memcpy(myInputs, cc, sizeof(*cc));
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_SINGLE_COMPONENT);
b_wu8(&b, CMD_CTYPE_PLAYERCTRL);
b_wu16(&b, Game.controlled);
b_wf32(&b, cc->yaw);
b_wf32(&b, cc->pitch);
b_wu32(&b, cc->keys);
enet_peer_send(peer, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
free(b.data);
enet_host_flush(host);
} else {
memset(myInputs, 0, sizeof(*cc));
}
}
void net_client_send_msg(struct bstr *data) {
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_MSG);
b_wraw(&b, data->data, data->wi);
enet_peer_send(peer, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
free(b.data);
enet_host_flush(host);
}

8
src/net_client.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
void net_client_init();
void net_client_connect(const char *addr, uint16_t port);
void net_client_receive();
void net_client_update();
void net_client_dejitter();
void net_client_send_msg(struct bstr *data);

344
src/net_server.c Normal file
View File

@ -0,0 +1,344 @@
#include"net_server.h"
#include"enet.h"
#include<stdio.h>
#include"luaapi.h"
#include"net.h"
#include"game.h"
static ENetHost *host;
struct Conn {
ENetPeer *e;
uint16_t lastTick;
uint16_t assigned;
int lua;
};
#define MAX_PLAYERS 64
static struct Conn conns[MAX_PLAYERS];
void net_server_init() {
host = enet_host_create(&(ENetAddress) {.host = ENET_HOST_ANY, .port = 26656}, 16, 1, 0, 0);
Game.isMultiplayer = 1;
Game.isAuthority = 1;
memset(conns, 0, sizeof(conns));
}
static void send_full_state(ENetPeer **peers, size_t peerCount) {
struct bstr b;
bstr(&b, 1024);
b_wu16(&b, Game.entities.physicsCount + Game.entities.renderCount + Game.entities.movementCount + Game.entities.bonedCount);
b_wu16(&b, Game.frame);
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
b_wu16(&b, CMD_SC_SINGLE_COMPONENT);
b_wu8(&b, CMD_CTYPE_PHYSICS);
struct CPhysics *c = Game.entities.physics + i;
b_wu16(&b, c->entity);
b_wu16(&b, c->trigger);
b_wu8(&b, c->type);
b_wu8(&b, c->dynamics);
if(c->type == CPHYSICS_BOX) {
b_wf32(&b, c->box.w);
b_wf32(&b, c->box.h);
b_wf32(&b, c->box.l);
} else if(c->type == CPHYSICS_CAPSULE) {
b_wf32(&b, c->capsule.length);
b_wf32(&b, c->capsule.radius);
} else abort();
if(c->geom) {
const float *p = dGeomGetPosition(c->geom);
b_wf32(&b, p[0]);
b_wf32(&b, p[1]);
b_wf32(&b, p[2]);
} else {
b_wf32(&b, c->start[0]);
b_wf32(&b, c->start[1]);
b_wf32(&b, c->start[2]);
}
if((c->dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
if(c->geom) {
const float *p = dBodyGetLinearVel(dGeomGetBody(c->geom));
b_wf32(&b, p[0]);
b_wf32(&b, p[1]);
b_wf32(&b, p[2]);
} else {
b_wf32(&b, c->vel[0]);
b_wf32(&b, c->vel[1]);
b_wf32(&b, c->vel[2]);
}
b_wf32(&b, c->speed);
}
b_wf32(&b, c->mass);
b_wf32(&b, c->friction);
b_wu32(&b, c->collide);
}
for(size_t i = 0; i < Game.entities.renderCount; i++) {
b_wu16(&b, CMD_SC_SINGLE_COMPONENT);
b_wu8(&b, CMD_CTYPE_RENDER);
struct CRender *c = Game.entities.render + i;
b_wu16(&b, c->entity);
b_wzstr(&b, c->mdl);
}
for(size_t i = 0; i < Game.entities.movementCount; i++) {
b_wu16(&b, CMD_SC_SINGLE_COMPONENT);
b_wu8(&b, CMD_CTYPE_MOVEMENT);
struct CMovement *c = Game.entities.movement + i;
b_wu16(&b, c->entity);
b_wf32(&b, c->dir[0]);
b_wf32(&b, c->dir[1]);
b_wf32(&b, c->dir[2]);
b_wf32(&b, c->pointing);
b_wu8(&b, c->jump);
b_wu8(&b, c->canJump);
b_wu16(&b, c->holding);
}
for(size_t i = 0; i < Game.entities.bonedCount; i++) {
b_wu16(&b, CMD_SC_SINGLE_COMPONENT);
b_wu8(&b, CMD_CTYPE_BONED);
struct CBoned *c = Game.entities.boned + i;
b_wu16(&b, c->entity);
b_wu8(&b, c->anim.standard);
}
if(peers) {
for(size_t i = 0; i < peerCount; i++) {
enet_peer_send(peers[i], 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
}
} else {
enet_host_broadcast(host, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
}
free(b.data);
}
static void pos_update() {
struct bstr b;
bstr(&b, 512);
b_wu16(&b, PKT_PERFRAME | 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_POSUPDATE);
b_wu16(&b, 0); // WARNING: THIS INDEX IS EDITED BELOW
b_wu16(&b, Game.conveyorCount);
for(size_t i = 0; i < Game.conveyorCount; i++) {
b_wf16(&b, Game.conveyors[i]->position);
}
for(size_t i = 0; i < Game.entities.physicsCount; i++) {
if(Game.entities.physics[i].geom) {
const float *p = dGeomGetPosition(Game.entities.physics[i].geom);
b_wf16(&b, p[0]);
b_wf16(&b, p[1]);
b_wf16(&b, p[2]);
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
dBodyID bid = dGeomGetBody(Game.entities.physics[i].geom);
p = dBodyGetLinearVel(bid);
b_wf16(&b, p[0]);
b_wf16(&b, p[1]);
b_wf16(&b, p[2]);
p = dBodyGetQuaternion(bid);
b_wf16(&b, p[1]);
b_wf16(&b, p[2]);
b_wf16(&b, p[3]);
b_wf16(&b, p[0]);
}
} else {
b_wf16(&b, Game.entities.physics[i].start[0]);
b_wf16(&b, Game.entities.physics[i].start[1]);
b_wf16(&b, Game.entities.physics[i].start[2]);
if((Game.entities.physics[i].dynamics & ~CPHYSICS_GHOST) == CPHYSICS_DYNAMIC) {
b_wf16(&b, 0);
b_wf16(&b, 0);
b_wf16(&b, 0);
b_wf16(&b, 0);
b_wf16(&b, 0);
b_wf16(&b, 0);
b_wf16(&b, 1);
}
}
}
for(size_t i = 0; i < Game.entities.movementCount; i++) {
b_wf16(&b, Game.entities.movement[i].pointing);
}
for(struct Conn *c = conns; c != conns + MAX_PLAYERS; c++) {
if(c->e) {
*((uint16_t*) &b.data[6]) = c->lastTick;
enet_peer_send(c->e, 0, enet_packet_create(b.data, b.wi, 0));
}
}
free(b.data);
}
void net_server_receive() {
ENetEvent ev;
while(enet_host_service(host, &ev, 0) > 0) {
if(ev.type == ENET_EVENT_TYPE_CONNECT) {
puts("Connect!");
size_t i;
for(i = 0; i < MAX_PLAYERS; i++) {
if(conns[i].e == NULL) {
break;
}
}
if(i == MAX_PLAYERS) {
enet_peer_disconnect(ev.peer, PEER_DISCONNECT_TOO_MANY_PLAYERS);
} else {
conns[i] = (struct Conn) {
.e = ev.peer,
.lastTick = 0,
.assigned = ENT_ID_INVALID,
};
ev.peer->data = conns + i;
conns[i].lua = luaapi_join(ev.peer, 0);
game_synccphysics();
send_full_state(&ev.peer, 1);
}
} else if(ev.type == ENET_EVENT_TYPE_RECEIVE) {
struct bstr b;
bwrap(&b, ev.packet->data, ev.packet->dataLength);
uint16_t cmdCount = b_ru16(&b) & ~PKT_PERFRAME;
uint16_t t = b_ru16(&b);
((struct Conn*) ev.peer->data)->lastTick = t;
for(size_t i = 0; i < cmdCount; i++) {
uint16_t cmd = b_ru16(&b);
if(cmd == CMD_SC_SINGLE_COMPONENT) {
uint8_t ctype = b_ru8(&b);
uint16_t ent = b_ru16(&b);
if(ctype == CMD_CTYPE_PLAYERCTRL) {
struct CPlayerCtrl *c = game_ensurecomponent(ent, playerctrl);
c->yaw = b_rf32(&b);
c->pitch = b_rf32(&b);
c->keys = b_ru32(&b);
game_ensurecomponent(ent, movement)->pointing = c->yaw;
}
} else if(cmd == CMD_SC_MSG) {
if(!luaapi_recvmsg(&b, ((struct Conn*) ev.peer->data)->lua)) {
// Corrupted packet.
break;
}
}
}
enet_packet_destroy(ev.packet);
} else if(ev.type == ENET_EVENT_TYPE_DISCONNECT) {
struct Conn *conn = ev.peer->data;
luaapi_leave(conn->lua);
conn->e = NULL;
}
}
}
void net_server_update() {
pos_update();
enet_host_flush(host);
}
void net_server_assign(ENetPeer *peer, uint16_t idx) {
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_ASSIGN);
b_wu16(&b, idx);
enet_peer_send(peer, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
free(b.data);
((struct Conn*) peer->data)->assigned = idx;
}
void net_server_force_load(ENetPeer *peer, const char *script) {
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_LOAD_SCRIPT);
b_wzstr(&b, script);
enet_peer_send(peer, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
free(b.data);
}
void net_server_send_msg(ENetPeer *peer, struct bstr *data) {
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_MSG);
b_wraw(&b, data->data, data->wi);
enet_peer_send(peer, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
free(b.data);
}
void net_server_broadcast_msg(struct bstr *data) {
struct bstr b;
bstr(&b, 64);
b_wu16(&b, 1);
b_wu16(&b, Game.frame);
b_wu16(&b, CMD_SC_MSG);
b_wraw(&b, data->data, data->wi);
for(size_t i = 0; i < MAX_PLAYERS; i++) {
if(conns[i].e) {
enet_peer_send(conns[i].e, 0, enet_packet_create(b.data, b.wi, ENET_PACKET_FLAG_RELIABLE));
}
}
free(b.data);
}

16
src/net_server.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include<stdint.h>
struct _ENetPeer;
struct bstr;
void net_server_init();
void net_server_receive();
void net_server_update();
void net_server_assign(struct _ENetPeer *peer, uint16_t idx);
void net_server_force_load(struct _ENetPeer *peer, const char *script);
void net_server_send_msg(struct _ENetPeer *peer, struct bstr *data);
void net_server_broadcast_msg(struct bstr *data);

160
src/resman.c Normal file
View File

@ -0,0 +1,160 @@
#include"resman.h"
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include"k3.h"
struct ResMan ResMan;
static uint64_t fnv1a(const char *str) {
uint64_t ret = 14695981039346656037ULL;
while(*str) {
ret ^= *(const uint8_t*) str;
ret *= 1099511628211ULL;
str++;
}
return ret;
}
static struct ResManRes *make(enum ResManType type, const char *name, size_t refs) {
struct ResManRes *ret = malloc(sizeof(*ret));
ret->type = type;
ret->name = strdup(name);
ret->refs = refs;
ret->next = NULL;
ret->thing = NULL;
if(type != RESMAN_OTHER) {
k3Log(k3_INFO, "Loading %s...", name);
if(!ResMan.handlers[type].loader(ResMan.handlers[type].ud, ret)) {
printf("Failed to load resource %s (%i)\n", name, type);
}
}
return ret;
}
struct ResManRes *resman_reffull(enum ResManType type, const char *name) {
size_t bucket = fnv1a(name) % RESMAN_BUCKETS;
struct ResManRes *ptr = ResMan.buckets[bucket];
if(!ptr) {
return (ResMan.buckets[bucket] = make(type, name, 1));
}
struct ResManRes *prev = NULL;
while(ptr) {
if(ptr->type == type && !strcmp(name, ptr->name)) {
ptr->refs++;
return ptr;
}
prev = ptr;
ptr = ptr->next;
}
return (prev->next = make(type, name, 1));
}
void *resman_ref(enum ResManType type, const char *name) {
return resman_reffull(type, name)->thing;
}
void resman_unref(enum ResManType type, const char *name) {
size_t bucket = fnv1a(name) % RESMAN_BUCKETS;
struct ResManRes *prev = NULL;
struct ResManRes *ptr = ResMan.buckets[bucket];
if(!ptr) goto notfound;
while(ptr) {
if(ptr->type == type && !strcmp(name, ptr->name)) {
ptr->refs--;
if(ptr->refs == 0) {
if(ResMan.handlers[type].unloader) {
ResMan.handlers[type].unloader(ResMan.handlers[type].ud, name, ptr->thing);
} else {
printf("Memory leak! No unloader for (%i)!\n", type);
}
if(prev) {
prev->next = ptr->next;
} else {
ResMan.buckets[bucket] = ptr->next;
}
free(ptr->name);
free(ptr);
}
return;
}
prev = ptr;
ptr = ptr->next;
}
notfound:
printf("Attempt to release unallocated resource %s (%i)!\n", name, type);
}
void resman_unref_ptr(enum ResManType type, void *v) {
for(size_t bucket = 0; bucket < RESMAN_BUCKETS; bucket++) {
struct ResManRes *prev = NULL;
struct ResManRes *ptr = ResMan.buckets[bucket];
while(ptr) {
if(ptr->thing == v) {
ptr->refs--;
if(ptr->refs == 0) {
if(ResMan.handlers[type].unloader) {
ResMan.handlers[type].unloader(ResMan.handlers[type].ud, ptr->name, ptr->thing);
} else {
printf("Memory leak! No unloader for (%i)!\n", type);
}
if(prev) {
prev->next = ptr->next;
} else {
ResMan.buckets[bucket] = ptr->next;
}
free(ptr->name);
free(ptr);
}
return;
}
prev = ptr;
ptr = ptr->next;
}
}
notfound:
printf("Attempt to release unnamed unallocated resource (%i)!\n", type);
}
struct ResManRes *resman_rev(void *v) {
for(size_t bucket = 0; bucket < RESMAN_BUCKETS; bucket++) {
struct ResManRes *prev = NULL;
struct ResManRes *ptr = ResMan.buckets[bucket];
while(ptr) {
if(ptr->thing == v) {
return ptr;
}
prev = ptr;
ptr = ptr->next;
}
}
return NULL;
}

46
src/resman.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include<stdint.h>
#include<stddef.h>
enum ResManType {
RESMAN_MODEL, RESMAN_MATERIAL, RESMAN_TEXTURE, RESMAN_PHYSICS, RESMAN_BIN, RESMAN_STREAM, RESMAN_FONT, RESMAN_OTHER, RESMAN_MAX
};
struct ResManRes {
enum ResManType type;
char *name;
void *thing;
size_t refs;
struct ResManRes *next;
};
struct ResManBin {
size_t length;
uint8_t data[];
};
typedef int(*ResManLoader)(void *ud, struct ResManRes*);
typedef void(*ResManUnloader)(void *ud, const char *name, void *data);
struct ResManHandler {
ResManLoader loader;
ResManUnloader unloader;
void *ud;
};
#define RESMAN_BUCKETS 32
extern struct ResMan {
struct ResManHandler handlers[RESMAN_MAX];
struct ResManRes *buckets[RESMAN_BUCKETS];
} ResMan;
struct ResManRes *resman_reffull(enum ResManType, const char*);
void *resman_ref(enum ResManType, const char*);
void resman_unref(enum ResManType, const char*);
void resman_unref_ptr(enum ResManType, void*);
struct ResManRes resman_find(const char*);
struct ResManRes *resman_rev(void*);

25
src/ssort.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include<stddef.h>
#include<string.h>
/* Stable sorting algorithm */
static inline void ssort(void *base, size_t nmemb, size_t size, intmax_t(*compar)(const void*, const void*)) {
void *tmp = alloca(size);
intmax_t i = 1;
while(i < nmemb) {
memcpy(tmp, base + size * i, size);
intmax_t j = i - 1;
while(j >= 0 && compar(base + size * j, tmp) > 0) {
memcpy(base + size * (j + 1), base + size * j, size);
j--;
}
memcpy(base + size * (j + 1), tmp, size);
i++;
}
}

7987
src/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

2634
src/stb_image_resize.h Normal file

File diff suppressed because it is too large Load Diff

395
src/stoon.c Normal file
View File

@ -0,0 +1,395 @@
#include"stoon.h"
#ifdef _WIN32
#include<winsock2.h>
#include<ws2tcpip.h>
#include<ntsecapi.h>
#else
#include<netdb.h>
#include<sys/socket.h>
#include<sys/random.h>
#include<arpa/inet.h>
#endif
#include<unistd.h>
#include<sys/types.h>
#include<sys/time.h>
#include<stdint.h>
#include<string.h>
#include<stdio.h>
#include<errno.h>
#define STUN_BINDING_REQUEST 0x0001
#define STUN_BINDING_INDICATION 0x1100
#define STUN_BINDING_RESPONSE 0x0101
#define STUN_XOR_MAPPED_ADDRESS 0x0020
#define STUN_MAGIC 0x2112A442
#define MAX_ATTRIB_BUFFER_SIZE 128
#define STUN_NETFAM_IPV4 1
#define STUN_NETFAM_IPV6 2
#ifdef _WIN32
#define RAND(b, i) RtlGenRandom(b, i)
#else
#define RAND(b, i) getrandom(b, i, 0)
#endif
struct StunMsg {
uint16_t type;
uint16_t len;
uint32_t magic;
uint8_t id[12];
};
static int stoon_init_mini(struct addrinfo *serv, uint16_t myport) {
#ifdef _WIN32
errno = 0;
int fd = socket(serv->ai_family, serv->ai_socktype, serv->ai_protocol);
ioctlsocket(fd, FIONBIO, &(unsigned long) {1});
#else
int fd = socket(serv->ai_family, serv->ai_socktype | SOCK_NONBLOCK, serv->ai_protocol);
#endif
if(serv->ai_family == AF_INET6) {
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*) &(int) {1}, sizeof(int));
}
char p[6];
sprintf(p, "%u", myport);
struct addrinfo *myaddr;
if(getaddrinfo(NULL, p, &(struct addrinfo) {.ai_family = serv->ai_family, .ai_socktype = SOCK_DGRAM, .ai_protocol = IPPROTO_UDP, .ai_flags = AI_PASSIVE}, &myaddr)) {
close(fd);
return -1;
}
if(bind(fd, myaddr->ai_addr, myaddr->ai_addrlen)) {
close(fd);
return -2;
}
freeaddrinfo(myaddr);
return fd;
}
struct Stoon stoon_init(const char *stunhost, uint16_t stunport, uint16_t myport) {
struct addrinfo *stunaddrs;
struct addrinfo *v4 = NULL;
struct addrinfo *v6 = NULL;
{
char p[6];
sprintf(p, "%u", stunport);
getaddrinfo(stunhost, p, &(struct addrinfo) {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_protocol = IPPROTO_UDP}, &stunaddrs);
for(struct addrinfo *addr = stunaddrs; addr; addr = addr->ai_next) {
if(addr->ai_family == AF_INET6) {
v6 = addr;
} else {
v4 = addr;
}
}
}
struct Stoon ret;
memset(&ret.my4, 0, sizeof(ret.my4));
if(v4) {
memcpy(&ret.stu4, v4->ai_addr, v4->ai_addrlen);
ret.fd4 = stoon_init_mini(v4, myport);
} else ret.fd4 = -1;
memset(&ret.my6, 0, sizeof(ret.my6));
if(v6) {
memcpy(&ret.stu6, v6->ai_addr, v6->ai_addrlen);
ret.fd6 = stoon_init_mini(v6, myport);
} else ret.fd6 = -1;
ret.poonchstage = 0;
freeaddrinfo(stunaddrs);
return ret;
}
static int stoon_req_mini(struct Stoon *this, int *fd, struct sockaddr *serv, int servlen) {
struct StunMsg req = {.type = htons(STUN_BINDING_REQUEST), .len = htons(0), .magic = htonl(STUN_MAGIC)};
RAND(req.id, sizeof(req.id));
if(sendto(*fd, (void*) &req, sizeof(req), 0, serv, servlen) == -1) {
if(errno == ENETUNREACH) {
close(*fd);
*fd = -1;
}
}
}
int stoon_req(struct Stoon *this) {
if(this->fd4 >= 0) stoon_req_mini(this, &this->fd4, (struct sockaddr*) &this->stu4, sizeof(struct sockaddr_in));
if(this->fd6 >= 0) stoon_req_mini(this, &this->fd6, (struct sockaddr*) &this->stu6, sizeof(struct sockaddr_in6));
}
static int stoon_listen_mini(struct Stoon *this, int fd, struct sockaddr *serv, int servlen) {
struct {
struct StunMsg base;
uint8_t attribs[MAX_ATTRIB_BUFFER_SIZE];
} res;
if(recvfrom(fd, (void*) &res, sizeof(res), 0, NULL, 0) < 0) {
return -1;
}
if(ntohs(res.base.type) != STUN_BINDING_RESPONSE) {
puts("Not binding response");
return -3;
}
uint16_t attribsLen = ntohs(res.base.len);
if(attribsLen > MAX_ATTRIB_BUFFER_SIZE) attribsLen = MAX_ATTRIB_BUFFER_SIZE;
for(uint8_t *d = res.attribs;;) {
// Overflow check
if((uintmax_t) (d - res.attribs) > MAX_ATTRIB_BUFFER_SIZE - 4) break;
uint16_t attribType = ntohs(((uint16_t*) d)[0]);
uint16_t attribLen = ntohs(((uint16_t*) d)[1]);
// Overflow check
if((uintmax_t) (d + 4 + attribLen - res.attribs) > MAX_ATTRIB_BUFFER_SIZE) break;
if(attribType == STUN_XOR_MAPPED_ADDRESS) {
uint16_t netfam = ntohs(((uint16_t*) d)[2]);
uint16_t publicPort = ntohs(((uint16_t*) d)[3]) ^ (STUN_MAGIC >> 16);
if(netfam == STUN_NETFAM_IPV4) {
uint32_t publicIp = ntohl(((uint32_t*) d)[2]) ^ STUN_MAGIC;
this->my4.sin_family = AF_INET;
memcpy(&this->my4.sin_addr, &publicIp, 4);
this->my4.sin_port = htons(publicPort);
return 1;
} else if(netfam == STUN_NETFAM_IPV6) {
uint32_t publicIp[4];
publicIp[0] = ((uint32_t*) d)[2] ^ htonl(STUN_MAGIC);
publicIp[1] = ((uint32_t*) d)[3] ^ ((uint32_t*) res.base.id)[0];
publicIp[2] = ((uint32_t*) d)[4] ^ ((uint32_t*) res.base.id)[1];
publicIp[3] = ((uint32_t*) d)[5] ^ ((uint32_t*) res.base.id)[2];
this->my6.sin6_family = AF_INET6;
memcpy(&this->my6.sin6_addr, &publicIp, 16);
this->my6.sin6_port = htons(publicPort);
return 1;
} else {
puts("Neither ipv4 nor ipv6???");
}
}
d += 4 + attribLen;
}
return 0;
}
int stoon_listen(struct Stoon *this) {
if(this->fd4 >= 0) if(stoon_listen_mini(this, this->fd4, (struct sockaddr*) &this->stu4, sizeof(struct sockaddr_in)) < 0) return 0;
if(this->fd6 >= 0) if(stoon_listen_mini(this, this->fd6, (struct sockaddr*) &this->stu6, sizeof(struct sockaddr_in6)) < 0) return 0;
return 1;
}
void stoon_keepalive(struct Stoon *this) {
struct StunMsg req = {.type = htons(STUN_BINDING_INDICATION), .len = htons(0), .magic = htonl(STUN_MAGIC)};
RAND(&req.id, sizeof(req.id));
if(this->fd4 >= 0) {
sendto(this->fd4, (void*) &req, sizeof(req), 0, (struct sockaddr*) &this->stu4, sizeof(struct sockaddr_in));
}
if(this->fd6 >= 0) {
sendto(this->fd6, (void*) &req, sizeof(req), 0, (struct sockaddr*) &this->stu6, sizeof(struct sockaddr_in6));
}
}
void stoon_kill(struct Stoon *this) {
if(this->fd4 >= 0) close(this->fd4);
this->fd4 = -1;
if(this->fd6 >= 0) close(this->fd6);
this->fd6 = -1;
}
int stoon_v4_available(struct Stoon *this) {
return this->fd4 >= 0;
}
int stoon_v6_available(struct Stoon *this) {
return this->fd6 >= 0;
}
void stoon_serialize(struct Stoon *this, uint8_t ret[static STOON_CONN_INFO_SIZE]) {
if(this->fd4 >= 0) {
memcpy(ret + 0, &this->my4.sin_addr, 4);
memcpy(ret + 4, &this->my4.sin_port, 2);
} else {
memset(ret, 0, 6);
}
if(this->fd6 >= 0) {
memcpy(ret + 6, &this->my6.sin6_addr, 16);
memcpy(ret + 22, &this->my6.sin6_port, 2);
} else {
memset(ret + 6, 0, 18);
}
}
int stoon_poonch(struct Stoon *this, const uint8_t peerdata[static STOON_CONN_INFO_SIZE]) {
struct {
uint32_t addr4;
uint16_t port4;
uint8_t addr6[16];
uint16_t port6;
} peer;
memcpy(&peer.addr4, peerdata + 0, 4);
memcpy(&peer.port4, peerdata + 4, 2);
memcpy(&peer.addr6, peerdata + 6, 16);
memcpy(&peer.port6, peerdata + 22, 2);
int myfd = -1;
struct sockaddr_storage peeraddr = {};
int peersz;
// Prioritize IPv6
if(this->my6.sin6_family && peer.port6) {
myfd = this->fd6;
struct sockaddr_in6 *pee = (void*) &peeraddr;
pee->sin6_family = AF_INET6;
memcpy(&pee->sin6_addr, &peer.addr6, 16);
pee->sin6_port = peer.port6;
peersz = sizeof(*pee);
} else if(this->my4.sin_family && peer.port4) {
myfd = this->fd4;
struct sockaddr_in *pee = (void*) &peeraddr;
pee->sin_family = AF_INET;
memcpy(&pee->sin_addr, &peer.addr4, 4);
pee->sin_port = peer.port4;
peersz = sizeof(*pee);
} else {
return STOON_POONCH_INCOMPATIBLE_NETFAMS;
}
if(this->poonchstage == POONCH_STAGE_KNOCKING) {
char buf[64];
inet_ntop(peeraddr.ss_family, peeraddr.ss_family == AF_INET ? peerdata + 0 : peerdata + 6, buf, sizeof(buf));
printf("Sending knock to %s:%u\n", buf, peeraddr.ss_family == AF_INET ? ntohs(peer.port4) : ntohs(peer.port6));
struct Poonch cmd = {.magic = POONCH_MAGIC, .stage = POONCH_STAGE_KNOCKING};
sendto(myfd, (void*) &cmd, sizeof(cmd), 0, (struct sockaddr*) &peeraddr, peersz);
}
struct Poonch comin;
if(recvfrom(myfd, (void*) &comin, sizeof(comin), 0, NULL, NULL) == sizeof(comin) && comin.magic == POONCH_MAGIC) {
puts("received somethign");
if(comin.stage == POONCH_STAGE_KNOCKING) {
puts("Received knock");
struct Poonch ret = {.magic = POONCH_MAGIC, .stage = POONCH_STAGE_ACK};
this->poonchstage = POONCH_STAGE_ACK;
sendto(myfd, (void*) &ret, sizeof(ret), 0, (struct sockaddr*) &peeraddr, peersz);
return STOON_POONCH_ACKED;
}
}
return STOON_POONCH_NO_KNOCK;
}
#ifdef STOON_STANDALONE
int main() {
#ifdef _WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1, 1), &wsadata);
#endif
struct Stoon stoon = stoon_init("stun.l.google.com", 19305, 26656);
stoon_req(&stoon);
usleep(1000000);
stoon_listen(&stoon);
stoon_keepalive(&stoon);
char buf[64];
fputs("IPv4: ", stdout);
if(stoon.my4.sin_family) {
inet_ntop(AF_INET, &stoon.my4.sin_addr, buf, sizeof(buf));
printf("%s:%u\n", buf, ntohs(stoon.my4.sin_port));
} else {
puts("unavailable");
}
fputs("IPv6: ", stdout);
if(stoon.my6.sin6_family) {
inet_ntop(AF_INET6, &stoon.my6.sin6_addr, buf, sizeof(buf));
printf("[%s]:%u\n", buf, ntohs(stoon.my6.sin6_port));
} else {
puts("unavailable");
}
uint8_t r[STOON_CONN_INFO_SIZE];
stoon_serialize(&stoon, r);
fputs("Serialized: ", stdout);
for(int i = 0; i < STOON_CONN_INFO_SIZE; i++) {
printf("%02x", r[i]);
}
fputc(10, stdout);
puts("Enter peer's connection string:");
char peerdatareadable[128];
fgets(peerdatareadable, sizeof(peerdatareadable), stdin);
uint8_t peerdata[STOON_CONN_INFO_SIZE] = {};
for(int i = 0;; i += 2) {
if(peerdatareadable[i] == 0) break;
int b = 0;
int c = peerdatareadable[i];
if(c >= '0' && c <= '9') {
b |= (c - '0') << 4;
} else if(c >= 'a' && c <= 'f') {
b |= (c - 'a' + 10) << 4;
}
c = peerdatareadable[i + 1];
if(c >= '0' && c <= '9') {
b |= (c - '0');
} else if(c >= 'a' && c <= 'f') {
b |= (c - 'a' + 10);
}
peerdata[i / 2] = b;
}
int got = 0;
while(1) {
if(stoon_poonch(&stoon, peerdata) == STOON_POONCH_NO_KNOCK) {
got++;
if(got >= 10 && stoon.poonchstage == POONCH_STAGE_ACK) {
puts("Gape successful!");
break;
}
} else {
got = 0;
}
usleep(500000);
}
stoon_kill(&stoon);
}
#endif

64
src/stoon.h Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#include<stddef.h>
#include<stdint.h>
#ifdef _WIN32
#include<winsock2.h>
#include<ws2tcpip.h>
#else
#include<netdb.h>
#endif
/* Standalone hole punching libary (IPv4 & IPv6 compatible) */
#define STOON_CONN_INFO_SIZE (4 + 2 + 16 + 2)
#define POONCH_MAGIC 0x504F4348
#define POONCH_STAGE_KNOCKING 0
#define POONCH_STAGE_ACK 1
struct Poonch {
uint32_t magic;
uint32_t stage;
};
struct Stoon {
struct sockaddr_in stu4;
struct sockaddr_in my4;
int fd4;
struct sockaddr_in6 stu6;
struct sockaddr_in6 my6;
int fd6;
uint8_t poonchstage;
};
// stunhost: Self-explanatory
// stunport: Self-explanatory
// myport: Local endpoint port
struct Stoon stoon_init(const char *stunhost, uint16_t stunport, uint16_t myport);
int stoon_req(struct Stoon*);
int stoon_listen(struct Stoon*);
// This is necessary in order to keep the NAT table entry active
// Some NATs wait as little as 25 seconds, so an interval *less* than
// 25s is recommended
void stoon_keepalive(struct Stoon*);
void stoon_kill(struct Stoon*);
int stoon_v4_available(struct Stoon*);
int stoon_v6_available(struct Stoon*);
void stoon_serialize(struct Stoon*, uint8_t ret[static STOON_CONN_INFO_SIZE]);
#define STOON_POONCH_INCOMPATIBLE_NETFAMS (-1)
#define STOON_POONCH_NO_KNOCK 0
#define STOON_POONCH_ACKED 1
int stoon_poonch(struct Stoon*, const uint8_t peerdata[static STOON_CONN_INFO_SIZE]);