diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e69ff0..67814bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,9 +48,10 @@ set(MAIN_FILES
src/model.c
src/network.c
src/path.c
+ src/png.c
+ src/random.c
src/vecmath.c
src/wav.c
- src/png.c
)
set(GL_FILES
diff --git a/docs/modding_lua.txt b/docs/modding_lua.txt
index 0415266..93924c0 100644
--- a/docs/modding_lua.txt
+++ b/docs/modding_lua.txt
@@ -726,6 +726,20 @@ common.json_write(fname, value) @
to tell the difference between tables and arrays it checks for the existence of the key "1"
+common.prng_new(seed, stream) @
+ creates a new pseudo-random number generator instance
+
+ seed and stream are both optional
+
+ returns a table of the following functions:
+ - seed(seed, stream)
+ - allows reseeding of the RNG, both arguments are optional
+ - random()
+ - random(max)
+ - random(min, max)
+ - returns a random number between min and max, defaulting to values of 0 and 1
+
+
str = common.net_pack(fmt, ...) @
packs data into a string
diff --git a/include/common.h b/include/common.h
index 218e6ef..ad644a5 100644
--- a/include/common.h
+++ b/include/common.h
@@ -449,6 +449,11 @@ enum
BT_MAX
};
+typedef struct prng {
+ uint64_t state;
+ uint64_t stream;
+} prng_t;
+
typedef struct packet packet_t;
struct packet
{
@@ -688,6 +693,12 @@ img_t *img_parse_png(int len, const char *data, lua_State *L);
img_t *img_load_png(const char *fname, lua_State *L);
void img_write_png(const char *fname, img_t *img);
+// random.c
+void prng_seed(prng_t *rng, uint64_t seed, uint64_t stream);
+uint32_t prng_random(prng_t *rng);
+double prng_random_double(prng_t *rng);
+double prng_random_double_range(prng_t *rng, double minimum, double maximum);
+
// vecmath.c
vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec);
void mtx_identity(matrix_t *mtx);
diff --git a/src/lua.c b/src/lua.c
index 689bb2d..1d13058 100644
--- a/src/lua.c
+++ b/src/lua.c
@@ -193,6 +193,7 @@ int icelua_fn_client_mk_set_title(lua_State *L)
#include "lua_mus.h"
#include "lua_model.h"
#include "lua_net.h"
+#include "lua_random.h"
#include "lua_tcp.h"
#include "lua_udp.h"
#include "lua_util.h"
@@ -319,6 +320,7 @@ struct icelua_entry icelua_common[] = {
{icelua_fn_common_net_unpack_array, "net_unpack_array"},
{icelua_fn_common_net_send, "net_send"},
{icelua_fn_common_net_recv, "net_recv"},
+ {icelua_fn_common_prng_new, "prng_new"},
{icelua_fn_common_tcp_connect, "tcp_connect"},
{icelua_fn_common_tcp_send, "tcp_send"},
{icelua_fn_common_tcp_recv, "tcp_recv"},
diff --git a/src/lua_random.h b/src/lua_random.h
new file mode 100644
index 0000000..91fab0e
--- /dev/null
+++ b/src/lua_random.h
@@ -0,0 +1,104 @@
+/*
+ This file is part of Iceball.
+
+ Iceball is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Iceball is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Iceball. If not, see .
+*/
+
+// Notes:
+// * There's no `get_seed` function because state is stored in a uint64, which
+// we can't shove into a double. We could return the original seed and steps
+// taken, but without a way to take arbitrary steps, this is pretty useless.
+// It would also require storing more state, but it's only 128 bits at the
+// moment, which is already pretty small for a PRNG.
+// * We could add a clone function that creates a new one with the same state.
+// Useless for networking though.
+
+int icelua_fn_cl_prng_seed(lua_State *L)
+{
+ int top = icelua_assert_stack(L, 0, 2);
+ uint64_t seed = 0; // TODO: Seed from time or something, if not given
+ uint64_t stream = 0;
+ if (top >= 1) {
+ seed = (uint64_t)lua_tointeger(L, 1);
+ if (top >= 2) {
+ stream = (uint64_t)lua_tointeger(L, 2);
+ }
+ }
+ prng_t *rng = lua_touserdata(L, lua_upvalueindex(1));
+ prng_seed(rng, seed, stream);
+ return 0;
+}
+
+// Simulates Lua's math.random
+// 0 args: [0-1]
+// 1 args: [0-max]
+// 2 args: [min-max]
+int icelua_fn_cl_prng_random(lua_State *L)
+{
+ int top = icelua_assert_stack(L, 0, 2);
+ prng_t *rng = lua_touserdata(L, lua_upvalueindex(1));
+ double result;
+ if (top == 0) {
+ result = prng_random_double(rng);
+ } else {
+ double minimum;
+ double maximum;
+ if (top == 1) {
+ minimum = 0;
+ maximum = lua_tonumber(L, 1);
+ } else {
+ minimum = lua_tonumber(L, 1);
+ maximum = lua_tonumber(L, 2);
+ }
+ result = prng_random_double_range(rng, minimum, maximum);
+ }
+ lua_pushnumber(L, result);
+ return 1;
+}
+
+int icelua_fn_common_prng_new(lua_State *L)
+{
+ int top = icelua_assert_stack(L, 0, 2);
+ uint64_t seed = 0; // TODO: Seed from time or something, if not given
+ uint64_t stream = 0;
+ if (top >= 1) {
+ seed = (uint64_t)lua_tointeger(L, 1);
+ if (top >= 2) {
+ stream = (uint64_t)lua_tointeger(L, 2);
+ }
+ }
+
+ // "this" table
+ lua_createtable(L, 0, 4);
+
+ // PRNG state - uses upvalues, not visible to Lua, but hey, GC
+ prng_t *rng = lua_newuserdata(L, sizeof(prng_t));
+ prng_seed(rng, seed, stream);
+
+ lua_pushstring(L, "seed"); // Function name
+ lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack
+ lua_pushcclosure(L, &icelua_fn_cl_prng_seed, 1); // Create closure, 1 upvalue (the RNG state)
+ lua_settable(L, -4); // Insert closure into table
+
+ lua_pushstring(L, "random"); // Function name
+ lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack
+ lua_pushcclosure(L, &icelua_fn_cl_prng_random, 1); // Create closure, 1 upvalue (the RNG state)
+ lua_settable(L, -4); // Insert closure into table
+
+ // Pop the RNG state off the stack.
+ lua_pop(L, 1);
+
+ return 1;
+
+}
diff --git a/src/random.c b/src/random.c
new file mode 100644
index 0000000..29c940e
--- /dev/null
+++ b/src/random.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of Iceball.
+
+ Iceball is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Iceball is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Iceball. If not, see .
+*/
+
+#include "common.h"
+
+// Basic implementation of the PCG psuedorandom number generator.
+// Specifically, PCG-XSH-RR, as that's what the paper recommends for general use.
+// This may diverge from the official implementations, but I wanted to give credit.
+// TODO: We could drop the stream part and use a set value. We have no real use
+// for it, and it complicates the API. It does give us more possible sequences though.
+// On the other hand, using a set value does allow us to ensure that a relatively
+// good value is chosen (co-primes, etc.).
+
+#define PRNG_MULTIPLIER 6364136223846793005ULL
+
+void prng_seed(prng_t *rng, uint64_t seed, uint64_t stream) {
+ rng->state = 0;
+ // This must be odd (although it will still work sub-optimally if even)
+ rng->stream = (stream << 1) | 1;
+ // Initialise the state
+ prng_random(rng);
+ rng->state += seed;
+ // Properly initialise the state (diverge the streams)
+ prng_random(rng);
+}
+
+uint32_t prng_random(prng_t *rng) {
+ uint64_t state = rng->state;
+
+ // Update stored state
+ rng->state = state * PRNG_MULTIPLIER + rng->stream;
+
+ // Generate number
+ // Top 5 bits specify rotation (for 32 bit result):
+ // * 64 - 5 = 59
+ // * 32 - 5 = 27
+ // * (5 + 32) / 2 = 18
+ uint32_t xor_shifted = (uint32_t)(((state >> 18) ^ state) >> 27);
+ uint32_t rotation = (uint32_t)(state >> 59);
+ return (xor_shifted >> rotation) | (xor_shifted << (-rotation & 31));
+}
+
+double prng_random_double(prng_t *rng) {
+ uint32_t random = prng_random(rng);
+ return (double)random / UINT32_MAX;
+}
+
+double prng_random_double_range(prng_t *rng, double minimum, double maximum) {
+ uint32_t random = prng_random(rng);
+ double d = (double)random / UINT32_MAX;
+ return minimum + d * (maximum - minimum);
+}