Initial commit
This commit is contained in:
commit
49c44fe87c
27
.gitea/workflows/k4.yaml
Normal file
27
.gitea/workflows/k4.yaml
Normal 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
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "k3"]
|
||||
path = k3
|
||||
url = https://mid.net.ua/git/mid/k3
|
36
Makefile
Normal file
36
Makefile
Normal 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
247
k3ex.py
Normal 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
77
physex.py
Normal 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
1479
src/cwalk.c
Normal file
File diff suppressed because it is too large
Load Diff
498
src/cwalk.h
Normal file
498
src/cwalk.h
Normal 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
6069
src/enet.h
Normal file
File diff suppressed because it is too large
Load Diff
898
src/game.c
Normal file
898
src/game.c
Normal 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
254
src/game.h
Normal 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();
|
395
src/k3mix.c
Normal file
395
src/k3mix.c
Normal 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
50
src/k3mix.h
Normal 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
41
src/k4.h
Normal 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
973
src/komihash.h
Normal 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
347
src/loaders.inc
Normal 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
3422
src/luaapi.c
Normal file
File diff suppressed because it is too large
Load Diff
51
src/luaapi.h
Normal file
51
src/luaapi.h
Normal 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
962
src/main.c
Normal 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
206
src/net.h
Normal 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
287
src/net_client.c
Normal 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
8
src/net_client.h
Normal 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
344
src/net_server.c
Normal 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
16
src/net_server.h
Normal 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
160
src/resman.c
Normal 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
46
src/resman.h
Normal 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
25
src/ssort.h
Normal 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
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
2634
src/stb_image_resize.h
Normal file
File diff suppressed because it is too large
Load Diff
395
src/stoon.c
Normal file
395
src/stoon.c
Normal 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
64
src/stoon.h
Normal 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]);
|
Loading…
Reference in New Issue
Block a user