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

556 lines
18 KiB
C++
Raw Normal View History

#include "world.hpp"
#include "pprint.hpp"
#include <cmath>
#include <iostream>
static void tangible_getall(LuaStack &LS0, LuaSlot list, const util::IdVector &idv) {
LuaVar tangibles, tan;
LuaStack 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);
}
LS.result();
}
LuaDefine(tangible_animstate, "tan",
"|Get the entire animation state of the tangible."
"|Returns six values: graphic,plane,x,y,z,facing.") {
LuaArg tanobj;
LuaRet graphic, plane, x, y, z, facing;
LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj);
const AnimStep &aqback = tan->anim_queue_.back();
LS.set(graphic, aqback.graphic());
LS.set(plane, aqback.plane());
LS.set(x, aqback.xyz().x);
LS.set(y, aqback.xyz().y);
LS.set(z, aqback.xyz().z);
LS.set(facing, aqback.facing());
return LS.result();
}
LuaDefine(tangible_xyz, "tan",
"|Get the current coordinates of the tangible."
"|Returns three values: x, y, z") {
LuaArg tanobj;
LuaRet x, y, z;
LuaStack LS(L, tanobj, x, y, z);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj);
const AnimStep &aqback = tan->anim_queue_.back();
LS.set(x, aqback.xyz().x);
LS.set(y, aqback.xyz().y);
LS.set(z, aqback.xyz().z);
return LS.result();
}
LuaDefine(tangible_animate, "tan,configtable",
"|Add an animation step to the tangible."
"|The configtable is a table containing any of the following:"
"|action,graphic,plane,x,y,z,facing") {
LuaArg tanobj, config;
LuaStack LS(L, tanobj, config);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj);
int64_t id = w->alloc_id_predictable();
const AnimStep &prev = tan->anim_queue_.back();
AnimStep step;
2021-11-23 14:22:12 -05:00
step.from_lua(L, config.index(), false, prev);
if (step.action() == "") {
luaL_error(L, "animation action must be specified");
}
tan->anim_queue_.add(id, step);
tan->update_plane_item();
return LS.result();
}
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;
LuaStack LS(L, tanobj, classname, classtab, mt);
World *w = World::fetch_global_pointer(L);
w->tangible_get(LS, tanobj);
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_getclass, "tan",
"|Get the class of the tangible, if any."
"|The return value is a string, the class name, not"
"|the class table.") {
LuaArg tanobj;
LuaVar mt, classtab;
LuaRet classname;
LuaStack LS(L, tanobj, mt, classtab, classname);
World *w = World::fetch_global_pointer(L);
w->tangible_get(LS, tanobj);
LS.getmetatable(mt, tanobj);
LS.rawget(classtab, mt, "__index");
eng::string name = LS.classname(classtab);
if (name == "") {
LS.set(classname, LuaNil);
} else {
LS.set(classname, name);
}
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;
LuaStack LS(L, tanobj);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj);
assert(tan != nullptr); // this should be checked above.
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, "configtable",
"|Build a new tangible object."
"|The configtable must contain: class,x,y,z,plane,graphic."
"|The configtable can optionally contain: facing.") {
LuaArg config;
LuaVar classname, classtab, mt;
LuaRet database;
LuaStack LS(L, config, classname, classtab, database, mt);
LS.checktable(config);
// Get the class of the new tangible.
LS.rawget(classname, config, "class");
eng::string err = LS.getclass(classtab, classname);
if (err != "") {
luaL_error(L, "%s", err.c_str());
}
// Parse the initial animation step.
AnimStep initstep, blank;
2021-11-23 14:22:12 -05:00
initstep.from_lua(L, config.index(), true, blank);
if (!initstep.has_xyz()) {
luaL_error(L, "You must specify (X,Y,Z) for new tangible");
}
if (!initstep.has_plane()) {
luaL_error(L, "You must specify plane for new tangible");
}
if (!initstep.has_graphic()) {
luaL_error(L, "You must specify graphic for new tangible");
}
// TODO: generate error if there's extra crap in the config table.
World *w = World::fetch_global_pointer(L);
int64_t new_id = w->alloc_id_predictable();
Tangible *tan = w->tangible_make(L, new_id, "nowhere", true);
lua_replace(L, database.index());
// Update the class of the new tangible.
LS.getmetatable(mt, database);
LS.rawset(mt, "__index", classtab);
// Update the animation queue and planemap of the new tangible.
int64_t stepid = w->alloc_id_predictable();
tan->anim_queue_.add(stepid, initstep);
tan->update_plane_item();
return LS.result();
}
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;
LuaStack 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;
LuaStack LS(L, actor1, actor2, bldz);
World *w = World::fetch_global_pointer(L);
bool bulldoze = LS.ckboolean(bldz);
Tangible *tan1 = w->tangible_get(LS, actor1);
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);
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;
LuaStack 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;
LuaStack 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;
LuaStack 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_near, "tan,radius,omit_nowhere,omit_self",
"|Scan near the specified tangible."
"|If omit_nowhere is true, and the tangible is on the nowhere plane,"
"|then the scan returns empty. If omit_self is true, then the "
"|tangible passed in is omitted from the results.") {
LuaArg ltan, lradius, lomit_nowhere, lomit_self;
LuaRet list;
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, ltan);
double radius = LS.cknumber(lradius);
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
bool omit_self = LS.ckboolean(lomit_self);
const AnimStep &aqback = tan->anim_queue_.back();
util::IdVector idv = w->plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, omit_nowhere, tan->id(), omit_self);
tangible_getall(LS, list, idv);
return LS.result();
}
LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
"|Scan the specified plane."
"|If omit_nowhere is true, and the plane is 'nowhere', then"
"|the scan returns empty.") {
LuaArg lplane, lx, ly, lradius, lomit_nowhere;
LuaRet list;
LuaStack LS(L, lplane, lx, ly, lradius, lomit_nowhere, list);
World *w = World::fetch_global_pointer(L);
eng::string plane = LS.ckstring(lplane);
double x = LS.cknumber(lx);
double y = LS.cknumber(ly);
double radius = LS.cknumber(lradius);
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
util::IdVector idv = w->plane_map_.scan_radius(plane, x, y, radius, omit_nowhere, 0, false);
tangible_getall(LS, list, idv);
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;
LuaStack 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"
"|"
"|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 < -LuaStack::MAXINT) || (highf > LuaStack::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) & LuaStack::MAXINT;
count = uint64_t(dcount) & LuaStack::MAXINT;
if (dseed < 0) {
salt = 0x35c9a6082a097ade;
} else {
salt = 0x4785d086ead90c20;
}
lua_pop(L, 2);
lua_pushstring(L, "count");
lua_pushnumber(L, double((count + 1) & LuaStack::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() & LuaStack::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 > LuaStack::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 object or objects.") {
World *w = World::fetch_global_pointer(L);
std::ostream *ostream = w->lthread_print_stream();
LuaStack LS(L);
for (int i = 1; i <= lua_gettop(L); i++) {
LuaSpecial root(i);
pprint(LS, root, true, ostream);
(*ostream) << std::endl;
}
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();
LuaStack 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 LS.result();
2021-12-15 23:03:43 -05:00
}
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;
LuaStack LS(L, func);
eng::string doc = SourceDB::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();
}
LuaDefine(http_get, "request",
"|Make an HTTP GET request. Returns an HTTP response."
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
World *w = World::fetch_global_pointer(L);
w->guard_blockable(L, "http.get");
LuaArg request;
LuaRet response;
LuaStack LS(L, request, response);
2022-05-06 13:16:27 -04:00
HttpClientRequest req;
// Parse the request and make sure it's valid.
req.set_config(LS, request);
req.set_defaults();
eng::string error = req.check();
if (!error.empty()) {
2022-05-06 13:16:27 -04:00
HttpClientResponse::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);
}