Files
integration/luprex/cpp/core/world-accessor.cpp

1134 lines
39 KiB
C++
Raw Normal View History

#include "world.hpp"
#include "pprint.hpp"
#include "serializelua.hpp"
#include <cmath>
LuaTokenConstant(tangible_auto, "auto", "");
static void tangible_getall(LuaCoreStack &LS0, LuaSlot list, const util::IdVector &idv) {
LuaVar tangibles, tan;
LuaExtStack LS(LS0.state(), tangibles, tan);
LS.rawget(tangibles, LuaRegistry, "tangibles");
assert(LS.istable(tangibles));
LS.set(list, LuaNewTable);
int index = 1;
for (int64_t id : idv) {
LS.rawget(tan, tangibles, id);
assert(LS.istable(tan));
LS.rawset(list, index++, tan);
}
}
LuaDefine(tangible_xyz, "tan",
"|Get the current coordinates of the tangible and the plane."
"|Returns four values: x, y, z, plane") {
LuaArg tanobj;
LuaRet x, y, z, plane;
LuaDefStack LS(L, tanobj, x, y, z, plane);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimCoreState pos = tan->anim_queue_.get_final_core_state();
LS.set(x, pos.xyz.x);
LS.set(y, pos.xyz.y);
LS.set(z, pos.xyz.z);
LS.set(plane, pos.plane);
return LS.result();
}
LuaDefine(tangible_animdebug, "tan",
"|Return a debug string showing the entire animation queue"
"|") {
LuaArg tanobj;
LuaRet result;
LuaDefStack LS(L, tanobj, result);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
LS.set(result, tan->anim_queue_.steps_debug_string());
return LS.result();
}
2023-10-03 18:17:24 -04:00
LuaDefine(tangible_animfinal, "tan",
"|Return the final step in the animation queue."
"|"
2023-10-03 18:17:24 -04:00
"|The animation queue stores animation steps. This function returns"
"|the final animation step. An animation step consists of key-value"
"|pairs. Some of those key-value pairs describe the last thing that"
"|happened to the tangible. Others describe the final resting place"
"|of the tangible."
"|"
2023-10-03 18:17:24 -04:00
"|For example, if the tangible were a pirate chest, the key-value"
"|pairs might be:"
"|"
"| action='open' # last thing the chest did"
"| xyz={1,2,3} # xyz coordinate"
"| plane='earth' # plane where the chest is located"
"| facing=0 # rotation of the chest"
"| open=true # chest can be open or closed"
"| fullness=0.8 # how big the heap of coins is"
"|"
2023-10-03 18:17:24 -04:00
"|See doc(tangible.animinit) for more information about animation queues."
"|") {
LuaArg tanobj;
LuaRet result;
LuaDefStack LS(L, tanobj, result);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
2023-10-03 18:17:24 -04:00
AnimState state = tan->anim_queue_.get_final_everything();
state.to_lua(LS, result, true, true);
return LS.result();
}
LuaDefine(tangible_animinit, "tan,config",
2023-10-03 18:17:24 -04:00
"|Reinitialize the animation queue and specify persistent state."
"|"
"|Every tangible has an animation queue. The queue consists of a"
"|sequence of animation steps. Each step consists of a list of"
"|key-value pairs. For example, if you want a human person to jump"
"|three feet in the air, you might find this animation step in the"
"|animation queue:"
"|"
"| action='jump' - the name of the animation to perform"
"| height=3.0 - the height to which you want him to jump"
"| xyz={1,2,3} - person's xyz coordinate during the jump"
"| plane=earth - plane where the jump takes place"
"|"
"|Some of those key-value pairs are 'persistent'. For example, xyz is"
"|persistent. That means that the player must always have an xyz"
"|coordinate. Every single animation step in the queue must"
"|contain a value for xyz. Likewise, 'plane' is a persistent variable."
"|The player must always be on some plane or another."
"|"
"|When you add an animation step to the animation queue, you do not have"
"|to always specify xyz and plane. For example, you can legally say:"
"|"
"| tangible.animate(a, nil, {action='jump', height=3.0}))"
"|"
"|This adds a step to the animation queue. That step contains"
"|xyz and plane, even though we didn't specify xyz and plane in"
"|the 'animate' command above. The values for xyz and plane will be"
"|copied over from the previous animation step. In this way, those values"
"|get persisted: they stay the same unless you change them in"
"|the 'animate' command."
"|"
"|There are four hardwired persistent variables: plane,xyz,facing,bp."
"|These variables are persistent no matter what. This function,"
2023-10-03 18:17:24 -04:00
"|tangible.animinit, optionally allows you to create more persistent"
"|variables. For example, let's say you have a pirate chest. You might"
"|want to add two persistent variables in addition to the usual set:"
"|"
"| open=true - whether the chest is open or closed"
"| heapsize=0.8 - the size of the heap of coins"
"|"
"|Making a variable persistent means that it will always have a value"
"|no matter what you do."
"|"
"|This function, tangible.animinit, is used to reconfigure the set of"
2023-10-03 18:17:24 -04:00
"|persistent state variables that are retained by the tangible's"
"|animation queue. You must provide a table containing all the"
"|persistent values you want, and their initial values."
"|") {
LuaArg tanobj, config;
LuaDefStack LS(L, tanobj, config);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state;
eng::string error = state.from_lua(LS, config, true, false);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
AnimState defsource = tan->anim_queue_.get_final_persistent();
error = state.add_defaults(&defsource);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
tan->anim_queue_.clear(state);
tan->update_plane_item();
return LS.result();
}
2023-10-03 18:17:24 -04:00
LuaDefine(tangible_animate, "tan,options,config",
"|Add an animation step to the tangible."
"|"
"|The animation queue stores animation steps. This function, "
"|tangible.animate, adds one new animation step to the queue."
"|"
2023-10-03 18:17:24 -04:00
"|It might be useful to read doc(tangible.animinit) before reading"
"|more."
"|"
"|An animation step is just a list of key-value pairs. Therefore,"
"|the config table must be key-value pairs. Keys must be simple"
"|identifiers. Values can be numbers, strings, booleans, or"
"|coordinates."
"|"
2023-10-03 18:17:24 -04:00
"|Some of the key-value pairs may match the name of a persistent"
"|variable. If so, that key-value pair permanently changes the"
2023-10-03 18:17:24 -04:00
"|value of that persistent variable. The new value will"
"|be retained for all future animation steps."
"|"
2023-10-03 18:17:24 -04:00
"|Some of the key-value pairs may not match the name of any persistent"
"|variable. If so, that key-value pair is part of the"
"|animation step, but nothing is propagated forward to future animation"
"|steps."
"|"
2023-10-03 18:17:24 -04:00
"|The options can be nil, or options can be a table containing"
"|the following flags:"
"|"
"| replace: if true, then the last step in the queue is removed,"
"| and the new animation replaces it. Persistent state is carried"
"| over from the step that was replaced."
"|"
"|") {
2023-10-03 18:17:24 -04:00
LuaArg tanobj, options, steptab;
LuaVar option;
LuaDefStack LS(L, option, tanobj, options, steptab);
bool replace = false;
if (!LS.isnil(options)) {
LuaKeywordParser kp(LS, options);
if (kp.optional(option, "replace")) {
2023-10-03 18:17:24 -04:00
replace = LS.ckboolean(option);
}
kp.final_check_throw();
2023-10-03 18:17:24 -04:00
}
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState previous = tan->anim_queue_.get_final_persistent();
AnimState update;
eng::string error = update.from_lua(LS, steptab, false, true);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
AnimState merge;
error = merge.merge(previous, update);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
}
2023-10-03 18:17:24 -04:00
if (replace) {
tan->anim_queue_.replace(merge);
2023-10-03 18:17:24 -04:00
} else {
tan->anim_queue_.add(merge);
2023-10-03 18:17:24 -04:00
}
tan->update_plane_item();
return LS.result();
}
2023-10-03 18:17:24 -04:00
LuaDefine(tangible_setclass, "tan,class",
"|Set the class of the tangible."
"|"
"|The class can be a 'class table' (ie, a table of methods), "
"|or it can be a string that names a class. The tangible is "
"|given an __index metamethod that points at the class table."
"|") {
LuaArg tanobj, classname;
LuaVar classtab, mt;
LuaDefStack LS(L, tanobj, classname, classtab, mt);
World *w = World::fetch_global_pointer(L);
w->tangible_get(LS, tanobj, false);
eng::string err = LS.getclass(classtab, classname);
if (err != "") {
luaL_error(L, "%s", err.c_str());
2021-10-13 19:41:59 -04:00
}
LS.getmetatable(mt, tanobj);
LS.rawset(mt, "__index", classtab);
return LS.result();
}
LuaDefine(tangible_getclassname, "tan",
"|Get the classname of the tangible, if any."
"|"
"|The return value is a string (or nil)."
"|") {
LuaArg tanobj;
LuaRet classname;
LuaDefStack LS(L, tanobj, classname);
World *w = World::fetch_global_pointer(L);
w->tangible_get(LS, tanobj, false);
eng::string name = LS.classname(tanobj);
if (name == "") {
LS.set(classname, LuaNil);
} else {
LS.set(classname, name);
}
return LS.result();
}
LuaDefine(tangible_getclass, "tan",
"|Get the class of the tangible, if any."
"|"
"|The return value is a class table (or nil)."
"|") {
LuaArg tanobj;
LuaRet classtab;
LuaDefStack LS(L, tanobj, classtab);
World *w = World::fetch_global_pointer(L);
w->tangible_get(LS, tanobj, false);
LS.tangetclass(classtab, tanobj);
return LS.result();
}
LuaDefine(tangible_delete, "tan",
"|Delete the specified tangible."
"|"
"|This cannot be used to delete player tangibles,"
"|To delete a player, use tangible.redirect") {
LuaArg tanobj;
LuaDefStack LS(L, tanobj);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, true);
if (tan == nullptr) {
return LS.result();
}
if (tan->is_an_actor()) {
luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead.");
return 0;
}
w->tangible_delete(tan->id());
return LS.result();
}
LuaDefine(tangible_build, "config",
"|Build a new tangible object."
"|"
"|The configuration table must contain the keyword 'class', which"
"|must be the name of a class created with 'makeclass'. It may also"
"|contain the following values:"
"|"
"| bp - the unreal blueprint, defaults to class name."
"| plane - the plane, defaults to actor.plane"
"| xyz - the xyz coordinate, defaults to actor.xyz"
"| facing - the rotation, defaults to actor.facing"
"|"
"|Tangible.build will create an initial animstate containing only"
"|bp, plane, xyz, and facing."
"|"
"|After creating the tangible and setting up the initial animation"
"|state, build will call the constructor for the tangible."
"|The constructor must have this prototype:"
"|"
"| function myclass.init(place, actor, config)"
"|"
"|The configuration table passed to tangible.build is then passed"
"|directly to the init function. The constructor should use the keywords"
"|module to parse the keyword arguments."
"|"
"|The constructor is not allowed to block."
){
LuaArg config;
LuaVar classname, classtab, bp, plane, xyz, facing, mt, func;
LuaRet database;
LuaDefStack LS(L, config, classname, classtab, bp, plane, xyz, facing, mt, func, database);
World *w = World::fetch_global_pointer(L);
LuaKeywordParser kp(LS, config);
kp.required(classname, "class");
kp.optional(bp, "bp");
kp.optional(plane, "plane");
kp.optional(xyz, "xyz");
kp.optional(facing, "facing");
kp.check_throw();
// Verify the class.
eng::string err = LS.getclass(classtab, classname);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
// Calculate the initial animation state.
AnimState state;
if (!LS.isnil(bp)) {
state.set_string("bp", LS.ckstring(bp));
} else {
state.set_string("bp", LS.classname(classtab));
}
if (!LS.isnil(plane)) {
state.set_string("plane", LS.ckstring(plane));
}
if (!LS.isnil(xyz)) {
state.set_dxyz("xyz", LS.ckxyz(xyz));
}
if (!LS.isnil(facing)) {
state.set_number("facing", LS.cknumber(facing));
}
// Add default values from the actor. Set persistent flags.
Tangible *actor = w->tangible_get(w->lthread_actor_id_);
AnimState actorstate = actor->anim_queue_.get_final_persistent();
err = state.add_defaults(&actorstate);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
int64_t new_id = w->alloc_id_predictable();
Tangible *tan = w->tangible_make(LS, database, new_id);
// Update the class of the new tangible.
LS.getmetatable(mt, database);
LS.rawset(mt, "__index", classtab);
// Initialize the animstate of the new tangible.
tan->anim_queue_.clear(state);
tan->update_plane_item();
// Call the constructor and finish.
LS.rawget(func, classtab, "init");
if (!LS.isfunction(func)) {
return LS.result();
}
return LS.tailcall(false, func, database, config);
}
LuaDefine(tangible_get, "id",
"|Get the tangible with the specified id."
"|This is for debugging only and will be removed in"
"|the released version.") {
LuaArg id;
LuaVar tangibles;
LuaRet database;
LuaDefStack LS(L, id, tangibles, database);
int64_t nid = LS.ckinteger(id);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(database, tangibles, id);
if (!LS.istable(database)) {
luaL_error(L, "Not a tangible ID: %d", nid);
}
return LS.result();
}
LuaDefine(tangible_redirect, "tan1,tan2,bulldozetan1",
"|Redirect is not working yet") {
LuaArg actor1, actor2, bldz;
LuaDefStack LS(L, actor1, actor2, bldz);
World *w = World::fetch_global_pointer(L);
bool bulldoze = LS.ckboolean(bldz);
Tangible *tan1 = w->tangible_get(LS, actor1, false);
if (!tan1->is_an_actor()) {
luaL_error(L, "redirect source is not an actor");
}
if (LS.isnil(actor2)) {
w->redirects_[tan1->id()] = 0;
} else {
Tangible *tan2 = w->tangible_get(LS, actor2, false);
tan2->configure_id_pool_for_actor();
w->redirects_[tan1->id()] = tan2->id();
}
if (bulldoze) {
w->tangible_delete(tan1->id());
}
return LS.result();
}
LuaDefine(tangible_id, "tan",
"|Return the tangible's id number."
"|This is for debugging only and will be removed"
"|in the released version.") {
LuaArg tanobj;
LuaRet id;
LuaDefStack LS(L, tanobj, id);
2021-11-26 12:28:59 -05:00
int64_t tid = LS.tanid(tanobj);
if (tid == 0) {
luaL_error(L, "Not a tangible");
}
LS.set(id, tid);
return LS.result();
}
LuaDefine(tangible_actor, "",
"|Return the current actor.") {
LuaRet actor;
LuaVar tangibles;
LuaDefStack LS(L, tangibles, actor);
World *w = World::fetch_global_pointer(L);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(actor, tangibles, w->lthread_actor_id_);
return LS.result();
}
LuaDefine(tangible_place, "",
"|Return the current place.") {
LuaRet place;
LuaVar tangibles;
LuaDefStack LS(L, tangibles, place);
World *w = World::fetch_global_pointer(L);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(place, tangibles, w->lthread_place_id_);
return LS.result();
}
LuaDefine(tangible_find, "config",
"|Find tangibles by their location."
"|"
"|There are multiple ways to specify the plane, the bounding"
"|box, and the shape of the search. The most basic is to"
"|include these parameters in the table:"
"|"
"| plane : the plane to search (a string)"
"| center : xyz of the center of the search (a vector)"
"| radius : the radius of the search (a vector or number)"
"| shape : 'box', 'sphere', or 'cylinder'"
"|"
"|Shape has a default: 'sphere'. The other parameters do"
"|not have default values."
"|"
"|If you specify the radius as a vector, ie, different radii in"
"|each dimension, then the 'sphere' shape will actually be a"
"|spheroid, and the 'cylinder' shape will actually be a cylindroid."
"|"
"|Instead of specifying the center and plane, you can specify"
"|a tangible to search near:"
"|"
"| near : a tangible in whose vicinity the search occurs"
"|"
"|If you specify 'near', then by default, the near tangible is"
"|filtered out of the search. If you want to include it in the"
"|results, set the 'include' flag to true."
"|"
"| include : include the 'near' object in the results"
"|"
"|If you want to search an entire plane, you can use the"
"|wholeplane option, which replaces all the other options:"
"|"
"| wholeplane: the name of the plane to scan in its entirety"
"|"
"|It is valid to use 0.0 as a radius. For example, you could"
"|use shape='cylinder' and radiusz=0.0 to scan a flat"
"|circular area."
"|"
"|It is also valid to use math.huge (infinity) as a radius. For"
"|example, you could use shape='cylinder' and radius.z=math.huge"
"|to scan an infinitely tall cylinder."
"|"
"|If you are making a 2D game, it works fine to set all object Z"
"|coordinates to zero, and then do searches with centerz=0.0"
"|and radiusz=0.0."
"|") {
LuaArg config;
LuaRet result;
LuaDefStack LS(L, config, result);
LuaKeywordParser kw(LS, config);
PlaneScan scan;
scan.configure(kw);
2022-07-22 17:07:40 -04:00
kw.final_check_throw();
World *w = World::fetch_global_pointer(L);
util::IdVector idv;
w->get_near(scan, &idv);
tangible_getall(LS, result, idv);
return LS.result();
}
2022-05-16 14:21:09 -04:00
LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
"|Start a thread."
"|"
"|Every thread is owned by a tangible. The first argument"
"|to 'tangible.start' indicates the tangible that owns"
2022-05-16 15:17:08 -04:00
"|the new thread. Instead of passing a single tangible,"
"|you can pass a list of tangibles, in which case a thread"
"|is started on each tangible."
2022-05-16 14:21:09 -04:00
"|"
"|The function can be a lua closure, or it can be a string."
"|If it's a string, then the tangible's class will be"
"|used to look up the relevant closure."
"|"
"|The arguments arg1,arg2... will be passed to the"
"|function."
"|"
"|Actor and place aren't passed to the function unless"
"|you manually include them in the list arg1, arg2, etc."
"|The new thread can, however, use the builtin "
"|functions 'tangible.actor' and 'tangible.place' to obtain "
"|actor and place. Actor will be the same actor who "
"|called 'tangible.start'. Place will be the tangible that"
"|owns the thread, ie, the tangible passed to 'tangible.start'."
"|"
"|The new thread doesn't start running instantly:"
"|it waits until the current thread is finished. The"
"|current thread must either block (eg, 'wait') or terminate"
"|before the new thread can actually begin execution."
"|"
"|If you start a thread, then start another, then both of"
"|the newly-started threads wait until the current thread"
"|is finished. At that point, it is undefined which of the"
"|two new threads runs first."
"|"
"|Threads are owned by tangibles. If a tangible is"
"|deleted, then none of its threads will ever be resumed,"
"|for any reason."
"|"
"|However, if a thread deletes its own place (ie, the tangible"
"|that owns the thread), then the thread will be allowed"
"|to continue running until it blocks. But from that point"
"|forward, the thread will never be resumed for any reason.") {
int top = lua_gettop(L);
if (top < 2) {
luaL_error(L, "Not enough arguments to tangible.start");
return 0;
}
int varlen = top - 2;
World *w = World::fetch_global_pointer(L);
2022-05-16 15:17:08 -04:00
w->guard_blockable(L, "tangible.start");
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
LuaVar mt, classtab, plthreads, thread, thinfo, func, tanlist;
LuaDefStack LS(L, mt, classtab, plthreads, thread, thinfo, func, tanlist);
2022-05-16 14:21:09 -04:00
LuaSpecial place(1);
2022-05-16 15:17:08 -04:00
LuaSpecial fname(2);
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
// If they passed in a single tangible, convert it to a tangible list.
2022-05-16 14:21:09 -04:00
int64_t place_id = LS.tanid(place);
2022-05-16 15:17:08 -04:00
if (place_id != 0) {
LS.newtable(tanlist);
LS.rawset(tanlist, 1, place);
} else {
LS.set(tanlist, place);
if (!LS.istable(tanlist)) {
luaL_error(L, "tangible.start expects a tangible or list of tangibles");
2022-05-16 14:21:09 -04:00
return 0;
}
}
// Check the entire tangible list for validity.
for (int i = 1; ; i++) {
LS.rawget(place, tanlist, i);
if (LS.isnil(place)) break;
w->tangible_get(LS, place, false);
}
// Start threads on all the tangibles.
2022-05-16 15:17:08 -04:00
for (int i = 1; ; i++) {
LS.rawget(place, tanlist, i);
if (LS.isnil(place)) break;
// Confirm that the place is a valid tangible,
// and get the tangible ID.
place_id = LS.tanid(place);
// Get place's metatable and threads table.
LS.getmetatable(mt, place);
assert(LS.istable(mt));
LS.rawget(plthreads, mt, "threads");
assert(LS.istable(plthreads));
// Get the function closure.
if (LS.isfunction(fname)) {
LS.set(func, fname);
} else if (LS.isstring(fname)) {
LS.rawget(classtab, mt, "__index");
assert(LS.istable(classtab));
LS.rawget(func, classtab, fname);
if (!LS.isfunction(func)) {
eng::string cfname = LS.ckstring(fname);
luaL_error(L, "tangible doesn't have method: %s", cfname.c_str());
return 0;
}
} else {
luaL_error(L, "invalid function, expected closure or string");
return 0;
}
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
// Create a new thread, set up function and arguments.
lua_State *CO = LS.newthread(thread);
lua_pushvalue(L, func.index());
for (int i = 0; i < varlen; i++) {
lua_pushvalue(L, i + 3);
}
lua_xmove(L, CO, varlen + 1);
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
// Create the thread info table.
LS.newtable(thinfo);
LS.rawset(thinfo, "thread", thread);
LS.rawset(thinfo, "actorid", w->lthread_actor_id_);
LS.rawset(thinfo, "isnew", true);
LS.rawset(thinfo, "useppool", false);
LS.rawset(thinfo, "print", false);
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
// Get a thread ID for the new thread, store it in
// the thread table.
int64_t tid = w->alloc_id_predictable();
LS.rawset(plthreads, tid, thinfo);
2022-05-16 14:21:09 -04:00
2022-05-16 15:17:08 -04:00
// Push the thread's ID into the runnable thread queue.
w->schedule(0, tid, place_id);
}
2022-05-16 14:21:09 -04:00
return LS.result();
}
LuaDefine(wait, "nticks",
"|Wait the specified number of ticks.") {
World *w = World::fetch_global_pointer(L);
w->guard_blockable(L, "wait");
// Parse the argument.
LuaArg seconds;
LuaDefStack LS(L, seconds);
int64_t n = LS.ckinteger(seconds);
if ((n < 0) || (n > 1000000)) {
luaL_error(L, "Argument to wait must be between 0 and 1000000");
return LS.result();
}
// Schedule a continuation.
w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_);
return lua_yield(L, 0);
}
LuaDefine(nopredict, "",
"|Stop predictive execution of this thread.") {
2021-11-21 13:35:39 -05:00
World *w = World::fetch_global_pointer(L);
w->guard_nopredict(L, "nopredict");
if (lua_gettop(L) != 0) {
luaL_error(L, "nopredict takes no arguments");
2021-11-21 13:35:39 -05:00
}
return 0;
2021-11-21 13:35:39 -05:00
}
LuaDefine(math_random, "(args...)",
"|Generate random numbers."
"|"
"|What it generates depends on the arguments:"
"|"
"| () - a float in range [0.0, 1.0)"
"| (high) - an int between 1 and high inclusive"
"| (low, high) - an int between low and high inclusive"
"|"
"|You may also pass in a randomstate table"
"|as an optional first argument."
"|"
"|math.random tries to cooperate with predictive"
"|reexecution to be as predictable as possible."
"|To achieve predictability, we used an ad-hoc"
"|random number generator. It passes a variety of"
"|statistical tests, but it's not well-studied."
"|"
"|If you want actually want nonpredictability, or"
"|if you need the assurance of a well-studied random"
"|number generator, use math.mtrandom or"
"|math.cryptrandom instead.") {
// Parse the arguments.
// This is hairy because there's a lot of possibilities.
bool passed_in_randomstate = false;
int arg = 1;
if ((lua_gettop(L) >= arg) && (lua_istable(L, arg))) {
passed_in_randomstate = true;
arg += 1;
}
bool have_range = false;
int64_t low, high;
if ((lua_gettop(L) >= arg) && (lua_type(L, arg) == LUA_TNUMBER)) {
double lowf, highf;
if ((lua_gettop(L) >= arg+1) && (lua_type(L, arg+1) == LUA_TNUMBER)) {
lowf = std::floor(lua_tonumber(L, arg));
highf = std::floor(lua_tonumber(L, arg + 1));
arg += 2;
} else {
lowf = 1;
highf = std::floor(lua_tonumber(L, arg));
arg += 1;
}
if ((lowf < -LuaCoreStack::MAXINT) || (highf > LuaCoreStack::MAXINT)) {
luaL_error(L, "math.random range exceeds MAXINT");
return 0;
}
if (lowf > highf) {
luaL_error(L, "math.random range low > high");
return 0;
}
low = int64_t(lowf);
high = int64_t(highf);
have_range = true;
}
if (lua_gettop(L) >= arg) {
luaL_error(L, "math.random accepts an optional randomstate and an optional range");
return 0;
}
// Generate the seed, count, and salt.
// The salt prevents accidental duplication between user-specified
// seeds and system-generated seeds.
uint64_t seed, count, salt;
if (passed_in_randomstate) {
lua_pushstring(L, "seed");
lua_rawget(L, 1);
lua_pushstring(L, "count");
lua_rawget(L, 1);
if ((lua_type(L, -1) != LUA_TNUMBER) ||
(lua_type(L, -2) != LUA_TNUMBER)) {
luaL_error(L, "Not a valid randomstate table");
return 0;
}
double dseed = lua_tonumber(L, -2);
double dcount = lua_tonumber(L, -1);
seed = uint64_t(dseed) & LuaCoreStack::MAXINT;
count = uint64_t(dcount) & LuaCoreStack::MAXINT;
if (dseed < 0) {
salt = 0x35c9a6082a097ade;
} else {
salt = 0x4785d086ead90c20;
}
lua_pop(L, 2);
lua_pushstring(L, "count");
lua_pushnumber(L, double((count + 1) & LuaCoreStack::MAXINT));
lua_rawset(L, 1);
} else {
World *w = World::fetch_global_pointer(L);
if (w->lthread_use_ppool_) {
Tangible *actor = w->tangible_get(w->lthread_actor_id_);
seed = w->lthread_actor_id_;
count = actor->id_player_pool_.get_seqno();
salt = 0x3ab0fb84aedc3764;
} else {
// TODO: maybe throw in a 'donotpredict' here.
seed = 123456;
count = w->id_global_pool_.get_seqno();
salt = 0x6f493c90faf0139d;
}
}
if (!have_range) {
2022-04-06 15:09:28 -04:00
// Generate the hash and convert to a double.
uint64_t hash = util::hash_ints(seed, count, salt, 456);
lua_pushnumber(L, util::hash_to_double(hash));
} else {
2022-04-06 15:09:28 -04:00
// Generate the hash and scale it into the desired range.
// This code is not quite right: the results are not quite
// uniform, this is especially true for very long ranges.
uint64_t hash = util::hash_ints(seed, count, salt, 456);
uint64_t range = (high - low) + 1;
uint64_t offset = (hash & 0x7FFFFFFFFFFFFFFF) % range;
int64_t result = low + int64_t(offset);
lua_pushnumber(L, result);
}
return 1;
}
2022-03-31 17:16:17 -04:00
LuaDefine(math_randomstate, "seed",
"|Create and return a randomstate table."
"|This is a lua table that stores the state for a random"
"|number generator. A randomstate table can be passed"
"|to math.random."
"|"
2022-03-31 17:16:17 -04:00
"|You can optionally omit the seed, in which case a"
"|seed will be chosen randomly. Automatically-generated"
"|seeds are guaranteed never to be the same as"
"|user-specified seeds.") {
double seed;
if (lua_gettop(L) == 0) {
World *w = World::fetch_global_pointer(L);
int64_t iseed = (w->id_global_pool_.get_seqno() & LuaCoreStack::MAXINT) + 1;
seed = -iseed;
} else if (lua_gettop(L) == 1) {
if (lua_type(L, 1) != LUA_TNUMBER) {
luaL_error(L, "math.randomstate takes an optional integer seed");
return 0;
}
seed = lua_tonumber(L, 1);
if ((seed < 0.0) || (seed > LuaCoreStack::MAXINT) || (std::floor(seed) != seed)) {
luaL_error(L, "math.randomstate seed must be an integer 0-MAXINT");
return 0;
}
} else {
luaL_error(L, "math.randomstate takes an optional integer seed");
return 0;
}
lua_newtable(L);
lua_pushstring(L, "seed");
lua_pushnumber(L, seed);
lua_rawset(L, -3);
lua_pushstring(L, "count");
lua_pushnumber(L, 0);
lua_rawset(L, -3);
return 1;
}
LuaSandboxBuiltin(math_randomseed, "", "");
LuaDefine(pprint, "obj1, obj2, ...",
"|Pretty-print the specified objects."
"|"
"|See also: pprintx, which has a lot more options."
"|This function uses the default options: pretty print indented,"
"|start at indentation level zero, and always expand the"
"|top-level table."
"|") {
World *w = World::fetch_global_pointer(L);
std::ostream *ostream = w->lthread_print_stream();
2023-04-13 13:26:45 -04:00
LuaExtraArgs extra;
LuaDefStack LS(L, extra);
for (int i = 0; i < extra.size(); i++) {
pprint(LS, extra[i], PrettyPrintOptions(), ostream);
(*ostream) << std::endl;
}
return LS.result();
}
LuaDefine(pprintx, "options",
"|Pretty-print the specified object, with options"
"|"
"|Options is a table with these fields:"
"|"
"| value - the object to pretty-print"
"| indent - if false, suppress newlines and indentation (default: true)"
"| level - base level of indentation (default: zero)"
"| expand - if true, force expansion of top-level table (default: false)"
"|"
"|About the expand flag: normally, when you print a class, it just "
"|prints '<class name>', and when you print a tangible, it just"
"|prints '<tangible id>'. But sometimes, you want to see the details."
"|The expand flag forces it to expand the top-level table, even if the"
"|top-level table is a tangible or class."
"|") {
World *w = World::fetch_global_pointer(L);
std::ostream *ostream = w->lthread_print_stream();
LuaArg loptions;
LuaVar value;
LuaDefStack LS(L, loptions, value);
PrettyPrintOptions options;
LuaKeywordParser kp(LS, loptions);
options.parse(kp);
if (!kp.optional(value, "value")) {
LS.set(value, LuaNil);
}
kp.final_check_throw();
pprint(LS, value, options, ostream);
return LS.result();
}
LuaDefine(print, "obj1, obj2, ...",
"|Print object or objects.") {
World *w = World::fetch_global_pointer(L);
std::ostream *ostream = w->lthread_print_stream();
LuaCoreStack LS(L);
int n = lua_gettop(L);
for (int i = 1; i <= n; i++) {
LuaSpecial root(i);
2021-12-15 23:03:43 -05:00
atomic_print(LS, root, false, ostream);
if (i < n) (*ostream) << " ";
}
(*ostream) << std::endl;
return 0;
2021-12-15 23:03:43 -05:00
}
2024-02-13 16:49:24 -05:00
LuaDefine(dprint, "obj1, obj2, ...",
"|Print object or objects on the debug console.") {
std::ostringstream oss;
LuaCoreStack LS(L);
int n = lua_gettop(L);
for (int i = 1; i <= n; i++) {
LuaSpecial root(i);
atomic_print(LS, root, false, &oss);
if (i < n) oss << " ";
}
oss << std::endl;
util::dprintview(oss.str());
return 0;
}
LuaDefine(doc, "function",
"|Print documentation for specified function.") {
2021-12-15 23:03:43 -05:00
World *w = World::fetch_global_pointer(L);
std::ostream *ostream = w->lthread_print_stream();
2021-12-15 23:03:43 -05:00
LuaArg func;
LuaDefStack LS(L, func);
eng::string doc = w->get_source().function_docs(LS, func);
2021-12-15 23:03:43 -05:00
if (doc == "") {
(*ostream) << "no doc found" << std::endl;
}
(*ostream) << doc;
return LS.result();
}
int lfn_http_request(lua_State *L, const char *method) {
World *w = World::fetch_global_pointer(L);
w->guard_blockable(L, "http.get");
LuaArg request;
LuaRet response;
LuaDefStack LS(L, request, response);
2022-07-22 17:07:40 -04:00
LuaKeywordParser kp(LS, request);
2022-05-06 13:16:27 -04:00
HttpClientRequest req;
// Parse the request and make sure it's valid.
2022-05-20 17:12:58 -04:00
// If not, immediately pass a '400 bad request' back to lua.
req.set_method(method);
2022-07-22 17:07:40 -04:00
req.configure(kp);
kp.final_check_throw();
req.set_defaults();
eng::string error = req.check();
if (!error.empty()) {
2022-05-20 17:12:58 -04:00
HttpParser::store_fail(LS, response, 400, util::ss("Bad Request: ", error));
return LS.result();
}
// Give the request an ID.
req.set_request_id(w->id_global_pool_.get_one());
req.set_place_id(w->lthread_place_id_);
req.set_thread_id(w->lthread_thread_id_);
// Store it in the global request table.
w->http_requests_[req.request_id()] = req;
// Block.
return lua_yield(L, 0);
}
LuaDefine(http_get, "request",
"|Make an HTTP GET request. Returns an HTTP response."
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
return lfn_http_request(L, "GET");
}
LuaDefine(http_head, "request",
"|Make an HTTP HEAD request. Returns an HTTP response."
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
return lfn_http_request(L, "HEAD");
}
LuaDefine(http_post, "request",
"|Make an HTTP POST request. Returns an HTTP response."
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
return lfn_http_request(L, "POST");
}
LuaDefine(global_set, "varname, value",
"|Store data in the global data table."
"|"
"|The variable name must be a string which is a valid"
"|lua identifier."
"|"
"|You can store global data using global.set, then you can"
"|retrieve it using global.get."
"|"
"|The global data table is not the same thing as the lua "
"|environment table. Trying to store data in the lua environment"
"|table will seem to work, at first, but the data will not get"
"|difference transmitted, and will eventually be cleared and lost."
"|Therefore, it is essential that global data be stored in the"
"|global data table (using global.set) instead of in the lua"
"|environment table."
"|"
"|Data is stored in a serialized form. The data is serialized"
"|at the moment you call global.set. It is deserialized when you"
"|call global.get. Therefore, each time you call global.get, you"
"|construct another copy of the value."
"|"
"|The serialization routine can only serialize certain kinds of"
"|values. For example, closures cannot be serialized at all."
"|Serializing a tangible serializes the tangible's ID, but"
"|none of the contents of the tangible. See doc(table.serialize)"
"|for more information about what can and can't be serialized."
"|") {
LuaArg varname;
LuaArg value;
LuaDefStack LS(L, varname, value);
// Check the varname argument.
eng::string gvar = LS.ckstring(varname);
if (!sv::is_lua_id(gvar)) {
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
return LS.result();
}
// Serialize the data.
StreamBuffer sb;
eng::string error = serialize_lua(LS, value, &sb);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
return LS.result();
}
World *w = World::fetch_global_pointer(L);
w->set_global(gvar, sb.view());
return LS.result();
}
LuaDefine(global_get, "varname",
"|Get data stored using global.set"
"|"
"|See doc(global.set) for information on how to store global data."
"|"
"|Performance note: each time you call global.get, you deserialize the"
"|stored serialized version, constructing another copy of the data."
"|") {
LuaArg varname;
LuaRet value;
LuaDefStack LS(L, varname, value);
LS.set(value, LuaNil);
// Check the varname argument.
eng::string gvar = LS.ckstring(varname);
if (!sv::is_lua_id(gvar)) {
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
return LS.result();
}
// Fetch the serialized blob.
World *w = World::fetch_global_pointer(L);
std::string_view data = w->get_global(gvar);
if (data.empty()) return LS.result();
// Deserialize the blob.
StreamBuffer sb(data);
eng::string error = deserialize_lua(LS, value, &sb);
if (!error.empty()) {
luaL_error(L, "%s", error.c_str());
return LS.result();
}
return LS.result();
}
LuaDefine(global_once, "varname",
"|For a given string, returns true exactly once"
"|"
"|The semantics and difference transmission behavior of global.once"
"|are identical to the semantics of global.set, since global.once"
"|uses global.set under the covers."
"|") {
LuaArg varname;
LuaRet result;
LuaDefStack LS(L, varname, result);
LS.set(result, false);
// Check the varname argument.
eng::string gvar = LS.ckstring(varname);
if (!sv::is_lua_id(gvar)) {
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
return LS.result();
}
gvar += ":once";
World *w = World::fetch_global_pointer(L);
LS.set(result, false);
if (w->get_global(gvar).empty()) {
LS.set(result, true);
w->set_global(gvar, "x");
}
return LS.result();
}
LuaDefine(global_clearonce, "varname",
"|Reset the specified once-flag"
"|"
"|The semantics and difference transmission behavior of global.clearonce"
"|are identical to the semantics of global.set, since global.once"
"|uses global.set under the covers."
"|") {
LuaArg varname;
LuaDefStack LS(L, varname);
// Check the varname argument.
eng::string gvar = LS.ckstring(varname);
if (!sv::is_lua_id(gvar)) {
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
return LS.result();
}
gvar += ":once";
World *w = World::fetch_global_pointer(L);
w->set_global(gvar, "");
return LS.result();
}