#include "world.hpp" #include "pprint.hpp" #include #include 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; 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()); } 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; 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); 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); 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(); } if (!lua_isyieldable(L)) { // in a probe, wait throws an error. luaL_error(L, "cannot wait in a probe."); return LS.result(); } else if (!w->is_authoritative()) { // in a nonauth model, yield is converted to nopredict. lua_yield(L, 0); return LS.result(); } else { // in an authoritative model, wait schedules a continuation. w->thread_sched_.add(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_); lua_yield(L, 0); return LS.result(); } } LuaDefine(nopredict, "", "|Stop predictive execution of this thread.") { if (lua_gettop(L) != 0) { luaL_error(L, "tangible.nopredict takes no arguments"); } World *w = World::fetch_global_pointer(L); if (lua_isyieldable(L) && !w->is_authoritative()) { return lua_yield(L, 0); } return 0; } 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) { // 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 { // 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; } 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." "|" "|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); atomic_print(LS, root, false, ostream); if (i < n) (*ostream) << " "; } (*ostream) << std::endl; return LS.result(); } LuaDefine(doc, "function", "|Print documentation for specified function.") { World *w = World::fetch_global_pointer(L); std::ostream *ostream = w->lthread_print_stream(); LuaArg func; LuaStack LS(L, func); eng::string doc = SourceDB::function_docs(LS, func); if (doc == "") { (*ostream) << "no doc found" << std::endl; } (*ostream) << doc; return LS.result(); }