/* * * Singe 3 * Copyright (C) 2006-2026 Scott Duensing * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include #include #include #include #include #include #include "../thirdparty/lua/src/lua.h" #include "../thirdparty/lua/src/lualib.h" #include "../thirdparty/lua/src/lauxlib.h" #include "../thirdparty/uthash/src/uthash.h" #include "../thirdparty/manymouse/manymouse.h" #include "rotoZoom.h" #include "../thirdparty/luafilesystem/src/lfs.h" #include "../thirdparty/luasec/src/context.h" #include "../thirdparty/luasec/src/ssl.h" #include "../thirdparty/luasec/src/x509.h" #include "../thirdparty/luasocket/src/luasocket.h" #include "../thirdparty/luasocket/src/mime.h" #ifndef _WIN32 #include "../thirdparty/luasocket/src/unix.h" // There is no serial.h, so fake it. LUASOCKET_API int luaopen_socket_serial(lua_State *L); #endif // There is no header for rs232 binding. Make our own. int luaopen_luars232(lua_State *L); // Nor for lsqlite3. int luaopen_lsqlite3(lua_State *L); // There is no header for ssl.config binding. Make our own. LSEC_API int luaopen_ssl_config(lua_State *L); #include "main.h" #include "util.h" #include "frameFile.h" #include "vfs.h" #include "scene.h" #include "hdr.h" #include "ktx2.h" #include "nav.h" #include "model.h" #include "physics.h" #include "particles.h" #include "videoPlayer.h" #include "singe.h" // We have to do the embedding here so the Lua module // definitions can find their length properly. They // can't be external to this source file. #define EMBED_HERE #include "embedded.h" // soundSetVolume/soundGetVolume use this legacy scale; the mixer uses MIX_MAX_VOLUME. #define AUDIO_MAX_VOLUME 63 #define MAX_MICE 4 #define MOUSE_AXIS_COUNT 2 #define MAX_CONTROLLERS 4 #define CONTROLLER_AXIS_COUNT 6 #define CONTROLLER_BUTTON_COUNT 15 #define CONTROLLER_DEAD_ZONE_DEFAULT 15000 #define AXIS_COUNT (MAX_CONTROLLERS * CONTROLLER_AXIS_COUNT + MAX_MICE * MOUSE_AXIS_COUNT) #define AXIS_INDEX_CONTROLLER(c, a) ((c) * CONTROLLER_AXIS_COUNT + (a)) #define AXIS_INDEX_MOUSE(m, a) (MAX_CONTROLLERS * CONTROLLER_AXIS_COUNT + (m) * MOUSE_AXIS_COUNT + (a)) // Input codes handed to scripts and controls.cfg. Framework.singe builds its tables from these. #define CODE_GAMEPAD_BASE 500 #define CODE_GAMEPAD_STRIDE 100 // Per controller #define CODE_AXIS_STRIDE 3 // Per axis: axis, negative direction, positive direction #define CODE_AXIS_NEGATIVE 1 #define CODE_AXIS_POSITIVE 2 #define CODE_GAMEPAD_BUTTON_OFFSET (CONTROLLER_AXIS_COUNT * CODE_AXIS_STRIDE) #define CODE_MOUSE_BASE 1000 #define CODE_MOUSE_STRIDE 100 // Per mouse #define CODE_MOUSE_BUTTON_COUNT 5 // Left, right, middle, X1, X2 #define CODE_MOUSE_WHEEL_UP (CODE_MOUSE_BUTTON_COUNT) #define CODE_MOUSE_WHEEL_DOWN (CODE_MOUSE_BUTTON_COUNT + 1) // Codes below the gamepad range are keyboard scancodes, so every key SDL defines must sit under it. SDL_COMPILE_TIME_ASSERT(codeGamepadBase, CODE_GAMEPAD_BASE > SDL_SCANCODE_ENDCALL); #define FRAME_TICK_MS 15 // Minimum time between onOverlayUpdate calls #define IDLE_SLEEP_MS 1 #define OVERLAY_SCALE_DEFAULT 0.5 #define CONSOLE_FONT_GLYPHS 256 #define DEGREES_PER_CIRCLE 360.0 #define ANIMATION_MIN_DELAY_MS 10 // GIFs often carry a zero delay #define SCREENSHOT_MAX 10000 #define COLOR_BYTE_MAX 255 #define NAV_DRAW_VERTICES 4096 // Starting room for navDraw, doubled until the mesh fits #define SOUND_QUEUE_SIZE 64 #define MS_PER_SECOND_NUMBER 1000.0 #define INPUT_GRACE_MS 1000 // Presses this soon after a script starts or focus arrives were held over from before #define EFFECT_TRACKS 16 // Sound effect "channels" scripts can play at once #define SOUND_DEFAULT_NEAR 1.0f // A positioned sound is at full volume within this distance ... #define SOUND_DEFAULT_FAR 30.0f // ... and silent beyond this #define SOUND_FADE_FRACTION 0.2f // Of the far distance, over which it fades to silence #define SOUND_LOOP_FOREVER -1 #define SOUND_DIRECTION_EPSILON 0.0001f // Closer than this to the listener has no direction #define HEIGHTMAP_MAX 1025 // Pixels per side of a heightmap image #define WATCH_INTERVAL_MS 1000 // How often --reload checks the script files #define AUDIO_CALIBRATION_FILE "audio.cfg" // Per-machine audio delay, in the data root #define CONTROLS_FILE "controls.cfg" // Input mappings, built in and overridden per game #define SOUND_CHANNEL_NONE -1 // soundPlay's answer when every channel is busy (SOUND_ERROR_INVALID) #define COLOR_COMPONENTS 4 // r, g, b, a #define BYTES_PER_KIB 1024 #define SHAPE_SIZES 3 // a, b, c sizing a primitive shape #define VEHICLE_DRIVE_INPUTS 4 // forward, right, brake, hand brake #define MESH_SEGMENTS_DEFAULT 24 // Around a cone or cylinder #define MESH_SPHERE_SEGMENTS_DEFAULT 32 // Around a sphere or torus #define EMITTER_DEFAULT_BOUNCE 0.5f // emitterSetCollide's optional arguments #define EMITTER_DEFAULT_FRICTION 0.2f #define EMITTER_DEFAULT_FLOOR 0.0f #define WATER_DEFAULT_LINEAR_DRAG 0.5f // bodySetWater's optional arguments #define WATER_DEFAULT_ANGULAR_DRAG 0.1f #define LISTENER_CAMERA -1 // soundSetListener's default: the scene camera #define EFFECT_TAG "effects" #define HELD_KEYS_MAX 64 // Keys physically down at once #define PAUSE_TEXT "PAUSED" #define PAUSE_TEXT_SCALE 3 // Console font is small; scale the indicator up #define QUAD_VERTICES 4 // A textured quad for SDL_RenderGeometry ... #define QUAD_INDICES 6 // ... as two triangles #define TRAIL_POINT_FLOATS 3 // x, y, z per recorded trail point #define COLOR_KEY_VALUE 0 #define BLUE_SCREEN_BLUE 255 #define LOGO_FADE_STEPS 256 #define LOGO_MARGIN 40 // Pixels of breathing room around a logo in the logo space #define LOGO_SPACE_WIDTH 1920 // Logical size the splash screens are laid out in #define LOGO_SPACE_HEIGHT 1080 #define LOGO_FADE_STEP_MS 3 #define LOGO_HOLD_MS 750 // Decoded video frames are BGRA in memory; this is the matching alpha-less surface format. #if SDL_BYTEORDER == SDL_LIL_ENDIAN #define VIDEO_SURFACE_FORMAT SDL_PIXELFORMAT_XRGB8888 #else #define VIDEO_SURFACE_FORMAT SDL_PIXELFORMAT_BGRX8888 #endif typedef enum KeyboardModeE { KEYBOARD_NORMAL = 0, KEYBOARD_FULL = 1 } KeyboardModeE; typedef enum MouseModeE { MOUSE_SINGLE = 100, MOUSE_MANY = 200 } MouseModeE; // Values match the Daphne LDP states scripts have always compared against. typedef enum DiscStateE { DISC_STOPPED = 2, DISC_PLAYING = 3, DISC_PAUSED = 4, DISC_EJECTED = 5 // The game has no disc at all } DiscStateE; typedef enum FontQualityE { FONT_QUALITY_SOLID = 1, FONT_QUALITY_SHADED = 2, FONT_QUALITY_BLENDED = 3 } FontQualityE; typedef enum RenderQualityE { RENDER_PIXELATED = 0, RENDER_SMOOTH = 1 } RenderQualityE; // How an io function hooked through the vfs uses its file name. typedef enum IoHookModeE { IO_HOOK_READ = 0, IO_HOOK_OPEN = 1, // The mode string decides IO_HOOK_WRITE = 2 } IoHookModeE; typedef enum OverlayResultE { OVERLAY_NOT_UPDATED = 0, OVERLAY_UPDATED = 1 } OverlayResultE; // Index into _global.controlMappings and the SWITCH_* value handed to scripts. typedef enum InputE { INPUT_UP = 0, INPUT_LEFT, INPUT_DOWN, INPUT_RIGHT, INPUT_1P_START, INPUT_2P_START, INPUT_ACTION_1, INPUT_ACTION_2, INPUT_ACTION_3, INPUT_1P_COIN, INPUT_2P_COIN, INPUT_SKILL_EASY, INPUT_SKILL_MEDIUM, INPUT_SKILL_HARD, INPUT_SERVICE, INPUT_TEST_MODE, INPUT_RESET_CPU, INPUT_SCREENSHOT, INPUT_QUIT, INPUT_PAUSE, INPUT_CONSOLE, // Added in Singe 2.00 INPUT_ACTION_4, INPUT_TILT, INPUT_GRAB, INPUT_COUNT } InputE; typedef struct LuaModuleS { const char *name; union { const char *source; lua_CFunction openf; }; size_t length; } LuaModuleT; typedef struct InputNameS { const char *configName; // Table name in controls.cfg const char *switchName; // Constant name in scripts } InputNameT; typedef struct HeldKeyS { int32_t keysym; int32_t scancode; } HeldKeyT; typedef struct MouseS { int32_t x; int32_t y; char name[64]; } MouseT; // Renderer textures made from an emitter's frames for drawing 2D particles, refreshed when they change. typedef struct ParticleTexturesS { int32_t id; uint32_t version; int32_t count; SDL_Texture **textures; ParticleBlendE blend; // The blend mode the textures were last set to UT_hash_handle hh; } ParticleTexturesT; typedef struct SpriteS { int32_t id; IMG_Animation *animation; // NULL for still images SDL_Surface *originalSurface; // Owned unless it points into animation->frames SDL_Surface *surface; // What gets drawn: originalSurface, or a transformed copy bool surfaceOwned; // True when surface is a transformed copy to free double angle; double scaleX; double scaleY; int32_t smooth; int32_t currentFrame; bool loop; bool animating; uint64_t lastTick; uint64_t ticks; uint64_t loopMs; // One pass through every frame's (clamped) delay UT_hash_handle hh; } SpriteT; // A loose script file --reload watches. typedef struct WatchedFileS { char *name; int64_t modified; } WatchedFileT; typedef struct SoundS { int32_t id; MIX_Audio *audio; UT_hash_handle hh; } SoundT; // Where a playing effect channel sits: in the scene (following a node or placed by hand), or // panned across the speakers, or neither. typedef struct EffectS { int32_t node; // Followed while it exists; LISTENER_CAMERA when the position is given by hand Vec3T position; // World position when not following a node float nearBy; // Full volume within this distance ... float farOff; // ... silent beyond this float relative[3]; // The last position handed to the mixer, listener space float gain; // The last distance gain float baseGain; // The effects volume the track gain was last built from bool positioned; } EffectT; typedef struct FontS { int32_t id; TTF_Font *font; UT_hash_handle hh; } FontT; typedef struct VideoS { int32_t id; int32_t handle; int64_t lastFrame; bool wasPlayingBeforePause; bool transformChanged; SDL_Texture *texture; SDL_Surface *transformedSurface; double angle; double scaleX; double scaleY; int32_t smooth; UT_hash_handle hh; } VideoT; typedef struct MappingS { int32_t inputCount; int32_t *input; } MappingT; typedef struct GlobalS { MouseT mice[MAX_MICE]; lua_State *luaContext; SDL_Color colorForeground; SDL_Color colorBackground; SDL_Surface *overlay; SDL_Texture *overlayTexture; SDL_Window *window; SDL_Renderer *renderer; SDL_GPUDevice *device; bool reloadRequested; // singeReload, F5 or a watched file changing WatchedFileT *watched; int32_t watchedCount; uint64_t watchTick; // When the watched files were last checked SDL_Texture *videoTexture; SDL_Surface *consoleFontSurface; SDL_Gamepad *controllers[MAX_CONTROLLERS]; int32_t controllerDeadZone; int32_t consoleFontWidth; int32_t consoleFontHeight; int32_t nextSpriteId; int32_t nextSoundId; int32_t nextFontId; int32_t nextVideoId; int32_t nextScreenshot; // First index worth checking; lower ones are taken int32_t effectsVolume; int32_t listenerNode; // For positioned sounds; LISTENER_CAMERA for the scene camera KeyboardModeE keyboardMode; bool keyboardState[SDL_SCANCODE_COUNT]; bool keySuppressed[SDL_SCANCODE_COUNT]; // Held before this script started; not a press for it bool buttonSuppressed[MAX_CONTROLLERS][CONTROLLER_BUTTON_COUNT]; uint64_t inputGraceUntil; // Until then, new presses are treated as held over too int32_t keyboardLastDown; int32_t keyboardLastUp; int32_t frameFileHandle; int32_t videoHandle; // -1 when the game has no disc int32_t canvasWidth; // World size: the disc's, or the configured canvas int32_t canvasHeight; FontQualityE fontQuality; MouseModeE mouseMode; int32_t mouseCount; int32_t axisCache[AXIS_COUNT]; int32_t axisCode[AXIS_COUNT]; // Direction code currently pressed per axis, 0 when none int32_t soundQueue[SOUND_QUEUE_SIZE]; // Channels finished since the last frame (audio thread writes) int32_t soundQueueCount; bool frozen; // Engine pause: the script is not being run bool switchHeld[INPUT_COUNT]; // Switches the script believes are down (MODE_NORMAL) HeldKeyT heldKeys[HELD_KEYS_MAX]; // Keys the script believes are down (MODE_FULL) int32_t heldKeyCount; HeldKeyT physicalKeys[HELD_KEYS_MAX]; // Keys and buttons physically down right now int32_t physicalKeyCount; SDL_Texture *pauseTexture; int32_t pauseTextureWidth; int32_t pauseTextureHeight; double overlayScaleX; // Overlay size / video size double overlayScaleY; bool overlayDirty; bool pauseState; // by RDG2010 bool pauseEnabled; // by RDG2010 bool refreshDisplay; bool running; bool discStopped; bool mouseEnabled; bool mouseGrabbed; bool requestScreenShot; bool wasPlayingBeforePause; VideoT *videoList; SpriteT *spriteList; ParticleTexturesT *particleTextures; // 2D particle textures by emitter Vec3T *navDrawVertices; // navDraw's triangle list, kept between frames int32_t navDrawCapacity; SDL_Vertex *quadVertices; // Scratch for 2D particle quads and trails, grown as needed int32_t *quadIndices; int32_t quadCapacity; // In quads int32_t *frameStarts; // Per emitter frame, where its particles' quads begin in the index scratch int32_t frameStartCapacity; SoundT *soundList; FontT *fontList; FontT *fontCurrent; MappingT controlMappings[INPUT_COUNT]; ConfigT *conf; // Local copy of command line options } GlobalT; static GlobalT _global; static MIX_Track *_effectTracks[EFFECT_TRACKS]; static EffectT _effects[EFFECT_TRACKS]; // One entry per InputE, in order. The config name is the controls.cfg table; the switch name is the Lua constant. static const InputNameT _inputNames[INPUT_COUNT] = { { "INPUT_UP", "SWITCH_UP" }, { "INPUT_LEFT", "SWITCH_LEFT" }, { "INPUT_DOWN", "SWITCH_DOWN" }, { "INPUT_RIGHT", "SWITCH_RIGHT" }, { "INPUT_1P_START", "SWITCH_START1" }, { "INPUT_2P_START", "SWITCH_START2" }, { "INPUT_ACTION_1", "SWITCH_BUTTON1" }, { "INPUT_ACTION_2", "SWITCH_BUTTON2" }, { "INPUT_ACTION_3", "SWITCH_BUTTON3" }, { "INPUT_1P_COIN", "SWITCH_COIN1" }, { "INPUT_2P_COIN", "SWITCH_COIN2" }, { "INPUT_SKILL_EASY", "SWITCH_SKILL1" }, { "INPUT_SKILL_MEDIUM", "SWITCH_SKILL2" }, { "INPUT_SKILL_HARD", "SWITCH_SKILL3" }, { "INPUT_SERVICE", "SWITCH_SERVICE" }, { "INPUT_TEST_MODE", "SWITCH_TEST" }, { "INPUT_RESET_CPU", "SWITCH_RESET" }, { "INPUT_SCREENSHOT", "SWITCH_SCREENSHOT" }, { "INPUT_QUIT", "SWITCH_QUIT" }, { "INPUT_PAUSE", "SWITCH_PAUSE" }, { "INPUT_CONSOLE", "SWITCH_CONSOLE" }, { "INPUT_ACTION_4", "SWITCH_BUTTON4" }, { "INPUT_TILT", "SWITCH_TILT" }, { "INPUT_GRAB", "SWITCH_GRAB" } }; // SDL numbers mouse buttons left, middle, right; scripts number them left, right, middle. static const int32_t _sdlMouseButtonToCode[] = { 0, 0, 2, 1, 3, 4 }; static int32_t _apiUnimplemented(lua_State *L, const char *method); static int32_t _argAnimation(lua_State *L, const char *method, int32_t model, int32_t index); static int32_t _argAnimationLayer(lua_State *L, const char *method, int32_t index); static bool _argBoolean(lua_State *L, const char *method, int32_t index); static int32_t _argChannel(lua_State *L, const char *method, int32_t index); static uint8_t _argColorByte(lua_State *L, const char *method, int32_t index); static void _argCheck(lua_State *L, const char *method, int32_t minimum, int32_t maximum); static QuatT _argEuler(lua_State *L, const char *method, int32_t index); static float *_argFloatTable(lua_State *L, const char *method, int32_t index, int32_t *count); static FontT *_argFont(lua_State *L, const char *method, int32_t index); static int32_t _argHandle(lua_State *L, const char *method, int32_t index, bool (*valid)(int32_t), const char *noun); static int32_t _argInteger(lua_State *L, const char *method, int32_t index); static int32_t _argBody(lua_State *L, const char *method, int32_t index); static int32_t _argEmitter(lua_State *L, const char *method, int32_t index); static bool _argMapImage(lua_State *L, const char *method, int32_t index, SDL_Surface **surface, Ktx2ImageT *ktx2); static int32_t _argMaterial(lua_State *L, const char *method, int32_t index); static int32_t _argMorph(lua_State *L, const char *method, int32_t node, int32_t index); static int32_t _argMesh(lua_State *L, const char *method, int32_t index); static int32_t _argNav(lua_State *L, const char *method, int32_t index); static int32_t _argNavAgent(lua_State *L, const char *method, int32_t index); static int32_t _argNode(lua_State *L, const char *method, int32_t index); static int32_t _argNodeWith(lua_State *L, const char *method, int32_t index, bool (*exists)(int32_t), const char *noun); static void _argOptionalColor(lua_State *L, const char *method, int32_t index, uint8_t *r, uint8_t *g, uint8_t *b); static int32_t _argPlayer(lua_State *L, const char *method, int32_t index); static int32_t _argRagdoll(lua_State *L, const char *method, int32_t index); static int32_t _argSoft(lua_State *L, const char *method, int32_t index); static int32_t _argVehicle(lua_State *L, const char *method, int32_t index); static int64_t _argInteger64(lua_State *L, const char *method, int32_t index); static double _argNumber(lua_State *L, const char *method, int32_t index); static SoundT *_argSound(lua_State *L, const char *method, int32_t index); static SpriteT *_argSprite(lua_State *L, const char *method, int32_t index); static const char *_argString(lua_State *L, const char *method, int32_t index); static Vec3T _argVec3(lua_State *L, const char *method, int32_t index); static int32_t _argView(lua_State *L, const char *method, int32_t index); static VideoT *_argVideo(lua_State *L, const char *method, int32_t index); static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base); static void _callLua(const char *func, const char *sig, ...); static bool _clipLine(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2); static int32_t _effectTrackFree(void); static float _effectGain(const EffectT *effect, float distance); static void _effectReset(int32_t channel); static void _effectStopped(void *userdata, MIX_Track *track); static int32_t _controllerSlot(SDL_JoystickID which); static bool _delayAndPump(uint32_t ms); static void _deliverKey(bool down, int32_t keysym, int32_t scancode); static int64_t _discGetFrame(void); static void _discSeek(int64_t frame); static void _doLogos(void); static void _drawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t pixel); static void _drawParticles2D(ParticleLayerE layer, const SDL_FRect *target); static void _drawTrails2D(const EmitterViewT *view, const ParticleTexturesT *cache, const SDL_FRect *target, float scaleX, float scaleY); static void _drawPauseIndicator(const SDL_FRect *target); static InputE _engineSwitch(int32_t scancode); static void _fireMouseMoved(int32_t device, int32_t x, int32_t y, int32_t xr, int32_t yr); static void _fitRect(int32_t width, int32_t height, int32_t spaceWidth, int32_t spaceHeight, int32_t margin, SDL_FRect *rect); static void _fontDestroy(FontT *font); static void _freezeGame(bool freeze); static void _heldListUpdate(HeldKeyT *list, int32_t *count, bool down, int32_t keysym, int32_t scancode); static void _installFileHooks(lua_State *L); static int32_t _lfsAttributes(lua_State *L); static int32_t _lfsDir(lua_State *L); static int32_t _lfsDirIterator(lua_State *L); static int32_t _lfsMkdir(lua_State *L); static int32_t _lfsRmdir(lua_State *L); static int32_t _loadAudioCalibration(void); static void _loadControlMappings(void); static void _loadControlsFile(const char *path); static SDL_Surface *_loadEmbeddedPng(const uint8_t *data, size_t length); static SDL_Texture *_loadEmbeddedTexture(const uint8_t *data, size_t length, SDL_Surface **surface); static int32_t _luaCallOriginal(lua_State *L); static void _luaDie(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4))) __attribute__((noreturn)); static int32_t _luaDofile(lua_State *L); static int32_t _luaFileSearcher(lua_State *L); static char *_luaFormat(lua_State *L, const char *method, const char *fmt, va_list args); static int32_t _luaIoHook(lua_State *L); static int32_t _luaLoadfile(lua_State *L); static int32_t _luaLoadFile(lua_State *L, const char *name, const char *mode, bool watch); static int32_t _luaopenLfs(lua_State *L); static int32_t _luaPanic(lua_State *L); static int32_t _luaSearcher(lua_State *L); static void _luaTrace(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4))); static int32_t _luaTraceback(lua_State *L); static int32_t _materialSetMap(lua_State *L, const char *method, MaterialMapE map, bool hasStrength); static float _mixerGain(int32_t effectsVolume); static int32_t _mouseCode(int32_t device, int32_t button); static uint32_t _overlayColor(const SDL_Color *color); static void _overlayResize(int32_t width, int32_t height); static void _overlayTouched(void); static void _pauseAllVideos(bool pause); static void _processKey(bool down, int32_t keysym, int32_t scancode); static void _progTrace(const char *fmt, ...) __attribute__((format(printf, 1, 2))); static int32_t _pushVec3(lua_State *L, Vec3T v); static void _quadIndex(int32_t *indices, int32_t slot, int32_t quad); static void _quadScratch(int32_t quads, int32_t frames); static void _navCallbacks(void); static void _physicsCallbacks(void); static void _particleTexturesDestroy(ParticleTexturesT *cache); static void _particleTexturesFree(int32_t emitter); static void _particleTexturesFreeAll(void); static ParticleTexturesT *_particleTexturesGet(const EmitterViewT *view); static void _registerApi(lua_State *L); static void _reloadScript(void); static void _resetScriptState(void); static void _runScript(bool fatal); static SDL_Texture *_sceneVideoSource(int32_t player); static ConfigT *_scriptConfFromTable(lua_State *L, const char *method); static void _pushConstants(lua_State *L); static void _putPixel(int32_t x, int32_t y, uint32_t pixel); static void _readColor(lua_State *L, const char *method, SDL_Color *color, uint8_t defaultAlpha); static void _releaseAxis(int32_t axisIndex); static SDL_Surface *_renderText(lua_State *L, const char *method, const char *message); static void _saveAudioCalibration(int32_t milliseconds); static void _selectDefaultAudioTrack(int32_t handle); static void _setMouseCaptured(bool captured); static void _setPause(bool paused, bool fromKey); static void _soundDestroy(SoundT *sound); static int32_t _soundQueueDrain(int32_t *finished); static void _spriteDestroy(SpriteT *sprite); static void _spriteFreeSurface(SpriteT *sprite); static void _spriteRebuildSurface(SpriteT *sprite); static void _startControllers(void); static void _startLuaContext(lua_State *L); static void _stopControllers(void); static void _subsystemsInit(void); static void _subsystemsQuit(void); static void _suppressHeldInput(void); static uint32_t _surfaceCornerKey(SDL_Surface *surface); static void _surfaceUnpack(SDL_Surface **surface); static void _takeScreenshot(void); static void _unloadScriptResources(void); static void _updatePauseState(void); static void _updateSounds(void); static void _videoDestroy(VideoT *video); static void _watchFile(const char *name); static bool _watchedChanged(void); static int32_t apiAnimationGetTime(lua_State *L); static int32_t apiAnimationIsPlaying(lua_State *L); static int32_t apiAnimationPause(lua_State *L); static int32_t apiAnimationPlay(lua_State *L); static int32_t apiAnimationPlayLayer(lua_State *L); static int32_t apiAnimationResume(lua_State *L); static int32_t apiAnimationSetLayerMask(lua_State *L); static int32_t apiAnimationSetLayerWeight(lua_State *L); static int32_t apiAnimationSetTime(lua_State *L); static int32_t apiAnimationStop(lua_State *L); static int32_t apiBodyApplyForce(lua_State *L); static int32_t apiBodyApplyImpulse(lua_State *L); static int32_t apiBodyDelete(lua_State *L); static int32_t apiBodyGetAngularVelocity(lua_State *L); static int32_t apiBodyGetVelocity(lua_State *L); static int32_t apiBodyIsResting(lua_State *L); static int32_t apiBodyNew(lua_State *L); static int32_t apiBodySetAngularVelocity(lua_State *L); static int32_t apiBodySetBounce(lua_State *L); static int32_t apiBodySetBuoyancy(lua_State *L); static int32_t apiBodySetCurrent(lua_State *L); static int32_t apiBodySetEnabled(lua_State *L); static int32_t apiBodySetFriction(lua_State *L); static int32_t apiBodySetMass(lua_State *L); static int32_t apiBodySetTrigger(lua_State *L); static int32_t apiBodySetVelocity(lua_State *L); static int32_t apiBodySetWater(lua_State *L); static int32_t apiCameraSet(lua_State *L); static int32_t apiCameraSetOrthographic(lua_State *L); static int32_t apiCameraSetPerspective(lua_State *L); static int32_t apiColorBackground(lua_State *L); static int32_t apiColorForeground(lua_State *L); static int32_t apiControllerGetAxis(lua_State *L); static int32_t apiControllerGetButton(lua_State *L); static int32_t apiDebugPrint(lua_State *L); static int32_t apiDiscAudio(lua_State *L); static int32_t apiDiscChangeSpeed(lua_State *L); static int32_t apiDiscGetAudioTrack(lua_State *L); static int32_t apiDiscGetAudioTracks(lua_State *L); static int32_t apiDiscGetFrame(lua_State *L); static int32_t apiDiscGetHeight(lua_State *L); static int32_t apiDiscGetLanguage(lua_State *L); static int32_t apiDiscGetState(lua_State *L); static int32_t apiDiscGetWidth(lua_State *L); static int32_t apiDiscPause(lua_State *L); static int32_t apiDiscPlay(lua_State *L); static int32_t apiDiscSearch(lua_State *L); static int32_t apiDiscSearchBlanking(lua_State *L); static int32_t apiDiscSetAudioTrack(lua_State *L); static int32_t apiDiscSetFPS(lua_State *L); static int32_t apiDiscSkipBackward(lua_State *L); static int32_t apiDiscSkipBlanking(lua_State *L); static int32_t apiDiscSkipForward(lua_State *L); static int32_t apiDiscSkipToFrame(lua_State *L); static int32_t apiDiscStepBackward(lua_State *L); static int32_t apiDiscStepForward(lua_State *L); static int32_t apiDiscStop(lua_State *L); static int32_t apiEmitterBurst(lua_State *L); static int32_t apiEmitterClear(lua_State *L); static int32_t apiEmitterDelete(lua_State *L); static int32_t apiEmitterDraw(lua_State *L); static int32_t apiEmitterGetCount(lua_State *L); static int32_t apiEmitterIsActive(lua_State *L); static int32_t apiEmitterNew(lua_State *L); static int32_t apiEmitterSetBlend(lua_State *L); static int32_t apiEmitterSetCollide(lua_State *L); static int32_t apiEmitterSetColor(lua_State *L); static int32_t apiEmitterSetDirection(lua_State *L); static int32_t apiEmitterSetDrag(lua_State *L); static int32_t apiEmitterSetFrames(lua_State *L); static int32_t apiEmitterSetGravity(lua_State *L); static int32_t apiEmitterSetLayer(lua_State *L); static int32_t apiEmitterSetLife(lua_State *L); static int32_t apiEmitterSetLit(lua_State *L); static int32_t apiEmitterSetLocal(lua_State *L); static int32_t apiEmitterSetMax(lua_State *L); static int32_t apiEmitterSetPosition(lua_State *L); static int32_t apiEmitterSetRadius(lua_State *L); static int32_t apiEmitterSetRate(lua_State *L); static int32_t apiEmitterSetSize(lua_State *L); static int32_t apiEmitterSetSoftness(lua_State *L); static int32_t apiEmitterSetSpeed(lua_State *L); static int32_t apiEmitterSetSpin(lua_State *L); static int32_t apiEmitterSetSpread(lua_State *L); static int32_t apiEmitterSetTexture(lua_State *L); static int32_t apiEmitterSetTrail(lua_State *L); static int32_t apiEmitterStart(lua_State *L); static int32_t apiEmitterStop(lua_State *L); static int32_t apiFontLoad(lua_State *L); static int32_t apiFontPrint(lua_State *L); static int32_t apiFontQuality(lua_State *L); static int32_t apiFontSelect(lua_State *L); static int32_t apiFontToSprite(lua_State *L); static int32_t apiFontUnload(lua_State *L); static int32_t apiJointBall(lua_State *L); static int32_t apiJointDelete(lua_State *L); static int32_t apiJointHinge(lua_State *L); static int32_t apiJointSetLimits(lua_State *L); static int32_t apiJointSlider(lua_State *L); static int32_t apiKeyboardGetLastDown(lua_State *L); static int32_t apiKeyboardGetLastUp(lua_State *L); static int32_t apiKeyboardGetMode(lua_State *L); static int32_t apiKeyboardGetModifiers(lua_State *L); static int32_t apiKeyboardIsDown(lua_State *L); static int32_t apiKeyboardSetMode(lua_State *L); static int32_t apiLightNew(lua_State *L); static int32_t apiLightSetColor(lua_State *L); static int32_t apiLightSetCone(lua_State *L); static int32_t apiLightSetIntensity(lua_State *L); static int32_t apiLightSetRange(lua_State *L); static int32_t apiLightSetShadow(lua_State *L); static int32_t apiLineDraw(lua_State *L); static int32_t apiMaterialDelete(lua_State *L); static int32_t apiMaterialNew(lua_State *L); static int32_t apiMaterialSetBlend(lua_State *L); static int32_t apiMaterialSetColor(lua_State *L); static int32_t apiMaterialSetDoubleSided(lua_State *L); static int32_t apiMaterialSetEmissive(lua_State *L); static int32_t apiMaterialSetEmissiveMap(lua_State *L); static int32_t apiMaterialSetFilter(lua_State *L); static int32_t apiMaterialSetMetallic(lua_State *L); static int32_t apiMaterialSetMetallicRoughnessMap(lua_State *L); static int32_t apiMaterialSetNormalMap(lua_State *L); static int32_t apiMaterialSetOcclusionMap(lua_State *L); static int32_t apiMaterialSetRoughness(lua_State *L); static int32_t apiMaterialSetTexture(lua_State *L); static int32_t apiMaterialSetTiling(lua_State *L); static int32_t apiMaterialSetUnlit(lua_State *L); static int32_t apiMaterialSetVideo(lua_State *L); static int32_t apiMaterialSetView(lua_State *L); static int32_t apiMeshBox(lua_State *L); static int32_t apiMeshCone(lua_State *L); static int32_t apiMeshCylinder(lua_State *L); static int32_t apiMeshDelete(lua_State *L); static int32_t apiMeshHeightmap(lua_State *L); static int32_t apiMeshNew(lua_State *L); static int32_t apiMeshPlane(lua_State *L); static int32_t apiMeshSphere(lua_State *L); static int32_t apiMeshTorus(lua_State *L); static int32_t apiModelDelete(lua_State *L); static int32_t apiModelGetAnimations(lua_State *L); static int32_t apiModelInstance(lua_State *L); static int32_t apiModelLoad(lua_State *L); static int32_t apiMouseGetPosition(lua_State *L); static int32_t apiMouseHowMany(lua_State *L); static int32_t apiMouseSetCaptured(lua_State *L); static int32_t apiMouseSetEnabled(lua_State *L); static int32_t apiMouseSetMode(lua_State *L); static int32_t apiNavAddNode(lua_State *L); static int32_t apiNavAgentDelete(lua_State *L); static int32_t apiNavAgentGetVelocity(lua_State *L); static int32_t apiNavAgentIsArrived(lua_State *L); static int32_t apiNavAgentMoveTo(lua_State *L); static int32_t apiNavAgentNew(lua_State *L); static int32_t apiNavAgentSetPlayer(lua_State *L); static int32_t apiNavAgentStop(lua_State *L); static int32_t apiNavBuild(lua_State *L); static int32_t apiNavDelete(lua_State *L); static int32_t apiNavDraw(lua_State *L); static int32_t apiNavLoad(lua_State *L); static int32_t apiNavNearest(lua_State *L); static int32_t apiNavNew(lua_State *L); static int32_t apiNavPath(lua_State *L); static int32_t apiNavRandomPoint(lua_State *L); static int32_t apiNavRaycast(lua_State *L); static int32_t apiNavSave(lua_State *L); static int32_t apiNodeDelete(lua_State *L); static int32_t apiNodeFind(lua_State *L); static int32_t apiNodeGetChildren(lua_State *L); static int32_t apiNodeGetMorph(lua_State *L); static int32_t apiNodeGetMorphs(lua_State *L); static int32_t apiNodeGetName(lua_State *L); static int32_t apiNodeGetParent(lua_State *L); static int32_t apiNodeGetPosition(lua_State *L); static int32_t apiNodeGetQuaternion(lua_State *L); static int32_t apiNodeGetRotation(lua_State *L); static int32_t apiNodeGetScale(lua_State *L); static int32_t apiNodeGetWorldPosition(lua_State *L); static int32_t apiNodeLookAt(lua_State *L); static int32_t apiNodeMove(lua_State *L); static int32_t apiNodeNew(lua_State *L); static int32_t apiNodeRotate(lua_State *L); static int32_t apiNodeSetBillboard(lua_State *L); static int32_t apiNodeSetMaterial(lua_State *L); static int32_t apiNodeSetMesh(lua_State *L); static int32_t apiNodeSetMorph(lua_State *L); static int32_t apiNodeSetName(lua_State *L); static int32_t apiNodeSetParent(lua_State *L); static int32_t apiNodeSetPosition(lua_State *L); static int32_t apiNodeSetQuaternion(lua_State *L); static int32_t apiNodeSetRotation(lua_State *L); static int32_t apiNodeSetScale(lua_State *L); static int32_t apiNodeSetShadow(lua_State *L); static int32_t apiNodeSetSprite(lua_State *L); static int32_t apiNodeSetSpriteFrame(lua_State *L); static int32_t apiNodeSetText(lua_State *L); static int32_t apiNodeSetVisible(lua_State *L); static int32_t apiOsClock(lua_State *L); static int32_t apiOverlayBox(lua_State *L); static int32_t apiOverlayCircle(lua_State *L); static int32_t apiOverlayClear(lua_State *L); static int32_t apiOverlayEllipse(lua_State *L); static int32_t apiOverlayGetHeight(lua_State *L); static int32_t apiOverlayGetWidth(lua_State *L); static int32_t apiOverlayLine(lua_State *L); static int32_t apiOverlayPlot(lua_State *L); static int32_t apiOverlayPrint(lua_State *L); static int32_t apiOverlaySetResolution(lua_State *L); static int32_t apiPhysicsRaycast(lua_State *L); static int32_t apiPhysicsSet2D(lua_State *L); static int32_t apiPhysicsSetDebug(lua_State *L); static int32_t apiPhysicsSetEnabled(lua_State *L); static int32_t apiPhysicsSetGravity(lua_State *L); static int32_t apiPlayerDelete(lua_State *L); static int32_t apiPlayerGetGround(lua_State *L); static int32_t apiPlayerGetVelocity(lua_State *L); static int32_t apiPlayerIsOnGround(lua_State *L); static int32_t apiPlayerIsSwimming(lua_State *L); static int32_t apiPlayerJump(lua_State *L); static int32_t apiPlayerMove(lua_State *L); static int32_t apiPlayerNew(lua_State *L); static int32_t apiPlayerSetEnabled(lua_State *L); static int32_t apiPlayerSetGravityScale(lua_State *L); static int32_t apiPlayerSetMass(lua_State *L); static int32_t apiPlayerSetPosition(lua_State *L); static int32_t apiPlayerSetPush(lua_State *L); static int32_t apiPlayerSetSlope(lua_State *L); static int32_t apiPlayerSetStep(lua_State *L); static int32_t apiPlayerSetSwim(lua_State *L); static int32_t apiPlayerSetVelocity(lua_State *L); static int32_t apiRagdollActivate(lua_State *L); static int32_t apiRagdollApplyImpulse(lua_State *L); static int32_t apiRagdollDeactivate(lua_State *L); static int32_t apiRagdollDelete(lua_State *L); static int32_t apiRagdollIsActive(lua_State *L); static int32_t apiRagdollIsResting(lua_State *L); static int32_t apiRagdollNew(lua_State *L); static int32_t apiRagdollSetJoint(lua_State *L); static int32_t apiRagdollSetStrength(lua_State *L); static int32_t apiSceneEnable(lua_State *L); static int32_t apiSceneGetSize(lua_State *L); static int32_t apiSceneGetStats(lua_State *L); static int32_t apiSceneProject(lua_State *L); static int32_t apiSceneSetAmbient(lua_State *L); static int32_t apiSceneSetAntialias(lua_State *L); static int32_t apiSceneSetBackground(lua_State *L); static int32_t apiSceneSetBloom(lua_State *L); static int32_t apiSceneSetEnvironment(lua_State *L); static int32_t apiSceneSetExposure(lua_State *L); static int32_t apiSceneSetFog(lua_State *L); static int32_t apiSceneSetShadowCascades(lua_State *L); static int32_t apiSceneSetShadowDistance(lua_State *L); static int32_t apiSceneSetShadowSize(lua_State *L); static int32_t apiSceneSetSky(lua_State *L); static int32_t apiSceneSetSkyIntensity(lua_State *L); static int32_t apiSceneSetTonemap(lua_State *L); static int32_t apiSceneUnproject(lua_State *L); static int32_t apiScriptExecute(lua_State *L); static int32_t apiScriptPush(lua_State *L); static int32_t apiSingeGetAudioCalibration(lua_State *L); static int32_t apiSingeGetAudioDelay(lua_State *L); static int32_t apiSingeGetAudioLatency(lua_State *L); static int32_t apiSingeGetDataPath(lua_State *L); static int32_t apiSingeGetHeight(lua_State *L); static int32_t apiSingeGetPauseFlag(lua_State *L); static int32_t apiSingeGetScriptPath(lua_State *L); static int32_t apiSingeGetTicks(lua_State *L); static int32_t apiSingeGetWidth(lua_State *L); static int32_t apiSingeQuit(lua_State *L); static int32_t apiSingeReload(lua_State *L); static int32_t apiSingeScreenshot(lua_State *L); static int32_t apiSingeSetAudioCalibration(lua_State *L); static int32_t apiSingeSetAudioDelay(lua_State *L); static int32_t apiSingeSetGameName(lua_State *L); static int32_t apiSingeSetPauseFlag(lua_State *L); static int32_t apiSingeSetPauseKeyEnabled(lua_State *L); static int32_t apiSingeVersion(lua_State *L); static int32_t apiSingeWantsCrosshairs(lua_State *L); static int32_t apiSoftDelete(lua_State *L); static int32_t apiSoftNew(lua_State *L); static int32_t apiSoftPin(lua_State *L); static int32_t apiSoftSetDamping(lua_State *L); static int32_t apiSoftSetMass(lua_State *L); static int32_t apiSoftSetPressure(lua_State *L); static int32_t apiSoftSetStiffness(lua_State *L); static int32_t apiSoftUnpin(lua_State *L); static int32_t apiSoundFullStop(lua_State *L); static int32_t apiSoundGetPosition(lua_State *L); static int32_t apiSoundGetVolume(lua_State *L); static int32_t apiSoundIsPlaying(lua_State *L); static int32_t apiSoundLoad(lua_State *L); static int32_t apiSoundPause(lua_State *L); static int32_t apiSoundPlay(lua_State *L); static int32_t apiSoundResume(lua_State *L); static int32_t apiSoundSetListener(lua_State *L); static int32_t apiSoundSetNode(lua_State *L); static int32_t apiSoundSetPan(lua_State *L); static int32_t apiSoundSetPosition(lua_State *L); static int32_t apiSoundSetRange(lua_State *L); static int32_t apiSoundSetVolume(lua_State *L); static int32_t apiSoundStop(lua_State *L); static int32_t apiSoundUnload(lua_State *L); static int32_t apiSpriteDraw(lua_State *L); static int32_t apiSpriteGetFrame(lua_State *L); static int32_t apiSpriteGetHeight(lua_State *L); static int32_t apiSpriteGetWidth(lua_State *L); static int32_t apiSpriteIsPlaying(lua_State *L); static int32_t apiSpriteLoad(lua_State *L); static int32_t apiSpriteLoop(lua_State *L); static int32_t apiSpritePause(lua_State *L); static int32_t apiSpritePlay(lua_State *L); static int32_t apiSpriteQuality(lua_State *L); static int32_t apiSpriteRotate(lua_State *L); static int32_t apiSpriteRotateAndScale(lua_State *L); static int32_t apiSpriteScale(lua_State *L); static int32_t apiSpriteSetFrame(lua_State *L); static int32_t apiSpriteUnload(lua_State *L); static int32_t apiTerrainGetHeight(lua_State *L); static int32_t apiVehicleAddWheel(lua_State *L); static int32_t apiVehicleDelete(lua_State *L); static int32_t apiVehicleDrive(lua_State *L); static int32_t apiVehicleGetGear(lua_State *L); static int32_t apiVehicleGetRpm(lua_State *L); static int32_t apiVehicleGetSpeed(lua_State *L); static int32_t apiVehicleGetWheelSlip(lua_State *L); static int32_t apiVehicleIsWheelOnGround(lua_State *L); static int32_t apiVehicleNew(lua_State *L); static int32_t apiVehicleSetAntiRoll(lua_State *L); static int32_t apiVehicleSetBrakes(lua_State *L); static int32_t apiVehicleSetEngine(lua_State *L); static int32_t apiVehicleSetGears(lua_State *L); static int32_t apiVehicleSetSteering(lua_State *L); static int32_t apiVehicleSetRudder(lua_State *L); static int32_t apiVehicleSetSuspension(lua_State *L); static int32_t apiVehicleSetThrust(lua_State *L); static int32_t apiVehicleSetWheel(lua_State *L); static int32_t apiVideoDraw(lua_State *L); static int32_t apiVideoGetAudioTrack(lua_State *L); static int32_t apiVideoGetAudioTracks(lua_State *L); static int32_t apiVideoGetFrame(lua_State *L); static int32_t apiVideoGetFrameCount(lua_State *L); static int32_t apiVideoGetHeight(lua_State *L); static int32_t apiVideoGetLanguage(lua_State *L); static int32_t apiVideoGetLanguageDescription(lua_State *L); static int32_t apiVideoGetVolume(lua_State *L); static int32_t apiVideoGetWidth(lua_State *L); static int32_t apiVideoIsPlaying(lua_State *L); static int32_t apiVideoLoad(lua_State *L); static int32_t apiVideoPause(lua_State *L); static int32_t apiVideoPlay(lua_State *L); static int32_t apiVideoQuality(lua_State *L); static int32_t apiVideoRotate(lua_State *L); static int32_t apiVideoRotateAndScale(lua_State *L); static int32_t apiVideoScale(lua_State *L); static int32_t apiVideoSeek(lua_State *L); static int32_t apiVideoSetAudioTrack(lua_State *L); static int32_t apiVideoSetVolume(lua_State *L); static int32_t apiVideoUnload(lua_State *L); static int32_t apiViewDelete(lua_State *L); static int32_t apiViewNew(lua_State *L); static int32_t apiViewSetCamera(lua_State *L); static int32_t apiVldpGetPixel(lua_State *L); static int32_t apiVldpSetVerbose(lua_State *L); #define MODL(name, array) { name, { (const char *)array }, sizeof(array) } #define MODC(name, openf) { name, { (const char *)openf }, 0 } // Lua Modules static const LuaModuleT _luaModules[] = { // LuaFileSystem MODC("lfs", _luaopenLfs), // SQLite for script data MODC("sqlite3", luaopen_lsqlite3), // LuaSocket MODC("mime.core", luaopen_mime_core), MODC("socket.core", luaopen_socket_core), MODL("ltn12", ltn12_lua), MODL("mbox", mbox_lua), MODL("mime", mime_lua), MODL("socket", socket_lua), MODL("socket.ftp", ftp_lua), MODL("socket.headers", headers_lua), MODL("socket.http", http_lua), MODL("socket.smtp", smtp_lua), MODL("socket.tp", tp_lua), MODL("socket.url", url_lua), #ifndef _WIN32 MODC("socket.unix", luaopen_socket_unix), MODC("socket.serial", luaopen_socket_serial), #endif // LuaSec MODC("ssl.core", luaopen_ssl_core), MODC("ssl.context", luaopen_ssl_context), MODC("ssl.x509", luaopen_ssl_x509), MODC("ssl.config", luaopen_ssl_config), MODL("ssl.https", https_lua), MODL("ssl", ssl_lua), // LuaRS232 MODC("rs232.core", luaopen_luars232), MODL("rs232", rs232_lua), // binaryheap MODL("binaryheap", binaryheap_lua), // timerwheel MODL("timerwheel", timerwheel_lua), // json MODL("json", json_lua), // Copas MODL("copas", copas_lua), MODL("copas.ftp", copas_ftp_lua), MODL("copas.http", copas_http_lua), MODL("copas.lock", copas_lock_lua), MODL("copas.queue", copas_queue_lua), MODL("copas.semaphore", copas_semaphore_lua), MODL("copas.smtp", copas_smtp_lua), MODL("copas.timer", copas_timer_lua), }; // ===== Internal helpers ===== // Legacy Daphne functions that Singe never implemented. They trace and return nothing. static int32_t _apiUnimplemented(lua_State *L, const char *method) { _luaTrace(L, method, "Unimplemented"); return 0; } // An animation by name or by its number from 1, as modelGetAnimations lists them. static int32_t _argAnimation(lua_State *L, const char *method, int32_t model, int32_t index) { int32_t animation; if (lua_type(L, index) == LUA_TSTRING) { animation = modelAnimationIndex(model, lua_tostring(L, index)); if (animation < 0) { _luaDie(L, method, "Model %d has no animation named %s.", model, lua_tostring(L, index)); } return animation; } return _argInteger(L, method, index) - 1; } // A layer number above the base, 1 to ANIMATION_LAYERS. static int32_t _argAnimationLayer(lua_State *L, const char *method, int32_t index) { int32_t layer = _argInteger(L, method, index); if ((layer < 1) || (layer > ANIMATION_LAYERS)) { _luaDie(L, method, "Layer must be 1 to %d: %d", ANIMATION_LAYERS, layer); } return layer; } static bool _argBoolean(lua_State *L, const char *method, int32_t index) { if (!lua_isboolean(L, index)) { _luaDie(L, method, "Argument %d must be a boolean.", index); } return lua_toboolean(L, index) != 0; } // An effect channel number as soundPlay returned it. static int32_t _argChannel(lua_State *L, const char *method, int32_t index) { int32_t channel = _argInteger(L, method, index); if ((channel < 0) || (channel >= EFFECT_TRACKS)) { _luaDie(L, method, "Invalid channel: %d", channel); } return channel; } // A colour component argument, clamped to 0..255. static uint8_t _argColorByte(lua_State *L, const char *method, int32_t index) { return (uint8_t)SDL_clamp(_argInteger(L, method, index), 0, COLOR_BYTE_MAX); } // Dies unless the argument count is within [minimum, maximum]. static void _argCheck(lua_State *L, const char *method, int32_t minimum, int32_t maximum) { int32_t n = lua_gettop(L); if ((n < minimum) || (n > maximum)) { if (minimum == maximum) { _luaDie(L, method, "Expected %d argument(s), got %d.", minimum, n); } _luaDie(L, method, "Expected %d to %d arguments, got %d.", minimum, maximum, n); } } static FontT *_argFont(lua_State *L, const char *method, int32_t index) { int32_t id = _argInteger(L, method, index); FontT *font = NULL; HASH_FIND_INT(_global.fontList, &id, font); if (!font) { _luaDie(L, method, "No font at index %d.", id); } return font; } // The scene's rotation arguments: three Euler angles in degrees. static QuatT _argEuler(lua_State *L, const char *method, int32_t index) { return quatFromEuler((float)_argNumber(L, method, index), (float)_argNumber(L, method, index + 1), (float)_argNumber(L, method, index + 2)); } // A table of numbers as a float array (caller frees). A nil argument gives NULL and count 0. static float *_argFloatTable(lua_State *L, const char *method, int32_t index, int32_t *count) { float *values; int32_t x; *count = 0; if (lua_isnoneornil(L, index)) { return NULL; } if (!lua_istable(L, index)) { _luaDie(L, method, "Argument %d must be a table of numbers.", index); } *count = (int32_t)lua_rawlen(L, index); values = SDL_calloc((size_t)SDL_max(*count, 1), sizeof(float)); if (values == NULL) { _luaDie(L, method, "Out of memory."); } for (x = 0; x < *count; x++) { lua_rawgeti(L, index, x + 1); if (!lua_isnumber(L, -1)) { SDL_free(values); _luaDie(L, method, "Argument %d, element %d is not a number.", index, x + 1); } values[x] = (float)lua_tonumber(L, -1); lua_pop(L, 1); } return values; } // A handle argument the given test accepts, named for the message: "No material 7." static int32_t _argHandle(lua_State *L, const char *method, int32_t index, bool (*valid)(int32_t), const char *noun) { int32_t handle = _argInteger(L, method, index); if (!valid(handle)) { _luaDie(L, method, "No %s %d.", noun, handle); } return handle; } static int32_t _argInteger(lua_State *L, const char *method, int32_t index) { return (int32_t)_argNumber(L, method, index); } // A morph target of a node's mesh, by name or by number from 1, checked. static int32_t _argMorph(lua_State *L, const char *method, int32_t node, int32_t index) { int32_t target; if (lua_type(L, index) == LUA_TSTRING) { target = meshFindMorph(nodeGetMesh(node), lua_tostring(L, index)); if (target < 0) { _luaDie(L, method, "Node %d has no morph target named %s.", node, lua_tostring(L, index)); } return target; } target = _argInteger(L, method, index) - 1; if ((target < 0) || (target >= nodeGetMorphCount(node))) { _luaDie(L, method, "Node %d has no morph target %d.", node, target + 1); } return target; } // A node that carries a physics body, checked. static int32_t _argBody(lua_State *L, const char *method, int32_t index) { return _argNodeWith(L, method, index, bodyExists, "body"); } // An emitter handle argument, checked. static int32_t _argEmitter(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, emitterValid, "emitter"); } // A texture argument for a material map: a loaded sprite (its surface comes back), or the name of a // KTX2 file (transcoded into ktx2, true comes back and the caller frees it). static bool _argMapImage(lua_State *L, const char *method, int32_t index, SDL_Surface **surface, Ktx2ImageT *ktx2) { const char *name; char *bytes; size_t size = 0; bool ok; *surface = NULL; memset(ktx2, 0, sizeof(*ktx2)); if (lua_type(L, index) != LUA_TSTRING) { *surface = _argSprite(L, method, index)->originalSurface; return false; } name = lua_tostring(L, index); bytes = vfsRead(name, &size); if (bytes == NULL) { _luaDie(L, method, "Unable to read %s", name); } if (!ktx2Is(bytes, size)) { free(bytes); _luaDie(L, method, "%s is not a KTX2 texture (load other pictures with spriteLoad).", name); } ok = ktx2Transcode(bytes, size, sceneCompressedFormat(), ktx2); free(bytes); if (!ok) { _luaDie(L, method, "%s: %s", name, SDL_GetError()); } return true; } // A material handle argument, checked. static int32_t _argMaterial(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, materialValid, "material"); } // A mesh handle argument, checked. static int32_t _argMesh(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, meshValid, "mesh"); } // A navigation mesh handle from navNew or navLoad. static int32_t _argNav(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, navValid, "navigation mesh"); } // An agent handle from navAgentNew. static int32_t _argNavAgent(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, navAgentValid, "navigation agent"); } // A scene node handle argument, checked. static int32_t _argNode(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, nodeValid, "node"); } // A node argument carrying the given kind of thing: "Node 7 has no body." static int32_t _argNodeWith(lua_State *L, const char *method, int32_t index, bool (*exists)(int32_t), const char *noun) { int32_t node = _argNode(L, method, index); if (!exists(node)) { _luaDie(L, method, "Node %d has no %s.", node, noun); } return node; } // r, g, b at index onwards when the script gave them; the caller's defaults otherwise. static void _argOptionalColor(lua_State *L, const char *method, int32_t index, uint8_t *r, uint8_t *g, uint8_t *b) { if (lua_gettop(L) < index + 2) { return; } *r = _argColorByte(L, method, index); *g = _argColorByte(L, method, index + 1); *b = _argColorByte(L, method, index + 2); } // A node carrying a player, checked. static int32_t _argPlayer(lua_State *L, const char *method, int32_t index) { return _argNodeWith(L, method, index, playerExists, "player"); } // A node carrying a ragdoll, checked. static int32_t _argRagdoll(lua_State *L, const char *method, int32_t index) { return _argNodeWith(L, method, index, ragdollExists, "ragdoll"); } // A node carrying a soft body, checked. static int32_t _argSoft(lua_State *L, const char *method, int32_t index) { return _argNodeWith(L, method, index, softExists, "soft body"); } // A node carrying a vehicle, checked. static int32_t _argVehicle(lua_State *L, const char *method, int32_t index) { return _argNodeWith(L, method, index, vehicleExists, "vehicle"); } static int64_t _argInteger64(lua_State *L, const char *method, int32_t index) { return (int64_t)_argNumber(L, method, index); } static double _argNumber(lua_State *L, const char *method, int32_t index) { if (!lua_isnumber(L, index)) { _luaDie(L, method, "Argument %d must be a number.", index); } return lua_tonumber(L, index); } static SoundT *_argSound(lua_State *L, const char *method, int32_t index) { int32_t id = _argInteger(L, method, index); SoundT *sound = NULL; HASH_FIND_INT(_global.soundList, &id, sound); if (!sound) { _luaDie(L, method, "No sound at index %d.", id); } return sound; } static SpriteT *_argSprite(lua_State *L, const char *method, int32_t index) { int32_t id = _argInteger(L, method, index); SpriteT *sprite = NULL; HASH_FIND_INT(_global.spriteList, &id, sprite); if (!sprite) { _luaDie(L, method, "No sprite at index %d.", id); } return sprite; } static const char *_argString(lua_State *L, const char *method, int32_t index) { if (!lua_isstring(L, index)) { _luaDie(L, method, "Argument %d must be a string.", index); } return lua_tostring(L, index); } // Three numbers as a vector. static Vec3T _argVec3(lua_State *L, const char *method, int32_t index) { return vec3((float)_argNumber(L, method, index), (float)_argNumber(L, method, index + 1), (float)_argNumber(L, method, index + 2)); } // A view handle from viewNew. static int32_t _argView(lua_State *L, const char *method, int32_t index) { return _argHandle(L, method, index, viewValid, "view"); } static VideoT *_argVideo(lua_State *L, const char *method, int32_t index) { int32_t id = _argInteger(L, method, index); VideoT *video = NULL; HASH_FIND_INT(_global.videoList, &id, video); if (!video) { _luaDie(L, method, "No video at index %d.", id); } return video; } // Builds a config for scriptExecute/scriptPush from the games.dat style table at stack index 1. static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { const char *confKey = NULL; const char *valueString = NULL; bool valueBoolean = false; int64_t valueNumber = 0; ConfigT *c = NULL; // Start with the given config, but every entry brings its own video, container and data directory. c = cloneConf(base); free(c->container); c->container = NULL; c->disc = false; c->isFrameFile = false; free(c->videoFile); c->videoFile = NULL; free(c->dataDir); c->dataDir = NULL; // Update with data in the table on the top of the Lua stack. lua_pushnil(L); while (lua_next(L, 1)) { // Keys must be strings; converting other keys in place would confuse lua_next. if (lua_type(L, 2) != LUA_TSTRING) { lua_pop(L, 1); continue; } confKey = lua_tostring(L, 2); valueString = NULL; valueBoolean = false; valueNumber = 0; // Get value switch (lua_type(L, 3)) { case LUA_TSTRING: valueString = lua_tostring(L, 3); break; case LUA_TBOOLEAN: valueBoolean = lua_toboolean(L, 3); break; case LUA_TNUMBER: valueNumber = (int64_t)lua_tonumber(L, 3); break; default: break; } // Update config with new data if (strcmp(confKey, "SCRIPT") == 0) { if (valueString == NULL) { utilDie("SCRIPT must be a string."); } free(c->scriptFile); c->scriptFile = strdup(valueString); utilFixPathSeparators(&c->scriptFile, false); } else if (strcmp(confKey, "CONTAINER") == 0) { if (valueString == NULL) { utilDie("CONTAINER must be a string."); } free(c->container); c->container = strdup(valueString); utilFixPathSeparators(&c->container, false); } else if (strcmp(confKey, "VIDEO") == 0) { if (valueString == NULL) { utilDie("VIDEO must be a string."); } free(c->videoFile); c->videoFile = strdup(valueString); utilFixPathSeparators(&c->videoFile, false); c->isFrameFile = isFrameFileName(c->videoFile); } else if (strcmp(confKey, "STRETCH") == 0) { c->stretchVideo = valueBoolean; } else if (strcmp(confKey, "NO_MOUSE") == 0) { c->noMouse = valueBoolean; } else if (strcmp(confKey, "RESOLUTION_X") == 0) { c->xResolution = (int32_t)valueNumber; } else if (strcmp(confKey, "RESOLUTION_Y") == 0) { c->yResolution = (int32_t)valueNumber; } else if (strcmp(confKey, "SINDEN_GUN") == 0) { if ((valueString == NULL) || !parseSindenString(valueString, c)) { c->sindenArgc = 0; } } else if (strcmp(confKey, "AUDIO_TRACK") == 0) { c->audioOutputTrack = (int32_t)valueNumber; } else if (strcmp(confKey, "AUDIO_DELAY") == 0) { c->audioDelayMs = (int32_t)valueNumber; } else if (strcmp(confKey, "LEGACY_SPRITE_ARGS") == 0) { c->legacySpriteArgs = valueBoolean; } else if (strcmp(confKey, "CANVAS_X") == 0) { c->canvasWidth = (int32_t)valueNumber; } else if (strcmp(confKey, "CANVAS_Y") == 0) { c->canvasHeight = (int32_t)valueNumber; } // Clean up for next pair lua_pop(L, 1); } // The VIDEO line is the disc: present and not blank means a laserdisc game. if ((c->videoFile != NULL) && (c->videoFile[0] == 0)) { free(c->videoFile); c->videoFile = NULL; } c->disc = (c->videoFile != NULL); if ((c->canvasWidth <= 0) || (c->canvasHeight <= 0)) { utilDie("%s: CANVAS_X and CANVAS_Y must be positive.", c->scriptFile); } return c; } // Calls a global Lua function. sig lists argument types (d, i, s), then '>' and result types. static void _callLua(const char *func, const char *sig, ...) { va_list vl; bool done = false; int32_t narg = 0; int32_t nres = 0; int32_t handler = 0; double d = 0; const int32_t top = lua_gettop(_global.luaContext); // Get Function lua_getglobal(_global.luaContext, func); if (!lua_isfunction(_global.luaContext, -1)) { // Function does not exist. Bail. lua_settop(_global.luaContext, top); return; } if (_global.conf->scriptTracing) { utilTrace("%s", func); } // Traceback handler sits below the function. lua_pushcfunction(_global.luaContext, _luaTraceback); lua_insert(_global.luaContext, -2); handler = lua_gettop(_global.luaContext) - 1; // Push Arguments. Room for all of them (the results reuse it) is checked once, up front. luaL_checkstack(_global.luaContext, (int)strlen(sig) + 1, "Too many arguments"); va_start(vl, sig); while ((*sig) && (!done)) { switch (*sig++) { case 'd': // Double lua_pushnumber(_global.luaContext, va_arg(vl, double)); break; case 'i': // Int lua_pushinteger(_global.luaContext, va_arg(vl, int)); // Promoted type for varargs. break; case 'b': // Boolean (passed as an int) lua_pushboolean(_global.luaContext, va_arg(vl, int32_t)); break; case 's': // String lua_pushstring(_global.luaContext, va_arg(vl, char *)); break; case '>': done = true; break; default: utilDie("Invalid argument option (%c)", *(sig - 1)); } if (!done) { narg++; } } // Do the call. Script errors are fatal, like every other error in Singe. nres = (int32_t)strlen(sig); if (lua_pcall(_global.luaContext, narg, nres, handler) != 0) { utilDie("Error executing function '%s': %s", func, lua_tostring(_global.luaContext, -1)); } // Retrieve results nres = -nres; // Stack index of first result while (*sig) { switch (*sig++) { case 'd': // Double *va_arg(vl, double *) = lua_tonumber(_global.luaContext, nres); break; case 'i': // Int - nil or non-numbers read as zero. d = lua_tonumber(_global.luaContext, nres); *va_arg(vl, int32_t *) = (int32_t)d; break; default: utilDie("Invalid option (%c)", *(sig - 1)); } nres++; } va_end(vl); lua_settop(_global.luaContext, top); } // First effect track that is neither playing nor paused, or SOUND_CHANNEL_NONE when all are busy. static int32_t _effectTrackFree(void) { int32_t x = 0; for (x = 0; x < EFFECT_TRACKS; x++) { if (!MIX_TrackPlaying(_effectTracks[x]) && !MIX_TrackPaused(_effectTracks[x])) { return x; } } return SOUND_CHANNEL_NONE; } // The volume of a positioned sound at a distance: full within near, inverse distance beyond it, // and fading out over the last fifth before far. static float _effectGain(const EffectT *effect, float distance) { float gain = 1.0f; float fadeStart; if (distance > effect->nearBy) { gain = effect->nearBy / distance; } fadeStart = effect->farOff * (1.0f - SOUND_FADE_FRACTION); if (distance > fadeStart) { gain *= SDL_clamp((effect->farOff - distance) / (effect->farOff - fadeStart), 0.0f, 1.0f); } return gain; } // A channel about to play afresh: no position, no pan, the default range. static void _effectReset(int32_t channel) { EffectT *effect = &_effects[channel]; memset(effect, 0, sizeof(*effect)); effect->node = LISTENER_CAMERA; effect->nearBy = SOUND_DEFAULT_NEAR; effect->farOff = SOUND_DEFAULT_FAR; effect->gain = 1.0f; MIX_SetTrack3DPosition(_effectTracks[channel], NULL); } // SDL_mixer calls this from its mixing thread. Just queue the channel; the game loop reads it under the mixer lock. static void _effectStopped(void *userdata, MIX_Track *track) { (void)track; if (_global.soundQueueCount < SOUND_QUEUE_SIZE) { _global.soundQueue[_global.soundQueueCount++] = (int32_t)(intptr_t)userdata; } } // Liang-Barsky clip of a line to the overlay. False when none of it is on the overlay; otherwise // the ends are moved onto it. static bool _clipLine(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) { const double dx = *x2 - *x1; const double dy = *y2 - *y1; double t0 = 0.0; double t1 = 1.0; double r = 0.0; double p[4]; double q[4]; int32_t i = 0; // Each edge as the parametric distance to it: left, right, top, bottom. p[0] = -dx; q[0] = *x1; p[1] = dx; q[1] = (_global.overlay->w - 1) - *x1; p[2] = -dy; q[2] = *y1; p[3] = dy; q[3] = (_global.overlay->h - 1) - *y1; for (i = 0; i < 4; i++) { if (p[i] == 0.0) { // Parallel to this edge: outside it means gone altogether. if (q[i] < 0.0) { return false; } continue; } r = q[i] / p[i]; if (p[i] < 0.0) { if (r > t1) { return false; } t0 = SDL_max(t0, r); } else { if (r < t0) { return false; } t1 = SDL_min(t1, r); } } *x2 = (int32_t)lround(*x1 + t1 * dx); *y2 = (int32_t)lround(*y1 + t1 * dy); *x1 = (int32_t)lround(*x1 + t0 * dx); *y1 = (int32_t)lround(*y1 + t0 * dy); return true; } // Maps an SDL joystick instance ID to our controller slot, or -1. static int32_t _controllerSlot(SDL_JoystickID which) { int32_t x = 0; for (x = 0; x < MAX_CONTROLLERS; x++) { if ((_global.controllers[x] != NULL) && (SDL_GetGamepadID(_global.controllers[x]) == which)) { return x; } } return -1; } // Sleeps while keeping the window responsive. Returns false if the user asked to quit. static bool _delayAndPump(uint32_t ms) { SDL_Event event; uint64_t until = SDL_GetTicks() + ms; do { while (SDL_PollEvent(&event)) { if (event.type == SDL_EVENT_QUIT) { _global.running = false; } } SDL_Delay(IDLE_SLEEP_MS); } while (SDL_GetTicks() < until); return _global.running; } // Hands a key, button, or axis direction to the script, tracking what it now believes is held. static void _deliverKey(bool down, int32_t keysym, int32_t scancode) { int32_t move = 0; int32_t index = 0; if (_global.keyboardMode == KEYBOARD_FULL) { _heldListUpdate(_global.heldKeys, &_global.heldKeyCount, down, keysym, scancode); _callLua(down ? "onInputPressed" : "onInputReleased", "i", keysym); _callLua(down ? "onKeyPressed" : "onKeyReleased", "ii", keysym, scancode); return; } // Mappable switches. The pause switch belongs to the engine while its key is enabled. for (move = 0; move < INPUT_COUNT; move++) { if ((move == INPUT_PAUSE) && _global.pauseEnabled) { continue; } for (index = 0; index < _global.controlMappings[move].inputCount; index++) { if (_global.controlMappings[move].input[index] == scancode) { _global.switchHeld[move] = down; _callLua(down ? "onInputPressed" : "onInputReleased", "i", move); } } } } // The disc's current frame, whichever kind it is: a frame file counts across all its segments. // Only with a disc (videoHandle >= 0). static int64_t _discGetFrame(void) { if (_global.conf->isFrameFile) { return frameFileGetFrame(_global.frameFileHandle, _global.videoHandle); } return videoGetFrame(_global.videoHandle); } // Seeks the laserdisc, whichever kind it is, held within its ends (a frame file clamps inside its // segments itself) rather than wrapping around. static void _discSeek(int64_t frame) { int64_t actualFrame = 0; int64_t count = 0; if (_global.conf->isFrameFile) { frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &actualFrame); } else if (_global.videoHandle >= 0) { count = videoGetFrameCount(_global.videoHandle); if (count > 0) { frame = SDL_clamp(frame, 0, count - 1); } videoSeek(_global.videoHandle, frame); } } // Splash screens: fade in the Kangaroo Punch logo, cross fade to the Singe logo, fade out. static void _doLogos(void) { int32_t i = 0; int32_t w = 0; int32_t h = 0; bool keepGoing = true; SDL_Surface *surfKangaroo = NULL; SDL_Surface *surfSinge = NULL; SDL_Texture *texKangaroo = NULL; SDL_Texture *texSinge = NULL; SDL_FRect rectKangaroo; SDL_FRect rectSinge; SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED; SDL_GetRenderLogicalPresentation(_global.renderer, &w, &h, &mode); texKangaroo = _loadEmbeddedTexture(kangarooPunchLogo_png, kangarooPunchLogo_png_len, &surfKangaroo); texSinge = _loadEmbeddedTexture(singeLogo_png, singeLogo_png_len, &surfSinge); // Both logos are fitted into the same space, so the cross fade neither stretches nor jumps. SDL_SetRenderLogicalPresentation(_global.renderer, LOGO_SPACE_WIDTH, LOGO_SPACE_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); _fitRect(surfKangaroo->w, surfKangaroo->h, LOGO_SPACE_WIDTH, LOGO_SPACE_HEIGHT, LOGO_MARGIN, &rectKangaroo); _fitRect(surfSinge->w, surfSinge->h, LOGO_SPACE_WIDTH, LOGO_SPACE_HEIGHT, LOGO_MARGIN, &rectSinge); // Fade in to white with Kangaroo logo for (i = 0; keepGoing && (i < LOGO_FADE_STEPS); i++) { SDL_SetRenderDrawColor(_global.renderer, (uint8_t)i, (uint8_t)i, (uint8_t)i, SDL_ALPHA_OPAQUE); SDL_RenderClear(_global.renderer); SDL_SetTextureAlphaMod(texKangaroo, (uint8_t)i); SDL_RenderTexture(_global.renderer, texKangaroo, NULL, &rectKangaroo); SDL_RenderPresent(_global.renderer); keepGoing = _delayAndPump(LOGO_FADE_STEP_MS); } keepGoing = keepGoing && _delayAndPump(LOGO_HOLD_MS); // Cross fade to Singe logo for (i = 0; keepGoing && (i < LOGO_FADE_STEPS); i++) { SDL_RenderClear(_global.renderer); SDL_SetTextureAlphaMod(texKangaroo, (uint8_t)(LOGO_FADE_STEPS - 1 - i)); SDL_RenderTexture(_global.renderer, texKangaroo, NULL, &rectKangaroo); SDL_SetTextureAlphaMod(texSinge, (uint8_t)i); SDL_RenderTexture(_global.renderer, texSinge, NULL, &rectSinge); SDL_RenderPresent(_global.renderer); keepGoing = _delayAndPump(LOGO_FADE_STEP_MS); } keepGoing = keepGoing && _delayAndPump(LOGO_HOLD_MS); // Fade to black for (i = LOGO_FADE_STEPS - 1; keepGoing && (i >= 0); i--) { SDL_SetRenderDrawColor(_global.renderer, (uint8_t)i, (uint8_t)i, (uint8_t)i, SDL_ALPHA_OPAQUE); SDL_RenderClear(_global.renderer); SDL_SetTextureAlphaMod(texSinge, (uint8_t)i); SDL_RenderTexture(_global.renderer, texSinge, NULL, &rectSinge); SDL_RenderPresent(_global.renderer); keepGoing = _delayAndPump(LOGO_FADE_STEP_MS); } SDL_DestroyTexture(texSinge); SDL_DestroyTexture(texKangaroo); SDL_DestroySurface(surfSinge); SDL_DestroySurface(surfKangaroo); SDL_SetRenderLogicalPresentation(_global.renderer, w, h, mode); } // Bresenham line into the overlay, clipped to it first so a line from far off screen costs only // its visible part. The overlay must be locked by the caller. static void _drawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t pixel) { int32_t x = 0; int32_t y = 0; int32_t dx = 0; int32_t dy = 0; int32_t incX = 0; int32_t incY = 0; int32_t balance = 0; if (!_clipLine(&x1, &y1, &x2, &y2)) { return; } x = x1; y = y1; dx = abs(x2 - x1); dy = abs(y2 - y1); incX = (x2 >= x1) ? 1 : -1; incY = (y2 >= y1) ? 1 : -1; if (dx >= dy) { dy <<= 1; balance = dy - dx; dx <<= 1; while (x != x2) { _putPixel(x, y, pixel); if (balance >= 0) { y += incY; balance -= dx; } balance += dy; x += incX; } } else { dx <<= 1; balance = dx - dy; dy <<= 1; while (y != y2) { _putPixel(x, y, pixel); if (balance >= 0) { x += incX; balance -= dy; } balance += dx; y += incY; } } _putPixel(x, y, pixel); } // Draws the 2D emitters queued this frame on one layer: a textured, tinted, rotated quad per particle // through SDL_RenderGeometry, in overlay pixels mapped onto the window target. Every quad is built // in one pass; with several frames the indices are bucketed by frame so each texture is one call. static void _drawParticles2D(ParticleLayerE layer, const SDL_FRect *target) { int32_t emitters[PARTICLES_QUEUE_MAX]; int32_t n = particlesQueued2D(layer, emitters, (int32_t)SDL_arraysize(emitters)); float scaleX = target->w / (float)_global.overlay->w; float scaleY = target->h / (float)_global.overlay->h; SDL_Vertex *vertices = NULL; SDL_Vertex *v = NULL; int32_t *indices = NULL; int32_t *starts = NULL; ParticleTexturesT *cache = NULL; ParticleViewT *particle = NULL; EmitterViewT view; SDL_FColor color; float c = 0.0f; float s = 0.0f; float half = 0.0f; float dx = 0.0f; float dy = 0.0f; int32_t begin = 0; int32_t count = 0; int32_t e = 0; int32_t i = 0; int32_t f = 0; int32_t frame = 0; for (e = 0; e < n; e++) { if (!particlesViewEmitter(emitters[e], &view) || (view.count == 0) || (view.frameCount == 0)) { continue; } cache = _particleTexturesGet(&view); if (cache->blend != view.blend) { for (f = 0; f < cache->count; f++) { SDL_SetTextureBlendMode(cache->textures[f], (view.blend == PARTICLE_ADD) ? SDL_BLENDMODE_ADD : SDL_BLENDMODE_BLEND); } cache->blend = view.blend; } _quadScratch(view.count, cache->count); vertices = _global.quadVertices; indices = _global.quadIndices; starts = _global.frameStarts; memset(starts, 0, (size_t)cache->count * sizeof(int32_t)); for (i = 0; i < view.count; i++) { particle = &view.particles[i]; v = &vertices[i * QUAD_VERTICES]; c = SDL_cosf(DEGREES_TO_RADIANS(particle->angle)); s = SDL_sinf(DEGREES_TO_RADIANS(particle->angle)); half = particle->size * 0.5f; color.r = particle->colour[0]; color.g = particle->colour[1]; color.b = particle->colour[2]; color.a = particle->colour[3]; for (f = 0; f < QUAD_VERTICES; f++) { dx = ((f == 1) || (f == 2)) ? half : -half; dy = (f >= 2) ? half : -half; v[f].position.x = target->x + (particle->position.x + dx * c - dy * s) * scaleX; v[f].position.y = target->y + (particle->position.y + dx * s + dy * c) * scaleY; v[f].color = color; v[f].tex_coord.x = ((f == 1) || (f == 2)) ? 1.0f : 0.0f; v[f].tex_coord.y = (f >= 2) ? 1.0f : 0.0f; } starts[SDL_clamp(particle->frame, 0, cache->count - 1)]++; } // Each frame's quads start where the earlier frames' end; filling advances each start to its end. begin = 0; for (f = 0; f < cache->count; f++) { count = starts[f]; starts[f] = begin; begin += count; } for (i = 0; i < view.count; i++) { frame = SDL_clamp(view.particles[i].frame, 0, cache->count - 1); _quadIndex(indices, starts[frame]++, i); } begin = 0; for (f = 0; f < cache->count; f++) { if (starts[f] > begin) { SDL_RenderGeometry(_global.renderer, cache->textures[f], vertices, view.count * QUAD_VERTICES, indices + begin * QUAD_INDICES, (starts[f] - begin) * QUAD_INDICES); } begin = starts[f]; } _drawTrails2D(&view, cache, target, scaleX, scaleY); } } // The 2D emitter's trails: a ribbon of quads through each particle's recorded points, fading // toward the tail, textured by the middle column of the first frame so the disc's edge softens it. static void _drawTrails2D(const EmitterViewT *view, const ParticleTexturesT *cache, const SDL_FRect *target, float scaleX, float scaleY) { SDL_Vertex *vertices = NULL; SDL_Vertex *v = NULL; int32_t *indices = NULL; const float *points = NULL; SDL_FColor color; int32_t segments = 0; int32_t count = 0; int32_t i = 0; int32_t j = 0; int32_t k = 0; float half = view->trailWidth * 0.5f; float x0 = 0.0f; float y0 = 0.0f; float x1 = 0.0f; float y1 = 0.0f; float dx = 0.0f; float dy = 0.0f; float length = 0.0f; float nx = 0.0f; float ny = 0.0f; float side = 0.0f; bool head = false; if ((view->trailLength < 2) || (view->trailCounts == NULL) || (view->count == 0)) { return; } _quadScratch(view->count * (view->trailLength - 1), 0); vertices = _global.quadVertices; indices = _global.quadIndices; for (i = 0; i < view->count; i++) { points = view->trailPoints + (size_t)i * (size_t)view->trailLength * TRAIL_POINT_FLOATS; count = view->trailCounts[i]; color.r = view->particles[i].colour[0]; color.g = view->particles[i].colour[1]; color.b = view->particles[i].colour[2]; for (j = 1; j < count; j++) { x0 = view->trailOffset.x + points[(j - 1) * TRAIL_POINT_FLOATS]; y0 = view->trailOffset.y + points[(j - 1) * TRAIL_POINT_FLOATS + 1]; x1 = view->trailOffset.x + points[j * TRAIL_POINT_FLOATS]; y1 = view->trailOffset.y + points[j * TRAIL_POINT_FLOATS + 1]; dx = x1 - x0; dy = y1 - y0; length = SDL_sqrtf(dx * dx + dy * dy); nx = (length > 0.0f) ? -dy / length * half : 0.0f; ny = (length > 0.0f) ? dx / length * half : 0.0f; v = &vertices[segments * QUAD_VERTICES]; for (k = 0; k < QUAD_VERTICES; k++) { head = (k == 1) || (k == 2); side = (k >= 2) ? 1.0f : -1.0f; v[k].position.x = target->x + ((head ? x1 : x0) + nx * side) * scaleX; v[k].position.y = target->y + ((head ? y1 : y0) + ny * side) * scaleY; v[k].color = color; v[k].color.a = view->particles[i].colour[3] * (float)(head ? j : j - 1) / (float)(count - 1); v[k].tex_coord.x = 0.5f; v[k].tex_coord.y = (side < 0.0f) ? 0.0f : 1.0f; } _quadIndex(indices, segments, segments); segments++; } } if (segments > 0) { SDL_RenderGeometry(_global.renderer, cache->textures[0], vertices, segments * QUAD_VERTICES, indices, segments * QUAD_INDICES); } } // Draws the PAUSED indicator, built from the console font on first use, centered on the target. static void _drawPauseIndicator(const SDL_FRect *target) { SDL_Surface *text = NULL; SDL_Rect src; SDL_Rect dest; SDL_FRect where; int32_t i = 0; if (_global.pauseTexture == NULL) { _global.pauseTextureWidth = (int32_t)strlen(PAUSE_TEXT) * _global.consoleFontWidth; _global.pauseTextureHeight = _global.consoleFontHeight; text = SDL_CreateSurface(_global.pauseTextureWidth, _global.pauseTextureHeight, SDL_PIXELFORMAT_BGRA32); if (text == NULL) { utilDie("%s", SDL_GetError()); } src.y = 0; src.w = _global.consoleFontWidth; src.h = _global.consoleFontHeight; dest.y = 0; dest.w = _global.consoleFontWidth; dest.h = _global.consoleFontHeight; for (i = 0; PAUSE_TEXT[i] != 0; i++) { src.x = (uint8_t)PAUSE_TEXT[i] * _global.consoleFontWidth; dest.x = i * _global.consoleFontWidth; SDL_BlitSurface(_global.consoleFontSurface, &src, text, &dest); } _global.pauseTexture = SDL_CreateTextureFromSurface(_global.renderer, text); SDL_DestroySurface(text); if (_global.pauseTexture == NULL) { utilDie("%s", SDL_GetError()); } } where.w = (float)(_global.pauseTextureWidth * PAUSE_TEXT_SCALE); where.h = (float)(_global.pauseTextureHeight * PAUSE_TEXT_SCALE); where.x = target->x + (target->w - where.w) / 2.0f; where.y = target->y + (target->h - where.h) / 2.0f; SDL_RenderTexture(_global.renderer, _global.pauseTexture, NULL, &where); } // Which engine owned switch (pause, quit, screenshot, grab) a code is mapped to, or INPUT_COUNT. static InputE _engineSwitch(int32_t scancode) { static const InputE owned[] = { INPUT_PAUSE, INPUT_QUIT, INPUT_SCREENSHOT, INPUT_GRAB }; int32_t i = 0; int32_t index = 0; for (i = 0; i < (int32_t)(sizeof(owned) / sizeof(owned[0])); i++) { for (index = 0; index < _global.controlMappings[owned[i]].inputCount; index++) { if (_global.controlMappings[owned[i]].input[index] == scancode) { return owned[i]; } } } return INPUT_COUNT; } // Caches a mouse position (overlay coordinates) and tells the script. static void _fireMouseMoved(int32_t device, int32_t x, int32_t y, int32_t xr, int32_t yr) { _global.axisCache[AXIS_INDEX_MOUSE(device, 0)] = x; _global.axisCache[AXIS_INDEX_MOUSE(device, 1)] = y; if (!_global.frozen) { _callLua("onMouseMoved", "iiiii", x, y, xr, yr, device); } } // Largest rectangle of the given aspect that fits inside the space with a margin, centred. static void _fitRect(int32_t width, int32_t height, int32_t spaceWidth, int32_t spaceHeight, int32_t margin, SDL_FRect *rect) { float scale = SDL_min((float)(spaceWidth - 2 * margin) / (float)width, (float)(spaceHeight - 2 * margin) / (float)height); rect->w = (float)width * scale; rect->h = (float)height * scale; rect->x = ((float)spaceWidth - rect->w) * 0.5f; rect->y = ((float)spaceHeight - rect->h) * 0.5f; } static void _fontDestroy(FontT *font) { if (_global.fontCurrent == font) { _global.fontCurrent = NULL; } HASH_DEL(_global.fontList, font); TTF_CloseFont(font->font); free(font); } // Engine pause. Freezing releases everything the script thinks is held so it never sees a // stale button; thawing presses whatever is still physically down. static void _freezeGame(bool freeze) { int32_t i = 0; HeldKeyT held[HELD_KEYS_MAX]; int32_t heldCount = 0; if (freeze) { for (i = 0; i < INPUT_COUNT; i++) { if (_global.switchHeld[i]) { _global.switchHeld[i] = false; _callLua("onInputReleased", "i", i); } } // Copy first: the callbacks may not touch the list, but keep it simple. heldCount = _global.heldKeyCount; memcpy(held, _global.heldKeys, sizeof(HeldKeyT) * (size_t)heldCount); _global.heldKeyCount = 0; for (i = 0; i < heldCount; i++) { _callLua("onInputReleased", "i", held[i].keysym); _callLua("onKeyReleased", "ii", held[i].keysym, held[i].scancode); } _global.frozen = true; } else { _global.frozen = false; for (i = 0; i < _global.physicalKeyCount; i++) { _deliverKey(true, _global.physicalKeys[i].keysym, _global.physicalKeys[i].scancode); } _global.keyboardLastDown = SDL_SCANCODE_UNKNOWN; _global.keyboardLastUp = SDL_SCANCODE_UNKNOWN; } _global.refreshDisplay = true; } // Maintains a list of keys that are down. static void _heldListUpdate(HeldKeyT *list, int32_t *count, bool down, int32_t keysym, int32_t scancode) { int32_t i = 0; for (i = 0; i < *count; i++) { if (list[i].scancode == scancode) { break; } } if (down) { if ((i == *count) && (*count < HELD_KEYS_MAX)) { list[*count].keysym = keysym; list[*count].scancode = scancode; (*count)++; } } else { if (i < *count) { (*count)--; list[i] = list[*count]; } } } // Replaces dofile, loadfile, and the io functions that take a file name with versions that resolve // the name through the vfs. Each closure keeps the original as its first upvalue. static void _installFileHooks(lua_State *L) { static const struct { const char *name; IoHookModeE mode; } ioHooks[] = { { "input", IO_HOOK_READ }, { "lines", IO_HOOK_READ }, { "open", IO_HOOK_OPEN }, { "output", IO_HOOK_WRITE } }; size_t i = 0; lua_getglobal(L, "dofile"); lua_pushcclosure(L, _luaDofile, 1); lua_setglobal(L, "dofile"); lua_getglobal(L, "loadfile"); lua_pushcclosure(L, _luaLoadfile, 1); lua_setglobal(L, "loadfile"); lua_getglobal(L, "io"); for (i = 0; i < sizeof(ioHooks) / sizeof(ioHooks[0]); i++) { lua_getfield(L, -1, ioHooks[i].name); lua_pushinteger(L, ioHooks[i].mode); lua_pushcclosure(L, _luaIoHook, 2); lua_setfield(L, -2, ioHooks[i].name); } lua_pop(L, 1); } // lfs.attributes through the vfs: a packed entry answers with mode, size and modification; a // plain filesystem path goes to the original (upvalue 1). static int32_t _lfsAttributes(lua_State *L) { const char *name = luaL_checkstring(L, 1); const char *attribute = luaL_optstring(L, 2, NULL); int64_t size = 0; int64_t modified = 0; bool directory = false; if (vfsIsFilesystem(name)) { return _luaCallOriginal(L); } directory = vfsIsDirectory(name); if (!directory && !vfsStat(name, &size, &modified)) { lua_pushnil(L); lua_pushfstring(L, "cannot obtain information from file '%s'", name); return 2; } lua_newtable(L); lua_pushstring(L, directory ? "directory" : "file"); lua_setfield(L, -2, "mode"); lua_pushinteger(L, (lua_Integer)size); lua_setfield(L, -2, "size"); lua_pushinteger(L, (lua_Integer)modified); lua_setfield(L, -2, "modification"); lua_pushinteger(L, (lua_Integer)modified); lua_setfield(L, -2, "change"); lua_pushinteger(L, (lua_Integer)modified); lua_setfield(L, -2, "access"); if (attribute != NULL) { lua_getfield(L, -1, attribute); } return 1; } // lfs.dir through the vfs: a packed directory iterates the union of loose, overlay and packed // entries; a plain filesystem path goes to the original (upvalue 1). static int32_t _lfsDir(lua_State *L) { const char *name = luaL_checkstring(L, 1); char **list = NULL; int32_t count = 0; int32_t i = 0; if (vfsIsFilesystem(name)) { return _luaCallOriginal(L); } if (!vfsIsDirectory(name)) { return luaL_error(L, "cannot open %s: No such file or directory", name); } list = vfsList(name, &count); lua_createtable(L, count, 0); for (i = 0; i < count; i++) { lua_pushstring(L, list[i]); lua_rawseti(L, -2, i + 1); } vfsListFree(list, count); lua_pushinteger(L, 0); lua_pushcclosure(L, _lfsDirIterator, 2); return 1; } // The iterator lfs.dir returns for a packed directory: upvalues are the entry table and the index. static int32_t _lfsDirIterator(lua_State *L) { lua_Integer index = lua_tointeger(L, lua_upvalueindex(2)) + 1; lua_pushinteger(L, index); lua_copy(L, -1, lua_upvalueindex(2)); lua_pop(L, 1); lua_rawgeti(L, lua_upvalueindex(1), index); return 1; } // lfs.mkdir through the vfs: inside a packed game the directory is made where its files will be // written, the data overlay; a plain filesystem path goes to the original (upvalue 1). static int32_t _lfsMkdir(lua_State *L) { const char *name = luaL_checkstring(L, 1); char *path = NULL; bool ok = false; if (vfsIsFilesystem(name)) { return _luaCallOriginal(L); } path = vfsFilePath(name, true); if (path == NULL) { lua_pushnil(L); lua_pushfstring(L, "%s reaches outside the game", name); return 2; } ok = utilMkDirP(path, DIRECTORY_MODE); free(path); if (!ok) { lua_pushnil(L); lua_pushfstring(L, "cannot create %s", name); return 2; } lua_pushboolean(L, true); return 1; } // lfs.rmdir through the vfs: removes the overlay directory of a packed name; the packed copy // itself is read only and stays. static int32_t _lfsRmdir(lua_State *L) { const char *name = luaL_checkstring(L, 1); char *path = NULL; bool ok = false; if (vfsIsFilesystem(name)) { return _luaCallOriginal(L); } path = vfsFilePath(name, true); if (path == NULL) { lua_pushnil(L); lua_pushfstring(L, "%s reaches outside the game", name); return 2; } ok = (rmdir(path) == 0); free(path); if (!ok) { lua_pushnil(L); lua_pushfstring(L, "cannot remove %s", name); return 2; } lua_pushboolean(L, true); return 1; } // The per-machine audio delay lives beside the data directories, since it is not a property of any game. static int32_t _loadAudioCalibration(void) { char *path = utilCreateString("%s%s", _global.conf->dataDirBase, AUDIO_CALIBRATION_FILE); size_t bytes = 0; char *data = utilReadFile(path, &bytes); int32_t value = 0; if (data) { if (sscanf(data, "delay = %d", &value) != 1) { value = 0; } free(data); } free(path); if ((value < -VIDEO_AUDIO_DELAY_MAX) || (value > VIDEO_AUDIO_DELAY_MAX)) { value = 0; } return value; } // The built-in controls.cfg, then every override in turn (the working directory, above and inside // the data directory, beside the script), each place only once, in a throwaway Lua state that has // the framework but not the API. Leaves the dead zone and the switch mappings behind. static void _loadControlMappings(void) { lua_State *L = NULL; char *scriptDir = utilGetUpToLastPathComponent(_global.conf->scriptFile); char *candidates[4]; bool seen = false; int32_t c = 0; int32_t x = 0; int32_t y = 0; _progTrace("Creating Lua context for Singe setup"); L = luaL_newstate(); _global.luaContext = L; _startLuaContext(L); // Load framework - NOTE! SINGE API NOT AVAILABLE AT THIS POINT! // Any calls in the framework need to be wrapped with nil checks! _progTrace("Loading Singe framework"); if (luaL_loadbuffer(L, (const char *)Framework_singe, Framework_singe_len, "Framework.singe") || lua_pcall(L, 0, 0, 0)) { utilDie("%s", lua_tostring(L, -1)); } _progTrace("Loading default control mappings"); if (luaL_loadbuffer(L, (const char *)controls_cfg, controls_cfg_len, CONTROLS_FILE) || lua_pcall(L, 0, 0, 0)) { utilDie("%s", lua_tostring(L, -1)); } candidates[0] = strdup(CONTROLS_FILE); candidates[1] = utilCreateString("%s..%c%s", _global.conf->dataDir, utilGetPathSeparator(), CONTROLS_FILE); candidates[2] = utilCreateString("%s%s", _global.conf->dataDir, CONTROLS_FILE); candidates[3] = utilCreateString("%s%s", scriptDir, CONTROLS_FILE); for (c = 0; c < (int32_t)SDL_arraysize(candidates); c++) { // Without -d the data directory is the script's, so the same file would run twice. seen = false; for (x = 0; x < c; x++) { if (strcmp(candidates[x], candidates[c]) == 0) { seen = true; } } if (!seen) { _loadControlsFile(candidates[c]); } } for (c = 0; c < (int32_t)SDL_arraysize(candidates); c++) { free(candidates[c]); } free(scriptDir); // Parse results lua_getglobal(L, "DEAD_ZONE"); if (lua_isnumber(L, -1)) { _global.controllerDeadZone = (int32_t)lua_tonumber(L, -1); } lua_pop(L, 1); _progTrace("Controller dead zone is %d", _global.controllerDeadZone); for (x = 0; x < INPUT_COUNT; x++) { // Each INPUT_* table holds { name = ..., value = ... } entries; collect the values. lua_getglobal(L, _inputNames[x].configName); if (!lua_istable(L, -1)) { utilSay("Configuration option %s missing!", _inputNames[x].configName); lua_pop(L, 1); continue; } y = (int32_t)lua_rawlen(L, -1); _global.controlMappings[x].input = (int32_t *)calloc((size_t)(y + 1), sizeof(int32_t)); if (!_global.controlMappings[x].input) { utilDie("Unable to allocate memory for control mappings."); } _global.controlMappings[x].inputCount = 0; lua_pushnil(L); while (lua_next(L, -2)) { if (lua_istable(L, -1)) { lua_getfield(L, -1, "value"); if (lua_isnumber(L, -1) && (_global.controlMappings[x].inputCount < y)) { _global.controlMappings[x].input[_global.controlMappings[x].inputCount++] = (int32_t)lua_tonumber(L, -1); } lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L, 1); } lua_close(L); _global.luaContext = NULL; } // Runs a controls.cfg if it exists. static void _loadControlsFile(const char *path) { if (vfsExists(path)) { _progTrace("Loading %s", path); if (_luaLoadFile(_global.luaContext, path, NULL, _global.conf->reload) || lua_pcall(_global.luaContext, 0, 0, 0)) { utilDie("%s", lua_tostring(_global.luaContext, -1)); } } } static SDL_Surface *_loadEmbeddedPng(const uint8_t *data, size_t length) { SDL_Surface *surface = IMG_LoadTyped_IO(SDL_IOFromConstMem(data, length), true, "PNG"); if (surface == NULL) { utilDie("%s", SDL_GetError()); } _surfaceUnpack(&surface); return surface; } // Loads an embedded PNG as a texture. The surface stays alive so callers can read its size. static SDL_Texture *_loadEmbeddedTexture(const uint8_t *data, size_t length, SDL_Surface **surface) { SDL_Texture *texture = NULL; *surface = _loadEmbeddedPng(data, length); texture = SDL_CreateTextureFromSurface(_global.renderer, *surface); if (texture == NULL) { utilDie("%s", SDL_GetError()); } return texture; } // Hands a hooked call to the original function kept as upvalue 1, returning all it returns. static int32_t _luaCallOriginal(lua_State *L) { lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } // Reports a script level error with the calling Lua line and exits. static void _luaDie(lua_State *L, const char *method, const char *fmt, ...) { va_list args; char *message = NULL; va_start(args, fmt); message = _luaFormat(L, method, fmt, args); va_end(args); if (_global.conf->scriptTracing) { utilTrace("%s", message); } utilDie("%s", message); } // dofile(name) through the vfs; without a name the original reads stdin. static int32_t _luaDofile(lua_State *L) { const char *name = luaL_optstring(L, 1, NULL); if (name == NULL) { return _luaCallOriginal(L); } lua_settop(L, 1); if (_luaLoadFile(L, name, NULL, _global.conf->reload) != LUA_OK) { return lua_error(L); } lua_call(L, 0, LUA_MULTRET); return lua_gettop(L) - 1; } // require() of a game's own module: name.lua, name/init.lua, or name.singe under the script's // directory, then relative to the game root. static int32_t _luaFileSearcher(lua_State *L) { static const char *const patterns[] = { "%s%s.lua", "%s%s/init.lua", "%s%s.singe", NULL }; char *module = strdup(lua_tostring(L, 1)); char *scriptDir = utilGetUpToLastPathComponent(_global.conf->scriptFile); const char *prefixes[2]; char *name = NULL; char *p = NULL; int32_t x = 0; int32_t y = 0; prefixes[0] = scriptDir; prefixes[1] = ""; for (p = module; *p != 0; p++) { if (*p == '.') { *p = '/'; } } for (y = 0; y < 2; y++) { for (x = 0; patterns[x] != NULL; x++) { name = utilCreateString(patterns[x], prefixes[y], module); if (vfsExists(name)) { if (_luaLoadFile(L, name, NULL, _global.conf->reload) != LUA_OK) { lua_pushfstring(L, "error loading module '%s' from file '%s':\n\t%s", lua_tostring(L, 1), name, lua_tostring(L, -1)); free(name); free(module); free(scriptDir); return lua_error(L); } lua_pushstring(L, name); free(name); free(module); free(scriptDir); return 2; } free(name); } } lua_pushfstring(L, "\n\tno file '%s%s.lua' in the game", scriptDir, module); free(module); free(scriptDir); return 1; } // Formats "line:method: message" for tracing and errors. Caller frees. static char *_luaFormat(lua_State *L, const char *method, const char *fmt, va_list args) { lua_Debug ar; int32_t line = 0; char *body = NULL; char *message = NULL; if (lua_getstack(L, 1, &ar) && lua_getinfo(L, "Sl", &ar)) { line = ar.currentline; } body = utilCreateStringVArgs(fmt, args); if (!body) { utilDie("Unable to allocate trace string."); } message = utilCreateString("%d:%s: %s", line, method, body); if (!message) { utilDie("Unable to allocate trace string."); } free(body); return message; } // io.open and friends with a name resolved through the vfs. Upvalues: the original, and how the name is used. static int32_t _luaIoHook(lua_State *L) { IoHookModeE mode = (IoHookModeE)lua_tointeger(L, lua_upvalueindex(2)); const char *modeString = NULL; char *path = NULL; bool writing = (mode == IO_HOOK_WRITE); if (lua_type(L, 1) == LUA_TSTRING) { if (mode == IO_HOOK_OPEN) { modeString = luaL_optstring(L, 2, "r"); writing = (strpbrk(modeString, "wa+") != NULL); } path = vfsFilePath(lua_tostring(L, 1), writing); if (path == NULL) { // A name inside a packed game that resolves nowhere: io.open reports it the Lua way, the others raise. if (mode != IO_HOOK_OPEN) { return luaL_error(L, "%s reaches outside the game", lua_tostring(L, 1)); } lua_pushnil(L); lua_pushfstring(L, "%s reaches outside the game", lua_tostring(L, 1)); return 2; } lua_pushstring(L, path); lua_replace(L, 1); free(path); } return _luaCallOriginal(L); } // loadfile(name, mode, env) through the vfs; without a name the original reads stdin. static int32_t _luaLoadfile(lua_State *L) { const char *name = luaL_optstring(L, 1, NULL); const char *mode = luaL_optstring(L, 2, NULL); if (name == NULL) { return _luaCallOriginal(L); } if (_luaLoadFile(L, name, mode, _global.conf->reload) != LUA_OK) { lua_pushnil(L); lua_insert(L, -2); return 2; } if (!lua_isnone(L, 3)) { lua_pushvalue(L, 3); if (lua_setupvalue(L, -2, 1) == NULL) { lua_pop(L, 1); } } return 1; } // Loads a chunk from the vfs, leaving the function or an error message on the stack. Returns the // Lua status. With watch, a loose file joins the list --reload checks. static int32_t _luaLoadFile(lua_State *L, const char *name, const char *mode, bool watch) { char *data = NULL; char *chunkName = NULL; size_t bytes = 0; int32_t status = LUA_ERRFILE; data = vfsRead(name, &bytes); if (data == NULL) { lua_pushfstring(L, "cannot open %s", name); return status; } chunkName = utilCreateString("@%s", name); status = luaL_loadbufferx(L, data, bytes, chunkName, mode); free(chunkName); free(data); if ((status == LUA_OK) && watch) { _watchFile(name); } return status; } // A fresh Lua state with the standard libraries, the vfs hooks, the constants and the whole API. static void _createScriptContext(void) { _progTrace("Creating Lua context for script"); _global.luaContext = luaL_newstate(); _startLuaContext(_global.luaContext); _registerApi(_global.luaContext); } // require("lfs") with dir and attributes routed through the vfs, so a packed game can list itself. static int32_t _luaopenLfs(lua_State *L) { luaopen_lfs(L); lua_getfield(L, -1, "dir"); lua_pushcclosure(L, _lfsDir, 1); lua_setfield(L, -2, "dir"); lua_getfield(L, -1, "attributes"); lua_pushcclosure(L, _lfsAttributes, 1); lua_setfield(L, -2, "attributes"); lua_getfield(L, -1, "symlinkattributes"); lua_pushcclosure(L, _lfsAttributes, 1); lua_setfield(L, -2, "symlinkattributes"); lua_getfield(L, -1, "mkdir"); lua_pushcclosure(L, _lfsMkdir, 1); lua_setfield(L, -2, "mkdir"); lua_getfield(L, -1, "rmdir"); lua_pushcclosure(L, _lfsRmdir, 1); lua_setfield(L, -2, "rmdir"); return 1; } // Lua panic handler: something went wrong outside a protected call. static int32_t _luaPanic(lua_State *L) { lua_Debug ar; int32_t level = 0; utilSay("Singe has panicked! Very bad!"); utilSay("Error: %s", lua_tostring(L, -1)); utilSay("Stack trace:"); while (lua_getstack(L, level, &ar) != 0) { lua_getinfo(L, "nSl", &ar); utilSay(" %d: function `%s' at line %d %s", level, ar.name ? ar.name : "?", ar.currentline, ar.short_src); level++; } utilSay("Trace complete."); return 0; } // package.searchers entry serving the embedded Lua modules. // https://leiradel.github.io/2020/03/01/Embedding-Lua-Modules.html static int32_t _luaSearcher(lua_State *L) { const char *modname = lua_tostring(L, 1); size_t i = 0; for (i = 0; i < sizeof(_luaModules) / sizeof(_luaModules[0]); i++) { if (strcmp(modname, _luaModules[i].name) == 0) { if (_luaModules[i].length != 0) { // It's a Lua module, return the chunk that defines the module. if (luaL_loadbufferx(L, _luaModules[i].source, _luaModules[i].length, modname, "t") != LUA_OK) { return lua_error(L); } } else { // It's a native module, return the native function that defines the module. lua_pushcfunction(L, _luaModules[i].openf); } return 1; } } // A searcher explains itself with a string when it has nothing. lua_pushfstring(L, "\n\tno embedded module '%s'", modname); return 1; } static void _luaTrace(lua_State *L, const char *method, const char *fmt, ...) { va_list args; char *message = NULL; if (_global.conf->scriptTracing) { va_start(args, fmt); message = _luaFormat(L, method, fmt, args); va_end(args); utilTrace("%s", message); free(message); } } // Message handler for lua_pcall: appends a traceback to the error. static int32_t _luaTraceback(lua_State *L) { const char *message = lua_tostring(L, 1); if (message == NULL) { message = "(error object is not a string)"; } luaL_traceback(L, L, message, 1); return 1; } // materialSetXxxMap(material[, image[, strength]]): a loaded sprite's surface or a KTX2 file as one // of the material's maps; nil (or nothing) clears it. Strength is only for the normal and // occlusion maps. static int32_t _materialSetMap(lua_State *L, const char *method, MaterialMapE map, bool hasStrength) { int32_t material = 0; SDL_Surface *surface = NULL; Ktx2ImageT ktx2; bool ok = false; float strength = 1.0f; _argCheck(L, method, 1, hasStrength ? 3 : 2); material = _argMaterial(L, method, 1); if (hasStrength && (lua_gettop(L) >= 3)) { strength = (float)_argNumber(L, method, 3); } if ((lua_gettop(L) >= 2) && !lua_isnil(L, 2) && _argMapImage(L, method, 2, &surface, &ktx2)) { ok = materialSetMap(material, map, &ktx2, strength); ktx2Free(&ktx2); if (!ok) { _luaDie(L, method, "Unable to upload the texture."); } return 0; } if (!materialSetMapSurface(material, map, surface, strength)) { _luaDie(L, method, "%s", SDL_GetError()); } return 0; } // Converts the script visible 0..AUDIO_MAX_VOLUME scale to the mixer's scale. static float _mixerGain(int32_t effectsVolume) { return (float)effectsVolume / (float)AUDIO_MAX_VOLUME; } // A colour as the overlay surface stores it. static uint32_t _overlayColor(const SDL_Color *color) { return SDL_MapRGBA(SDL_GetPixelFormatDetails(_global.overlay->format), NULL, color->r, color->g, color->b, color->a); } // Input code for a mouse button (0 = left, 1 = right, 2 = middle, ...) or wheel offset. static int32_t _mouseCode(int32_t device, int32_t button) { return CODE_MOUSE_BASE + device * CODE_MOUSE_STRIDE + button; } // Replaces the overlay surface and texture (and the scene's targets) at a new size; its contents are lost. static void _overlayResize(int32_t width, int32_t height) { SDL_DestroySurface(_global.overlay); _global.overlay = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_BGRA32); if (_global.overlay == NULL) { utilDie("%s", SDL_GetError()); } SDL_SetSurfaceBlendMode(_global.overlay, SDL_BLENDMODE_BLEND); SDL_DestroyTexture(_global.overlayTexture); _global.overlayTexture = SDL_CreateTexture(_global.renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, width, height); if (_global.overlayTexture == NULL) { utilDie("%s", SDL_GetError()); } sceneResize(width, height); SDL_SetTextureBlendMode(_global.overlayTexture, SDL_BLENDMODE_BLEND); _global.overlayScaleX = (double)width / (double)_global.canvasWidth; _global.overlayScaleY = (double)height / (double)_global.canvasHeight; _overlayTouched(); } // Every overlay drawing call ends here so the texture is only re-uploaded when needed. static void _overlayTouched(void) { _global.overlayDirty = true; } static void _pauseAllVideos(bool pause) { VideoT *video = NULL; VideoT *temp = NULL; HASH_ITER(hh, _global.videoList, video, temp) { if (pause) { if (videoIsPlaying(video->handle)) { video->wasPlayingBeforePause = true; videoPause(video->handle); } } else { if (video->wasPlayingBeforePause) { video->wasPlayingBeforePause = false; videoPlay(video->handle); } } } } // Routes a key, button, or axis direction code: engine switches first, then the script. static void _processKey(bool down, int32_t keysym, int32_t scancode) { InputE engine = INPUT_COUNT; bool keyboard = (scancode < CODE_GAMEPAD_BASE); // Every defined scancode is below the gamepad range // Physical state is tracked even while the game is frozen. if (down) { _global.keyboardLastDown = scancode; } else { _global.keyboardLastUp = scancode; } if (keyboard && (scancode >= 0) && (scancode < SDL_SCANCODE_COUNT)) { _global.keyboardState[scancode] = down; } _heldListUpdate(_global.physicalKeys, &_global.physicalKeyCount, down, keysym, scancode); // Engine owned switches act on the press. Keyboard mappings only count in MODE_NORMAL, so // full mode keeps every key for the game; gamepad and mouse buttons cannot be typed, so they // always count. if ((_global.keyboardMode == KEYBOARD_NORMAL) || !keyboard) { engine = _engineSwitch(scancode); } if (down && _global.conf->reload && (scancode == SDL_SCANCODE_F5)) { _global.reloadRequested = true; } if (down) { switch (engine) { case INPUT_PAUSE: if (_global.pauseEnabled) { _setPause(!_global.pauseState, true); } break; case INPUT_GRAB: _setMouseCaptured(!_global.mouseGrabbed); break; case INPUT_QUIT: _global.running = false; break; case INPUT_SCREENSHOT: // Force a redraw so the shot is taken now, even while paused. _global.requestScreenShot = true; _global.refreshDisplay = true; break; default: break; } } // The script never sees the pause key while the engine owns it, or anything while frozen. if (_global.frozen || ((engine == INPUT_PAUSE) && _global.pauseEnabled)) { return; } _deliverKey(down, keysym, scancode); } static void _progTrace(const char *fmt, ...) { va_list args; if (_global.conf->programTracing) { va_start(args, fmt); utilTraceVArgs(fmt, args); va_end(args); } } // Pushes a vector as three results. static int32_t _pushVec3(lua_State *L, Vec3T v) { lua_pushnumber(L, v.x); lua_pushnumber(L, v.y); lua_pushnumber(L, v.z); return 3; } // Writes the two triangles of quad number quad (corners quad * 4 onwards) at index slot. static void _quadIndex(int32_t *indices, int32_t slot, int32_t quad) { int32_t *out = indices + slot * QUAD_INDICES; int32_t first = quad * QUAD_VERTICES; out[0] = first + 0; out[1] = first + 1; out[2] = first + 2; out[3] = first + 0; out[4] = first + 2; out[5] = first + 3; } // Vertex and index room for this many textured quads, and start slots for this many frames, // kept between frames and grown only when a bigger emitter comes along. static void _quadScratch(int32_t quads, int32_t frames) { if (quads > _global.quadCapacity) { _global.quadVertices = SDL_realloc(_global.quadVertices, (size_t)quads * QUAD_VERTICES * sizeof(SDL_Vertex)); _global.quadIndices = SDL_realloc(_global.quadIndices, (size_t)quads * QUAD_INDICES * sizeof(int32_t)); if ((_global.quadVertices == NULL) || (_global.quadIndices == NULL)) { utilDie("Out of memory drawing particles."); } _global.quadCapacity = quads; } if (frames > _global.frameStartCapacity) { _global.frameStarts = SDL_realloc(_global.frameStarts, (size_t)frames * sizeof(int32_t)); if (_global.frameStarts == NULL) { utilDie("Out of memory drawing particles."); } _global.frameStartCapacity = frames; } } // Frees the renderer textures kept for an emitter's 2D drawing. static void _particleTexturesDestroy(ParticleTexturesT *cache) { int32_t f = 0; HASH_DEL(_global.particleTextures, cache); for (f = 0; f < cache->count; f++) { SDL_DestroyTexture(cache->textures[f]); } SDL_free(cache->textures); SDL_free(cache); } static void _particleTexturesFree(int32_t emitter) { ParticleTexturesT *cache = NULL; HASH_FIND_INT(_global.particleTextures, &emitter, cache); if (cache != NULL) { _particleTexturesDestroy(cache); } } // Every emitter's textures; the emitters themselves go with particlesQuit. static void _particleTexturesFreeAll(void) { ParticleTexturesT *cache = NULL; ParticleTexturesT *temp = NULL; HASH_ITER(hh, _global.particleTextures, cache, temp) { _particleTexturesDestroy(cache); } } // The textures for an emitter's frames, made on first use and remade when the frames change. static ParticleTexturesT *_particleTexturesGet(const EmitterViewT *view) { ParticleTexturesT *cache = NULL; int32_t f = 0; HASH_FIND_INT(_global.particleTextures, &view->id, cache); if ((cache != NULL) && (cache->version != view->textureVersion)) { _particleTexturesDestroy(cache); cache = NULL; } if (cache != NULL) { return cache; } cache = SDL_calloc(1, sizeof(ParticleTexturesT)); if (cache == NULL) { utilDie("Out of memory for particle textures."); } cache->id = view->id; cache->version = view->textureVersion; cache->count = view->frameCount; cache->blend = view->blend; cache->textures = SDL_calloc((size_t)view->frameCount, sizeof(SDL_Texture *)); if (cache->textures == NULL) { utilDie("Out of memory for particle textures."); } for (f = 0; f < view->frameCount; f++) { cache->textures[f] = SDL_CreateTextureFromSurface(_global.renderer, view->frames[f]); if (cache->textures[f] == NULL) { utilDie("%s", SDL_GetError()); } SDL_SetTextureScaleMode(cache->textures[f], SDL_SCALEMODE_LINEAR); SDL_SetTextureBlendMode(cache->textures[f], (view->blend == PARTICLE_ADD) ? SDL_BLENDMODE_ADD : SDL_BLENDMODE_BLEND); } HASH_ADD_INT(_global.particleTextures, id, cache); return cache; } // onNavArrived(agent) for every agent that reached its target this frame. static void _navCallbacks(void) { int32_t agents[NAV_ARRIVAL_QUEUE]; int32_t count = navPollArrived(agents, (int32_t)SDL_arraysize(agents)); int32_t x = 0; for (x = 0; x < count; x++) { _callLua("onNavArrived", "i", agents[x]); } } // Hands the step's contacts and trigger overlaps to the script: onCollision(nodeA, nodeB, x, y, z, // speed) and onTrigger(trigger, other, entered), when the script defines them. Physics keeps what // one batch cannot hold, so the queue is drained until empty. static void _physicsCallbacks(void) { PhysicsEventT events[PHYSICS_MAX_EVENTS]; PhysicsEventT *event = NULL; int32_t count = 0; int32_t x = 0; while ((count = physicsGetEvents(events, (int32_t)SDL_arraysize(events))) > 0) { for (x = 0; x < count; x++) { event = &events[x]; if (event->type == PHYSICS_EVENT_COLLISION) { _callLua("onCollision", "iidddd", event->nodeA, event->nodeB, (double)event->point.x, (double)event->point.y, (double)event->point.z, (double)event->speed); } else { _callLua("onTrigger", "iib", event->nodeA, event->nodeB, (event->type == PHYSICS_EVENT_ENTER) ? 1 : 0); } } } } // Every Singe call a script may make. Comments give the version each call appeared in. static void _registerApi(lua_State *L) { lua_register(L, "animationGetTime", apiAnimationGetTime); // 3.00 lua_register(L, "animationIsPlaying", apiAnimationIsPlaying); // 3.00 lua_register(L, "animationPause", apiAnimationPause); // 3.00 lua_register(L, "animationPlay", apiAnimationPlay); // 3.00 lua_register(L, "animationPlayLayer", apiAnimationPlayLayer); // 3.00 lua_register(L, "animationResume", apiAnimationResume); // 3.00 lua_register(L, "animationSetLayerMask", apiAnimationSetLayerMask); // 3.00 lua_register(L, "animationSetLayerWeight", apiAnimationSetLayerWeight); // 3.00 lua_register(L, "animationSetTime", apiAnimationSetTime); // 3.00 lua_register(L, "animationStop", apiAnimationStop); // 3.00 lua_register(L, "bodyApplyForce", apiBodyApplyForce); // 3.00 lua_register(L, "bodyApplyImpulse", apiBodyApplyImpulse); // 3.00 lua_register(L, "bodyDelete", apiBodyDelete); // 3.00 lua_register(L, "bodyGetAngularVelocity", apiBodyGetAngularVelocity); // 3.00 lua_register(L, "bodyGetVelocity", apiBodyGetVelocity); // 3.00 lua_register(L, "bodyIsResting", apiBodyIsResting); // 3.00 lua_register(L, "bodyNew", apiBodyNew); // 3.00 lua_register(L, "bodySetAngularVelocity", apiBodySetAngularVelocity); // 3.00 lua_register(L, "bodySetBounce", apiBodySetBounce); // 3.00 lua_register(L, "bodySetBuoyancy", apiBodySetBuoyancy); // 3.00 lua_register(L, "bodySetCurrent", apiBodySetCurrent); // 3.00 lua_register(L, "bodySetEnabled", apiBodySetEnabled); // 3.00 lua_register(L, "bodySetFriction", apiBodySetFriction); // 3.00 lua_register(L, "bodySetMass", apiBodySetMass); // 3.00 lua_register(L, "bodySetTrigger", apiBodySetTrigger); // 3.00 lua_register(L, "bodySetVelocity", apiBodySetVelocity); // 3.00 lua_register(L, "bodySetWater", apiBodySetWater); // 3.00 lua_register(L, "cameraSet", apiCameraSet); // 3.00 lua_register(L, "cameraSetOrthographic", apiCameraSetOrthographic); // 3.00 lua_register(L, "cameraSetPerspective", apiCameraSetPerspective); // 3.00 lua_register(L, "colorBackground", apiColorBackground); // 1.xx lua_register(L, "colorForeground", apiColorForeground); // 1.xx lua_register(L, "controllerGetAxis", apiControllerGetAxis); // 2.00 lua_register(L, "controllerGetButton", apiControllerGetButton); // 2.10 lua_register(L, "debugPrint", apiDebugPrint); // 1.xx lua_register(L, "discAudio", apiDiscAudio); // 1.xx lua_register(L, "discChangeSpeed", apiDiscChangeSpeed); // 1.xx lua_register(L, "discGetAudioTrack", apiDiscGetAudioTrack); // 2.10 lua_register(L, "discGetAudioTracks", apiDiscGetAudioTracks); // 2.10 lua_register(L, "discGetFrame", apiDiscGetFrame); // 1.xx lua_register(L, "discGetHeight", apiDiscGetHeight); // 2.00 lua_register(L, "discGetLanguage", apiDiscGetLanguage); // 2.10 lua_register(L, "discGetState", apiDiscGetState); // 1.xx RDG lua_register(L, "discGetWidth", apiDiscGetWidth); // 2.00 lua_register(L, "discPause", apiDiscPause); // 1.xx lua_register(L, "discPauseAtFrame", apiDiscSearch); // 1.18 Same as discSearch. lua_register(L, "discPlay", apiDiscPlay); // 1.xx lua_register(L, "discSearch", apiDiscSearch); // 1.xx lua_register(L, "discSearchBlanking", apiDiscSearchBlanking); // 1.xx lua_register(L, "discSetAudioTrack", apiDiscSetAudioTrack); // 2.10 lua_register(L, "discSetFPS", apiDiscSetFPS); // 1.xx lua_register(L, "discSkipBackward", apiDiscSkipBackward); // 1.xx lua_register(L, "discSkipBlanking", apiDiscSkipBlanking); // 1.xx lua_register(L, "discSkipForward", apiDiscSkipForward); // 1.xx lua_register(L, "discSkipToFrame", apiDiscSkipToFrame); // 1.xx lua_register(L, "discStepBackward", apiDiscStepBackward); // 1.xx lua_register(L, "discStepForward", apiDiscStepForward); // 1.xx lua_register(L, "discStop", apiDiscStop); // 1.xx lua_register(L, "emitterBurst", apiEmitterBurst); // 3.00 lua_register(L, "emitterClear", apiEmitterClear); // 3.00 lua_register(L, "emitterDelete", apiEmitterDelete); // 3.00 lua_register(L, "emitterDraw", apiEmitterDraw); // 3.00 lua_register(L, "emitterGetCount", apiEmitterGetCount); // 3.00 lua_register(L, "emitterIsActive", apiEmitterIsActive); // 3.00 lua_register(L, "emitterNew", apiEmitterNew); // 3.00 lua_register(L, "emitterSetBlend", apiEmitterSetBlend); // 3.00 lua_register(L, "emitterSetCollide", apiEmitterSetCollide); // 3.00 lua_register(L, "emitterSetColor", apiEmitterSetColor); // 3.00 lua_register(L, "emitterSetDirection", apiEmitterSetDirection); // 3.00 lua_register(L, "emitterSetDrag", apiEmitterSetDrag); // 3.00 lua_register(L, "emitterSetFrames", apiEmitterSetFrames); // 3.00 lua_register(L, "emitterSetGravity", apiEmitterSetGravity); // 3.00 lua_register(L, "emitterSetLayer", apiEmitterSetLayer); // 3.00 lua_register(L, "emitterSetLife", apiEmitterSetLife); // 3.00 lua_register(L, "emitterSetLit", apiEmitterSetLit); // 3.00 lua_register(L, "emitterSetLocal", apiEmitterSetLocal); // 3.00 lua_register(L, "emitterSetMax", apiEmitterSetMax); // 3.00 lua_register(L, "emitterSetPosition", apiEmitterSetPosition); // 3.00 lua_register(L, "emitterSetRadius", apiEmitterSetRadius); // 3.00 lua_register(L, "emitterSetRate", apiEmitterSetRate); // 3.00 lua_register(L, "emitterSetSize", apiEmitterSetSize); // 3.00 lua_register(L, "emitterSetSoftness", apiEmitterSetSoftness); // 3.00 lua_register(L, "emitterSetSpeed", apiEmitterSetSpeed); // 3.00 lua_register(L, "emitterSetSpin", apiEmitterSetSpin); // 3.00 lua_register(L, "emitterSetSpread", apiEmitterSetSpread); // 3.00 lua_register(L, "emitterSetTexture", apiEmitterSetTexture); // 3.00 lua_register(L, "emitterSetTrail", apiEmitterSetTrail); // 3.00 lua_register(L, "emitterStart", apiEmitterStart); // 3.00 lua_register(L, "emitterStop", apiEmitterStop); // 3.00 lua_register(L, "fontLoad", apiFontLoad); // 1.xx lua_register(L, "fontPrint", apiFontPrint); // 1.xx lua_register(L, "fontQuality", apiFontQuality); // 1.xx lua_register(L, "fontSelect", apiFontSelect); // 1.xx lua_register(L, "fontToSprite", apiFontToSprite); // 1.xx lua_register(L, "fontUnload", apiFontUnload); // 2.00 lua_register(L, "jointBall", apiJointBall); // 3.00 lua_register(L, "jointDelete", apiJointDelete); // 3.00 lua_register(L, "jointHinge", apiJointHinge); // 3.00 lua_register(L, "jointSetLimits", apiJointSetLimits); // 3.00 lua_register(L, "jointSlider", apiJointSlider); // 3.00 lua_register(L, "keyboardGetLastDown", apiKeyboardGetLastDown); // 2.10 lua_register(L, "keyboardGetLastUp", apiKeyboardGetLastUp); // 2.10 lua_register(L, "keyboardGetMode", apiKeyboardGetMode); // 1.xx RDG lua_register(L, "keyboardGetModifiers", apiKeyboardGetModifiers); // 2.10 lua_register(L, "keyboardIsDown", apiKeyboardIsDown); // 2.10 lua_register(L, "keyboardSetMode", apiKeyboardSetMode); // 1.xx RDG lua_register(L, "lightNew", apiLightNew); // 3.00 lua_register(L, "lightSetColor", apiLightSetColor); // 3.00 lua_register(L, "lightSetCone", apiLightSetCone); // 3.00 lua_register(L, "lightSetIntensity", apiLightSetIntensity); // 3.00 lua_register(L, "lightSetRange", apiLightSetRange); // 3.00 lua_register(L, "lightSetShadow", apiLightSetShadow); // 3.00 lua_register(L, "lineDraw", apiLineDraw); // 3.00 lua_register(L, "materialDelete", apiMaterialDelete); // 3.00 lua_register(L, "materialNew", apiMaterialNew); // 3.00 lua_register(L, "materialSetBlend", apiMaterialSetBlend); // 3.00 lua_register(L, "materialSetColor", apiMaterialSetColor); // 3.00 lua_register(L, "materialSetDoubleSided", apiMaterialSetDoubleSided); // 3.00 lua_register(L, "materialSetEmissive", apiMaterialSetEmissive); // 3.00 lua_register(L, "materialSetEmissiveMap", apiMaterialSetEmissiveMap); // 3.00 lua_register(L, "materialSetFilter", apiMaterialSetFilter); // 3.00 lua_register(L, "materialSetMetallic", apiMaterialSetMetallic); // 3.00 lua_register(L, "materialSetMetallicRoughnessMap", apiMaterialSetMetallicRoughnessMap); // 3.00 lua_register(L, "materialSetNormalMap", apiMaterialSetNormalMap); // 3.00 lua_register(L, "materialSetOcclusionMap", apiMaterialSetOcclusionMap); // 3.00 lua_register(L, "materialSetRoughness", apiMaterialSetRoughness); // 3.00 lua_register(L, "materialSetTexture", apiMaterialSetTexture); // 3.00 lua_register(L, "materialSetTiling", apiMaterialSetTiling); // 3.00 lua_register(L, "materialSetUnlit", apiMaterialSetUnlit); // 3.00 lua_register(L, "materialSetVideo", apiMaterialSetVideo); // 3.00 lua_register(L, "materialSetView", apiMaterialSetView); // 3.00 lua_register(L, "meshBox", apiMeshBox); // 3.00 lua_register(L, "meshCone", apiMeshCone); // 3.00 lua_register(L, "meshCylinder", apiMeshCylinder); // 3.00 lua_register(L, "meshDelete", apiMeshDelete); // 3.00 lua_register(L, "meshHeightmap", apiMeshHeightmap); // 3.00 lua_register(L, "meshNew", apiMeshNew); // 3.00 lua_register(L, "meshPlane", apiMeshPlane); // 3.00 lua_register(L, "meshSphere", apiMeshSphere); // 3.00 lua_register(L, "meshTorus", apiMeshTorus); // 3.00 lua_register(L, "modelDelete", apiModelDelete); // 3.00 lua_register(L, "modelGetAnimations", apiModelGetAnimations); // 3.00 lua_register(L, "modelInstance", apiModelInstance); // 3.00 lua_register(L, "modelLoad", apiModelLoad); // 3.00 lua_register(L, "mouseGetPosition", apiMouseGetPosition); // 2.00 lua_register(L, "mouseHowMany", apiMouseHowMany); // 1.18 RDG lua_register(L, "mouseSetCaptured", apiMouseSetCaptured); // 2.00 lua_register(L, "mouseSetEnabled", apiMouseSetEnabled); // 3.00 mouseEnable/mouseDisable are framework aliases. lua_register(L, "mouseSetMode", apiMouseSetMode); // 1.18 RDG lua_register(L, "navAddNode", apiNavAddNode); // 3.00 lua_register(L, "navAgentDelete", apiNavAgentDelete); // 3.00 lua_register(L, "navAgentGetVelocity", apiNavAgentGetVelocity); // 3.00 lua_register(L, "navAgentIsArrived", apiNavAgentIsArrived); // 3.00 lua_register(L, "navAgentMoveTo", apiNavAgentMoveTo); // 3.00 lua_register(L, "navAgentNew", apiNavAgentNew); // 3.00 lua_register(L, "navAgentSetPlayer", apiNavAgentSetPlayer); // 3.00 lua_register(L, "navAgentStop", apiNavAgentStop); // 3.00 lua_register(L, "navBuild", apiNavBuild); // 3.00 lua_register(L, "navDelete", apiNavDelete); // 3.00 lua_register(L, "navDraw", apiNavDraw); // 3.00 lua_register(L, "navLoad", apiNavLoad); // 3.00 lua_register(L, "navNearest", apiNavNearest); // 3.00 lua_register(L, "navNew", apiNavNew); // 3.00 lua_register(L, "navPath", apiNavPath); // 3.00 lua_register(L, "navRandomPoint", apiNavRandomPoint); // 3.00 lua_register(L, "navRaycast", apiNavRaycast); // 3.00 lua_register(L, "navSave", apiNavSave); // 3.00 lua_register(L, "nodeDelete", apiNodeDelete); // 3.00 lua_register(L, "nodeFind", apiNodeFind); // 3.00 lua_register(L, "nodeGetChildren", apiNodeGetChildren); // 3.00 lua_register(L, "nodeGetMorph", apiNodeGetMorph); // 3.00 lua_register(L, "nodeGetMorphs", apiNodeGetMorphs); // 3.00 lua_register(L, "nodeGetName", apiNodeGetName); // 3.00 lua_register(L, "nodeGetParent", apiNodeGetParent); // 3.00 lua_register(L, "nodeGetPosition", apiNodeGetPosition); // 3.00 lua_register(L, "nodeGetQuaternion", apiNodeGetQuaternion); // 3.00 lua_register(L, "nodeGetRotation", apiNodeGetRotation); // 3.00 lua_register(L, "nodeGetScale", apiNodeGetScale); // 3.00 lua_register(L, "nodeGetWorldPosition", apiNodeGetWorldPosition); // 3.00 lua_register(L, "nodeLookAt", apiNodeLookAt); // 3.00 lua_register(L, "nodeMove", apiNodeMove); // 3.00 lua_register(L, "nodeNew", apiNodeNew); // 3.00 lua_register(L, "nodeRotate", apiNodeRotate); // 3.00 lua_register(L, "nodeSetBillboard", apiNodeSetBillboard); // 3.00 lua_register(L, "nodeSetMaterial", apiNodeSetMaterial); // 3.00 lua_register(L, "nodeSetMesh", apiNodeSetMesh); // 3.00 lua_register(L, "nodeSetMorph", apiNodeSetMorph); // 3.00 lua_register(L, "nodeSetName", apiNodeSetName); // 3.00 lua_register(L, "nodeSetParent", apiNodeSetParent); // 3.00 lua_register(L, "nodeSetPosition", apiNodeSetPosition); // 3.00 lua_register(L, "nodeSetQuaternion", apiNodeSetQuaternion); // 3.00 lua_register(L, "nodeSetRotation", apiNodeSetRotation); // 3.00 lua_register(L, "nodeSetScale", apiNodeSetScale); // 3.00 lua_register(L, "nodeSetShadow", apiNodeSetShadow); // 3.00 lua_register(L, "nodeSetSprite", apiNodeSetSprite); // 3.00 lua_register(L, "nodeSetSpriteFrame", apiNodeSetSpriteFrame); // 3.00 lua_register(L, "nodeSetText", apiNodeSetText); // 3.00 lua_register(L, "nodeSetVisible", apiNodeSetVisible); // 3.00 lua_register(L, "overlayBox", apiOverlayBox); // 2.00 lua_register(L, "overlayCircle", apiOverlayCircle); // 2.00 lua_register(L, "overlayClear", apiOverlayClear); // 1.xx lua_register(L, "overlayEllipse", apiOverlayEllipse); // 2.00 lua_register(L, "overlayGetHeight", apiOverlayGetHeight); // 1.xx lua_register(L, "overlayGetWidth", apiOverlayGetWidth); // 1.xx lua_register(L, "overlayLine", apiOverlayLine); // 2.00 lua_register(L, "overlayPlot", apiOverlayPlot); // 2.00 lua_register(L, "overlayPrint", apiOverlayPrint); // 1.xx lua_register(L, "overlaySetResolution", apiOverlaySetResolution); // 2.00 lua_register(L, "physicsRaycast", apiPhysicsRaycast); // 3.00 lua_register(L, "physicsSet2D", apiPhysicsSet2D); // 3.00 lua_register(L, "physicsSetDebug", apiPhysicsSetDebug); // 3.00 lua_register(L, "physicsSetEnabled", apiPhysicsSetEnabled); // 3.00 lua_register(L, "physicsSetGravity", apiPhysicsSetGravity); // 3.00 lua_register(L, "playerDelete", apiPlayerDelete); // 3.00 lua_register(L, "playerGetGround", apiPlayerGetGround); // 3.00 lua_register(L, "playerGetVelocity", apiPlayerGetVelocity); // 3.00 lua_register(L, "playerIsOnGround", apiPlayerIsOnGround); // 3.00 lua_register(L, "playerIsSwimming", apiPlayerIsSwimming); // 3.00 lua_register(L, "playerJump", apiPlayerJump); // 3.00 lua_register(L, "playerMove", apiPlayerMove); // 3.00 lua_register(L, "playerNew", apiPlayerNew); // 3.00 lua_register(L, "playerSetEnabled", apiPlayerSetEnabled); // 3.00 lua_register(L, "playerSetGravityScale", apiPlayerSetGravityScale); // 3.00 lua_register(L, "playerSetMass", apiPlayerSetMass); // 3.00 lua_register(L, "playerSetPosition", apiPlayerSetPosition); // 3.00 lua_register(L, "playerSetPush", apiPlayerSetPush); // 3.00 lua_register(L, "playerSetSlope", apiPlayerSetSlope); // 3.00 lua_register(L, "playerSetStep", apiPlayerSetStep); // 3.00 lua_register(L, "playerSetSwim", apiPlayerSetSwim); // 3.00 lua_register(L, "playerSetVelocity", apiPlayerSetVelocity); // 3.00 lua_register(L, "ragdollActivate", apiRagdollActivate); // 3.00 lua_register(L, "ragdollApplyImpulse", apiRagdollApplyImpulse); // 3.00 lua_register(L, "ragdollDeactivate", apiRagdollDeactivate); // 3.00 lua_register(L, "ragdollDelete", apiRagdollDelete); // 3.00 lua_register(L, "ragdollIsActive", apiRagdollIsActive); // 3.00 lua_register(L, "ragdollIsResting", apiRagdollIsResting); // 3.00 lua_register(L, "ragdollNew", apiRagdollNew); // 3.00 lua_register(L, "ragdollSetJoint", apiRagdollSetJoint); // 3.00 lua_register(L, "ragdollSetStrength", apiRagdollSetStrength); // 3.00 lua_register(L, "sceneEnable", apiSceneEnable); // 3.00 lua_register(L, "sceneGetSize", apiSceneGetSize); // 3.00 lua_register(L, "sceneGetStats", apiSceneGetStats); // 3.00 lua_register(L, "sceneProject", apiSceneProject); // 3.00 lua_register(L, "sceneSetAmbient", apiSceneSetAmbient); // 3.00 lua_register(L, "sceneSetAntialias", apiSceneSetAntialias); // 3.00 lua_register(L, "sceneSetBackground", apiSceneSetBackground); // 3.00 lua_register(L, "sceneSetBloom", apiSceneSetBloom); // 3.00 lua_register(L, "sceneSetEnvironment", apiSceneSetEnvironment); // 3.00 lua_register(L, "sceneSetExposure", apiSceneSetExposure); // 3.00 lua_register(L, "sceneSetFog", apiSceneSetFog); // 3.00 lua_register(L, "sceneSetShadowCascades", apiSceneSetShadowCascades); // 3.00 lua_register(L, "sceneSetShadowDistance", apiSceneSetShadowDistance); // 3.00 lua_register(L, "sceneSetShadowSize", apiSceneSetShadowSize); // 3.00 lua_register(L, "sceneSetSky", apiSceneSetSky); // 3.00 lua_register(L, "sceneSetSkyIntensity", apiSceneSetSkyIntensity); // 3.00 lua_register(L, "sceneSetTonemap", apiSceneSetTonemap); // 3.00 lua_register(L, "sceneUnproject", apiSceneUnproject); // 3.00 lua_register(L, "scriptExecute", apiScriptExecute); // 2.00 lua_register(L, "scriptPush", apiScriptPush); // 2.00 lua_register(L, "singeGetAudioCalibration", apiSingeGetAudioCalibration); // 3.00 lua_register(L, "singeGetAudioDelay", apiSingeGetAudioDelay); // 3.00 lua_register(L, "singeGetAudioLatency", apiSingeGetAudioLatency); // 3.00 lua_register(L, "singeGetDataPath", apiSingeGetDataPath); // 2.00 lua_register(L, "singeGetHeight", apiSingeGetHeight); // 1.xx lua_register(L, "singeGetPauseFlag", apiSingeGetPauseFlag); // 1.xx RDG lua_register(L, "singeGetScriptPath", apiSingeGetScriptPath); // 1.15 RDG lua_register(L, "singeGetTicks", apiSingeGetTicks); // 3.00 lua_register(L, "singeGetWidth", apiSingeGetWidth); // 1.xx lua_register(L, "singeQuit", apiSingeQuit); // 1.xx RDG lua_register(L, "singeReload", apiSingeReload); // 3.00 lua_register(L, "singeScreenshot", apiSingeScreenshot); // 1.xx lua_register(L, "singeSetAudioCalibration", apiSingeSetAudioCalibration); // 3.00 lua_register(L, "singeSetAudioDelay", apiSingeSetAudioDelay); // 3.00 lua_register(L, "singeSetGameName", apiSingeSetGameName); // 1.15 RDG lua_register(L, "singeSetPauseFlag", apiSingeSetPauseFlag); // 1.xx RDG lua_register(L, "singeSetPauseKeyEnabled", apiSingeSetPauseKeyEnabled); // 3.00 singeEnablePauseKey/singeDisablePauseKey are framework aliases. lua_register(L, "singeVersion", apiSingeVersion); // 1.xx RDG lua_register(L, "singeWantsCrosshairs", apiSingeWantsCrosshairs); // 2.00 lua_register(L, "softDelete", apiSoftDelete); // 3.00 lua_register(L, "softNew", apiSoftNew); // 3.00 lua_register(L, "softPin", apiSoftPin); // 3.00 lua_register(L, "softSetDamping", apiSoftSetDamping); // 3.00 lua_register(L, "softSetMass", apiSoftSetMass); // 3.00 lua_register(L, "softSetPressure", apiSoftSetPressure); // 3.00 lua_register(L, "softSetStiffness", apiSoftSetStiffness); // 3.00 lua_register(L, "softUnpin", apiSoftUnpin); // 3.00 lua_register(L, "soundFullStop", apiSoundFullStop); // 1.16 lua_register(L, "soundGetPosition", apiSoundGetPosition); // 3.00 lua_register(L, "soundGetVolume", apiSoundGetVolume); // 1.16 lua_register(L, "soundIsPlaying", apiSoundIsPlaying); // 1.16 RDG lua_register(L, "soundLoad", apiSoundLoad); // 1.xx lua_register(L, "soundPause", apiSoundPause); // 1.16 RDG lua_register(L, "soundPlay", apiSoundPlay); // 1.xx lua_register(L, "soundResume", apiSoundResume); // 1.16 RDG lua_register(L, "soundSetListener", apiSoundSetListener); // 3.00 lua_register(L, "soundSetNode", apiSoundSetNode); // 3.00 lua_register(L, "soundSetPan", apiSoundSetPan); // 3.00 lua_register(L, "soundSetPosition", apiSoundSetPosition); // 3.00 lua_register(L, "soundSetRange", apiSoundSetRange); // 3.00 lua_register(L, "soundSetVolume", apiSoundSetVolume); // 1.16 lua_register(L, "soundStop", apiSoundStop); // 1.xx RDG lua_register(L, "soundUnload", apiSoundUnload); // 2.00 lua_register(L, "spriteDraw", apiSpriteDraw); // 1.xx Handle first since 3.00. lua_register(L, "spriteGetFrame", apiSpriteGetFrame); // 2.10 lua_register(L, "spriteGetHeight", apiSpriteGetHeight); // 2.00 lua_register(L, "spriteGetWidth", apiSpriteGetWidth); // 2.00 lua_register(L, "spriteIsPlaying", apiSpriteIsPlaying); // 2.10 lua_register(L, "spriteLoad", apiSpriteLoad); // 1.xx lua_register(L, "spriteLoop", apiSpriteLoop); // 2.10 Handle first since 3.00. lua_register(L, "spritePause", apiSpritePause); // 2.10 lua_register(L, "spritePlay", apiSpritePlay); // 2.10 lua_register(L, "spriteQuality", apiSpriteQuality); // 2.10 Handle first since 3.00. lua_register(L, "spriteRotate", apiSpriteRotate); // 2.10 Handle first since 3.00. lua_register(L, "spriteRotateAndScale", apiSpriteRotateAndScale); // 2.10 Handle first since 3.00. lua_register(L, "spriteScale", apiSpriteScale); // 2.10 Handle first since 3.00. lua_register(L, "spriteSetFrame", apiSpriteSetFrame); // 2.10 Handle first since 3.00. lua_register(L, "spriteUnload", apiSpriteUnload); // 2.00 lua_register(L, "terrainGetHeight", apiTerrainGetHeight); // 3.00 lua_register(L, "vehicleAddWheel", apiVehicleAddWheel); // 3.00 lua_register(L, "vehicleDelete", apiVehicleDelete); // 3.00 lua_register(L, "vehicleDrive", apiVehicleDrive); // 3.00 lua_register(L, "vehicleGetGear", apiVehicleGetGear); // 3.00 lua_register(L, "vehicleGetRpm", apiVehicleGetRpm); // 3.00 lua_register(L, "vehicleGetSpeed", apiVehicleGetSpeed); // 3.00 lua_register(L, "vehicleGetWheelSlip", apiVehicleGetWheelSlip); // 3.00 lua_register(L, "vehicleIsWheelOnGround", apiVehicleIsWheelOnGround); // 3.00 lua_register(L, "vehicleNew", apiVehicleNew); // 3.00 lua_register(L, "vehicleSetAntiRoll", apiVehicleSetAntiRoll); // 3.00 lua_register(L, "vehicleSetBrakes", apiVehicleSetBrakes); // 3.00 lua_register(L, "vehicleSetEngine", apiVehicleSetEngine); // 3.00 lua_register(L, "vehicleSetGears", apiVehicleSetGears); // 3.00 lua_register(L, "vehicleSetSteering", apiVehicleSetSteering); // 3.00 lua_register(L, "vehicleSetRudder", apiVehicleSetRudder); // 3.00 lua_register(L, "vehicleSetSuspension", apiVehicleSetSuspension); // 3.00 lua_register(L, "vehicleSetThrust", apiVehicleSetThrust); // 3.00 lua_register(L, "vehicleSetWheel", apiVehicleSetWheel); // 3.00 lua_register(L, "videoDraw", apiVideoDraw); // 2.00 lua_register(L, "videoGetAudioTrack", apiVideoGetAudioTrack); // 2.10 lua_register(L, "videoGetAudioTracks", apiVideoGetAudioTracks); // 2.10 lua_register(L, "videoGetFrame", apiVideoGetFrame); // 2.00 lua_register(L, "videoGetFrameCount", apiVideoGetFrameCount); // 2.00 lua_register(L, "videoGetHeight", apiVideoGetHeight); // 2.00 lua_register(L, "videoGetLanguage", apiVideoGetLanguage); // 2.10 lua_register(L, "videoGetLanguageDescription", apiVideoGetLanguageDescription); // 2.10 lua_register(L, "videoGetVolume", apiVideoGetVolume); // 2.00 lua_register(L, "videoGetWidth", apiVideoGetWidth); // 2.00 lua_register(L, "videoIsPlaying", apiVideoIsPlaying); // 2.00 lua_register(L, "videoLoad", apiVideoLoad); // 2.00 lua_register(L, "videoPause", apiVideoPause); // 2.00 lua_register(L, "videoPlay", apiVideoPlay); // 2.00 lua_register(L, "videoQuality", apiVideoQuality); // 2.10 lua_register(L, "videoRotate", apiVideoRotate); // 2.10 lua_register(L, "videoRotateAndScale", apiVideoRotateAndScale); // 2.10 lua_register(L, "videoScale", apiVideoScale); // 2.10 lua_register(L, "videoSeek", apiVideoSeek); // 2.00 lua_register(L, "videoSetAudioTrack", apiVideoSetAudioTrack); // 2.10 lua_register(L, "videoSetVolume", apiVideoSetVolume); // 2.00 lua_register(L, "videoUnload", apiVideoUnload); // 2.00 lua_register(L, "viewDelete", apiViewDelete); // 3.00 lua_register(L, "viewNew", apiViewNew); // 3.00 lua_register(L, "viewSetCamera", apiViewSetCamera); // 3.00 lua_register(L, "vldpGetHeight", apiDiscGetHeight); // 1.xx Same as discGetHeight. lua_register(L, "vldpGetPixel", apiVldpGetPixel); // 1.xx lua_register(L, "vldpGetWidth", apiDiscGetWidth); // 1.xx Same as discGetWidth. lua_register(L, "vldpSetVerbose", apiVldpSetVerbose); // 1.xx } // Runs the game again from its script without leaving: every script-owned thing goes (sounds, // the Lua state, fonts, sprites, videos, the scene, physics, navigation, particles, the overlay // back to its default size), the engine (window, GPU device, disc, controllers) stays, and the // script runs afresh. An error in it is traced and the game sits empty until the next reload. static void _reloadScript(void) { _progTrace("Reloading %s", _global.conf->scriptFile); _global.reloadRequested = false; MIX_StopTag(videoGetMixer(), EFFECT_TAG, 0); lua_close(_global.luaContext); _unloadScriptResources(); _subsystemsQuit(); _subsystemsInit(); _overlayResize((int32_t)(_global.canvasWidth * OVERLAY_SCALE_DEFAULT), (int32_t)(_global.canvasHeight * OVERLAY_SCALE_DEFAULT)); _resetScriptState(); _createScriptContext(); _runScript(false); _global.refreshDisplay = true; } // Loads and runs the game script under the traceback handler. A failure is fatal when the game // starts; on a reload it is reported and the game sits empty until the next one. static void _runScript(bool fatal) { _progTrace("Running %s", _global.conf->scriptFile); lua_pushcfunction(_global.luaContext, _luaTraceback); if (_luaLoadFile(_global.luaContext, _global.conf->scriptFile, NULL, _global.conf->reload) || lua_pcall(_global.luaContext, 0, 0, -2)) { if (fatal) { utilDie("Error running script: %s", lua_tostring(_global.luaContext, -1)); } utilSay("Error running script: %s", lua_tostring(_global.luaContext, -1)); } lua_settop(_global.luaContext, 0); } // A player's current frame for the 3D scene's video materials: the disc's texture is already // updated for this frame; a loaded video is advanced here (drawing it on the overlay too is harmless). static SDL_Texture *_sceneVideoSource(int32_t player) { VideoT *video; if ((player == _global.videoHandle) && (player >= 0)) { return _global.videoTexture; } for (video = _global.videoList; video != NULL; video = video->hh.next) { if (video->handle == player) { videoUpdate(video->handle, &video->texture); return video->texture; } } return NULL; } // scriptExecute and scriptPush: the games.dat style table at argument 1 as a config, with the data // directory the launched game will write to. Caller destroys it. static ConfigT *_scriptConfFromTable(lua_State *L, const char *method) { ConfigT *conf = NULL; _argCheck(L, method, 1, 1); if (!lua_istable(L, 1)) { _luaDie(L, method, "Argument 1 must be a table."); } conf = _buildConfFromTable(L, _global.conf); conf->dataDir = resolveDataDir(conf); if (conf->dataDir == NULL) { _luaDie(L, method, "Unable to create the data directory for %s.", conf->scriptFile); } return conf; } // Constants every script (and controls.cfg) can rely on. These are the single source of truth. static void _pushConstants(lua_State *L) { int32_t x = 0; for (x = 0; x < INPUT_COUNT; x++) { lua_pushinteger(L, x); lua_setglobal(L, _inputNames[x].switchName); } lua_pushinteger(L, FONT_QUALITY_SOLID); lua_setglobal(L, "FONT_QUALITY_SOLID"); lua_pushinteger(L, FONT_QUALITY_SHADED); lua_setglobal(L, "FONT_QUALITY_SHADED"); lua_pushinteger(L, FONT_QUALITY_BLENDED); lua_setglobal(L, "FONT_QUALITY_BLENDED"); lua_pushinteger(L, KEYBOARD_NORMAL); lua_setglobal(L, "MODE_NORMAL"); lua_pushinteger(L, KEYBOARD_FULL); lua_setglobal(L, "MODE_FULL"); lua_pushinteger(L, MOUSE_SINGLE); lua_setglobal(L, "MOUSE_SINGLE"); lua_pushinteger(L, MOUSE_MANY); lua_setglobal(L, "MOUSE_MANY"); lua_pushinteger(L, MOUSE_SINGLE); lua_setglobal(L, "SINGLE_MOUSE"); lua_pushinteger(L, MOUSE_MANY); lua_setglobal(L, "MANY_MOUSE"); lua_pushinteger(L, OVERLAY_NOT_UPDATED); lua_setglobal(L, "OVERLAY_NOT_UPDATED"); lua_pushinteger(L, OVERLAY_UPDATED); lua_setglobal(L, "OVERLAY_UPDATED"); lua_pushinteger(L, RENDER_PIXELATED); lua_setglobal(L, "RENDER_PIXELATED"); lua_pushinteger(L, RENDER_SMOOTH); lua_setglobal(L, "RENDER_SMOOTH"); lua_pushinteger(L, DISC_STOPPED); lua_setglobal(L, "DISC_STOPPED"); lua_pushinteger(L, DISC_PLAYING); lua_setglobal(L, "DISC_PLAYING"); lua_pushinteger(L, DISC_PAUSED); lua_setglobal(L, "DISC_PAUSED"); lua_pushinteger(L, DISC_EJECTED); lua_setglobal(L, "DISC_EJECTED"); // 3D light types lua_pushinteger(L, LIGHT_DIRECTIONAL); lua_setglobal(L, "LIGHT_DIRECTIONAL"); lua_pushinteger(L, LIGHT_POINT); lua_setglobal(L, "LIGHT_POINT"); lua_pushinteger(L, LIGHT_SPOT); lua_setglobal(L, "LIGHT_SPOT"); // Physics bodies and shapes lua_pushinteger(L, BODY_STATIC); lua_setglobal(L, "BODY_STATIC"); lua_pushinteger(L, BODY_DYNAMIC); lua_setglobal(L, "BODY_DYNAMIC"); lua_pushinteger(L, BODY_KINEMATIC); lua_setglobal(L, "BODY_KINEMATIC"); lua_pushinteger(L, SHAPE_BOX); lua_setglobal(L, "SHAPE_BOX"); lua_pushinteger(L, SHAPE_SPHERE); lua_setglobal(L, "SHAPE_SPHERE"); lua_pushinteger(L, SHAPE_CAPSULE); lua_setglobal(L, "SHAPE_CAPSULE"); lua_pushinteger(L, SHAPE_CYLINDER); lua_setglobal(L, "SHAPE_CYLINDER"); lua_pushinteger(L, SHAPE_HULL); lua_setglobal(L, "SHAPE_HULL"); lua_pushinteger(L, SHAPE_MESH); lua_setglobal(L, "SHAPE_MESH"); lua_pushinteger(L, JOINT_HINGE); lua_setglobal(L, "JOINT_HINGE"); lua_pushinteger(L, JOINT_BALL); lua_setglobal(L, "JOINT_BALL"); lua_pushinteger(L, JOINT_SLIDER); lua_setglobal(L, "JOINT_SLIDER"); lua_pushinteger(L, DEBUG_NONE); lua_setglobal(L, "DEBUG_NONE"); lua_pushinteger(L, DEBUG_SHAPES); lua_setglobal(L, "DEBUG_SHAPES"); lua_pushinteger(L, DEBUG_CONSTRAINTS); lua_setglobal(L, "DEBUG_CONSTRAINTS"); lua_pushinteger(L, DEBUG_CONTACTS); lua_setglobal(L, "DEBUG_CONTACTS"); lua_pushinteger(L, DEBUG_VELOCITIES); lua_setglobal(L, "DEBUG_VELOCITIES"); lua_pushinteger(L, DEBUG_STATIC); lua_setglobal(L, "DEBUG_STATIC"); lua_pushinteger(L, DEBUG_ALL); lua_setglobal(L, "DEBUG_ALL"); // Particles lua_pushinteger(L, PARTICLE_ALPHA); lua_setglobal(L, "PARTICLE_ALPHA"); lua_pushinteger(L, PARTICLE_ADD); lua_setglobal(L, "PARTICLE_ADD"); lua_pushinteger(L, PARTICLE_OVER); lua_setglobal(L, "PARTICLE_OVER"); lua_pushinteger(L, PARTICLE_UNDER); lua_setglobal(L, "PARTICLE_UNDER"); // Billboards lua_pushinteger(L, BILLBOARD_NONE); lua_setglobal(L, "BILLBOARD_NONE"); lua_pushinteger(L, BILLBOARD_ALL); lua_setglobal(L, "BILLBOARD_ALL"); lua_pushinteger(L, BILLBOARD_Y); lua_setglobal(L, "BILLBOARD_Y"); // Particle collisions lua_pushinteger(L, COLLIDE_NONE); lua_setglobal(L, "COLLIDE_NONE"); lua_pushinteger(L, COLLIDE_FLOOR); lua_setglobal(L, "COLLIDE_FLOOR"); lua_pushinteger(L, COLLIDE_SCENE); lua_setglobal(L, "COLLIDE_SCENE"); // Texture filtering lua_pushinteger(L, FILTER_LINEAR); lua_setglobal(L, "FILTER_LINEAR"); lua_pushinteger(L, FILTER_NEAREST); lua_setglobal(L, "FILTER_NEAREST"); // Tone curves lua_pushinteger(L, TONEMAP_NONE); lua_setglobal(L, "TONEMAP_NONE"); lua_pushinteger(L, TONEMAP_NEUTRAL); lua_setglobal(L, "TONEMAP_NEUTRAL"); lua_pushinteger(L, TONEMAP_ACES); lua_setglobal(L, "TONEMAP_ACES"); // Vehicles lua_pushinteger(L, VEHICLE_CAR); lua_setglobal(L, "VEHICLE_CAR"); lua_pushinteger(L, VEHICLE_MOTORCYCLE); lua_setglobal(L, "VEHICLE_MOTORCYCLE"); lua_pushinteger(L, VEHICLE_TANK); lua_setglobal(L, "VEHICLE_TANK"); lua_pushinteger(L, VEHICLE_BOAT); lua_setglobal(L, "VEHICLE_BOAT"); // Soft bodies lua_pushinteger(L, SOFT_CLOTH); lua_setglobal(L, "SOFT_CLOTH"); lua_pushinteger(L, SOFT_BODY); lua_setglobal(L, "SOFT_BODY"); lua_pushinteger(L, SOFT_ROPE); lua_setglobal(L, "SOFT_ROPE"); lua_pushinteger(L, SOUND_CHANNEL_NONE); lua_setglobal(L, "SOUND_ERROR_INVALID"); lua_pushinteger(L, SOUND_CHANNEL_NONE); lua_setglobal(L, "SOUND_REMOVE_HANDLE"); // Input code layout so Framework.singe can build the GAMEPAD_N and MOUSE_N tables. lua_pushinteger(L, CODE_GAMEPAD_BASE); lua_setglobal(L, "SINGE_GAMEPAD_BASE"); lua_pushinteger(L, CODE_GAMEPAD_STRIDE); lua_setglobal(L, "SINGE_GAMEPAD_STRIDE"); lua_pushinteger(L, CODE_AXIS_STRIDE); lua_setglobal(L, "SINGE_AXIS_STRIDE"); lua_pushinteger(L, CODE_GAMEPAD_BUTTON_OFFSET); lua_setglobal(L, "SINGE_GAMEPAD_BUTTON_OFFSET"); lua_pushinteger(L, CODE_MOUSE_BASE); lua_setglobal(L, "SINGE_MOUSE_BASE"); lua_pushinteger(L, CODE_MOUSE_STRIDE); lua_setglobal(L, "SINGE_MOUSE_STRIDE"); lua_pushinteger(L, MAX_CONTROLLERS); lua_setglobal(L, "SINGE_MAX_CONTROLLERS"); lua_pushinteger(L, MAX_MICE); lua_setglobal(L, "SINGE_MAX_MICE"); lua_pushinteger(L, SINGE_VERSION_MAJOR); lua_setglobal(L, "SINGE_VERSION_MAJOR"); lua_pushinteger(L, SINGE_VERSION_MINOR); lua_setglobal(L, "SINGE_VERSION_MINOR"); lua_pushstring(L, VERSION_STRING); lua_setglobal(L, "SINGE_VERSION_STRING"); lua_pushnumber(L, SINGE_VERSION); lua_setglobal(L, "SINGE_FRAMEWORK_VERSION"); lua_pushinteger(L, _global.controllerDeadZone); lua_setglobal(L, "SINGE_DEAD_ZONE"); lua_pushboolean(L, _global.conf->legacySpriteArgs); lua_setglobal(L, "SINGE_LEGACY_SPRITE_ARGS"); lua_pushboolean(L, _global.conf->disc); lua_setglobal(L, "SINGE_DISC"); } // Writes one overlay pixel. The overlay is always BGRA32, one uint32_t per pixel (_overlayResize // makes it so), and must be locked by the caller. static void _putPixel(int32_t x, int32_t y, uint32_t pixel) { SDL_Surface *surface = _global.overlay; uint32_t *row = NULL; if ((x < 0) || (x >= surface->w) || (y < 0) || (y >= surface->h)) { return; } row = (uint32_t *)((uint8_t *)surface->pixels + (size_t)y * (size_t)surface->pitch); row[x] = pixel; } // colorXxx(r, g, b[, a]) with components clamped to 0..255. static void _readColor(lua_State *L, const char *method, SDL_Color *color, uint8_t defaultAlpha) { int32_t n = lua_gettop(L); uint8_t value[COLOR_COMPONENTS] = { 0, 0, 0, defaultAlpha }; int32_t x = 0; _argCheck(L, method, 3, COLOR_COMPONENTS); for (x = 0; x < n; x++) { value[x] = _argColorByte(L, method, x + 1); } color->r = value[0]; color->g = value[1]; color->b = value[2]; color->a = value[3]; _luaTrace(L, method, "%d %d %d %d", color->r, color->g, color->b, color->a); } // Releases whatever direction code an axis was holding. static void _releaseAxis(int32_t axisIndex) { if (_global.axisCode[axisIndex] != 0) { _processKey(false, 0, _global.axisCode[axisIndex]); _global.axisCode[axisIndex] = 0; } } // Everything a script sets about itself, back to what a script may expect at its start: colours, // font quality, keyboard mode, listener, pause, the effect channels and their completion queue, // what the previous script believed was held, and --reload's file list. Keys physically down now // were held over from before this script, so they are ignored until released. Nothing here calls // into Lua, so it is safe between one state closing and the next opening. static void _resetScriptState(void) { int32_t finished[SOUND_QUEUE_SIZE]; int32_t x = 0; for (x = 0; x < EFFECT_TRACKS; x++) { _effectReset(x); } _soundQueueDrain(finished); _global.colorForeground.r = SDL_ALPHA_OPAQUE; _global.colorForeground.g = SDL_ALPHA_OPAQUE; _global.colorForeground.b = SDL_ALPHA_OPAQUE; _global.colorForeground.a = SDL_ALPHA_OPAQUE; memset(&_global.colorBackground, 0, sizeof(_global.colorBackground)); _global.fontQuality = FONT_QUALITY_SOLID; _global.keyboardMode = KEYBOARD_NORMAL; _global.listenerNode = LISTENER_CAMERA; _global.pauseEnabled = true; _global.frozen = false; if (_global.pauseState) { _global.pauseState = false; _updatePauseState(); } memset(_global.switchHeld, 0, sizeof(_global.switchHeld)); memset(_global.axisCode, 0, sizeof(_global.axisCode)); _global.heldKeyCount = 0; _global.physicalKeyCount = 0; _global.keyboardLastDown = SDL_SCANCODE_UNKNOWN; _global.keyboardLastUp = SDL_SCANCODE_UNKNOWN; _suppressHeldInput(); for (x = 0; x < _global.watchedCount; x++) { free(_global.watched[x].name); } free(_global.watched); _global.watched = NULL; _global.watchedCount = 0; } // Renders text with the current font, quality, and colors. static SDL_Surface *_renderText(lua_State *L, const char *method, const char *message) { SDL_Surface *surface = NULL; if (_global.fontCurrent == NULL) { _luaDie(L, method, "No font selected."); } switch (_global.fontQuality) { case FONT_QUALITY_SOLID: surface = TTF_RenderText_Solid(_global.fontCurrent->font, message, 0, _global.colorForeground); break; case FONT_QUALITY_SHADED: surface = TTF_RenderText_Shaded(_global.fontCurrent->font, message, 0, _global.colorForeground, _global.colorBackground); break; case FONT_QUALITY_BLENDED: surface = TTF_RenderText_Blended(_global.fontCurrent->font, message, 0, _global.colorForeground); break; default: _luaDie(L, method, "Unknown font quality!"); } if (!surface) { _luaDie(L, method, "%s", SDL_GetError()); } SDL_SetSurfaceColorKey(surface, true, COLOR_KEY_VALUE); return surface; } static void _saveAudioCalibration(int32_t milliseconds) { char *path = utilCreateString("%s%s", _global.conf->dataDirBase, AUDIO_CALIBRATION_FILE); FILE *out = fopen(path, "w"); if (out) { fprintf(out, "delay = %d\n", milliseconds); fclose(out); } else { utilSay("Unable to write %s", path); } free(path); } // Applies the command line audio track to a freshly loaded video, when it has one. static void _selectDefaultAudioTrack(int32_t handle) { if ((_global.conf->audioOutputTrack >= 0) && (_global.conf->audioOutputTrack < videoGetAudioTracks(handle))) { videoSetAudioTrack(handle, _global.conf->audioOutputTrack); } } static void _setMouseCaptured(bool captured) { _global.mouseGrabbed = captured; SDL_SetWindowMouseGrab(_global.window, captured); if (captured) { SDL_HideCursor(); } else { SDL_ShowCursor(); } } // Changes the pause flag. Only the pause key freezes the script; a script that sets the flag // itself keeps running so it can clear it again. static void _setPause(bool paused, bool fromKey) { _global.pauseState = paused; _updatePauseState(); if (paused && fromKey && !_global.frozen) { _freezeGame(true); } if (!paused && _global.frozen) { _freezeGame(false); } } static void _soundDestroy(SoundT *sound) { HASH_DEL(_global.soundList, sound); MIX_DestroyAudio(sound->audio); free(sound); } // Takes the channels the mixer thread reported finished since the last call, under its lock. static int32_t _soundQueueDrain(int32_t *finished) { int32_t count = 0; videoLockAudio(); count = _global.soundQueueCount; memcpy(finished, _global.soundQueue, sizeof(int32_t) * (size_t)count); _global.soundQueueCount = 0; videoUnlockAudio(); return count; } static void _spriteDestroy(SpriteT *sprite) { HASH_DEL(_global.spriteList, sprite); _spriteFreeSurface(sprite); if (sprite->animation != NULL) { // Frames belong to the animation. IMG_FreeAnimation(sprite->animation); } else { SDL_DestroySurface(sprite->originalSurface); } free(sprite); } // Releases the drawn surface if it is a transformed copy. Animation frames are never freed here. static void _spriteFreeSurface(SpriteT *sprite) { if (sprite->surfaceOwned) { SDL_DestroySurface(sprite->surface); } sprite->surface = NULL; sprite->surfaceOwned = false; } // Rebuilds the drawn surface after a frame, angle, scale, or quality change. static void _spriteRebuildSurface(SpriteT *sprite) { _spriteFreeSurface(sprite); if ((sprite->angle == 0.0) && (sprite->scaleX == 1.0) && (sprite->scaleY == 1.0)) { // Untransformed sprites draw straight from the original. sprite->surface = sprite->originalSurface; } else { sprite->surface = rotoZoomSurface(sprite->originalSurface, -sprite->angle, sprite->scaleX, sprite->scaleY, sprite->smooth != 0); if (sprite->surface == NULL) { utilDie("Unable to transform sprite %d.", sprite->id); } sprite->surfaceOwned = true; } } static void _startControllers(void) { int32_t x = 0; int32_t count = 0; SDL_JoystickID *ids = SDL_GetGamepads(&count); _stopControllers(); // Clamp to the first few controllers found. if (count > MAX_CONTROLLERS) { count = MAX_CONTROLLERS; } for (x = 0; (ids != NULL) && (x < count); x++) { _global.controllers[x] = SDL_OpenGamepad(ids[x]); if (_global.controllers[x]) { _progTrace("Found %d - %s", x, SDL_GetGamepadName(_global.controllers[x])); } else { _progTrace("Controller %d not opened", x); } } SDL_free(ids); SDL_SetGamepadEventsEnabled(true); } // Prepares a Lua state: standard libraries, our constants, and the embedded module searcher. static void _startLuaContext(lua_State *L) { size_t length = 0; size_t i = 0; // What to do when bad things happen lua_atpanic(L, _luaPanic); // Register the standard libraries luaL_openlibs(L); // Games use os.clock() as a wall clock for debounces and timers, and it is processor time: with // the GPU decoding video the engine mostly sleeps, so those timers crawl. Give them what they meant. lua_getglobal(L, "os"); lua_pushcfunction(L, apiOsClock); lua_setfield(L, -2, "clock"); lua_pop(L, 1); // Every file a script names goes through the vfs. _installFileHooks(L); _pushConstants(L); // Put our searchers at the front of package.searchers: embedded modules, then game files. lua_getglobal(L, "package"); lua_getfield(L, -1, "searchers"); length = lua_rawlen(L, -1); for (i = length + 2; i > 2; i--) { lua_rawgeti(L, -1, (lua_Integer)(i - 2)); lua_rawseti(L, -2, (lua_Integer)i); } lua_pushcfunction(L, _luaSearcher); lua_rawseti(L, -2, 1); lua_pushcfunction(L, _luaFileSearcher); lua_rawseti(L, -2, 2); lua_pop(L, 2); } static void _stopControllers(void) { int32_t x = 0; for (x = 0; x < MAX_CONTROLLERS; x++) { if (_global.controllers[x] != NULL) { SDL_CloseGamepad(_global.controllers[x]); _global.controllers[x] = NULL; } } // Anything held on an axis is gone with the controller. for (x = 0; x < AXIS_COUNT; x++) { _releaseAxis(x); _global.axisCache[x] = 0; } } // The 3D side a script builds on: scene, physics, particles and navigation, in dependency order. static void _subsystemsInit(void) { sceneInit(_global.device, _global.renderer); physicsInit(); particlesInit(); navInit(); } static void _subsystemsQuit(void) { _particleTexturesFreeAll(); modelQuit(); navQuit(); particlesQuit(); physicsQuit(); sceneQuit(); } // Keys and buttons that are already down when a script starts, or when the window gains focus, are // not presses meant for this script: SDL reports them as fresh key downs, and the button that // confirmed "exit" in a game would otherwise relaunch it from the menu. They stay ignored until released. static void _suppressHeldInput(void) { int32_t count = 0; int32_t x = 0; int32_t b = 0; const bool *state = SDL_GetKeyboardState(&count); // SDL may not know about a held key or button yet (gamepads are polled on their own thread, and // X11 re-reports keys after the focus event), so presses that arrive soon after count as held too. _global.inputGraceUntil = SDL_GetTicks() + INPUT_GRACE_MS; for (x = 0; x < SDL_SCANCODE_COUNT; x++) { _global.keySuppressed[x] = (x < count) && state[x]; } for (x = 0; x < MAX_CONTROLLERS; x++) { for (b = 0; b < CONTROLLER_BUTTON_COUNT; b++) { _global.buttonSuppressed[x][b] = (_global.controllers[x] != NULL) && SDL_GetGamepadButton(_global.controllers[x], (SDL_GamepadButton)b); } } } // The colour key that matches a surface's top left pixel, in that surface's own format. static uint32_t _surfaceCornerKey(SDL_Surface *surface) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; uint8_t a = 0; if (!SDL_ReadSurfacePixel(surface, 0, 0, &r, &g, &b, &a)) { utilDie("%s", SDL_GetError()); } return SDL_MapSurfaceRGBA(surface, r, g, b, a); } // SDL3_image keeps 1, 2, and 4 bit indexed PNGs packed, and blits from those are unreliable. // Expand them to one byte per pixel, which is what SDL2 always produced. Replaces *surface. static void _surfaceUnpack(SDL_Surface **surface) { SDL_Surface *unpacked = NULL; if ((*surface != NULL) && SDL_ISPIXELFORMAT_INDEXED((*surface)->format) && (SDL_BITSPERPIXEL((*surface)->format) < 8)) { unpacked = SDL_ConvertSurface(*surface, SDL_PIXELFORMAT_INDEX8); if (unpacked == NULL) { utilDie("%s", SDL_GetError()); } SDL_DestroySurface(*surface); *surface = unpacked; } } // Saves the current frame buffer to the next free singeNNN.png in the data directory. // Must be called before SDL_RenderPresent for the frame being captured. static void _takeScreenshot(void) { int32_t x = 0; int32_t logicalW = 0; int32_t logicalH = 0; char *filename = NULL; SDL_Surface *surface = NULL; SDL_Surface *rgb = NULL; SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED; // Each script starts scanning at zero; later shots resume past the last one saved. for (x = _global.nextScreenshot; x < SCREENSHOT_MAX; x++) { free(filename); filename = utilCreateString("%ssinge%03d.png", _global.conf->dataDir, x); if (!utilFileExists(filename)) { break; } } if (x >= SCREENSHOT_MAX) { utilDie("Seriously? You have %d screenshots in this folder? Remove some.", SCREENSHOT_MAX); } _global.nextScreenshot = x + 1; // Read the whole window, letterbox included: with a logical size set, a NULL rect would // only cover the scaled viewport, so switch it off around the read. SDL_GetRenderLogicalPresentation(_global.renderer, &logicalW, &logicalH, &mode); SDL_SetRenderLogicalPresentation(_global.renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED); surface = SDL_RenderReadPixels(_global.renderer, NULL); SDL_SetRenderLogicalPresentation(_global.renderer, logicalW, logicalH, mode); if (surface == NULL) { utilDie("%s", SDL_GetError()); } rgb = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGB24); if (rgb == NULL) { utilDie("%s", SDL_GetError()); } if (!IMG_SavePNG(rgb, filename)) { utilDie("%s", SDL_GetError()); } _progTrace("Saved %s", filename); SDL_DestroySurface(rgb); SDL_DestroySurface(surface); free(filename); } // Everything a script loads: fonts, sounds, sprites and videos, whatever it forgot to unload. static void _unloadScriptResources(void) { FontT *font; FontT *fontTemp; SoundT *sound; SoundT *soundTemp; SpriteT *sprite; SpriteT *spriteTemp; VideoT *video; VideoT *videoTemp; HASH_ITER(hh, _global.fontList, font, fontTemp) { _progTrace("Unloading font handle %d", font->id); _fontDestroy(font); } HASH_ITER(hh, _global.soundList, sound, soundTemp) { _progTrace("Unloading sound handle %d", sound->id); _soundDestroy(sound); } HASH_ITER(hh, _global.spriteList, sprite, spriteTemp) { _progTrace("Unloading sprite handle %d", sprite->id); _spriteDestroy(sprite); } HASH_ITER(hh, _global.videoList, video, videoTemp) { _progTrace("Unloading video handle %d", video->id); _videoDestroy(video); } } static void _updatePauseState(void) { if (_global.pauseState) { // Pause laserdisc if ((_global.videoHandle >= 0) && !_global.discStopped && videoIsPlaying(_global.videoHandle)) { _global.wasPlayingBeforePause = true; videoPause(_global.videoHandle); } _pauseAllVideos(true); MIX_PauseTag(videoGetMixer(), EFFECT_TAG); } else { // Resume laserdisc if ((_global.videoHandle >= 0) && !_global.discStopped && _global.wasPlayingBeforePause) { _global.wasPlayingBeforePause = false; videoPlay(_global.videoHandle); } _pauseAllVideos(false); MIX_ResumeTag(videoGetMixer(), EFFECT_TAG); } } // Positioned sounds: every playing channel that sits in the scene is handed to the mixer in the // listener's frame (the camera's view, or a chosen node's) as a unit direction, since the mixer's // own distance curve is flat inside one unit, and its gain is the range curve here. A channel // following a node that has gone falls back to plain playback. static void _updateSounds(void) { Mat4T view = sceneGetView(); Vec3T listener = vec3(0.0f, 0.0f, 0.0f); QuatT inverse = quatIdentity(); Vec3T scale; Vec3T world; Vec3T relative; MIX_Point3D point; EffectT *effect = NULL; float baseGain = _mixerGain(_global.effectsVolume); float distance = 0.0f; float gain = 0.0f; bool useNode = false; int32_t x = 0; if ((_global.listenerNode != LISTENER_CAMERA) && nodeValid(_global.listenerNode) && nodeGetWorldTransform(_global.listenerNode, &listener, &inverse, &scale)) { inverse.x = -inverse.x; inverse.y = -inverse.y; inverse.z = -inverse.z; useNode = true; } for (x = 0; x < EFFECT_TRACKS; x++) { effect = &_effects[x]; if (!effect->positioned) { continue; } if (effect->node != LISTENER_CAMERA) { if (!nodeValid(effect->node)) { effect->positioned = false; MIX_SetTrack3DPosition(_effectTracks[x], NULL); MIX_SetTrackGain(_effectTracks[x], baseGain); continue; } world = nodeGetWorldPosition(effect->node); } else { world = effect->position; } relative = useNode ? quatRotate(inverse, vec3Subtract(world, listener)) : mat4TransformPoint(view, world); distance = vec3Length(relative); if (distance > SOUND_DIRECTION_EPSILON) { relative = vec3Scale(relative, 1.0f / distance); } gain = _effectGain(effect, distance); // The mixer only hears about changes. if ((gain == effect->gain) && (baseGain == effect->baseGain) && (relative.x == effect->relative[0]) && (relative.y == effect->relative[1]) && (relative.z == effect->relative[2])) { continue; } effect->gain = gain; effect->baseGain = baseGain; effect->relative[0] = relative.x; effect->relative[1] = relative.y; effect->relative[2] = relative.z; point.x = relative.x; point.y = relative.y; point.z = relative.z; MIX_SetTrack3DPosition(_effectTracks[x], &point); MIX_SetTrackGain(_effectTracks[x], baseGain * gain); } } static void _videoDestroy(VideoT *video) { HASH_DEL(_global.videoList, video); videoUnload(video->handle); SDL_DestroySurface(video->transformedSurface); free(video); } // --reload keeps the modification time of every loose script file the game loads. static void _watchFile(const char *name) { int64_t size = 0; int64_t modified = 0; int32_t x; if (!vfsIsFilesystem(name) || !vfsStat(name, &size, &modified)) { _progTrace("Not watching %s (packed or unreadable)", name); return; } for (x = 0; x < _global.watchedCount; x++) { if (strcmp(_global.watched[x].name, name) == 0) { _global.watched[x].modified = modified; return; } } _progTrace("Watching %s", name); _global.watched = realloc(_global.watched, sizeof(WatchedFileT) * (size_t)(_global.watchedCount + 1)); if (_global.watched == NULL) { utilDie("Out of memory watching %s", name); } _global.watched[_global.watchedCount].name = strdup(name); _global.watched[_global.watchedCount].modified = modified; _global.watchedCount++; } // Once a second: whether any watched file's modification time moved. static bool _watchedChanged(void) { uint64_t now = SDL_GetTicks(); int32_t x; if (now < _global.watchTick + WATCH_INTERVAL_MS) { return false; } _global.watchTick = now; for (x = 0; x < _global.watchedCount; x++) { int64_t size = 0; int64_t modified = 0; if (vfsStat(_global.watched[x].name, &size, &modified) && (modified != _global.watched[x].modified)) { _progTrace("%s changed", _global.watched[x].name); return true; } } return false; } // ===== Lua API ===== // seconds = animationGetTime(node) static int32_t apiAnimationGetTime(lua_State *L) { int32_t root; _argCheck(L, "animationGetTime", 1, 1); root = _argNode(L, "animationGetTime", 1); if (modelRootOf(root) < 0) { _luaDie(L, "animationGetTime", "Node %d is not a model instance.", root); } lua_pushnumber(L, animationGetTime(root)); return 1; } // playing = animationIsPlaying(node) static int32_t apiAnimationIsPlaying(lua_State *L) { _argCheck(L, "animationIsPlaying", 1, 1); lua_pushboolean(L, animationIsPlaying(_argNode(L, "animationIsPlaying", 1))); return 1; } // animationPause(node) static int32_t apiAnimationPause(lua_State *L) { int32_t root; _argCheck(L, "animationPause", 1, 1); root = _argNode(L, "animationPause", 1); if (!animationPause(root)) { _luaDie(L, "animationPause", "Node %d is not a model instance.", root); } return 0; } // animationPlay(node, nameOrIndex [, loop [, speed [, fade]]]): on a model instance's root node static int32_t apiAnimationPlay(lua_State *L) { int32_t root; int32_t model; int32_t index; bool loop = true; float speed = 1.0f; float fade = 0.0f; _argCheck(L, "animationPlay", 2, 5); root = _argNode(L, "animationPlay", 1); model = modelRootOf(root); if (model < 0) { _luaDie(L, "animationPlay", "Node %d is not a model instance.", root); } index = _argAnimation(L, "animationPlay", model, 2); if (lua_gettop(L) >= 3) { loop = _argBoolean(L, "animationPlay", 3); } if (lua_gettop(L) >= 4) { speed = (float)_argNumber(L, "animationPlay", 4); } if (lua_gettop(L) >= 5) { fade = (float)_argNumber(L, "animationPlay", 5); } if (!animationPlay(root, index, loop, speed, fade)) { _luaDie(L, "animationPlay", "Model %d has no animation %d.", model, index + 1); } _luaTrace(L, "animationPlay", "node %d animation %d%s x%.2f fade %.2f", root, index + 1, loop ? " looping" : "", speed, fade); return 0; } // animationPlayLayer(node, layer, nameOrIndex [, loop [, speed [, fade]]]): a clip blended over the base static int32_t apiAnimationPlayLayer(lua_State *L) { int32_t root; int32_t model; int32_t layer; int32_t index; bool loop = true; float speed = 1.0f; float fade = 0.0f; _argCheck(L, "animationPlayLayer", 3, 6); root = _argNode(L, "animationPlayLayer", 1); model = modelRootOf(root); if (model < 0) { _luaDie(L, "animationPlayLayer", "Node %d is not a model instance.", root); } layer = _argAnimationLayer(L, "animationPlayLayer", 2); index = _argAnimation(L, "animationPlayLayer", model, 3); if (lua_gettop(L) >= 4) { loop = _argBoolean(L, "animationPlayLayer", 4); } if (lua_gettop(L) >= 5) { speed = (float)_argNumber(L, "animationPlayLayer", 5); } if (lua_gettop(L) >= 6) { fade = (float)_argNumber(L, "animationPlayLayer", 6); } if (!animationPlayLayer(root, layer, index, loop, speed, fade)) { _luaDie(L, "animationPlayLayer", "Model %d has no animation %d.", model, index + 1); } return 0; } // animationResume(node) static int32_t apiAnimationResume(lua_State *L) { int32_t root; _argCheck(L, "animationResume", 1, 1); root = _argNode(L, "animationResume", 1); if (!animationResume(root)) { _luaDie(L, "animationResume", "Node %d has no animation to resume.", root); } return 0; } // animationSetLayerMask(node, layer [, maskNode]): limits the layer to the subtree under maskNode; none lifts it static int32_t apiAnimationSetLayerMask(lua_State *L) { int32_t root; int32_t layer; int32_t mask = ANIMATION_NO_MASK; _argCheck(L, "animationSetLayerMask", 2, 3); root = _argNode(L, "animationSetLayerMask", 1); layer = _argAnimationLayer(L, "animationSetLayerMask", 2); if ((lua_gettop(L) >= 3) && !lua_isnil(L, 3)) { mask = _argNode(L, "animationSetLayerMask", 3); } if (!animationSetLayerMask(root, layer, mask)) { _luaDie(L, "animationSetLayerMask", "Node %d is not a model instance, or node %d is not part of it.", root, mask); } return 0; } // animationSetLayerWeight(node, layer, weight): 0 to 1 static int32_t apiAnimationSetLayerWeight(lua_State *L) { int32_t root; int32_t layer; _argCheck(L, "animationSetLayerWeight", 3, 3); root = _argNode(L, "animationSetLayerWeight", 1); layer = _argAnimationLayer(L, "animationSetLayerWeight", 2); if (!animationSetLayerWeight(root, layer, (float)_argNumber(L, "animationSetLayerWeight", 3))) { _luaDie(L, "animationSetLayerWeight", "Node %d is not a model instance.", root); } return 0; } // animationSetTime(node, seconds) static int32_t apiAnimationSetTime(lua_State *L) { int32_t root; _argCheck(L, "animationSetTime", 2, 2); root = _argNode(L, "animationSetTime", 1); if (!animationSetTime(root, _argNumber(L, "animationSetTime", 2))) { _luaDie(L, "animationSetTime", "Node %d has no animation.", root); } return 0; } // animationStop(node [, layer]): every layer, or one of the layers above the base static int32_t apiAnimationStop(lua_State *L) { int32_t root; int32_t layer = ANIMATION_ALL_LAYERS; _argCheck(L, "animationStop", 1, 2); root = _argNode(L, "animationStop", 1); if (lua_gettop(L) >= 2) { layer = _argAnimationLayer(L, "animationStop", 2); } if (!animationStop(root, layer)) { _luaDie(L, "animationStop", "Node %d is not a model instance.", root); } return 0; } // bodyApplyForce(node, fx, fy, fz [, px, py, pz]): this step, at the centre or a world point static int32_t apiBodyApplyForce(lua_State *L) { int32_t node; Vec3T force; Vec3T at; _argCheck(L, "bodyApplyForce", 4, 7); node = _argBody(L, "bodyApplyForce", 1); force = _argVec3(L, "bodyApplyForce", 2); if (lua_gettop(L) >= 7) { at = _argVec3(L, "bodyApplyForce", 5); bodyApplyForce(node, force, &at); } else { bodyApplyForce(node, force, NULL); } return 0; } // bodyApplyImpulse(node, ix, iy, iz [, px, py, pz]): an instant change of momentum static int32_t apiBodyApplyImpulse(lua_State *L) { int32_t node; Vec3T impulse; Vec3T at; _argCheck(L, "bodyApplyImpulse", 4, 7); node = _argBody(L, "bodyApplyImpulse", 1); impulse = _argVec3(L, "bodyApplyImpulse", 2); if (lua_gettop(L) >= 7) { at = _argVec3(L, "bodyApplyImpulse", 5); bodyApplyImpulse(node, impulse, &at); } else { bodyApplyImpulse(node, impulse, NULL); } return 0; } // bodyDelete(node) static int32_t apiBodyDelete(lua_State *L) { _argCheck(L, "bodyDelete", 1, 1); bodyDelete(_argBody(L, "bodyDelete", 1)); return 0; } // x, y, z = bodyGetAngularVelocity(node): radians per second about each axis static int32_t apiBodyGetAngularVelocity(lua_State *L) { _argCheck(L, "bodyGetAngularVelocity", 1, 1); return _pushVec3(L, bodyGetAngularVelocity(_argBody(L, "bodyGetAngularVelocity", 1))); } // x, y, z = bodyGetVelocity(node): units per second static int32_t apiBodyGetVelocity(lua_State *L) { _argCheck(L, "bodyGetVelocity", 1, 1); return _pushVec3(L, bodyGetVelocity(_argBody(L, "bodyGetVelocity", 1))); } // resting = bodyIsResting(node): asleep static int32_t apiBodyIsResting(lua_State *L) { _argCheck(L, "bodyIsResting", 1, 1); lua_pushboolean(L, bodyIsResting(_argBody(L, "bodyIsResting", 1))); return 1; } // bodyNew(node, type, shape [, a [, b [, c]]]): BODY_* and SHAPE_*; a, b, c size the primitive shapes static int32_t apiBodyNew(lua_State *L) { int32_t node = 0; int32_t type = 0; int32_t shape = 0; float dims[SHAPE_SIZES] = { 0.0f, 0.0f, 0.0f }; int32_t x = 0; _argCheck(L, "bodyNew", 3, 6); node = _argNode(L, "bodyNew", 1); type = _argInteger(L, "bodyNew", 2); shape = _argInteger(L, "bodyNew", 3); if ((type < BODY_STATIC) || (type > BODY_KINEMATIC)) { _luaDie(L, "bodyNew", "Unknown body type %d.", type); } if ((shape < SHAPE_BOX) || (shape > SHAPE_MESH)) { _luaDie(L, "bodyNew", "Unknown shape %d.", shape); } for (x = 0; x < SHAPE_SIZES; x++) { if (lua_gettop(L) >= 4 + x) { dims[x] = (float)_argNumber(L, "bodyNew", 4 + x); } } if (!physicsAvailable()) { _luaDie(L, "bodyNew", "Physics is not available on this machine."); } if (!bodyNew(node, (BodyTypeE)type, (ShapeTypeE)shape, dims[0], dims[1], dims[2])) { _luaDie(L, "bodyNew", "Unable to create the body."); } _luaTrace(L, "bodyNew", "node %d type %d shape %d", node, type, shape); return 0; } // bodySetAngularVelocity(node, x, y, z) static int32_t apiBodySetAngularVelocity(lua_State *L) { _argCheck(L, "bodySetAngularVelocity", 4, 4); bodySetAngularVelocity(_argBody(L, "bodySetAngularVelocity", 1), _argVec3(L, "bodySetAngularVelocity", 2)); return 0; } // bodySetBounce(node, 0..1) static int32_t apiBodySetBounce(lua_State *L) { _argCheck(L, "bodySetBounce", 2, 2); bodySetBounce(_argBody(L, "bodySetBounce", 1), (float)_argNumber(L, "bodySetBounce", 2)); return 0; } // bodySetBuoyancy(node, factor): 1 floats neutrally in water, more floats, less sinks static int32_t apiBodySetBuoyancy(lua_State *L) { _argCheck(L, "bodySetBuoyancy", 2, 2); bodySetBuoyancy(_argBody(L, "bodySetBuoyancy", 1), (float)_argNumber(L, "bodySetBuoyancy", 2)); return 0; } // bodySetCurrent(node, x, y, z): the flow inside a water volume static int32_t apiBodySetCurrent(lua_State *L) { _argCheck(L, "bodySetCurrent", 4, 4); bodySetCurrent(_argBody(L, "bodySetCurrent", 1), _argVec3(L, "bodySetCurrent", 2)); return 0; } // bodySetEnabled(node, bool): out of the world and back static int32_t apiBodySetEnabled(lua_State *L) { _argCheck(L, "bodySetEnabled", 2, 2); bodySetEnabled(_argBody(L, "bodySetEnabled", 1), _argBoolean(L, "bodySetEnabled", 2)); return 0; } // bodySetFriction(node, friction) static int32_t apiBodySetFriction(lua_State *L) { _argCheck(L, "bodySetFriction", 2, 2); bodySetFriction(_argBody(L, "bodySetFriction", 1), (float)_argNumber(L, "bodySetFriction", 2)); return 0; } // bodySetMass(node, kilograms): dynamic bodies static int32_t apiBodySetMass(lua_State *L) { int32_t node; _argCheck(L, "bodySetMass", 2, 2); node = _argBody(L, "bodySetMass", 1); if (!bodySetMass(node, (float)_argNumber(L, "bodySetMass", 2))) { _luaDie(L, "bodySetMass", "Node %d is not a dynamic body, or the mass is not positive.", node); } return 0; } // bodySetTrigger(node, bool): a trigger reports what enters and leaves it (onTrigger) and pushes nothing static int32_t apiBodySetTrigger(lua_State *L) { _argCheck(L, "bodySetTrigger", 2, 2); bodySetTrigger(_argBody(L, "bodySetTrigger", 1), _argBoolean(L, "bodySetTrigger", 2)); return 0; } // bodySetVelocity(node, x, y, z) static int32_t apiBodySetVelocity(lua_State *L) { _argCheck(L, "bodySetVelocity", 4, 4); bodySetVelocity(_argBody(L, "bodySetVelocity", 1), _argVec3(L, "bodySetVelocity", 2)); return 0; } // bodySetWater(node, density, linearDrag, angularDrag): a static body becomes a water volume static int32_t apiBodySetWater(lua_State *L) { _argCheck(L, "bodySetWater", 2, 4); if (!bodySetWater(_argBody(L, "bodySetWater", 1), (float)_argNumber(L, "bodySetWater", 2), (lua_gettop(L) >= 3) ? (float)_argNumber(L, "bodySetWater", 3) : WATER_DEFAULT_LINEAR_DRAG, (lua_gettop(L) >= 4) ? (float)_argNumber(L, "bodySetWater", 4) : WATER_DEFAULT_ANGULAR_DRAG)) { _luaDie(L, "bodySetWater", "Water needs a static body."); } return 0; } // cameraSet(node): any node can be the camera (it looks down its own -Z); -1 restores the default view. static int32_t apiCameraSet(lua_State *L) { int32_t node; _argCheck(L, "cameraSet", 1, 1); node = _argInteger(L, "cameraSet", 1); if (!cameraSet(node)) { _luaDie(L, "cameraSet", "No node %d.", node); } _luaTrace(L, "cameraSet", "%d", node); return 0; } // cameraSetOrthographic(height, near, far) static int32_t apiCameraSetOrthographic(lua_State *L) { _argCheck(L, "cameraSetOrthographic", 3, 3); cameraSetOrthographic((float)_argNumber(L, "cameraSetOrthographic", 1), (float)_argNumber(L, "cameraSetOrthographic", 2), (float)_argNumber(L, "cameraSetOrthographic", 3)); return 0; } // cameraSetPerspective(fovDegrees, near, far) static int32_t apiCameraSetPerspective(lua_State *L) { _argCheck(L, "cameraSetPerspective", 3, 3); cameraSetPerspective((float)_argNumber(L, "cameraSetPerspective", 1), (float)_argNumber(L, "cameraSetPerspective", 2), (float)_argNumber(L, "cameraSetPerspective", 3)); return 0; } // colorBackground(r, g, b[, a]) Default alpha is transparent so overlayPrint shows the video through. static int32_t apiColorBackground(lua_State *L) { _readColor(L, "colorBackground", &_global.colorBackground, SDL_ALPHA_TRANSPARENT); return 0; } // colorForeground(r, g, b[, a]) static int32_t apiColorForeground(lua_State *L) { _readColor(L, "colorForeground", &_global.colorForeground, SDL_ALPHA_OPAQUE); return 0; } // value = controllerGetAxis(controller, axis) static int32_t apiControllerGetAxis(lua_State *L) { int32_t c = 0; int32_t a = 0; int32_t v = 0; _argCheck(L, "controllerGetAxis", 2, 2); c = _argInteger(L, "controllerGetAxis", 1); a = _argInteger(L, "controllerGetAxis", 2); if ((c < 0) || (c >= MAX_CONTROLLERS)) { _luaDie(L, "controllerGetAxis", "Invalid controller index: %d", c); } if ((a < 0) || (a >= CONTROLLER_AXIS_COUNT)) { _luaDie(L, "controllerGetAxis", "Invalid controller axis: %d", a); } v = _global.axisCache[AXIS_INDEX_CONTROLLER(c, a)]; _luaTrace(L, "controllerGetAxis", "%d %d %d", c, a, v); lua_pushinteger(L, v); return 1; } // pressed = controllerGetButton(controller, GAMEPAD_N.BUTTON_X.value) static int32_t apiControllerGetButton(lua_State *L) { int32_t c = 0; int32_t code = 0; int32_t button = 0; bool v = false; _argCheck(L, "controllerGetButton", 2, 2); c = _argInteger(L, "controllerGetButton", 1); code = _argInteger(L, "controllerGetButton", 2); if ((c < 0) || (c >= MAX_CONTROLLERS)) { _luaDie(L, "controllerGetButton", "Invalid controller index: %d", c); } // Convert the framework code back to SDL's button enumeration. button = code - CODE_GAMEPAD_BASE - (c * CODE_GAMEPAD_STRIDE) - CODE_GAMEPAD_BUTTON_OFFSET; if ((button < 0) || (button >= CONTROLLER_BUTTON_COUNT)) { _luaDie(L, "controllerGetButton", "Invalid controller button: %d", code); } if (_global.controllers[c] != NULL) { v = SDL_GetGamepadButton(_global.controllers[c], (SDL_GamepadButton)button); } _luaTrace(L, "controllerGetButton", "%d %d %d", c, code, v); lua_pushboolean(L, v); return 1; } // debugPrint(message) static int32_t apiDebugPrint(lua_State *L) { const char *message = NULL; _argCheck(L, "debugPrint", 1, 1); message = _argString(L, "debugPrint", 1); _luaTrace(L, "debugPrint", "%s", message); utilSay("%s", message); return 0; } // discAudio(channel, enabled) Channel 1 is left, 2 is right. static int32_t apiDiscAudio(lua_State *L) { int32_t channel = 0; int32_t left = 0; int32_t right = 0; bool onOff = false; _argCheck(L, "discAudio", 2, 2); channel = _argInteger(L, "discAudio", 1); onOff = _argBoolean(L, "discAudio", 2); if ((channel < 1) || (channel > 2)) { _luaDie(L, "discAudio", "Invalid audio channel: %d", channel); } if (_global.videoHandle >= 0) { videoGetVolume(_global.videoHandle, &left, &right); if (channel == 1) { left = onOff ? _global.conf->volumeVldp : 0; } else { right = onOff ? _global.conf->volumeVldp : 0; } videoSetVolume(_global.videoHandle, left, right); } _luaTrace(L, "discAudio", "%d %d", left, right); return 0; } static int32_t apiDiscChangeSpeed(lua_State *L) { return _apiUnimplemented(L, "discChangeSpeed"); } // track = discGetAudioTrack() static int32_t apiDiscGetAudioTrack(lua_State *L) { int32_t track = 0; if (_global.videoHandle >= 0) { track = videoGetAudioTrack(_global.videoHandle); } _luaTrace(L, "discGetAudioTrack", "%d", track); lua_pushinteger(L, track); return 1; } // count = discGetAudioTracks() static int32_t apiDiscGetAudioTracks(lua_State *L) { int32_t count = 0; if (_global.videoHandle >= 0) { count = videoGetAudioTracks(_global.videoHandle); } _luaTrace(L, "discGetAudioTracks", "%d", count); lua_pushinteger(L, count); return 1; } // frame = discGetFrame() static int32_t apiDiscGetFrame(lua_State *L) { int64_t frame = 0; if (!_global.discStopped && (_global.videoHandle >= 0)) { frame = _discGetFrame(); } _luaTrace(L, "discGetFrame", "%" PRId64, frame); lua_pushinteger(L, frame); return 1; } // height = discGetHeight() Also registered as vldpGetHeight. static int32_t apiDiscGetHeight(lua_State *L) { int32_t height = _global.canvasHeight; _luaTrace(L, "discGetHeight", "%d", height); lua_pushinteger(L, height); return 1; } // code = discGetLanguage(track) static int32_t apiDiscGetLanguage(lua_State *L) { int32_t track = 0; const char *language = "unk"; _argCheck(L, "discGetLanguage", 1, 1); track = _argInteger(L, "discGetLanguage", 1); if (_global.videoHandle >= 0) { if ((track < 0) || (track >= videoGetAudioTracks(_global.videoHandle))) { _luaDie(L, "discGetLanguage", "Invalid audio track: %d", track); } language = videoGetLanguage(_global.videoHandle, track); } _luaTrace(L, "discGetLanguage", "%d %s", track, language); lua_pushstring(L, language); return 1; } // state = discGetState() One of DISC_STOPPED, DISC_PLAYING, DISC_PAUSED, or DISC_EJECTED (no disc). static int32_t apiDiscGetState(lua_State *L) { DiscStateE state = DISC_PAUSED; if (_global.videoHandle < 0) { state = DISC_EJECTED; } else if (_global.discStopped) { state = DISC_STOPPED; } else if (videoIsPlaying(_global.videoHandle)) { state = DISC_PLAYING; } _luaTrace(L, "discGetState", "%d", state); lua_pushinteger(L, state); return 1; } // width = discGetWidth() Also registered as vldpGetWidth. static int32_t apiDiscGetWidth(lua_State *L) { int32_t width = _global.canvasWidth; _luaTrace(L, "discGetWidth", "%d", width); lua_pushinteger(L, width); return 1; } // discPause() static int32_t apiDiscPause(lua_State *L) { if (_global.discStopped) { _luaTrace(L, "discPause", "Ignored. Disc is stopped."); return 0; } if (_global.videoHandle >= 0) { videoPause(_global.videoHandle); } _luaTrace(L, "discPause", "Paused."); return 0; } // discPlay() static int32_t apiDiscPlay(lua_State *L) { if (_global.videoHandle >= 0) { videoPlay(_global.videoHandle); } _global.discStopped = false; _luaTrace(L, "discPlay", "Playing."); return 0; } // discSearch(frame) Seeks, shows the frame, and pauses. Also registered as discPauseAtFrame. static int32_t apiDiscSearch(lua_State *L) { int64_t frame = 0; _argCheck(L, "discSearch", 1, 1); frame = _argInteger64(L, "discSearch", 1); _discSeek(frame); if (_global.videoHandle >= 0) { videoPause(_global.videoHandle); } _global.discStopped = false; _luaTrace(L, "discSearch", "%" PRId64, frame); return 0; } static int32_t apiDiscSearchBlanking(lua_State *L) { return _apiUnimplemented(L, "discSearchBlanking"); } // discSetAudioTrack(track) static int32_t apiDiscSetAudioTrack(lua_State *L) { int32_t track = 0; _argCheck(L, "discSetAudioTrack", 1, 1); track = _argInteger(L, "discSetAudioTrack", 1); if (_global.videoHandle >= 0) { if ((track < 0) || (track >= videoGetAudioTracks(_global.videoHandle))) { _luaDie(L, "discSetAudioTrack", "Invalid audio track: %d", track); } videoSetAudioTrack(_global.videoHandle, track); } _luaTrace(L, "discSetAudioTrack", "%d", track); return 0; } static int32_t apiDiscSetFPS(lua_State *L) { return _apiUnimplemented(L, "discSetFPS"); } // discSkipBackward(frames) Play/pause state is unchanged. static int32_t apiDiscSkipBackward(lua_State *L) { int64_t frame = 0; _argCheck(L, "discSkipBackward", 1, 1); if (_global.discStopped || (_global.videoHandle < 0)) { _luaTrace(L, "discSkipBackward", "Ignored. Disc is stopped."); return 0; } frame = _discGetFrame() - _argInteger64(L, "discSkipBackward", 1); _discSeek(frame); _luaTrace(L, "discSkipBackward", "%" PRId64, frame); return 0; } static int32_t apiDiscSkipBlanking(lua_State *L) { return _apiUnimplemented(L, "discSkipBlanking"); } // discSkipForward(frames) Play/pause state is unchanged. static int32_t apiDiscSkipForward(lua_State *L) { int64_t frame = 0; _argCheck(L, "discSkipForward", 1, 1); if (_global.discStopped || (_global.videoHandle < 0)) { _luaTrace(L, "discSkipForward", "Ignored. Disc is stopped."); return 0; } frame = _discGetFrame() + _argInteger64(L, "discSkipForward", 1); _discSeek(frame); _luaTrace(L, "discSkipForward", "%" PRId64, frame); return 0; } // discSkipToFrame(frame) Seeks and plays no matter the disc state. static int32_t apiDiscSkipToFrame(lua_State *L) { int64_t frame = 0; _argCheck(L, "discSkipToFrame", 1, 1); frame = _argInteger64(L, "discSkipToFrame", 1); _discSeek(frame); if (_global.videoHandle >= 0) { videoPlay(_global.videoHandle); } _global.discStopped = false; _luaTrace(L, "discSkipToFrame", "%" PRId64, frame); return 0; } // discStepBackward() Go back a frame and pause. Ignored while stopped, like the skips. static int32_t apiDiscStepBackward(lua_State *L) { int64_t frame = 0; if (_global.discStopped || (_global.videoHandle < 0)) { _luaTrace(L, "discStepBackward", "Ignored. Disc is stopped."); return 0; } frame = _discGetFrame() - 1; _discSeek(frame); videoPause(_global.videoHandle); _luaTrace(L, "discStepBackward", "%" PRId64, frame); return 0; } // discStepForward() Go forward a frame and pause. Ignored while stopped, like the skips. static int32_t apiDiscStepForward(lua_State *L) { int64_t frame = 0; if (_global.discStopped || (_global.videoHandle < 0)) { _luaTrace(L, "discStepForward", "Ignored. Disc is stopped."); return 0; } frame = _discGetFrame() + 1; _discSeek(frame); videoPause(_global.videoHandle); _luaTrace(L, "discStepForward", "%" PRId64, frame); return 0; } // discStop() Pauses and shows the classic blue screen until the next play or search. static int32_t apiDiscStop(lua_State *L) { if (_global.discStopped) { _luaTrace(L, "discStop", "Ignored. Disc is stopped."); return 0; } if (_global.videoHandle >= 0) { videoPause(_global.videoHandle); } _global.discStopped = true; _global.refreshDisplay = true; _luaTrace(L, "discStop", "Stopped."); return 0; } static int32_t apiEmitterBurst(lua_State *L) { _argCheck(L, "emitterBurst", 2, 2); emitterBurst(_argEmitter(L, "emitterBurst", 1), _argInteger(L, "emitterBurst", 2)); return 0; } static int32_t apiEmitterClear(lua_State *L) { _argCheck(L, "emitterClear", 1, 1); emitterClear(_argEmitter(L, "emitterClear", 1)); return 0; } static int32_t apiEmitterDelete(lua_State *L) { int32_t emitter = 0; _argCheck(L, "emitterDelete", 1, 1); emitter = _argEmitter(L, "emitterDelete", 1); _particleTexturesFree(emitter); emitterDelete(emitter); return 0; } static int32_t apiEmitterDraw(lua_State *L) { int32_t emitter = 0; _argCheck(L, "emitterDraw", 1, 1); emitter = _argEmitter(L, "emitterDraw", 1); if (emitterIs3D(emitter)) { _luaDie(L, "emitterDraw", "Emitter %d is a 3D emitter; it draws itself in the scene.", emitter); } particlesQueue2D(emitter); return 0; } static int32_t apiEmitterGetCount(lua_State *L) { _argCheck(L, "emitterGetCount", 1, 1); lua_pushinteger(L, emitterGetCount(_argEmitter(L, "emitterGetCount", 1))); return 1; } static int32_t apiEmitterIsActive(lua_State *L) { _argCheck(L, "emitterIsActive", 1, 1); lua_pushboolean(L, emitterIsActive(_argEmitter(L, "emitterIsActive", 1))); return 1; } static int32_t apiEmitterNew(lua_State *L) { int32_t node = -1; _argCheck(L, "emitterNew", 0, 1); if (lua_gettop(L) == 1) { node = _argNode(L, "emitterNew", 1); if (!sceneAvailable()) { _luaDie(L, "emitterNew", "3D particles need the 3D scene, which this machine cannot provide."); } } lua_pushinteger(L, emitterNew(node)); return 1; } static int32_t apiEmitterSetBlend(lua_State *L) { int32_t emitter = 0; int32_t blend = 0; _argCheck(L, "emitterSetBlend", 2, 2); emitter = _argEmitter(L, "emitterSetBlend", 1); blend = _argInteger(L, "emitterSetBlend", 2); if ((blend != PARTICLE_ALPHA) && (blend != PARTICLE_ADD)) { _luaDie(L, "emitterSetBlend", "Blend must be PARTICLE_ALPHA or PARTICLE_ADD."); } emitterSetBlend(emitter, (ParticleBlendE)blend); return 0; } // emitterSetCollide(emitter, COLLIDE_* [, bounce [, friction [, floor]]]) static int32_t apiEmitterSetCollide(lua_State *L) { int32_t emitter = 0; int32_t mode = 0; float bounce = EMITTER_DEFAULT_BOUNCE; float friction = EMITTER_DEFAULT_FRICTION; float floor = EMITTER_DEFAULT_FLOOR; _argCheck(L, "emitterSetCollide", 2, 5); emitter = _argEmitter(L, "emitterSetCollide", 1); mode = _argInteger(L, "emitterSetCollide", 2); if ((mode != COLLIDE_NONE) && (mode != COLLIDE_FLOOR) && (mode != COLLIDE_SCENE)) { _luaDie(L, "emitterSetCollide", "Mode must be COLLIDE_NONE, COLLIDE_FLOOR or COLLIDE_SCENE."); } if (lua_gettop(L) >= 3) { bounce = (float)_argNumber(L, "emitterSetCollide", 3); } if (lua_gettop(L) >= 4) { friction = (float)_argNumber(L, "emitterSetCollide", 4); } if (lua_gettop(L) >= 5) { floor = (float)_argNumber(L, "emitterSetCollide", 5); } emitterSetCollide(emitter, (ParticleCollideE)mode, bounce, friction, floor); return 0; } static int32_t apiEmitterSetColor(lua_State *L) { float start[COLOR_COMPONENTS]; float finish[COLOR_COMPONENTS]; int32_t c = 0; _argCheck(L, "emitterSetColor", 9, 9); for (c = 0; c < COLOR_COMPONENTS; c++) { start[c] = SDL_clamp((float)_argNumber(L, "emitterSetColor", 2 + c) / (float)COLOR_BYTE_MAX, 0.0f, 1.0f); finish[c] = SDL_clamp((float)_argNumber(L, "emitterSetColor", 6 + c) / (float)COLOR_BYTE_MAX, 0.0f, 1.0f); } emitterSetColor(_argEmitter(L, "emitterSetColor", 1), start, finish); return 0; } static int32_t apiEmitterSetDirection(lua_State *L) { int32_t emitter = 0; Vec3T direction; _argCheck(L, "emitterSetDirection", 3, 4); emitter = _argEmitter(L, "emitterSetDirection", 1); direction = vec3((float)_argNumber(L, "emitterSetDirection", 2), (float)_argNumber(L, "emitterSetDirection", 3), (lua_gettop(L) == 4) ? (float)_argNumber(L, "emitterSetDirection", 4) : 0.0f); emitterSetDirection(emitter, direction); return 0; } static int32_t apiEmitterSetDrag(lua_State *L) { _argCheck(L, "emitterSetDrag", 2, 2); emitterSetDrag(_argEmitter(L, "emitterSetDrag", 1), (float)_argNumber(L, "emitterSetDrag", 2)); return 0; } static int32_t apiEmitterSetFrames(lua_State *L) { _argCheck(L, "emitterSetFrames", 3, 3); emitterSetFrames(_argEmitter(L, "emitterSetFrames", 1), _argInteger(L, "emitterSetFrames", 2), _argInteger(L, "emitterSetFrames", 3)); return 0; } static int32_t apiEmitterSetGravity(lua_State *L) { int32_t emitter = 0; Vec3T gravity; _argCheck(L, "emitterSetGravity", 3, 4); emitter = _argEmitter(L, "emitterSetGravity", 1); gravity = vec3((float)_argNumber(L, "emitterSetGravity", 2), (float)_argNumber(L, "emitterSetGravity", 3), (lua_gettop(L) == 4) ? (float)_argNumber(L, "emitterSetGravity", 4) : 0.0f); emitterSetGravity(emitter, gravity); return 0; } static int32_t apiEmitterSetLayer(lua_State *L) { int32_t emitter = 0; int32_t layer = 0; _argCheck(L, "emitterSetLayer", 2, 2); emitter = _argEmitter(L, "emitterSetLayer", 1); layer = _argInteger(L, "emitterSetLayer", 2); if ((layer != PARTICLE_OVER) && (layer != PARTICLE_UNDER)) { _luaDie(L, "emitterSetLayer", "Layer must be PARTICLE_OVER or PARTICLE_UNDER."); } emitterSetLayer(emitter, (ParticleLayerE)layer); return 0; } static int32_t apiEmitterSetLife(lua_State *L) { _argCheck(L, "emitterSetLife", 3, 3); emitterSetLife(_argEmitter(L, "emitterSetLife", 1), (float)_argNumber(L, "emitterSetLife", 2), (float)_argNumber(L, "emitterSetLife", 3)); return 0; } // emitterSetLit(emitter, lit): 3D particles shaded by the scene's lights static int32_t apiEmitterSetLit(lua_State *L) { _argCheck(L, "emitterSetLit", 2, 2); emitterSetLit(_argEmitter(L, "emitterSetLit", 1), _argBoolean(L, "emitterSetLit", 2)); return 0; } static int32_t apiEmitterSetLocal(lua_State *L) { _argCheck(L, "emitterSetLocal", 2, 2); emitterSetLocal(_argEmitter(L, "emitterSetLocal", 1), _argBoolean(L, "emitterSetLocal", 2)); return 0; } static int32_t apiEmitterSetMax(lua_State *L) { int32_t emitter = 0; int32_t count = 0; _argCheck(L, "emitterSetMax", 2, 2); emitter = _argEmitter(L, "emitterSetMax", 1); count = _argInteger(L, "emitterSetMax", 2); if (count < 1) { _luaDie(L, "emitterSetMax", "An emitter needs room for at least one particle."); } emitterSetMax(emitter, count); return 0; } static int32_t apiEmitterSetPosition(lua_State *L) { int32_t emitter = 0; _argCheck(L, "emitterSetPosition", 3, 3); emitter = _argEmitter(L, "emitterSetPosition", 1); if (emitterIs3D(emitter)) { _luaDie(L, "emitterSetPosition", "Emitter %d follows its node; move the node instead.", emitter); } emitterSetPosition(emitter, vec3((float)_argNumber(L, "emitterSetPosition", 2), (float)_argNumber(L, "emitterSetPosition", 3), 0.0f)); return 0; } static int32_t apiEmitterSetRadius(lua_State *L) { _argCheck(L, "emitterSetRadius", 2, 2); emitterSetRadius(_argEmitter(L, "emitterSetRadius", 1), (float)_argNumber(L, "emitterSetRadius", 2)); return 0; } static int32_t apiEmitterSetRate(lua_State *L) { _argCheck(L, "emitterSetRate", 2, 2); emitterSetRate(_argEmitter(L, "emitterSetRate", 1), (float)_argNumber(L, "emitterSetRate", 2)); return 0; } static int32_t apiEmitterSetSize(lua_State *L) { _argCheck(L, "emitterSetSize", 3, 4); emitterSetSize(_argEmitter(L, "emitterSetSize", 1), (float)_argNumber(L, "emitterSetSize", 2), (float)_argNumber(L, "emitterSetSize", 3), (lua_gettop(L) == 4) ? (float)_argNumber(L, "emitterSetSize", 4) : 0.0f); return 0; } // emitterSetSoftness(emitter, distance): 3D particles fade over this distance where they meet geometry static int32_t apiEmitterSetSoftness(lua_State *L) { _argCheck(L, "emitterSetSoftness", 2, 2); emitterSetSoftness(_argEmitter(L, "emitterSetSoftness", 1), (float)_argNumber(L, "emitterSetSoftness", 2)); return 0; } static int32_t apiEmitterSetSpeed(lua_State *L) { _argCheck(L, "emitterSetSpeed", 3, 3); emitterSetSpeed(_argEmitter(L, "emitterSetSpeed", 1), (float)_argNumber(L, "emitterSetSpeed", 2), (float)_argNumber(L, "emitterSetSpeed", 3)); return 0; } static int32_t apiEmitterSetSpin(lua_State *L) { _argCheck(L, "emitterSetSpin", 3, 3); emitterSetSpin(_argEmitter(L, "emitterSetSpin", 1), (float)_argNumber(L, "emitterSetSpin", 2), (float)_argNumber(L, "emitterSetSpin", 3)); return 0; } static int32_t apiEmitterSetSpread(lua_State *L) { _argCheck(L, "emitterSetSpread", 2, 2); emitterSetSpread(_argEmitter(L, "emitterSetSpread", 1), (float)_argNumber(L, "emitterSetSpread", 2)); return 0; } static int32_t apiEmitterSetTexture(lua_State *L) { int32_t emitter = 0; SpriteT *sprite = NULL; _argCheck(L, "emitterSetTexture", 1, 2); emitter = _argEmitter(L, "emitterSetTexture", 1); if ((lua_gettop(L) == 2) && !lua_isnil(L, 2)) { sprite = _argSprite(L, "emitterSetTexture", 2); if (sprite->animation != NULL) { emitterSetTexture(emitter, sprite->animation->frames, sprite->animation->count); } else { emitterSetTexture(emitter, &sprite->originalSurface, 1); } } else { emitterSetTexture(emitter, NULL, 0); } return 0; } // emitterSetTrail(emitter, length, width): a ribbon of the last length positions behind each particle static int32_t apiEmitterSetTrail(lua_State *L) { _argCheck(L, "emitterSetTrail", 3, 3); emitterSetTrail(_argEmitter(L, "emitterSetTrail", 1), _argInteger(L, "emitterSetTrail", 2), (float)_argNumber(L, "emitterSetTrail", 3)); return 0; } static int32_t apiEmitterStart(lua_State *L) { _argCheck(L, "emitterStart", 1, 1); emitterStart(_argEmitter(L, "emitterStart", 1)); return 0; } static int32_t apiEmitterStop(lua_State *L) { _argCheck(L, "emitterStop", 1, 1); emitterStop(_argEmitter(L, "emitterStop", 1)); return 0; } // id = fontLoad(filename, points) The new font becomes current. static int32_t apiFontLoad(lua_State *L) { const char *name = NULL; int32_t points = 0; FontT *font = NULL; SDL_IOStream *io = NULL; _argCheck(L, "fontLoad", 2, 2); name = _argString(L, "fontLoad", 1); points = _argInteger(L, "fontLoad", 2); io = vfsOpenIO(name); if (io == NULL) { _luaDie(L, "fontLoad", "Unable to open %s", name); } font = (FontT *)calloc(1, sizeof(FontT)); if (!font) { _luaDie(L, "fontLoad", "Unable to allocate new font."); } font->font = TTF_OpenFontIO(io, true, (float)points); if (!font->font) { _luaDie(L, "fontLoad", "%s", SDL_GetError()); } font->id = _global.nextFontId++; _global.fontCurrent = font; HASH_ADD_INT(_global.fontList, id, font); _luaTrace(L, "fontLoad", "%s %d", name, font->id); lua_pushinteger(L, font->id); return 1; } // fontPrint(x, y, text) Uses the current font. static int32_t apiFontPrint(lua_State *L) { const char *message = NULL; SDL_Surface *text = NULL; SDL_Rect dest; _argCheck(L, "fontPrint", 3, 3); dest.x = _argInteger(L, "fontPrint", 1); dest.y = _argInteger(L, "fontPrint", 2); message = _argString(L, "fontPrint", 3); text = _renderText(L, "fontPrint", message); dest.w = text->w; dest.h = text->h; SDL_BlitSurface(text, NULL, _global.overlay, &dest); SDL_DestroySurface(text); _overlayTouched(); _luaTrace(L, "fontPrint", "%s", message); return 0; } // fontQuality(FONT_QUALITY_SOLID | FONT_QUALITY_SHADED | FONT_QUALITY_BLENDED) static int32_t apiFontQuality(lua_State *L) { int32_t quality = 0; _argCheck(L, "fontQuality", 1, 1); quality = _argInteger(L, "fontQuality", 1); if ((quality < FONT_QUALITY_SOLID) || (quality > FONT_QUALITY_BLENDED)) { _luaDie(L, "fontQuality", "Unknown font quality: %d", quality); } _global.fontQuality = (FontQualityE)quality; _luaTrace(L, "fontQuality", "%d", _global.fontQuality); return 0; } // fontSelect(id) static int32_t apiFontSelect(lua_State *L) { _argCheck(L, "fontSelect", 1, 1); _global.fontCurrent = _argFont(L, "fontSelect", 1); _luaTrace(L, "fontSelect", "%d", _global.fontCurrent->id); return 0; } // id = fontToSprite(text) Renders text with the current font into a new sprite. static int32_t apiFontToSprite(lua_State *L) { const char *message = NULL; SpriteT *sprite = NULL; _argCheck(L, "fontToSprite", 1, 1); message = _argString(L, "fontToSprite", 1); sprite = (SpriteT *)calloc(1, sizeof(SpriteT)); if (!sprite) { _luaDie(L, "fontToSprite", "Unable to allocate new text sprite."); } sprite->originalSurface = _renderText(L, "fontToSprite", message); sprite->surface = sprite->originalSurface; sprite->scaleX = 1.0; sprite->scaleY = 1.0; sprite->id = _global.nextSpriteId++; HASH_ADD_INT(_global.spriteList, id, sprite); _luaTrace(L, "fontToSprite", "%d %s", sprite->id, message); lua_pushinteger(L, sprite->id); return 1; } // fontUnload(id) static int32_t apiFontUnload(lua_State *L) { FontT *font = NULL; _argCheck(L, "fontUnload", 1, 1); font = _argFont(L, "fontUnload", 1); _luaTrace(L, "fontUnload", "%d", font->id); _fontDestroy(font); return 0; } // joint = jointBall(nodeA, nodeB, ax, ay, az): nodeB -1 fixes to the world; anchor in world space static int32_t apiJointBall(lua_State *L) { int32_t nodeA; int32_t nodeB; int32_t joint; _argCheck(L, "jointBall", 5, 5); nodeA = _argBody(L, "jointBall", 1); nodeB = _argInteger(L, "jointBall", 2); if ((nodeB != -1) && !bodyExists(nodeB)) { _luaDie(L, "jointBall", "Node %d has no body.", nodeB); } joint = jointNew(JOINT_BALL, nodeA, nodeB, _argVec3(L, "jointBall", 3), vec3(0.0f, 1.0f, 0.0f)); if (joint < 0) { _luaDie(L, "jointBall", "Unable to create the joint."); } lua_pushinteger(L, joint); return 1; } // jointDelete(joint) static int32_t apiJointDelete(lua_State *L) { int32_t joint; _argCheck(L, "jointDelete", 1, 1); joint = _argInteger(L, "jointDelete", 1); if (!jointDelete(joint)) { _luaDie(L, "jointDelete", "No joint %d.", joint); } return 0; } // joint = jointHinge(nodeA, nodeB, ax, ay, az, dx, dy, dz): anchor and axis in world space; nodeB -1 is the world static int32_t apiJointHinge(lua_State *L) { int32_t nodeA; int32_t nodeB; int32_t joint; _argCheck(L, "jointHinge", 8, 8); nodeA = _argBody(L, "jointHinge", 1); nodeB = _argInteger(L, "jointHinge", 2); if ((nodeB != -1) && !bodyExists(nodeB)) { _luaDie(L, "jointHinge", "Node %d has no body.", nodeB); } joint = jointNew(JOINT_HINGE, nodeA, nodeB, _argVec3(L, "jointHinge", 3), _argVec3(L, "jointHinge", 6)); if (joint < 0) { _luaDie(L, "jointHinge", "Unable to create the joint."); } lua_pushinteger(L, joint); return 1; } // jointSetLimits(joint, low, high): degrees for a hinge, distance for a slider static int32_t apiJointSetLimits(lua_State *L) { int32_t joint; _argCheck(L, "jointSetLimits", 3, 3); joint = _argInteger(L, "jointSetLimits", 1); if (!jointSetLimits(joint, (float)_argNumber(L, "jointSetLimits", 2), (float)_argNumber(L, "jointSetLimits", 3))) { _luaDie(L, "jointSetLimits", "Joint %d has no limits to set.", joint); } return 0; } // joint = jointSlider(nodeA, nodeB, ax, ay, az, dx, dy, dz): slides along the axis through the anchor; nodeB -1 is the world static int32_t apiJointSlider(lua_State *L) { int32_t nodeA; int32_t nodeB; int32_t joint; _argCheck(L, "jointSlider", 8, 8); nodeA = _argBody(L, "jointSlider", 1); nodeB = _argInteger(L, "jointSlider", 2); if ((nodeB != -1) && !bodyExists(nodeB)) { _luaDie(L, "jointSlider", "Node %d has no body.", nodeB); } joint = jointNew(JOINT_SLIDER, nodeA, nodeB, _argVec3(L, "jointSlider", 3), _argVec3(L, "jointSlider", 6)); if (joint < 0) { _luaDie(L, "jointSlider", "Unable to create the joint."); } lua_pushinteger(L, joint); return 1; } // scancode = keyboardGetLastDown() Cleared every frame. static int32_t apiKeyboardGetLastDown(lua_State *L) { _luaTrace(L, "keyboardGetLastDown", "%d", _global.keyboardLastDown); lua_pushinteger(L, _global.keyboardLastDown); return 1; } // scancode = keyboardGetLastUp() Cleared every frame. static int32_t apiKeyboardGetLastUp(lua_State *L) { _luaTrace(L, "keyboardGetLastUp", "%d", _global.keyboardLastUp); lua_pushinteger(L, _global.keyboardLastUp); return 1; } // mode = keyboardGetMode() static int32_t apiKeyboardGetMode(lua_State *L) { _luaTrace(L, "keyboardGetMode", "%d", _global.keyboardMode); lua_pushinteger(L, _global.keyboardMode); return 1; } // modifiers = keyboardGetModifiers() SDL KMOD_* bits; compare with the MODIFIER table. static int32_t apiKeyboardGetModifiers(lua_State *L) { SDL_Keymod m = SDL_GetModState(); _luaTrace(L, "keyboardGetModifiers", "%d", (int32_t)m); lua_pushinteger(L, (int32_t)m); return 1; } // down = keyboardIsDown(scancode) static int32_t apiKeyboardIsDown(lua_State *L) { int32_t scancode = 0; bool down = false; _argCheck(L, "keyboardIsDown", 1, 1); scancode = _argInteger(L, "keyboardIsDown", 1); if ((scancode >= 0) && (scancode < SDL_SCANCODE_COUNT)) { down = _global.keyboardState[scancode]; } _luaTrace(L, "keyboardIsDown", "%d %d", scancode, down); lua_pushboolean(L, down); return 1; } // keyboardSetMode(MODE_NORMAL | MODE_FULL) // MODE_NORMAL only reports inputs mapped in controls.cfg; MODE_FULL reports every key. static int32_t apiKeyboardSetMode(lua_State *L) { int32_t mode = 0; _argCheck(L, "keyboardSetMode", 1, 1); mode = _argInteger(L, "keyboardSetMode", 1); if ((mode != KEYBOARD_NORMAL) && (mode != KEYBOARD_FULL)) { _luaDie(L, "keyboardSetMode", "Unknown keyboard mode: %d", mode); } _global.keyboardMode = (KeyboardModeE)mode; _luaTrace(L, "keyboardSetMode", "%d", _global.keyboardMode); return 0; } // node = lightNew(type [, parent]): a node carrying a light static int32_t apiLightNew(lua_State *L) { int32_t type; int32_t parent = SCENE_ROOT_NODE; int32_t node; _argCheck(L, "lightNew", 1, 2); type = _argInteger(L, "lightNew", 1); if ((type < LIGHT_DIRECTIONAL) || (type > LIGHT_SPOT)) { _luaDie(L, "lightNew", "Unknown light type %d.", type); } if (lua_gettop(L) >= 2) { parent = _argNode(L, "lightNew", 2); } node = lightNew((LightTypeE)type, parent); if (node < 0) { _luaDie(L, "lightNew", "Unable to create the node."); } _luaTrace(L, "lightNew", "%d type %d under %d", node, type, parent); lua_pushinteger(L, node); return 1; } // lightSetColor(node, r, g, b) static int32_t apiLightSetColor(lua_State *L) { int32_t node; _argCheck(L, "lightSetColor", 4, 4); node = _argNode(L, "lightSetColor", 1); if (!lightSetColor(node, _argColorByte(L, "lightSetColor", 2), _argColorByte(L, "lightSetColor", 3), _argColorByte(L, "lightSetColor", 4))) { _luaDie(L, "lightSetColor", "Node %d is not a light.", node); } return 0; } // lightSetCone(node, innerDegrees, outerDegrees) for spot lights static int32_t apiLightSetCone(lua_State *L) { int32_t node; _argCheck(L, "lightSetCone", 3, 3); node = _argNode(L, "lightSetCone", 1); if (!lightSetCone(node, (float)_argNumber(L, "lightSetCone", 2), (float)_argNumber(L, "lightSetCone", 3))) { _luaDie(L, "lightSetCone", "Node %d is not a light.", node); } return 0; } // lightSetIntensity(node, intensity) static int32_t apiLightSetIntensity(lua_State *L) { int32_t node; _argCheck(L, "lightSetIntensity", 2, 2); node = _argNode(L, "lightSetIntensity", 1); if (!lightSetIntensity(node, (float)_argNumber(L, "lightSetIntensity", 2))) { _luaDie(L, "lightSetIntensity", "Node %d is not a light.", node); } return 0; } // lightSetRange(node, range): 0 for unlimited static int32_t apiLightSetRange(lua_State *L) { int32_t node; _argCheck(L, "lightSetRange", 2, 2); node = _argNode(L, "lightSetRange", 1); if (!lightSetRange(node, (float)_argNumber(L, "lightSetRange", 2))) { _luaDie(L, "lightSetRange", "Node %d is not a light.", node); } return 0; } // lightSetShadow(node, bool): this light casts shadows (a point light's cost six passes) static int32_t apiLightSetShadow(lua_State *L) { int32_t node; _argCheck(L, "lightSetShadow", 2, 2); node = _argNode(L, "lightSetShadow", 1); if (!lightSetShadow(node, _argBoolean(L, "lightSetShadow", 2))) { _luaDie(L, "lightSetShadow", "Node %d is not a light.", node); } return 0; } // lineDraw(x0, y0, z0, x1, y1, z1 [, r, g, b]): a world-space line over the scene for this frame, white by default static int32_t apiLineDraw(lua_State *L) { uint8_t r = COLOR_BYTE_MAX; uint8_t g = COLOR_BYTE_MAX; uint8_t b = COLOR_BYTE_MAX; _argCheck(L, "lineDraw", 6, 9); _argOptionalColor(L, "lineDraw", 7, &r, &g, &b); sceneDrawLine(_argVec3(L, "lineDraw", 1), _argVec3(L, "lineDraw", 4), r, g, b); return 0; } // materialDelete(material): a sprite node's own material (from nodeGetMaterial) stays with its node static int32_t apiMaterialDelete(lua_State *L) { int32_t material = 0; _argCheck(L, "materialDelete", 1, 1); material = _argMaterial(L, "materialDelete", 1); if (!materialDelete(material)) { _luaDie(L, "materialDelete", "Material %d belongs to a sprite node; clear the sprite instead.", material); } return 0; } // material = materialNew(): white, half rough static int32_t apiMaterialNew(lua_State *L) { int32_t material; material = materialNew(); if (material < 0) { _luaDie(L, "materialNew", "3D is not available on this machine."); } _luaTrace(L, "materialNew", "%d", material); lua_pushinteger(L, material); return 1; } // materialSetBlend(material, bool): alpha blended instead of opaque static int32_t apiMaterialSetBlend(lua_State *L) { _argCheck(L, "materialSetBlend", 2, 2); materialSetBlend(_argMaterial(L, "materialSetBlend", 1), _argBoolean(L, "materialSetBlend", 2)); return 0; } // materialSetColor(material, r, g, b [, a]) static int32_t apiMaterialSetColor(lua_State *L) { int32_t material = 0; uint8_t a = SDL_ALPHA_OPAQUE; _argCheck(L, "materialSetColor", 4, 5); material = _argMaterial(L, "materialSetColor", 1); if (lua_gettop(L) >= 5) { a = _argColorByte(L, "materialSetColor", 5); } materialSetColor(material, _argColorByte(L, "materialSetColor", 2), _argColorByte(L, "materialSetColor", 3), _argColorByte(L, "materialSetColor", 4), a); return 0; } // materialSetDoubleSided(material, bool) static int32_t apiMaterialSetDoubleSided(lua_State *L) { _argCheck(L, "materialSetDoubleSided", 2, 2); materialSetDoubleSided(_argMaterial(L, "materialSetDoubleSided", 1), _argBoolean(L, "materialSetDoubleSided", 2)); return 0; } // materialSetEmissive(material, r, g, b) static int32_t apiMaterialSetEmissive(lua_State *L) { _argCheck(L, "materialSetEmissive", 4, 4); materialSetEmissive(_argMaterial(L, "materialSetEmissive", 1), _argColorByte(L, "materialSetEmissive", 2), _argColorByte(L, "materialSetEmissive", 3), _argColorByte(L, "materialSetEmissive", 4)); return 0; } // materialSetEmissiveMap(material[, image]) nil clears. static int32_t apiMaterialSetEmissiveMap(lua_State *L) { return _materialSetMap(L, "materialSetEmissiveMap", MAP_EMISSIVE, false); } static int32_t apiMaterialSetFilter(lua_State *L) { int32_t material = 0; int32_t filter = 0; _argCheck(L, "materialSetFilter", 2, 2); material = _argMaterial(L, "materialSetFilter", 1); filter = _argInteger(L, "materialSetFilter", 2); if ((filter != FILTER_LINEAR) && (filter != FILTER_NEAREST)) { _luaDie(L, "materialSetFilter", "Filter must be FILTER_LINEAR or FILTER_NEAREST."); } materialSetFilter(material, (MaterialFilterE)filter); return 0; } // materialSetMetallic(material, 0..1) static int32_t apiMaterialSetMetallic(lua_State *L) { _argCheck(L, "materialSetMetallic", 2, 2); materialSetMetallic(_argMaterial(L, "materialSetMetallic", 1), (float)_argNumber(L, "materialSetMetallic", 2)); return 0; } // materialSetMetallicRoughnessMap(material[, image]) roughness in G, metallic in B; nil clears. static int32_t apiMaterialSetMetallicRoughnessMap(lua_State *L) { return _materialSetMap(L, "materialSetMetallicRoughnessMap", MAP_METALLIC_ROUGHNESS, false); } // materialSetNormalMap(material[, image[, strength]]) nil clears; strength defaults to 1. static int32_t apiMaterialSetNormalMap(lua_State *L) { return _materialSetMap(L, "materialSetNormalMap", MAP_NORMAL, true); } // materialSetOcclusionMap(material[, image[, strength]]) occlusion in R; nil clears; strength defaults to 1. static int32_t apiMaterialSetOcclusionMap(lua_State *L) { return _materialSetMap(L, "materialSetOcclusionMap", MAP_OCCLUSION, true); } // materialSetRoughness(material, 0..1) static int32_t apiMaterialSetRoughness(lua_State *L) { _argCheck(L, "materialSetRoughness", 2, 2); materialSetRoughness(_argMaterial(L, "materialSetRoughness", 1), (float)_argNumber(L, "materialSetRoughness", 2)); return 0; } // materialSetTexture(material [, image]): a sprite's image (or a KTX2 file) as the base colour; none clears it static int32_t apiMaterialSetTexture(lua_State *L) { return _materialSetMap(L, "materialSetTexture", MAP_BASE, false); } // materialSetTiling(material, u, v): how many times its textures repeat across a surface static int32_t apiMaterialSetTiling(lua_State *L) { _argCheck(L, "materialSetTiling", 3, 3); materialSetTiling(_argMaterial(L, "materialSetTiling", 1), (float)_argNumber(L, "materialSetTiling", 2), (float)_argNumber(L, "materialSetTiling", 3)); return 0; } // materialSetUnlit(material, bool): shows the base colour as is static int32_t apiMaterialSetUnlit(lua_State *L) { _argCheck(L, "materialSetUnlit", 2, 2); materialSetUnlit(_argMaterial(L, "materialSetUnlit", 1), _argBoolean(L, "materialSetUnlit", 2)); return 0; } // materialSetVideo(material [, video]): the disc (or a loaded video) as the base colour texture static int32_t apiMaterialSetVideo(lua_State *L) { int32_t material; int32_t player; VideoT *video; _argCheck(L, "materialSetVideo", 1, 2); material = _argMaterial(L, "materialSetVideo", 1); if (lua_gettop(L) >= 2) { video = _argVideo(L, "materialSetVideo", 2); player = video->handle; } else { if (_global.videoHandle < 0) { _luaDie(L, "materialSetVideo", "This game has no disc."); } player = _global.videoHandle; } materialSetVideo(material, player); return 0; } // materialSetView(material [, view]): a rendered view as the base colour texture; none restores the texture static int32_t apiMaterialSetView(lua_State *L) { int32_t material; int32_t view = -1; _argCheck(L, "materialSetView", 1, 2); material = _argMaterial(L, "materialSetView", 1); if ((lua_gettop(L) >= 2) && !lua_isnil(L, 2)) { view = _argView(L, "materialSetView", 2); } materialSetView(material, view); return 0; } // mesh = meshBox(width, height, depth) static int32_t apiMeshBox(lua_State *L) { int32_t mesh; _argCheck(L, "meshBox", 3, 3); mesh = meshBox((float)_argNumber(L, "meshBox", 1), (float)_argNumber(L, "meshBox", 2), (float)_argNumber(L, "meshBox", 3)); if (mesh < 0) { _luaDie(L, "meshBox", "Unable to create the mesh."); } _luaTrace(L, "meshBox", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // mesh = meshCone(radius, height [, segments]) static int32_t apiMeshCone(lua_State *L) { int32_t mesh = 0; int32_t segments = MESH_SEGMENTS_DEFAULT; _argCheck(L, "meshCone", 2, 3); if (lua_gettop(L) >= 3) { segments = _argInteger(L, "meshCone", 3); } mesh = meshCone((float)_argNumber(L, "meshCone", 1), (float)_argNumber(L, "meshCone", 2), segments); if (mesh < 0) { _luaDie(L, "meshCone", "Unable to create the mesh."); } _luaTrace(L, "meshCone", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // mesh = meshCylinder(radius, height [, segments]) static int32_t apiMeshCylinder(lua_State *L) { int32_t mesh = 0; int32_t segments = MESH_SEGMENTS_DEFAULT; _argCheck(L, "meshCylinder", 2, 3); if (lua_gettop(L) >= 3) { segments = _argInteger(L, "meshCylinder", 3); } mesh = meshCylinder((float)_argNumber(L, "meshCylinder", 1), (float)_argNumber(L, "meshCylinder", 2), segments); if (mesh < 0) { _luaDie(L, "meshCylinder", "Unable to create the mesh."); } _luaTrace(L, "meshCylinder", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // meshDelete(mesh) static int32_t apiMeshDelete(lua_State *L) { _argCheck(L, "meshDelete", 1, 1); meshDelete(_argMesh(L, "meshDelete", 1)); return 0; } // mesh = meshHeightmap(image, sizeX, sizeY, sizeZ): a terrain from a greyscale image (its red channel, // black low and white sizeY high), sizeX by sizeZ across, one vertex per pixel static int32_t apiMeshHeightmap(lua_State *L) { const char *name = NULL; SDL_IOStream *io = NULL; SDL_Surface *image = NULL; SDL_Surface *rgba = NULL; const uint8_t *row = NULL; float *heights = NULL; int32_t bytesPerPixel = 0; int32_t columns = 0; int32_t rows = 0; int32_t x = 0; int32_t y = 0; int32_t mesh = 0; _argCheck(L, "meshHeightmap", 4, 4); name = _argString(L, "meshHeightmap", 1); io = vfsOpenIO(name); if (io == NULL) { _luaDie(L, "meshHeightmap", "Unable to open %s", name); } image = IMG_Load_IO(io, true); if (image == NULL) { _luaDie(L, "meshHeightmap", "%s: %s", name, SDL_GetError()); } rgba = SDL_ConvertSurface(image, SDL_PIXELFORMAT_RGBA32); SDL_DestroySurface(image); if (rgba == NULL) { _luaDie(L, "meshHeightmap", "%s", SDL_GetError()); } if ((rgba->w < 2) || (rgba->h < 2) || (rgba->w > HEIGHTMAP_MAX) || (rgba->h > HEIGHTMAP_MAX)) { SDL_DestroySurface(rgba); _luaDie(L, "meshHeightmap", "%s must be 2 to %d pixels each way.", name, HEIGHTMAP_MAX); } columns = rgba->w - 1; rows = rgba->h - 1; heights = SDL_malloc(sizeof(float) * (size_t)rgba->w * (size_t)rgba->h); if (heights == NULL) { utilDie("Out of memory reading a heightmap."); } bytesPerPixel = (int32_t)SDL_BYTESPERPIXEL(rgba->format); for (y = 0; y < rgba->h; y++) { row = (const uint8_t *)rgba->pixels + (size_t)y * (size_t)rgba->pitch; for (x = 0; x < rgba->w; x++) { heights[y * rgba->w + x] = row[x * bytesPerPixel] / (float)COLOR_BYTE_MAX; } } SDL_DestroySurface(rgba); mesh = meshHeightmap(heights, columns, rows, (float)_argNumber(L, "meshHeightmap", 2), (float)_argNumber(L, "meshHeightmap", 3), (float)_argNumber(L, "meshHeightmap", 4)); SDL_free(heights); if (mesh < 0) { _luaDie(L, "meshHeightmap", "Unable to make a mesh from %s (is 3D available?).", name); } lua_pushinteger(L, mesh); return 1; } // mesh = meshNew(positions, normals, uvs, indices): tables of numbers; normals and uvs may be nil static int32_t apiMeshNew(lua_State *L) { float *positions; float *normals; float *uvs; float *indexValues; uint32_t *indices; int32_t positionCount; int32_t normalCount; int32_t uvCount; int32_t indexCount; int32_t vertexCount; int32_t x; int32_t mesh; _argCheck(L, "meshNew", 4, 4); positions = _argFloatTable(L, "meshNew", 1, &positionCount); normals = _argFloatTable(L, "meshNew", 2, &normalCount); uvs = _argFloatTable(L, "meshNew", 3, &uvCount); indexValues = _argFloatTable(L, "meshNew", 4, &indexCount); vertexCount = positionCount / 3; if ((positionCount == 0) || (positionCount % 3 != 0)) { _luaDie(L, "meshNew", "Positions must hold three numbers per vertex."); } if ((normals != NULL) && (normalCount != positionCount)) { _luaDie(L, "meshNew", "Normals must hold three numbers per vertex, or be nil."); } if ((uvs != NULL) && (uvCount != vertexCount * 2)) { _luaDie(L, "meshNew", "Texture coordinates must hold two numbers per vertex, or be nil."); } if ((indexCount < 3) || (indexCount % 3 != 0)) { _luaDie(L, "meshNew", "Indices must hold three vertex numbers per triangle."); } indices = SDL_calloc((size_t)indexCount, sizeof(uint32_t)); if (indices == NULL) { _luaDie(L, "meshNew", "Out of memory."); } // Scripts number vertices from 1, as they appear in the positions table. for (x = 0; x < indexCount; x++) { if ((indexValues[x] < 1.0f) || (indexValues[x] > (float)vertexCount)) { _luaDie(L, "meshNew", "Index %d refers to vertex %d; there are %d.", x + 1, (int32_t)indexValues[x], vertexCount); } indices[x] = (uint32_t)indexValues[x] - 1; } mesh = meshNew(positions, normals, uvs, vertexCount, indices, indexCount); SDL_free(positions); SDL_free(normals); SDL_free(uvs); SDL_free(indexValues); SDL_free(indices); if (mesh < 0) { _luaDie(L, "meshNew", "Unable to create the mesh."); } _luaTrace(L, "meshNew", "%d (%d vertices, %d triangles)", mesh, vertexCount, indexCount / 3); lua_pushinteger(L, mesh); return 1; } // mesh = meshPlane(width, depth): flat in XZ, facing up static int32_t apiMeshPlane(lua_State *L) { int32_t mesh; _argCheck(L, "meshPlane", 2, 4); if (lua_gettop(L) >= 3) { // meshPlane(width, depth, columns[, rows]): subdivided, for cloth. int32_t columns = _argInteger(L, "meshPlane", 3); int32_t rows = (lua_gettop(L) == 4) ? _argInteger(L, "meshPlane", 4) : columns; mesh = meshGrid((float)_argNumber(L, "meshPlane", 1), (float)_argNumber(L, "meshPlane", 2), columns, rows); } else { mesh = meshPlane((float)_argNumber(L, "meshPlane", 1), (float)_argNumber(L, "meshPlane", 2)); } if (mesh < 0) { _luaDie(L, "meshPlane", "Unable to create the mesh."); } _luaTrace(L, "meshPlane", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // mesh = meshSphere(radius [, segments]) static int32_t apiMeshSphere(lua_State *L) { int32_t mesh = 0; int32_t segments = MESH_SPHERE_SEGMENTS_DEFAULT; _argCheck(L, "meshSphere", 1, 2); if (lua_gettop(L) >= 2) { segments = _argInteger(L, "meshSphere", 2); } mesh = meshSphere((float)_argNumber(L, "meshSphere", 1), segments); if (mesh < 0) { _luaDie(L, "meshSphere", "Unable to create the mesh."); } _luaTrace(L, "meshSphere", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // mesh = meshTorus(radius, tubeRadius [, segments]) static int32_t apiMeshTorus(lua_State *L) { int32_t mesh = 0; int32_t segments = MESH_SPHERE_SEGMENTS_DEFAULT; _argCheck(L, "meshTorus", 2, 3); if (lua_gettop(L) >= 3) { segments = _argInteger(L, "meshTorus", 3); } mesh = meshTorus((float)_argNumber(L, "meshTorus", 1), (float)_argNumber(L, "meshTorus", 2), segments); if (mesh < 0) { _luaDie(L, "meshTorus", "Unable to create the mesh."); } _luaTrace(L, "meshTorus", "%d", mesh); lua_pushinteger(L, mesh); return 1; } // modelDelete(model): frees its meshes and materials; instances keep their nodes, bare static int32_t apiModelDelete(lua_State *L) { int32_t model; _argCheck(L, "modelDelete", 1, 1); model = _argInteger(L, "modelDelete", 1); if (!modelDelete(model)) { _luaDie(L, "modelDelete", "No model %d.", model); } return 0; } // names = modelGetAnimations(model): a table of animation names, in file order static int32_t apiModelGetAnimations(lua_State *L) { int32_t model; int32_t count; int32_t x; _argCheck(L, "modelGetAnimations", 1, 1); model = _argInteger(L, "modelGetAnimations", 1); if (!modelValid(model)) { _luaDie(L, "modelGetAnimations", "No model %d.", model); } count = modelGetAnimationCount(model); lua_createtable(L, count, 0); for (x = 0; x < count; x++) { lua_pushstring(L, modelGetAnimationName(model, x)); lua_rawseti(L, -2, x + 1); } return 1; } // node = modelInstance(model [, parent]): the model's node tree under parent; returns its root static int32_t apiModelInstance(lua_State *L) { int32_t model; int32_t parent = SCENE_ROOT_NODE; int32_t root; _argCheck(L, "modelInstance", 1, 2); model = _argInteger(L, "modelInstance", 1); if (!modelValid(model)) { _luaDie(L, "modelInstance", "No model %d.", model); } if (lua_gettop(L) >= 2) { parent = _argNode(L, "modelInstance", 2); } root = modelInstance(model, parent); if (root < 0) { _luaDie(L, "modelInstance", "Unable to instance model %d.", model); } _luaTrace(L, "modelInstance", "%d -> node %d under %d", model, root, parent); lua_pushinteger(L, root); return 1; } // model = modelLoad(name): a self-contained .glb through the vfs static int32_t apiModelLoad(lua_State *L) { const char *name; int32_t model; _argCheck(L, "modelLoad", 1, 1); name = _argString(L, "modelLoad", 1); model = modelLoad(name); if (model < 0) { _luaDie(L, "modelLoad", "%s", modelLastError()); } _luaTrace(L, "modelLoad", "%d %s", model, name); lua_pushinteger(L, model); return 1; } // x, y = mouseGetPosition(mouse) static int32_t apiMouseGetPosition(lua_State *L) { int32_t m = 0; int32_t x = 0; int32_t y = 0; _argCheck(L, "mouseGetPosition", 1, 1); m = _argInteger(L, "mouseGetPosition", 1); if ((m < 0) || (m >= MAX_MICE)) { _luaDie(L, "mouseGetPosition", "Invalid mouse index: %d", m); } x = _global.axisCache[AXIS_INDEX_MOUSE(m, 0)]; y = _global.axisCache[AXIS_INDEX_MOUSE(m, 1)]; _luaTrace(L, "mouseGetPosition", "%d %d %d", m, x, y); lua_pushinteger(L, x); lua_pushinteger(L, y); return 2; } // count = mouseHowMany() static int32_t apiMouseHowMany(lua_State *L) { _luaTrace(L, "mouseHowMany", "%d", _global.mouseCount); lua_pushinteger(L, _global.mouseCount); return 1; } // mouseSetCaptured(captured) Grabs and hides the cursor. static int32_t apiMouseSetCaptured(lua_State *L) { _argCheck(L, "mouseSetCaptured", 1, 1); _setMouseCaptured(_argBoolean(L, "mouseSetCaptured", 1)); _luaTrace(L, "mouseSetCaptured", "%d", _global.mouseGrabbed); return 0; } // mouseSetEnabled(enabled) Framework.singe aliases mouseEnable() and mouseDisable() to this. static int32_t apiMouseSetEnabled(lua_State *L) { _argCheck(L, "mouseSetEnabled", 1, 1); _global.mouseEnabled = _argBoolean(L, "mouseSetEnabled", 1) && !_global.conf->noMouse; _luaTrace(L, "mouseSetEnabled", "%d", _global.mouseEnabled); return 0; } // mouseSetMode(MOUSE_SINGLE | MOUSE_MANY) static int32_t apiMouseSetMode(lua_State *L) { int32_t mode = 0; _argCheck(L, "mouseSetMode", 1, 1); mode = _argInteger(L, "mouseSetMode", 1); if ((mode != MOUSE_SINGLE) && (mode != MOUSE_MANY)) { _luaDie(L, "mouseSetMode", "Unknown mouse mode: %d", mode); } _global.mouseMode = (MouseModeE)mode; _luaTrace(L, "mouseSetMode", "%d", _global.mouseMode); return 0; } // navAddNode(nav, node): the node's mesh and its children's become walkable geometry for navBuild static int32_t apiNavAddNode(lua_State *L) { _argCheck(L, "navAddNode", 2, 2); if (!navAddNode(_argNav(L, "navAddNode", 1), _argNode(L, "navAddNode", 2))) { _luaDie(L, "navAddNode", "The mesh is already built."); } return 0; } static int32_t apiNavAgentDelete(lua_State *L) { _argCheck(L, "navAgentDelete", 1, 1); navAgentDelete(_argNavAgent(L, "navAgentDelete", 1)); return 0; } // x, y, z = navAgentGetVelocity(agent) static int32_t apiNavAgentGetVelocity(lua_State *L) { Vec3T velocity = vec3(0.0f, 0.0f, 0.0f); _argCheck(L, "navAgentGetVelocity", 1, 1); navAgentGetVelocity(_argNavAgent(L, "navAgentGetVelocity", 1), &velocity); return _pushVec3(L, velocity); } static int32_t apiNavAgentIsArrived(lua_State *L) { _argCheck(L, "navAgentIsArrived", 1, 1); lua_pushboolean(L, navAgentIsArrived(_argNavAgent(L, "navAgentIsArrived", 1))); return 1; } // navAgentMoveTo(agent, x, y, z): true when a path exists static int32_t apiNavAgentMoveTo(lua_State *L) { int32_t agent; _argCheck(L, "navAgentMoveTo", 4, 4); agent = _argNavAgent(L, "navAgentMoveTo", 1); lua_pushboolean(L, navAgentMoveTo(agent, vec3((float)_argNumber(L, "navAgentMoveTo", 2), (float)_argNumber(L, "navAgentMoveTo", 3), (float)_argNumber(L, "navAgentMoveTo", 4)))); return 1; } // agent = navAgentNew(nav, node, radius, height, speed): the node walks the mesh from where it stands static int32_t apiNavAgentNew(lua_State *L) { int32_t nav; int32_t node; int32_t agent; _argCheck(L, "navAgentNew", 5, 5); nav = _argNav(L, "navAgentNew", 1); node = _argNode(L, "navAgentNew", 2); agent = navAgentNew(nav, node, (float)_argNumber(L, "navAgentNew", 3), (float)_argNumber(L, "navAgentNew", 4), (float)_argNumber(L, "navAgentNew", 5)); if (agent < 0) { _luaDie(L, "navAgentNew", "No agent available (is the mesh built, and are fewer than %d agents on it?).", NAV_MAX_CROWD_AGENTS); } lua_pushinteger(L, agent); return 1; } // navAgentSetPlayer(agent, player): steer the player controller on the node instead of placing the node static int32_t apiNavAgentSetPlayer(lua_State *L) { _argCheck(L, "navAgentSetPlayer", 2, 2); navAgentSetPlayer(_argNavAgent(L, "navAgentSetPlayer", 1), _argBoolean(L, "navAgentSetPlayer", 2)); return 0; } static int32_t apiNavAgentStop(lua_State *L) { _argCheck(L, "navAgentStop", 1, 1); navAgentStop(_argNavAgent(L, "navAgentStop", 1)); return 0; } // navBuild(nav): bakes the walkable mesh from everything added static int32_t apiNavBuild(lua_State *L) { int32_t nav; _argCheck(L, "navBuild", 1, 1); nav = _argNav(L, "navBuild", 1); if (!navBuild(nav)) { _luaDie(L, "navBuild", "Unable to bake navigation mesh %d (nothing walkable added, or already built).", nav); } return 0; } static int32_t apiNavDelete(lua_State *L) { _argCheck(L, "navDelete", 1, 1); navDelete(_argNav(L, "navDelete", 1)); return 0; } // navDraw(nav [, r, g, b]): the baked mesh's triangles as lines over the scene for this frame, cyan by default static int32_t apiNavDraw(lua_State *L) { int32_t nav = 0; int32_t capacity = 0; int32_t count = 0; int32_t x = 0; Vec3T *grown = NULL; Vec3T *vertices = NULL; uint8_t r = 0; uint8_t g = COLOR_BYTE_MAX; uint8_t b = COLOR_BYTE_MAX; _argCheck(L, "navDraw", 1, 4); nav = _argNav(L, "navDraw", 1); _argOptionalColor(L, "navDraw", 2, &r, &g, &b); // The triangle list is kept between frames (this is drawn every frame) and doubled until the mesh fits. capacity = SDL_max(_global.navDrawCapacity, NAV_DRAW_VERTICES); for (;;) { if (capacity > _global.navDrawCapacity) { grown = SDL_realloc(_global.navDrawVertices, (size_t)capacity * sizeof(Vec3T)); if (grown == NULL) { _luaDie(L, "navDraw", "Out of memory."); } _global.navDrawVertices = grown; _global.navDrawCapacity = capacity; } vertices = _global.navDrawVertices; count = navGetPolygons(nav, vertices, capacity); if (count + 3 <= capacity) { break; } capacity *= 2; } for (x = 0; x + 2 < count; x += 3) { sceneDrawLine(vertices[x], vertices[x + 1], r, g, b); sceneDrawLine(vertices[x + 1], vertices[x + 2], r, g, b); sceneDrawLine(vertices[x + 2], vertices[x], r, g, b); } return 0; } // nav = navLoad(name, agentRadius, agentHeight): a mesh navSave wrote, from the game or its data folder static int32_t apiNavLoad(lua_State *L) { const char *name = NULL; char *data = NULL; char *path = NULL; size_t size = 0; int32_t nav = 0; _argCheck(L, "navLoad", 3, 3); name = _argString(L, "navLoad", 1); data = vfsRead(name, &size); if (data == NULL) { // Not in the game: maybe in its data folder, where navSave writes. path = utilCreateString("%s%s", _global.conf->dataDir, name); data = utilReadFile(path, &size); free(path); } if (data == NULL) { _luaDie(L, "navLoad", "Unable to read %s", name); } nav = navLoad(data, size, (float)_argNumber(L, "navLoad", 2), (float)_argNumber(L, "navLoad", 3)); free(data); if (nav < 0) { _luaDie(L, "navLoad", "%s is not a navigation mesh, or every mesh slot is in use.", name); } lua_pushinteger(L, nav); return 1; } // x, y, z = navNearest(nav, x, y, z): the closest walkable point, or nil static int32_t apiNavNearest(lua_State *L) { Vec3T out; _argCheck(L, "navNearest", 4, 4); if (!navNearest(_argNav(L, "navNearest", 1), vec3((float)_argNumber(L, "navNearest", 2), (float)_argNumber(L, "navNearest", 3), (float)_argNumber(L, "navNearest", 4)), &out)) { lua_pushnil(L); return 1; } return _pushVec3(L, out); } // nav = navNew(agentRadius, agentHeight, maxSlope, maxStep) static int32_t apiNavNew(lua_State *L) { int32_t nav; _argCheck(L, "navNew", 4, 4); nav = navNew((float)_argNumber(L, "navNew", 1), (float)_argNumber(L, "navNew", 2), (float)_argNumber(L, "navNew", 3), (float)_argNumber(L, "navNew", 4)); if (nav < 0) { _luaDie(L, "navNew", "No navigation mesh slot available."); } lua_pushinteger(L, nav); return 1; } // points = navPath(nav, x0, y0, z0, x1, y1, z1): a table of {x, y, z} corners, or nil for no path static int32_t apiNavPath(lua_State *L) { Vec3T points[NAV_MAX_PATH]; int32_t count = 0; int32_t x = 0; _argCheck(L, "navPath", 7, 7); count = navPath(_argNav(L, "navPath", 1), vec3((float)_argNumber(L, "navPath", 2), (float)_argNumber(L, "navPath", 3), (float)_argNumber(L, "navPath", 4)), vec3((float)_argNumber(L, "navPath", 5), (float)_argNumber(L, "navPath", 6), (float)_argNumber(L, "navPath", 7)), points, (int32_t)SDL_arraysize(points)); if (count < 0) { lua_pushnil(L); return 1; } lua_createtable(L, count, 0); for (x = 0; x < count; x++) { lua_createtable(L, 3, 0); lua_pushnumber(L, points[x].x); lua_rawseti(L, -2, 1); lua_pushnumber(L, points[x].y); lua_rawseti(L, -2, 2); lua_pushnumber(L, points[x].z); lua_rawseti(L, -2, 3); lua_rawseti(L, -2, x + 1); } return 1; } // x, y, z = navRandomPoint(nav) static int32_t apiNavRandomPoint(lua_State *L) { Vec3T out; _argCheck(L, "navRandomPoint", 1, 1); if (!navRandomPoint(_argNav(L, "navRandomPoint", 1), &out)) { lua_pushnil(L); return 1; } return _pushVec3(L, out); } // blocked, x, y, z = navRaycast(nav, x0, y0, z0, x1, y1, z1): whether a straight walk leaves the mesh, and where static int32_t apiNavRaycast(lua_State *L) { Vec3T hit = vec3(0.0f, 0.0f, 0.0f); bool blocked; _argCheck(L, "navRaycast", 7, 7); blocked = navRaycast(_argNav(L, "navRaycast", 1), vec3((float)_argNumber(L, "navRaycast", 2), (float)_argNumber(L, "navRaycast", 3), (float)_argNumber(L, "navRaycast", 4)), vec3((float)_argNumber(L, "navRaycast", 5), (float)_argNumber(L, "navRaycast", 6), (float)_argNumber(L, "navRaycast", 7)), &hit); lua_pushboolean(L, blocked); if (!blocked) { return 1; } return 1 + _pushVec3(L, hit); } // navSave(nav, name): the baked mesh into the game's data folder, for navLoad static int32_t apiNavSave(lua_State *L) { int32_t nav; char *path; bool ok; _argCheck(L, "navSave", 2, 2); nav = _argNav(L, "navSave", 1); path = utilCreateString("%s%s", _global.conf->dataDir, _argString(L, "navSave", 2)); ok = navSave(nav, path); if (!ok) { _luaDie(L, "navSave", "Unable to write %s", path); } free(path); return 0; } // nodeDelete(node): the node and everything under it static int32_t apiNodeDelete(lua_State *L) { int32_t node; _argCheck(L, "nodeDelete", 1, 1); node = _argNode(L, "nodeDelete", 1); if (!nodeDelete(node)) { _luaDie(L, "nodeDelete", "The root node cannot be deleted."); } _luaTrace(L, "nodeDelete", "%d", node); return 0; } // node = nodeFind(name [, root]): by name, below root; nil when absent static int32_t apiNodeFind(lua_State *L) { const char *name = NULL; int32_t root = SCENE_ROOT_NODE; int32_t found = 0; _argCheck(L, "nodeFind", 1, 2); name = _argString(L, "nodeFind", 1); if (lua_gettop(L) >= 2) { root = _argNode(L, "nodeFind", 2); } found = nodeFind(root, name); if (found < 0) { lua_pushnil(L); } else { lua_pushinteger(L, found); } return 1; } // children = nodeGetChildren(node): a table of handles static int32_t apiNodeGetChildren(lua_State *L) { int32_t node; int32_t count; int32_t x; _argCheck(L, "nodeGetChildren", 1, 1); node = _argNode(L, "nodeGetChildren", 1); count = nodeGetChildCount(node); lua_createtable(L, count, 0); for (x = 0; x < count; x++) { lua_pushinteger(L, nodeGetChild(node, x)); lua_rawseti(L, -2, x + 1); } return 1; } // weight = nodeGetMorph(node, nameOrIndex) static int32_t apiNodeGetMorph(lua_State *L) { int32_t node; int32_t target; _argCheck(L, "nodeGetMorph", 2, 2); node = _argNode(L, "nodeGetMorph", 1); target = _argMorph(L, "nodeGetMorph", node, 2); lua_pushnumber(L, nodeGetMorphWeight(node, target)); return 1; } // names = nodeGetMorphs(node): the mesh's morph target names, in order (empty strings when unnamed) static int32_t apiNodeGetMorphs(lua_State *L) { int32_t node; int32_t count; int32_t x; _argCheck(L, "nodeGetMorphs", 1, 1); node = _argNode(L, "nodeGetMorphs", 1); count = nodeGetMorphCount(node); lua_createtable(L, count, 0); for (x = 0; x < count; x++) { lua_pushstring(L, meshGetMorphName(nodeGetMesh(node), x)); lua_rawseti(L, -2, x + 1); } return 1; } // name = nodeGetName(node) static int32_t apiNodeGetName(lua_State *L) { _argCheck(L, "nodeGetName", 1, 1); lua_pushstring(L, nodeGetName(_argNode(L, "nodeGetName", 1))); return 1; } // parent = nodeGetParent(node): nil for the root static int32_t apiNodeGetParent(lua_State *L) { int32_t parent; _argCheck(L, "nodeGetParent", 1, 1); parent = nodeGetParent(_argNode(L, "nodeGetParent", 1)); if (parent < 0) { lua_pushnil(L); } else { lua_pushinteger(L, parent); } return 1; } // x, y, z = nodeGetPosition(node) static int32_t apiNodeGetPosition(lua_State *L) { _argCheck(L, "nodeGetPosition", 1, 1); return _pushVec3(L, nodeGetPosition(_argNode(L, "nodeGetPosition", 1))); } // x, y, z, w = nodeGetQuaternion(node) static int32_t apiNodeGetQuaternion(lua_State *L) { QuatT q; _argCheck(L, "nodeGetQuaternion", 1, 1); q = nodeGetRotation(_argNode(L, "nodeGetQuaternion", 1)); lua_pushnumber(L, q.x); lua_pushnumber(L, q.y); lua_pushnumber(L, q.z); lua_pushnumber(L, q.w); return 4; } // x, y, z = nodeGetRotation(node): Euler degrees static int32_t apiNodeGetRotation(lua_State *L) { float x; float y; float z; _argCheck(L, "nodeGetRotation", 1, 1); quatToEuler(nodeGetRotation(_argNode(L, "nodeGetRotation", 1)), &x, &y, &z); lua_pushnumber(L, x); lua_pushnumber(L, y); lua_pushnumber(L, z); return 3; } // x, y, z = nodeGetScale(node) static int32_t apiNodeGetScale(lua_State *L) { _argCheck(L, "nodeGetScale", 1, 1); return _pushVec3(L, nodeGetScale(_argNode(L, "nodeGetScale", 1))); } // x, y, z = nodeGetWorldPosition(node): as of the last rendered frame static int32_t apiNodeGetWorldPosition(lua_State *L) { _argCheck(L, "nodeGetWorldPosition", 1, 1); return _pushVec3(L, nodeGetWorldPosition(_argNode(L, "nodeGetWorldPosition", 1))); } // nodeLookAt(node, x, y, z): points the node's -Z at a world point static int32_t apiNodeLookAt(lua_State *L) { _argCheck(L, "nodeLookAt", 4, 4); nodeLookAt(_argNode(L, "nodeLookAt", 1), _argVec3(L, "nodeLookAt", 2)); return 0; } // nodeMove(node, dx, dy, dz): along the node's own axes static int32_t apiNodeMove(lua_State *L) { _argCheck(L, "nodeMove", 4, 4); nodeMove(_argNode(L, "nodeMove", 1), _argVec3(L, "nodeMove", 2)); return 0; } // node = nodeNew([parent]): a new node at its parent's origin static int32_t apiNodeNew(lua_State *L) { int32_t parent = SCENE_ROOT_NODE; int32_t node; _argCheck(L, "nodeNew", 0, 1); if (lua_gettop(L) >= 1) { parent = _argNode(L, "nodeNew", 1); } node = nodeNew(parent); if (node < 0) { _luaDie(L, "nodeNew", "Unable to create the node."); } _luaTrace(L, "nodeNew", "%d under %d", node, parent); lua_pushinteger(L, node); return 1; } // nodeRotate(node, dx, dy, dz): degrees, about the node's own axes static int32_t apiNodeRotate(lua_State *L) { _argCheck(L, "nodeRotate", 4, 4); nodeRotate(_argNode(L, "nodeRotate", 1), _argEuler(L, "nodeRotate", 2)); return 0; } // nodeSetBillboard(node, BILLBOARD_*): the node turns to face the camera static int32_t apiNodeSetBillboard(lua_State *L) { int32_t node = 0; int32_t mode = 0; _argCheck(L, "nodeSetBillboard", 2, 2); node = _argNode(L, "nodeSetBillboard", 1); mode = _argInteger(L, "nodeSetBillboard", 2); if ((mode != BILLBOARD_NONE) && (mode != BILLBOARD_ALL) && (mode != BILLBOARD_Y)) { _luaDie(L, "nodeSetBillboard", "Mode must be BILLBOARD_NONE, BILLBOARD_ALL or BILLBOARD_Y."); } nodeSetBillboard(node, (BillboardE)mode); return 0; } // nodeSetMaterial(node, material): a new material on whatever mesh the node has (nil for none) static int32_t apiNodeSetMaterial(lua_State *L) { int32_t material = -1; _argCheck(L, "nodeSetMaterial", 2, 2); if (!lua_isnil(L, 2)) { material = _argMaterial(L, "nodeSetMaterial", 2); } nodeSetMaterial(_argNode(L, "nodeSetMaterial", 1), material); return 0; } // nodeSetMesh(node, mesh [, material]) static int32_t apiNodeSetMesh(lua_State *L) { int32_t node; int32_t mesh; int32_t material = -1; _argCheck(L, "nodeSetMesh", 2, 3); node = _argNode(L, "nodeSetMesh", 1); mesh = _argMesh(L, "nodeSetMesh", 2); if (lua_gettop(L) >= 3) { material = _argMaterial(L, "nodeSetMesh", 3); } nodeSetMesh(node, mesh, material); return 0; } // nodeSetMorph(node, nameOrIndex, weight): how much of a morph target shows, usually 0 to 1 static int32_t apiNodeSetMorph(lua_State *L) { int32_t node; int32_t target; _argCheck(L, "nodeSetMorph", 3, 3); node = _argNode(L, "nodeSetMorph", 1); target = _argMorph(L, "nodeSetMorph", node, 2); nodeSetMorphWeight(node, target, (float)_argNumber(L, "nodeSetMorph", 3)); return 0; } // nodeSetName(node, name) static int32_t apiNodeSetName(lua_State *L) { _argCheck(L, "nodeSetName", 2, 2); nodeSetName(_argNode(L, "nodeSetName", 1), _argString(L, "nodeSetName", 2)); return 0; } // nodeSetParent(node, parent): keeps the local transform static int32_t apiNodeSetParent(lua_State *L) { int32_t node; int32_t parent; _argCheck(L, "nodeSetParent", 2, 2); node = _argNode(L, "nodeSetParent", 1); parent = _argNode(L, "nodeSetParent", 2); if (!nodeSetParent(node, parent)) { _luaDie(L, "nodeSetParent", "Node %d cannot go under node %d.", node, parent); } return 0; } // nodeSetPosition(node, x, y, z) static int32_t apiNodeSetPosition(lua_State *L) { _argCheck(L, "nodeSetPosition", 4, 4); nodeSetPosition(_argNode(L, "nodeSetPosition", 1), _argVec3(L, "nodeSetPosition", 2)); return 0; } // nodeSetQuaternion(node, x, y, z, w) static int32_t apiNodeSetQuaternion(lua_State *L) { QuatT q; _argCheck(L, "nodeSetQuaternion", 5, 5); q.x = (float)_argNumber(L, "nodeSetQuaternion", 2); q.y = (float)_argNumber(L, "nodeSetQuaternion", 3); q.z = (float)_argNumber(L, "nodeSetQuaternion", 4); q.w = (float)_argNumber(L, "nodeSetQuaternion", 5); nodeSetRotation(_argNode(L, "nodeSetQuaternion", 1), q); return 0; } // nodeSetRotation(node, x, y, z): Euler degrees static int32_t apiNodeSetRotation(lua_State *L) { _argCheck(L, "nodeSetRotation", 4, 4); nodeSetRotation(_argNode(L, "nodeSetRotation", 1), _argEuler(L, "nodeSetRotation", 2)); return 0; } // nodeSetScale(node, s) or nodeSetScale(node, x, y, z) static int32_t apiNodeSetScale(lua_State *L) { int32_t node; Vec3T scale; _argCheck(L, "nodeSetScale", 2, 4); node = _argNode(L, "nodeSetScale", 1); if (lua_gettop(L) == 2) { scale.x = (float)_argNumber(L, "nodeSetScale", 2); scale.y = scale.x; scale.z = scale.x; } else { scale = _argVec3(L, "nodeSetScale", 2); } nodeSetScale(node, scale); return 0; } // nodeSetShadow(node, bool): whether the node's mesh casts shadows static int32_t apiNodeSetShadow(lua_State *L) { _argCheck(L, "nodeSetShadow", 2, 2); nodeSetShadow(_argNode(L, "nodeSetShadow", 1), _argBoolean(L, "nodeSetShadow", 2)); return 0; } // nodeSetSprite(node, sprite [, height [, lit]]): the sprite's picture (every frame of an animated one) // on a quad height world units tall, wide by its aspect; nil clears it static int32_t apiNodeSetSprite(lua_State *L) { int32_t node; SpriteT *sprite = NULL; SDL_Surface **frames; int32_t count; float height = 1.0f; bool lit = true; float width; _argCheck(L, "nodeSetSprite", 1, 4); node = _argNode(L, "nodeSetSprite", 1); if ((lua_gettop(L) < 2) || lua_isnil(L, 2)) { nodeSetSprite(node, NULL, 0, 0.0f, 0.0f, true); return 0; } sprite = _argSprite(L, "nodeSetSprite", 2); if (lua_gettop(L) >= 3) { height = (float)_argNumber(L, "nodeSetSprite", 3); } if (lua_gettop(L) >= 4) { lit = _argBoolean(L, "nodeSetSprite", 4); } if (sprite->animation != NULL) { frames = sprite->animation->frames; count = sprite->animation->count; } else { frames = &sprite->originalSurface; count = 1; } width = (frames[0]->h > 0) ? height * (float)frames[0]->w / (float)frames[0]->h : height; if (!nodeSetSprite(node, frames, count, width, height, lit)) { _luaDie(L, "nodeSetSprite", "Unable to put sprite %d on node %d (is 3D available?).", sprite->id, node); } return 0; } // nodeSetSpriteFrame(node, frame): which frame of the node's animated sprite shows, from 0 static int32_t apiNodeSetSpriteFrame(lua_State *L) { int32_t node; int32_t frame; _argCheck(L, "nodeSetSpriteFrame", 2, 2); node = _argNode(L, "nodeSetSpriteFrame", 1); frame = _argInteger(L, "nodeSetSpriteFrame", 2); if (!nodeSetSpriteFrame(node, frame)) { _luaDie(L, "nodeSetSpriteFrame", "Node %d has no sprite frame %d.", node, frame); } return 0; } // nodeSetText(node, text [, height]): the text in the selected font and colour on a quad height world // units tall (per line of the font), unlit; nil clears it static int32_t apiNodeSetText(lua_State *L) { int32_t node; SDL_Surface *surface; float height = 1.0f; float width; bool ok; _argCheck(L, "nodeSetText", 1, 3); node = _argNode(L, "nodeSetText", 1); if ((lua_gettop(L) < 2) || lua_isnil(L, 2)) { nodeSetSprite(node, NULL, 0, 0.0f, 0.0f, false); return 0; } if (lua_gettop(L) >= 3) { height = (float)_argNumber(L, "nodeSetText", 3); } surface = _renderText(L, "nodeSetText", _argString(L, "nodeSetText", 2)); width = (surface->h > 0) ? height * (float)surface->w / (float)surface->h : height; ok = nodeSetSprite(node, &surface, 1, width, height, false); SDL_DestroySurface(surface); if (!ok) { _luaDie(L, "nodeSetText", "Unable to put text on node %d (is 3D available?).", node); } return 0; } // nodeSetVisible(node, bool): hides the node and its children static int32_t apiNodeSetVisible(lua_State *L) { _argCheck(L, "nodeSetVisible", 2, 2); nodeSetVisible(_argNode(L, "nodeSetVisible", 1), _argBoolean(L, "nodeSetVisible", 2)); return 0; } // seconds = os.clock() Replaces Lua's processor-time clock with wall time since the engine started. static int32_t apiOsClock(lua_State *L) { lua_pushnumber(L, (lua_Number)SDL_GetTicks() / MS_PER_SECOND_NUMBER); return 1; } // overlayBox(x1, y1, x2, y2) Outline only. static int32_t apiOverlayBox(lua_State *L) { int32_t x1 = 0; int32_t y1 = 0; int32_t x2 = 0; int32_t y2 = 0; uint32_t pixel = 0; _argCheck(L, "overlayBox", 4, 4); x1 = _argInteger(L, "overlayBox", 1); y1 = _argInteger(L, "overlayBox", 2); x2 = _argInteger(L, "overlayBox", 3); y2 = _argInteger(L, "overlayBox", 4); pixel = _overlayColor(&_global.colorForeground); SDL_LockSurface(_global.overlay); _drawLine(x1, y1, x2, y1, pixel); _drawLine(x2, y1, x2, y2, pixel); _drawLine(x2, y2, x1, y2, pixel); _drawLine(x1, y2, x1, y1, pixel); SDL_UnlockSurface(_global.overlay); _overlayTouched(); _luaTrace(L, "overlayBox", "%d %d %d %d", x1, y1, x2, y2); return 0; } // overlayCircle(x, y, radius) Midpoint circle. static int32_t apiOverlayCircle(lua_State *L) { int32_t x0 = 0; int32_t y0 = 0; int32_t r = 0; int32_t x = 0; int32_t y = 0; int32_t dx = 1; int32_t dy = 1; int32_t err = 0; uint32_t pixel = 0; _argCheck(L, "overlayCircle", 3, 3); x0 = _argInteger(L, "overlayCircle", 1); y0 = _argInteger(L, "overlayCircle", 2); r = _argInteger(L, "overlayCircle", 3); x = r - 1; err = dx - (r << 1); pixel = _overlayColor(&_global.colorForeground); SDL_LockSurface(_global.overlay); while (x >= y) { _putPixel(x0 + x, y0 + y, pixel); _putPixel(x0 + y, y0 + x, pixel); _putPixel(x0 - y, y0 + x, pixel); _putPixel(x0 - x, y0 + y, pixel); _putPixel(x0 - x, y0 - y, pixel); _putPixel(x0 - y, y0 - x, pixel); _putPixel(x0 + y, y0 - x, pixel); _putPixel(x0 + x, y0 - y, pixel); if (err <= 0) { y++; err += dy; dy += 2; } if (err > 0) { x--; dx += 2; err += dx - (r << 1); } } SDL_UnlockSurface(_global.overlay); _overlayTouched(); _luaTrace(L, "overlayCircle", "%d %d %d", x0, y0, r); return 0; } // overlayClear() Fills with the background color. static int32_t apiOverlayClear(lua_State *L) { SDL_FillSurfaceRect(_global.overlay, NULL, _overlayColor(&_global.colorBackground)); _overlayTouched(); _luaTrace(L, "overlayClear", "Cleared."); return 0; } // overlayEllipse(x1, y1, x2, y2) Bresenham ellipse inside the given rectangle. static int32_t apiOverlayEllipse(lua_State *L) { int32_t x0 = 0; int32_t y0 = 0; int32_t x1 = 0; int32_t y1 = 0; int32_t a = 0; int32_t b = 0; int32_t b1 = 0; int32_t dx = 0; int32_t dy = 0; int32_t err = 0; int32_t e2 = 0; uint32_t pixel = 0; _argCheck(L, "overlayEllipse", 4, 4); x0 = _argInteger(L, "overlayEllipse", 1); y0 = _argInteger(L, "overlayEllipse", 2); x1 = _argInteger(L, "overlayEllipse", 3); y1 = _argInteger(L, "overlayEllipse", 4); pixel = _overlayColor(&_global.colorForeground); _luaTrace(L, "overlayEllipse", "%d %d %d %d", x0, y0, x1, y1); a = abs(x1 - x0); b = abs(y1 - y0); b1 = b & 1; // values of diameter dx = 4 * (1 - a) * b * b; dy = 4 * (b1 + 1) * a * a; // error increment err = dx + dy + b1 * a * a; if (x0 > x1) { // if called with swapped points x0 = x1; x1 += a; } if (y0 > y1) { // exchange them y0 = y1; } y0 += (b + 1) / 2; // starting pixel y1 = y0 - b1; a *= 8 * a; b1 = 8 * b * b; SDL_LockSurface(_global.overlay); do { _putPixel(x1, y0, pixel); // I. Quadrant _putPixel(x0, y0, pixel); // II. Quadrant _putPixel(x0, y1, pixel); // III. Quadrant _putPixel(x1, y1, pixel); // IV. Quadrant e2 = 2 * err; if (e2 <= dy) { // y step y0++; y1--; dy += a; err += dy; } if ((e2 >= dx) || (2 * err > dy)) { // x step x0++; x1--; dx += b1; err += dx; } } while (x0 <= x1); while (y0 - y1 < b) { // too early stop of flat ellipses a = 1 _putPixel(x0 - 1, y0, pixel); // finish tip of ellipse _putPixel(x1 + 1, y0, pixel); y0++; _putPixel(x0 - 1, y1, pixel); _putPixel(x1 + 1, y1, pixel); y1--; } SDL_UnlockSurface(_global.overlay); _overlayTouched(); return 0; } // height = overlayGetHeight() static int32_t apiOverlayGetHeight(lua_State *L) { _luaTrace(L, "overlayGetHeight", "%d", _global.overlay->h); lua_pushinteger(L, _global.overlay->h); return 1; } // width = overlayGetWidth() static int32_t apiOverlayGetWidth(lua_State *L) { _luaTrace(L, "overlayGetWidth", "%d", _global.overlay->w); lua_pushinteger(L, _global.overlay->w); return 1; } // overlayLine(x1, y1, x2, y2) static int32_t apiOverlayLine(lua_State *L) { int32_t x1 = 0; int32_t y1 = 0; int32_t x2 = 0; int32_t y2 = 0; uint32_t pixel = 0; _argCheck(L, "overlayLine", 4, 4); x1 = _argInteger(L, "overlayLine", 1); y1 = _argInteger(L, "overlayLine", 2); x2 = _argInteger(L, "overlayLine", 3); y2 = _argInteger(L, "overlayLine", 4); pixel = _overlayColor(&_global.colorForeground); SDL_LockSurface(_global.overlay); _drawLine(x1, y1, x2, y2, pixel); SDL_UnlockSurface(_global.overlay); _overlayTouched(); _luaTrace(L, "overlayLine", "%d %d %d %d", x1, y1, x2, y2); return 0; } // overlayPlot(x, y) static int32_t apiOverlayPlot(lua_State *L) { int32_t x = 0; int32_t y = 0; uint32_t pixel = 0; _argCheck(L, "overlayPlot", 2, 2); x = _argInteger(L, "overlayPlot", 1); y = _argInteger(L, "overlayPlot", 2); pixel = _overlayColor(&_global.colorForeground); SDL_LockSurface(_global.overlay); _putPixel(x, y, pixel); SDL_UnlockSurface(_global.overlay); _overlayTouched(); _luaTrace(L, "overlayPlot", "%d %d", x, y); return 0; } // overlayPrint(column, row, text) Built in console font; coordinates are character cells. static int32_t apiOverlayPrint(lua_State *L) { const uint8_t *text = NULL; int32_t i = 0; int32_t length = 0; int32_t fit = 0; SDL_Rect src; SDL_Rect dst; _argCheck(L, "overlayPrint", 3, 3); dst.x = _argInteger(L, "overlayPrint", 1) * _global.consoleFontWidth; dst.y = _argInteger(L, "overlayPrint", 2) * _global.consoleFontHeight; dst.w = _global.consoleFontWidth; dst.h = _global.consoleFontHeight; src.y = 0; src.w = _global.consoleFontWidth; src.h = _global.consoleFontHeight; text = (const uint8_t *)_argString(L, "overlayPrint", 3); _luaTrace(L, "overlayPrint", "%s", (const char *)text); // Clip to the right edge of the overlay. length = (int32_t)strlen((const char *)text); fit = (_global.overlay->w - dst.x) / _global.consoleFontWidth; if (fit < 0) { fit = 0; } if (length > fit) { length = fit; } for (i = 0; i < length; i++) { src.x = text[i] * _global.consoleFontWidth; SDL_BlitSurface(_global.consoleFontSurface, &src, _global.overlay, &dst); dst.x += _global.consoleFontWidth; } _overlayTouched(); return 0; } // overlaySetResolution(width, height) Replaces the overlay; its contents are lost. static int32_t apiOverlaySetResolution(lua_State *L) { int32_t width = 0; int32_t height = 0; _argCheck(L, "overlaySetResolution", 2, 2); width = _argInteger(L, "overlaySetResolution", 1); height = _argInteger(L, "overlaySetResolution", 2); if ((width <= 0) || (height <= 0)) { _luaDie(L, "overlaySetResolution", "Invalid overlay size: %dx%d", width, height); } _overlayResize(width, height); _luaTrace(L, "overlaySetResolution", "%d %d", width, height); return 0; } // node, hx, hy, hz, nx, ny, nz = physicsRaycast(x, y, z, dx, dy, dz [, maxDistance]): nil when nothing is hit static int32_t apiPhysicsRaycast(lua_State *L) { Vec3T origin; Vec3T direction; float maxDistance = 0.0f; int32_t node; Vec3T point; Vec3T normal; _argCheck(L, "physicsRaycast", 6, 7); origin = _argVec3(L, "physicsRaycast", 1); direction = _argVec3(L, "physicsRaycast", 4); if (lua_gettop(L) >= 7) { maxDistance = (float)_argNumber(L, "physicsRaycast", 7); } if (!physicsRaycast(origin, direction, maxDistance, &node, &point, &normal)) { lua_pushnil(L); return 1; } lua_pushinteger(L, node); _pushVec3(L, point); _pushVec3(L, normal); return 7; } // physicsSet2D(bool): bodies made from now on stay in the XY plane (2D games) static int32_t apiPhysicsSet2D(lua_State *L) { _argCheck(L, "physicsSet2D", 1, 1); physicsSet2D(_argBoolean(L, "physicsSet2D", 1)); return 0; } // physicsSetDebug(mask): DEBUG_* flags added together, drawn as lines over the scene each frame; DEBUG_NONE stops static int32_t apiPhysicsSetDebug(lua_State *L) { _argCheck(L, "physicsSetDebug", 1, 1); physicsSetDebug((uint32_t)_argInteger(L, "physicsSetDebug", 1)); return 0; } // physicsSetEnabled(bool): pauses the simulation without losing it static int32_t apiPhysicsSetEnabled(lua_State *L) { _argCheck(L, "physicsSetEnabled", 1, 1); physicsSetEnabled(_argBoolean(L, "physicsSetEnabled", 1)); return 0; } // physicsSetGravity(x, y, z): default 0, -9.81, 0 static int32_t apiPhysicsSetGravity(lua_State *L) { _argCheck(L, "physicsSetGravity", 3, 3); physicsSetGravity(_argVec3(L, "physicsSetGravity", 1)); return 0; } static int32_t apiPlayerDelete(lua_State *L) { _argCheck(L, "playerDelete", 1, 1); playerDelete(_argPlayer(L, "playerDelete", 1)); return 0; } // ground, nx, ny, nz = playerGetGround(node): the node stood on (nil in the air) and the normal. static int32_t apiPlayerGetGround(lua_State *L) { Vec3T normal; int32_t ground; _argCheck(L, "playerGetGround", 1, 1); ground = playerGetGround(_argPlayer(L, "playerGetGround", 1), &normal); if (ground < 0) { lua_pushnil(L); } else { lua_pushinteger(L, ground); } _pushVec3(L, normal); return 4; } static int32_t apiPlayerGetVelocity(lua_State *L) { _argCheck(L, "playerGetVelocity", 1, 1); _pushVec3(L, playerGetVelocity(_argPlayer(L, "playerGetVelocity", 1))); return 3; } static int32_t apiPlayerIsOnGround(lua_State *L) { _argCheck(L, "playerIsOnGround", 1, 1); lua_pushboolean(L, playerIsOnGround(_argPlayer(L, "playerIsOnGround", 1))); return 1; } static int32_t apiPlayerIsSwimming(lua_State *L) { _argCheck(L, "playerIsSwimming", 1, 1); lua_pushboolean(L, playerIsSwimming(_argPlayer(L, "playerIsSwimming", 1))); return 1; } static int32_t apiPlayerJump(lua_State *L) { _argCheck(L, "playerJump", 2, 2); lua_pushboolean(L, playerJump(_argPlayer(L, "playerJump", 1), (float)_argNumber(L, "playerJump", 2))); return 1; } // playerMove(node, vx, vz), playerMove(node, vx, vy, vz) when swimming, or in a 2D world playerMove(node, vx) static int32_t apiPlayerMove(lua_State *L) { int32_t node = 0; float vx = 0.0f; float vy = 0.0f; float vz = 0.0f; _argCheck(L, "playerMove", 2, 4); node = _argPlayer(L, "playerMove", 1); vx = (float)_argNumber(L, "playerMove", 2); if (lua_gettop(L) == 3) { vz = (float)_argNumber(L, "playerMove", 3); } else if (lua_gettop(L) == 4) { vy = (float)_argNumber(L, "playerMove", 3); vz = (float)_argNumber(L, "playerMove", 4); } playerMove(node, vec3(vx, vy, vz)); return 0; } // playerNew(node, radius, height) for a capsule, or playerNew(node, SHAPE_*, a, b, c). Three arguments // always mean a capsule, so a sphere or cylinder spells out every size (SHAPE_HULL needs none). static int32_t apiPlayerNew(lua_State *L) { int32_t node = 0; int32_t shape = SHAPE_CAPSULE; float dims[SHAPE_SIZES] = { 0.0f, 0.0f, 0.0f }; int32_t first = 2; int32_t x = 0; _argCheck(L, "playerNew", 2, 5); node = _argNode(L, "playerNew", 1); if (lua_isinteger(L, 2) && (lua_tointeger(L, 2) >= SHAPE_BOX) && (lua_tointeger(L, 2) <= SHAPE_MESH) && (lua_gettop(L) != 3)) { shape = (int32_t)lua_tointeger(L, 2); first = 3; } for (x = 0; x < SHAPE_SIZES; x++) { if (lua_gettop(L) >= first + x) { dims[x] = (float)_argNumber(L, "playerNew", first + x); } } if (shape == SHAPE_MESH) { _luaDie(L, "playerNew", "A player needs a convex shape; use SHAPE_HULL."); } if (!physicsAvailable()) { _luaDie(L, "playerNew", "Physics is not available on this machine."); } if (!playerNew(node, (ShapeTypeE)shape, dims[0], dims[1], dims[2])) { _luaDie(L, "playerNew", "Unable to create the player."); } _luaTrace(L, "playerNew", "node %d shape %d", node, shape); return 0; } static int32_t apiPlayerSetEnabled(lua_State *L) { _argCheck(L, "playerSetEnabled", 2, 2); playerSetEnabled(_argPlayer(L, "playerSetEnabled", 1), _argBoolean(L, "playerSetEnabled", 2)); return 0; } static int32_t apiPlayerSetGravityScale(lua_State *L) { _argCheck(L, "playerSetGravityScale", 2, 2); playerSetGravityScale(_argPlayer(L, "playerSetGravityScale", 1), (float)_argNumber(L, "playerSetGravityScale", 2)); return 0; } static int32_t apiPlayerSetMass(lua_State *L) { _argCheck(L, "playerSetMass", 2, 2); playerSetMass(_argPlayer(L, "playerSetMass", 1), (float)_argNumber(L, "playerSetMass", 2)); return 0; } static int32_t apiPlayerSetPosition(lua_State *L) { _argCheck(L, "playerSetPosition", 4, 4); playerSetPosition(_argPlayer(L, "playerSetPosition", 1), _argVec3(L, "playerSetPosition", 2)); return 0; } static int32_t apiPlayerSetPush(lua_State *L) { _argCheck(L, "playerSetPush", 2, 2); playerSetPush(_argPlayer(L, "playerSetPush", 1), (float)_argNumber(L, "playerSetPush", 2)); return 0; } static int32_t apiPlayerSetSlope(lua_State *L) { _argCheck(L, "playerSetSlope", 2, 2); playerSetSlope(_argPlayer(L, "playerSetSlope", 1), (float)_argNumber(L, "playerSetSlope", 2)); return 0; } static int32_t apiPlayerSetStep(lua_State *L) { _argCheck(L, "playerSetStep", 2, 2); playerSetStep(_argPlayer(L, "playerSetStep", 1), (float)_argNumber(L, "playerSetStep", 2)); return 0; } // playerSetSwim(node, sinkSpeed, drag): how the player behaves in water static int32_t apiPlayerSetSwim(lua_State *L) { _argCheck(L, "playerSetSwim", 3, 3); playerSetSwim(_argPlayer(L, "playerSetSwim", 1), (float)_argNumber(L, "playerSetSwim", 2), (float)_argNumber(L, "playerSetSwim", 3)); return 0; } static int32_t apiPlayerSetVelocity(lua_State *L) { _argCheck(L, "playerSetVelocity", 4, 4); playerSetVelocity(_argPlayer(L, "playerSetVelocity", 1), _argVec3(L, "playerSetVelocity", 2)); return 0; } static int32_t apiRagdollActivate(lua_State *L) { _argCheck(L, "ragdollActivate", 1, 1); if (!ragdollActivate(_argRagdoll(L, "ragdollActivate", 1))) { _luaDie(L, "ragdollActivate", "Unable to build the ragdoll's bodies."); } return 0; } // ragdollApplyImpulse(node, jointName, x, y, z) static int32_t apiRagdollApplyImpulse(lua_State *L) { _argCheck(L, "ragdollApplyImpulse", 5, 5); lua_pushboolean(L, ragdollApplyImpulse(_argRagdoll(L, "ragdollApplyImpulse", 1), _argString(L, "ragdollApplyImpulse", 2), _argVec3(L, "ragdollApplyImpulse", 3))); return 1; } static int32_t apiRagdollDeactivate(lua_State *L) { _argCheck(L, "ragdollDeactivate", 1, 1); ragdollDeactivate(_argRagdoll(L, "ragdollDeactivate", 1)); return 0; } static int32_t apiRagdollDelete(lua_State *L) { _argCheck(L, "ragdollDelete", 1, 1); ragdollDelete(_argRagdoll(L, "ragdollDelete", 1)); return 0; } static int32_t apiRagdollIsActive(lua_State *L) { _argCheck(L, "ragdollIsActive", 1, 1); lua_pushboolean(L, ragdollIsActive(_argRagdoll(L, "ragdollIsActive", 1))); return 1; } static int32_t apiRagdollIsResting(lua_State *L) { _argCheck(L, "ragdollIsResting", 1, 1); lua_pushboolean(L, ragdollIsResting(_argRagdoll(L, "ragdollIsResting", 1))); return 1; } // ragdollNew(node): node is a model instance with a skin static int32_t apiRagdollNew(lua_State *L) { int32_t node = 0; _argCheck(L, "ragdollNew", 1, 1); node = _argNode(L, "ragdollNew", 1); if (!physicsAvailable()) { _luaDie(L, "ragdollNew", "Physics is not available on this machine."); } if (!ragdollNew(node)) { _luaDie(L, "ragdollNew", "Node %d has no skinned model with bones under it.", node); } return 0; } // ragdollSetJoint(node, jointName, radius, swingDegrees, twistDegrees) static int32_t apiRagdollSetJoint(lua_State *L) { _argCheck(L, "ragdollSetJoint", 5, 5); if (!ragdollSetJoint(_argRagdoll(L, "ragdollSetJoint", 1), _argString(L, "ragdollSetJoint", 2), (float)_argNumber(L, "ragdollSetJoint", 3), (float)_argNumber(L, "ragdollSetJoint", 4), (float)_argNumber(L, "ragdollSetJoint", 5))) { _luaDie(L, "ragdollSetJoint", "No bone by that name."); } return 0; } static int32_t apiRagdollSetStrength(lua_State *L) { _argCheck(L, "ragdollSetStrength", 2, 2); ragdollSetStrength(_argRagdoll(L, "ragdollSetStrength", 1), (float)_argNumber(L, "ragdollSetStrength", 2)); return 0; } // sceneEnable(bool): turns the 3D layer on or off static int32_t apiSceneEnable(lua_State *L) { bool enabled; _argCheck(L, "sceneEnable", 1, 1); enabled = _argBoolean(L, "sceneEnable", 1); if (!sceneEnable(enabled)) { _luaDie(L, "sceneEnable", "3D is not available on this machine."); } _global.refreshDisplay = true; _luaTrace(L, "sceneEnable", "%s", enabled ? "on" : "off"); return 0; } // width, height = sceneGetSize(): the layer's size in overlay coordinates static int32_t apiSceneGetSize(lua_State *L) { int32_t width; int32_t height; sceneGetSize(&width, &height); lua_pushinteger(L, width); lua_pushinteger(L, height); return 2; } // sceneGetStats(): last frame's draws collected, inside the view, and draw calls made static int32_t apiSceneGetStats(lua_State *L) { int32_t total = 0; int32_t drawn = 0; int32_t batches = 0; int64_t textureBytes = 0; sceneGetStats(&total, &drawn, &batches, &textureBytes); lua_pushinteger(L, total); lua_pushinteger(L, drawn); lua_pushinteger(L, batches); lua_pushinteger(L, textureBytes / BYTES_PER_KIB); return 4; } // x, y, depth, inFront = sceneProject(wx, wy, wz): a world point in overlay coordinates static int32_t apiSceneProject(lua_State *L) { float x; float y; float depth; bool inFront; _argCheck(L, "sceneProject", 3, 3); inFront = sceneProject(_argVec3(L, "sceneProject", 1), &x, &y, &depth); lua_pushnumber(L, x); lua_pushnumber(L, y); lua_pushnumber(L, depth); lua_pushboolean(L, inFront); return 4; } // sceneSetAmbient(r, g, b): light from everywhere static int32_t apiSceneSetAmbient(lua_State *L) { _argCheck(L, "sceneSetAmbient", 3, 3); sceneSetAmbient((uint8_t)_argInteger(L, "sceneSetAmbient", 1), (uint8_t)_argInteger(L, "sceneSetAmbient", 2), (uint8_t)_argInteger(L, "sceneSetAmbient", 3)); return 0; } // sceneSetAntialias(bool): 4x multisampling, on by default where the GPU offers it static int32_t apiSceneSetAntialias(lua_State *L) { _argCheck(L, "sceneSetAntialias", 1, 1); sceneSetAntialias(_argBoolean(L, "sceneSetAntialias", 1)); return 0; } // The colour the scene clears to; alpha below 255 lets the video show through. static int32_t apiSceneSetBackground(lua_State *L) { int32_t r; int32_t g; int32_t b; int32_t a; _argCheck(L, "sceneSetBackground", 3, 4); r = _argInteger(L, "sceneSetBackground", 1); g = _argInteger(L, "sceneSetBackground", 2); b = _argInteger(L, "sceneSetBackground", 3); a = (lua_gettop(L) >= 4) ? _argInteger(L, "sceneSetBackground", 4) : SDL_ALPHA_OPAQUE; sceneSetBackground((uint8_t)r, (uint8_t)g, (uint8_t)b, (uint8_t)a); _global.refreshDisplay = true; return 0; } // sceneSetBloom(threshold, strength): the glow of everything brighter than the threshold; strength 0 for none static int32_t apiSceneSetBloom(lua_State *L) { _argCheck(L, "sceneSetBloom", 2, 2); sceneSetBloom((float)_argNumber(L, "sceneSetBloom", 1), (float)_argNumber(L, "sceneSetBloom", 2)); return 0; } // sceneSetEnvironment(lit): whether the sky lights the scene static int32_t apiSceneSetEnvironment(lua_State *L) { _argCheck(L, "sceneSetEnvironment", 1, 1); sceneSetEnvironment(_argBoolean(L, "sceneSetEnvironment", 1)); return 0; } static int32_t apiSceneSetExposure(lua_State *L) { _argCheck(L, "sceneSetExposure", 1, 1); sceneSetExposure((float)_argNumber(L, "sceneSetExposure", 1)); return 0; } // sceneSetFog(r, g, b, near, far) or sceneSetFog() for none static int32_t apiSceneSetFog(lua_State *L) { _argCheck(L, "sceneSetFog", 0, 5); if (lua_gettop(L) == 0) { sceneSetFog(0, 0, 0, 0.0f, 0.0f); return 0; } if (lua_gettop(L) != 5) { _luaDie(L, "sceneSetFog", "Expected r, g, b, near, far or nothing."); } sceneSetFog((uint8_t)_argInteger(L, "sceneSetFog", 1), (uint8_t)_argInteger(L, "sceneSetFog", 2), (uint8_t)_argInteger(L, "sceneSetFog", 3), (float)_argNumber(L, "sceneSetFog", 4), (float)_argNumber(L, "sceneSetFog", 5)); return 0; } // sceneSetShadowCascades(count): 1 to 4 maps along the view for a directional light's shadow static int32_t apiSceneSetShadowCascades(lua_State *L) { _argCheck(L, "sceneSetShadowCascades", 1, 1); sceneSetShadowCascades(_argInteger(L, "sceneSetShadowCascades", 1)); return 0; } // sceneSetShadowDistance(distance): how far from the camera cascaded shadows reach static int32_t apiSceneSetShadowDistance(lua_State *L) { _argCheck(L, "sceneSetShadowDistance", 1, 1); sceneSetShadowDistance((float)_argNumber(L, "sceneSetShadowDistance", 1)); return 0; } // sceneSetShadowSize(size): shadow map texels per side, 256 to 4096 (default 1024) static int32_t apiSceneSetShadowSize(lua_State *L) { _argCheck(L, "sceneSetShadowSize", 1, 1); sceneSetShadowSize(_argInteger(L, "sceneSetShadowSize", 1)); return 0; } // sceneSetSky(file) from an equirectangular image (Radiance .hdr keeps its range; PNG or JPEG is // decoded from sRGB), or sceneSetSky() / sceneSetSky(nil) for none static int32_t apiSceneSetSky(lua_State *L) { float *rgb = NULL; int32_t width = 0; int32_t height = 0; _argCheck(L, "sceneSetSky", 0, 1); if ((lua_gettop(L) == 0) || lua_isnil(L, 1)) { sceneSetSky(NULL, 0, 0); return 0; } rgb = hdrLoad(_argString(L, "sceneSetSky", 1), &width, &height); if (rgb == NULL) { _luaDie(L, "sceneSetSky", "%s", SDL_GetError()); } if (!sceneSetSky(rgb, width, height)) { SDL_free(rgb); _luaDie(L, "sceneSetSky", "Unable to build the sky (is 3D available?)."); } SDL_free(rgb); _luaTrace(L, "sceneSetSky", "%dx%d", width, height); return 0; } // sceneSetSkyIntensity(scale): brightness of the sky and its light, 1 as loaded static int32_t apiSceneSetSkyIntensity(lua_State *L) { _argCheck(L, "sceneSetSkyIntensity", 1, 1); sceneSetSkyIntensity((float)_argNumber(L, "sceneSetSkyIntensity", 1)); return 0; } static int32_t apiSceneSetTonemap(lua_State *L) { int32_t tonemap = 0; _argCheck(L, "sceneSetTonemap", 1, 1); tonemap = _argInteger(L, "sceneSetTonemap", 1); if ((tonemap != TONEMAP_NONE) && (tonemap != TONEMAP_NEUTRAL) && (tonemap != TONEMAP_ACES)) { _luaDie(L, "sceneSetTonemap", "Tonemap must be TONEMAP_NONE, TONEMAP_NEUTRAL or TONEMAP_ACES."); } sceneSetTonemap((SceneTonemapE)tonemap); return 0; } // x, y, z = sceneUnproject(sx, sy, distance): the world point that far along the ray through an overlay point static int32_t apiSceneUnproject(lua_State *L) { _argCheck(L, "sceneUnproject", 3, 3); return _pushVec3(L, sceneUnproject((float)_argNumber(L, "sceneUnproject", 1), (float)_argNumber(L, "sceneUnproject", 2), (float)_argNumber(L, "sceneUnproject", 3))); } // scriptExecute(config) Runs another script after this one ends. static int32_t apiScriptExecute(lua_State *L) { ConfigT *conf = _scriptConfFromTable(L, "scriptExecute"); queueScript(conf); destroyConf(&conf); _global.running = false; _luaTrace(L, "scriptExecute", "Queued."); return 0; } // scriptPush(config) Runs another script, then returns to this one. static int32_t apiScriptPush(lua_State *L) { ConfigT *conf = _scriptConfFromTable(L, "scriptPush"); queueScript(conf); destroyConf(&conf); queueScript(_global.conf); _global.running = false; _luaTrace(L, "scriptPush", "Queued."); return 0; } // milliseconds = singeGetAudioCalibration() The per-machine value saved by the menu's calibration screen. static int32_t apiSingeGetAudioCalibration(lua_State *L) { int32_t value = videoGetAudioCalibration(); _luaTrace(L, "singeGetAudioCalibration", "%d", value); lua_pushinteger(L, value); return 1; } // milliseconds = singeGetAudioDelay() static int32_t apiSingeGetAudioDelay(lua_State *L) { int32_t delay = videoGetAudioDelay(); _luaTrace(L, "singeGetAudioDelay", "%d", delay); lua_pushinteger(L, delay); return 1; } // milliseconds = singeGetAudioLatency() The audio device queue measured at startup. static int32_t apiSingeGetAudioLatency(lua_State *L) { int32_t value = videoGetAudioLatency(); _luaTrace(L, "singeGetAudioLatency", "%d", value); lua_pushinteger(L, value); return 1; } // path = singeGetDataPath() static int32_t apiSingeGetDataPath(lua_State *L) { _luaTrace(L, "singeGetDataPath", "%s", _global.conf->dataDir); lua_pushstring(L, _global.conf->dataDir); return 1; } // height = singeGetHeight() Window height in pixels. static int32_t apiSingeGetHeight(lua_State *L) { int32_t y = 0; SDL_GetWindowSize(_global.window, NULL, &y); _luaTrace(L, "singeGetHeight", "%d", y); lua_pushinteger(L, y); return 1; } // paused = singeGetPauseFlag() static int32_t apiSingeGetPauseFlag(lua_State *L) { _luaTrace(L, "singeGetPauseFlag", "%d", _global.pauseState); lua_pushboolean(L, _global.pauseState); return 1; } // path = singeGetScriptPath() static int32_t apiSingeGetScriptPath(lua_State *L) { _luaTrace(L, "singeGetScriptPath", "%s", _global.conf->scriptFile); lua_pushstring(L, _global.conf->scriptFile); return 1; } // milliseconds = singeGetTicks() Wall clock since the engine started. static int32_t apiSingeGetTicks(lua_State *L) { uint64_t ticks = SDL_GetTicks(); _luaTrace(L, "singeGetTicks", "%" PRIu64, ticks); lua_pushinteger(L, (lua_Integer)ticks); return 1; } // width = singeGetWidth() Window width in pixels. static int32_t apiSingeGetWidth(lua_State *L) { int32_t x = 0; SDL_GetWindowSize(_global.window, &x, NULL); _luaTrace(L, "singeGetWidth", "%d", x); lua_pushinteger(L, x); return 1; } // singeQuit() static int32_t apiSingeQuit(lua_State *L) { _luaTrace(L, "singeQuit", "Quit requested."); _global.running = false; return 0; } // singeReload(): runs the game again from its script at the end of this frame static int32_t apiSingeReload(lua_State *L) { _luaTrace(L, "singeReload", "Reload requested."); _global.reloadRequested = true; return 0; } // singeScreenshot() Saved after the next frame is drawn. static int32_t apiSingeScreenshot(lua_State *L) { _luaTrace(L, "singeScreenshot", "Screenshot requested."); _global.requestScreenShot = true; _global.refreshDisplay = true; return 0; } // singeSetAudioCalibration(milliseconds) Applies now and is remembered for every game on this machine. static int32_t apiSingeSetAudioCalibration(lua_State *L) { int32_t value = 0; _argCheck(L, "singeSetAudioCalibration", 1, 1); value = _argInteger(L, "singeSetAudioCalibration", 1); if ((value < -VIDEO_AUDIO_DELAY_MAX) || (value > VIDEO_AUDIO_DELAY_MAX)) { _luaDie(L, "singeSetAudioCalibration", "Audio calibration must be between %d and %d milliseconds: %d", -VIDEO_AUDIO_DELAY_MAX, VIDEO_AUDIO_DELAY_MAX, value); } videoSetAudioCalibration(value); _saveAudioCalibration(value); _luaTrace(L, "singeSetAudioCalibration", "%d", value); return 0; } // singeSetGameName(title) static int32_t apiSingeSetGameName(lua_State *L) { const char *title = NULL; _argCheck(L, "singeSetGameName", 1, 1); title = _argString(L, "singeSetGameName", 1); SDL_SetWindowTitle(_global.window, title); _luaTrace(L, "singeSetGameName", "%s", title); return 0; } // singeSetPauseFlag(paused) static int32_t apiSingeSetPauseFlag(lua_State *L) { _argCheck(L, "singeSetPauseFlag", 1, 1); _setPause(_argBoolean(L, "singeSetPauseFlag", 1), false); _luaTrace(L, "singeSetPauseFlag", "%d", _global.pauseState); return 0; } // singeSetAudioDelay(milliseconds) Positive when the audio device is heard later than it reports. static int32_t apiSingeSetAudioDelay(lua_State *L) { int32_t delay = 0; _argCheck(L, "singeSetAudioDelay", 1, 1); delay = _argInteger(L, "singeSetAudioDelay", 1); if ((delay < -VIDEO_AUDIO_DELAY_MAX) || (delay > VIDEO_AUDIO_DELAY_MAX)) { _luaDie(L, "singeSetAudioDelay", "Audio delay must be between %d and %d milliseconds: %d", -VIDEO_AUDIO_DELAY_MAX, VIDEO_AUDIO_DELAY_MAX, delay); } videoSetAudioDelay(delay); _luaTrace(L, "singeSetAudioDelay", "%d", delay); return 0; } // singeSetPauseKeyEnabled(enabled) Framework.singe aliases singeEnablePauseKey()/singeDisablePauseKey(). static int32_t apiSingeSetPauseKeyEnabled(lua_State *L) { _argCheck(L, "singeSetPauseKeyEnabled", 1, 1); _global.pauseEnabled = _argBoolean(L, "singeSetPauseKeyEnabled", 1); _luaTrace(L, "singeSetPauseKeyEnabled", "%d", _global.pauseEnabled); return 0; } // version = singeVersion() static int32_t apiSingeVersion(lua_State *L) { _luaTrace(L, "singeVersion", "%s", VERSION_STRING); lua_pushnumber(L, SINGE_VERSION); return 1; } // wanted = singeWantsCrosshairs() False when --nocrosshair was given. static int32_t apiSingeWantsCrosshairs(lua_State *L) { bool wanted = !_global.conf->noCrosshair; _luaTrace(L, "singeWantsCrosshairs", "%d", wanted); lua_pushboolean(L, wanted); return 1; } static int32_t apiSoftDelete(lua_State *L) { _argCheck(L, "softDelete", 1, 1); softDelete(_argSoft(L, "softDelete", 1)); return 0; } // softNew(node, SOFT_CLOTH | SOFT_BODY) from the node's mesh, or softNew(node, SOFT_ROPE, x, y, z, segments, radius) static int32_t apiSoftNew(lua_State *L) { int32_t node = 0; int32_t kind = 0; bool ok = false; _argCheck(L, "softNew", 2, 7); node = _argNode(L, "softNew", 1); kind = _argInteger(L, "softNew", 2); if (!physicsAvailable()) { _luaDie(L, "softNew", "Physics is not available on this machine."); } if (kind == SOFT_ROPE) { _argCheck(L, "softNew", 7, 7); ok = softNewRope(node, _argVec3(L, "softNew", 3), _argInteger(L, "softNew", 6), (float)_argNumber(L, "softNew", 7)); } else if ((kind == SOFT_CLOTH) || (kind == SOFT_BODY)) { _argCheck(L, "softNew", 2, 2); ok = softNew(node, (SoftKindE)kind); } else { _luaDie(L, "softNew", "Unknown soft body kind %d.", kind); } if (!ok) { _luaDie(L, "softNew", "Unable to make the soft body: the node needs a mesh (or a rope needs a length)."); } _luaTrace(L, "softNew", "node %d kind %d", node, kind); return 0; } // softPin(node, x, y, z[, otherNode]): hold the nearest particle there, or to that node static int32_t apiSoftPin(lua_State *L) { int32_t follow = -1; _argCheck(L, "softPin", 4, 5); if (lua_gettop(L) == 5) { follow = _argNode(L, "softPin", 5); } softPin(_argSoft(L, "softPin", 1), _argVec3(L, "softPin", 2), follow); return 0; } static int32_t apiSoftSetDamping(lua_State *L) { _argCheck(L, "softSetDamping", 2, 2); softSetDamping(_argSoft(L, "softSetDamping", 1), (float)_argNumber(L, "softSetDamping", 2)); return 0; } static int32_t apiSoftSetMass(lua_State *L) { _argCheck(L, "softSetMass", 2, 2); softSetMass(_argSoft(L, "softSetMass", 1), (float)_argNumber(L, "softSetMass", 2)); return 0; } static int32_t apiSoftSetPressure(lua_State *L) { _argCheck(L, "softSetPressure", 2, 2); softSetPressure(_argSoft(L, "softSetPressure", 1), (float)_argNumber(L, "softSetPressure", 2)); return 0; } static int32_t apiSoftSetStiffness(lua_State *L) { _argCheck(L, "softSetStiffness", 3, 3); softSetStiffness(_argSoft(L, "softSetStiffness", 1), (float)_argNumber(L, "softSetStiffness", 2), (float)_argNumber(L, "softSetStiffness", 3)); return 0; } static int32_t apiSoftUnpin(lua_State *L) { _argCheck(L, "softUnpin", 4, 4); softUnpin(_argSoft(L, "softUnpin", 1), _argVec3(L, "softUnpin", 2)); return 0; } // soundFullStop() Halts every sound effect channel. static int32_t apiSoundFullStop(lua_State *L) { _luaTrace(L, "soundFullStop", "Halting all channels."); MIX_StopTag(videoGetMixer(), EFFECT_TAG, 0); return 0; } // soundGetPosition(channel): where a positioned channel sits relative to the listener (x right, y up, // z back, unit length) and its distance gain; a channel with no position returns 0, 0, 0, 1. static int32_t apiSoundGetPosition(lua_State *L) { int32_t channel; EffectT *effect; _argCheck(L, "soundGetPosition", 1, 1); channel = _argChannel(L, "soundGetPosition", 1); effect = &_effects[channel]; lua_pushnumber(L, effect->relative[0]); lua_pushnumber(L, effect->relative[1]); lua_pushnumber(L, effect->relative[2]); lua_pushnumber(L, effect->gain); return 4; } // volume = soundGetVolume() 0 to AUDIO_MAX_VOLUME. static int32_t apiSoundGetVolume(lua_State *L) { _luaTrace(L, "soundGetVolume", "%d", _global.effectsVolume); lua_pushinteger(L, _global.effectsVolume); return 1; } // playing = soundIsPlaying(channel) static int32_t apiSoundIsPlaying(lua_State *L) { int32_t channel = 0; bool playing = false; _argCheck(L, "soundIsPlaying", 1, 1); channel = _argChannel(L, "soundIsPlaying", 1); playing = MIX_TrackPlaying(_effectTracks[channel]); _luaTrace(L, "soundIsPlaying", "%d %d", channel, playing); lua_pushboolean(L, playing); return 1; } // id = soundLoad(filename) static int32_t apiSoundLoad(lua_State *L) { const char *name = NULL; SoundT *sound = NULL; SDL_IOStream *io = NULL; _argCheck(L, "soundLoad", 1, 1); name = _argString(L, "soundLoad", 1); io = vfsOpenIO(name); if (io == NULL) { _luaDie(L, "soundLoad", "Unable to open %s", name); } sound = (SoundT *)calloc(1, sizeof(SoundT)); if (!sound) { _luaDie(L, "soundLoad", "Unable to allocate new sound."); } sound->audio = MIX_LoadAudio_IO(videoGetMixer(), io, true, true); if (!sound->audio) { _luaDie(L, "soundLoad", "%s", SDL_GetError()); } sound->id = _global.nextSoundId++; HASH_ADD_INT(_global.soundList, id, sound); _luaTrace(L, "soundLoad", "%d %s", sound->id, name); lua_pushinteger(L, sound->id); return 1; } // wasPlaying = soundPause(channel) static int32_t apiSoundPause(lua_State *L) { int32_t channel = 0; bool playing = false; _argCheck(L, "soundPause", 1, 1); channel = _argChannel(L, "soundPause", 1); playing = MIX_TrackPlaying(_effectTracks[channel]); MIX_PauseTrack(_effectTracks[channel]); _luaTrace(L, "soundPause", "%d %d", channel, playing); lua_pushboolean(L, playing); return 1; } // channel = soundPlay(id) Returns -1 (SOUND_ERROR_INVALID) when every channel is busy. // soundPlay(id [, loops]): loops 0 (the default) plays once, N repeats N more times, -1 forever static int32_t apiSoundPlay(lua_State *L) { SoundT *sound = NULL; int32_t channel = SOUND_CHANNEL_NONE; int32_t loops = 0; SDL_PropertiesID options; _argCheck(L, "soundPlay", 1, 2); sound = _argSound(L, "soundPlay", 1); if (lua_gettop(L) >= 2) { loops = _argInteger(L, "soundPlay", 2); if (loops < SOUND_LOOP_FOREVER) { _luaDie(L, "soundPlay", "Loops must be -1 (forever), 0 (once) or a repeat count: %d", loops); } } channel = _effectTrackFree(); if (channel >= 0) { _effectReset(channel); MIX_SetTrackAudio(_effectTracks[channel], sound->audio); MIX_SetTrackGain(_effectTracks[channel], _mixerGain(_global.effectsVolume)); options = SDL_CreateProperties(); SDL_SetNumberProperty(options, MIX_PROP_PLAY_LOOPS_NUMBER, loops); if (!MIX_PlayTrack(_effectTracks[channel], options)) { SDL_DestroyProperties(options); _luaDie(L, "soundPlay", "%s", SDL_GetError()); } SDL_DestroyProperties(options); } _luaTrace(L, "soundPlay", "%d %d loops %d", sound->id, channel, loops); lua_pushinteger(L, channel); return 1; } // wasPaused = soundResume(channel) static int32_t apiSoundResume(lua_State *L) { int32_t channel = 0; bool paused = false; _argCheck(L, "soundResume", 1, 1); channel = _argChannel(L, "soundResume", 1); paused = MIX_TrackPaused(_effectTracks[channel]); MIX_ResumeTrack(_effectTracks[channel]); _luaTrace(L, "soundResume", "%d %d", channel, paused); lua_pushboolean(L, paused); return 1; } // soundSetListener([node]): positioned sounds are heard from this node; none means the scene camera static int32_t apiSoundSetListener(lua_State *L) { _argCheck(L, "soundSetListener", 0, 1); _global.listenerNode = ((lua_gettop(L) >= 1) && !lua_isnil(L, 1)) ? _argNode(L, "soundSetListener", 1) : LISTENER_CAMERA; _luaTrace(L, "soundSetListener", "%d", _global.listenerNode); return 0; } // soundSetNode(channel [, node]): the channel follows the node through the scene; none stops following static int32_t apiSoundSetNode(lua_State *L) { int32_t channel; EffectT *effect; _argCheck(L, "soundSetNode", 1, 2); channel = _argChannel(L, "soundSetNode", 1); effect = &_effects[channel]; if ((lua_gettop(L) >= 2) && !lua_isnil(L, 2)) { effect->node = _argNode(L, "soundSetNode", 2); effect->positioned = true; } else { effect->node = LISTENER_CAMERA; effect->positioned = false; MIX_SetTrack3DPosition(_effectTracks[channel], NULL); MIX_SetTrackGain(_effectTracks[channel], _mixerGain(_global.effectsVolume)); } return 0; } // soundSetPan(channel, pan): -1 left to 1 right for sounds that are not in the scene static int32_t apiSoundSetPan(lua_State *L) { int32_t channel; float pan; MIX_StereoGains gains; _argCheck(L, "soundSetPan", 2, 2); channel = _argChannel(L, "soundSetPan", 1); pan = SDL_clamp((float)_argNumber(L, "soundSetPan", 2), -1.0f, 1.0f); _effects[channel].positioned = false; _effects[channel].node = LISTENER_CAMERA; gains.left = SDL_min(1.0f, 1.0f - pan); gains.right = SDL_min(1.0f, 1.0f + pan); MIX_SetTrackStereo(_effectTracks[channel], &gains); MIX_SetTrackGain(_effectTracks[channel], _mixerGain(_global.effectsVolume)); return 0; } // soundSetPosition(channel, x, y, z): places the channel in the scene, in world units static int32_t apiSoundSetPosition(lua_State *L) { int32_t channel; EffectT *effect; _argCheck(L, "soundSetPosition", 4, 4); channel = _argChannel(L, "soundSetPosition", 1); effect = &_effects[channel]; effect->node = LISTENER_CAMERA; effect->position = vec3((float)_argNumber(L, "soundSetPosition", 2), (float)_argNumber(L, "soundSetPosition", 3), (float)_argNumber(L, "soundSetPosition", 4)); effect->positioned = true; return 0; } // soundSetRange(channel, near, far): full volume within near, silent beyond far static int32_t apiSoundSetRange(lua_State *L) { int32_t channel; EffectT *effect; float nearBy; float farOff; _argCheck(L, "soundSetRange", 3, 3); channel = _argChannel(L, "soundSetRange", 1); nearBy = (float)_argNumber(L, "soundSetRange", 2); farOff = (float)_argNumber(L, "soundSetRange", 3); if ((nearBy <= 0.0f) || (farOff <= nearBy)) { _luaDie(L, "soundSetRange", "Range needs 0 < near < far: %.2f %.2f", nearBy, farOff); } effect = &_effects[channel]; effect->nearBy = nearBy; effect->farOff = farOff; return 0; } // soundSetVolume(volume) 0 to AUDIO_MAX_VOLUME, applied to every effect channel. static int32_t apiSoundSetVolume(lua_State *L) { int32_t volume = 0; _argCheck(L, "soundSetVolume", 1, 1); volume = _argInteger(L, "soundSetVolume", 1); if ((volume < 0) || (volume > AUDIO_MAX_VOLUME)) { _luaDie(L, "soundSetVolume", "Invalid sound volume value: %d", volume); } _global.effectsVolume = volume; MIX_SetTagGain(videoGetMixer(), EFFECT_TAG, _mixerGain(_global.effectsVolume)); _luaTrace(L, "soundSetVolume", "%d", _global.effectsVolume); return 0; } // wasPlaying = soundStop(channel) static int32_t apiSoundStop(lua_State *L) { int32_t channel = 0; bool playing = false; _argCheck(L, "soundStop", 1, 1); channel = _argChannel(L, "soundStop", 1); playing = MIX_TrackPlaying(_effectTracks[channel]); MIX_StopTrack(_effectTracks[channel], 0); _luaTrace(L, "soundStop", "%d %d", channel, playing); lua_pushboolean(L, playing); return 1; } // soundUnload(id) static int32_t apiSoundUnload(lua_State *L) { SoundT *sound = NULL; _argCheck(L, "soundUnload", 1, 1); sound = _argSound(L, "soundUnload", 1); _luaTrace(L, "soundUnload", "%d", sound->id); _soundDestroy(sound); return 0; } // spriteDraw(id, x, y[, centered]) - Draw at natural size // spriteDraw(id, x, y, x2, y2[, centered]) - Stretch into the rectangle static int32_t apiSpriteDraw(lua_State *L) { int32_t n = lua_gettop(L); bool center = false; bool stretched = false; bool newFrame = false; uint64_t now = 0; int32_t delay = 0; SpriteT *sprite = NULL; SDL_Rect dest; _argCheck(L, "spriteDraw", 3, 6); sprite = _argSprite(L, "spriteDraw", 1); dest.x = _argInteger(L, "spriteDraw", 2); dest.y = _argInteger(L, "spriteDraw", 3); dest.w = 0; dest.h = 0; if (n >= 5) { stretched = true; dest.w = _argInteger(L, "spriteDraw", 4) - dest.x + 1; dest.h = _argInteger(L, "spriteDraw", 5) - dest.y + 1; } if ((n == 4) || (n == 6)) { center = _argBoolean(L, "spriteDraw", n); } // Advance animation, if any. if ((sprite->animation != NULL) && sprite->animating) { now = SDL_GetTicks(); sprite->ticks += now - sprite->lastTick; sprite->lastTick = now; // Whole loops (after a long pause, say) land on the same frame, so they need not be stepped. if (sprite->loop && (sprite->ticks >= sprite->loopMs)) { sprite->ticks %= sprite->loopMs; } while (sprite->animating) { delay = sprite->animation->delays[sprite->currentFrame]; if (delay < ANIMATION_MIN_DELAY_MS) { delay = ANIMATION_MIN_DELAY_MS; } if (sprite->ticks < (uint64_t)delay) { break; } sprite->ticks -= (uint64_t)delay; sprite->currentFrame++; newFrame = true; if (sprite->currentFrame >= sprite->animation->count) { if (sprite->loop) { sprite->currentFrame = 0; } else { sprite->currentFrame = sprite->animation->count - 1; sprite->animating = false; } } } if (newFrame) { sprite->originalSurface = sprite->animation->frames[sprite->currentFrame]; _spriteRebuildSurface(sprite); } } if (!stretched) { dest.w = sprite->surface->w; dest.h = sprite->surface->h; } if (center) { // Move sprite so the drawing coordinate is the center of the sprite dest.x -= dest.w / 2; dest.y -= dest.h / 2; } if (stretched) { SDL_BlitSurfaceScaled(sprite->surface, NULL, _global.overlay, &dest, SDL_SCALEMODE_NEAREST); } else { SDL_BlitSurface(sprite->surface, NULL, _global.overlay, &dest); } _overlayTouched(); _luaTrace(L, "spriteDraw", "%d %d %d %d %d %d", sprite->id, dest.x, dest.y, dest.w, dest.h, center); return 0; } // frame = spriteGetFrame(id) static int32_t apiSpriteGetFrame(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteGetFrame", 1, 1); sprite = _argSprite(L, "spriteGetFrame", 1); _luaTrace(L, "spriteGetFrame", "%d %d", sprite->id, sprite->currentFrame); lua_pushinteger(L, sprite->currentFrame); return 1; } // height = spriteGetHeight(id) Height as drawn, after scaling and rotation. static int32_t apiSpriteGetHeight(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteGetHeight", 1, 1); sprite = _argSprite(L, "spriteGetHeight", 1); _luaTrace(L, "spriteGetHeight", "%d %d", sprite->id, sprite->surface->h); lua_pushinteger(L, sprite->surface->h); return 1; } // width = spriteGetWidth(id) Width as drawn, after scaling and rotation. static int32_t apiSpriteGetWidth(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteGetWidth", 1, 1); sprite = _argSprite(L, "spriteGetWidth", 1); _luaTrace(L, "spriteGetWidth", "%d %d", sprite->id, sprite->surface->w); lua_pushinteger(L, sprite->surface->w); return 1; } // playing = spriteIsPlaying(id) static int32_t apiSpriteIsPlaying(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteIsPlaying", 1, 1); sprite = _argSprite(L, "spriteIsPlaying", 1); _luaTrace(L, "spriteIsPlaying", "%d %d", sprite->id, sprite->animating); lua_pushboolean(L, sprite->animating); return 1; } // id = spriteLoad(filename) Animated GIF and WEBP files load as animations. static int32_t apiSpriteLoad(lua_State *L) { const char *name = NULL; SpriteT *sprite = NULL; SDL_IOStream *io = NULL; int32_t x = 0; _argCheck(L, "spriteLoad", 1, 1); name = _argString(L, "spriteLoad", 1); sprite = (SpriteT *)calloc(1, sizeof(SpriteT)); if (!sprite) { _luaDie(L, "spriteLoad", "Unable to allocate new sprite."); } // Try to load requested file as an animation first io = vfsOpenIO(name); if (io == NULL) { _luaDie(L, "spriteLoad", "%s", SDL_GetError()); } sprite->animation = IMG_LoadAnimation_IO(io, false); if ((sprite->animation != NULL) && (sprite->animation->count < 2)) { // Only one frame - keep it as a still image (a pixel copy; a blit would premultiply the alpha). sprite->originalSurface = SDL_DuplicateSurface(sprite->animation->frames[0]); if (sprite->originalSurface == NULL) { _luaDie(L, "spriteLoad", "%s", SDL_GetError()); } _surfaceUnpack(&sprite->originalSurface); IMG_FreeAnimation(sprite->animation); sprite->animation = NULL; } else { if (sprite->animation != NULL) { for (x = 0; x < sprite->animation->count; x++) { _surfaceUnpack(&sprite->animation->frames[x]); SDL_SetSurfaceColorKey(sprite->animation->frames[x], true, COLOR_KEY_VALUE); sprite->loopMs += (uint64_t)SDL_max(sprite->animation->delays[x], ANIMATION_MIN_DELAY_MS); } sprite->originalSurface = sprite->animation->frames[0]; } else { SDL_SeekIO(io, 0, SDL_IO_SEEK_SET); sprite->originalSurface = IMG_Load_IO(io, false); _surfaceUnpack(&sprite->originalSurface); } } SDL_CloseIO(io); if (!sprite->originalSurface) { _luaDie(L, "spriteLoad", "%s", SDL_GetError()); } if (sprite->animation == NULL) { // An animation's frames were keyed above, this one included. SDL_SetSurfaceColorKey(sprite->originalSurface, true, COLOR_KEY_VALUE); } sprite->surface = sprite->originalSurface; sprite->scaleX = 1.0; sprite->scaleY = 1.0; sprite->id = _global.nextSpriteId++; HASH_ADD_INT(_global.spriteList, id, sprite); _luaTrace(L, "spriteLoad", "%d %s", sprite->id, name); lua_pushinteger(L, sprite->id); return 1; } // spriteLoop(id, loop) static int32_t apiSpriteLoop(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteLoop", 2, 2); sprite = _argSprite(L, "spriteLoop", 1); sprite->loop = _argBoolean(L, "spriteLoop", 2); _luaTrace(L, "spriteLoop", "%d %d", sprite->id, sprite->loop); return 0; } // spritePause(id) static int32_t apiSpritePause(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spritePause", 1, 1); sprite = _argSprite(L, "spritePause", 1); sprite->animating = false; _luaTrace(L, "spritePause", "%d", sprite->id); return 0; } // spritePlay(id) static int32_t apiSpritePlay(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spritePlay", 1, 1); sprite = _argSprite(L, "spritePlay", 1); if (!sprite->animating) { sprite->lastTick = SDL_GetTicks(); sprite->animating = true; } _luaTrace(L, "spritePlay", "%d", sprite->id); return 0; } // spriteQuality(id, RENDER_PIXELATED | RENDER_SMOOTH) static int32_t apiSpriteQuality(lua_State *L) { SpriteT *sprite = NULL; int32_t smooth = 0; _argCheck(L, "spriteQuality", 2, 2); sprite = _argSprite(L, "spriteQuality", 1); smooth = _argInteger(L, "spriteQuality", 2) ? RENDER_SMOOTH : RENDER_PIXELATED; if (smooth != sprite->smooth) { sprite->smooth = smooth; _spriteRebuildSurface(sprite); } _luaTrace(L, "spriteQuality", "%d %d", sprite->id, sprite->smooth); return 0; } // spriteRotate(id, degrees) Clockwise. static int32_t apiSpriteRotate(lua_State *L) { SpriteT *sprite = NULL; double angle = 0.0; _argCheck(L, "spriteRotate", 2, 2); sprite = _argSprite(L, "spriteRotate", 1); angle = fmod(_argNumber(L, "spriteRotate", 2), DEGREES_PER_CIRCLE); if (angle != sprite->angle) { sprite->angle = angle; _spriteRebuildSurface(sprite); } _luaTrace(L, "spriteRotate", "%d %f", sprite->id, sprite->angle); return 0; } // spriteRotateAndScale(id, degrees, scale) or spriteRotateAndScale(id, degrees, scaleX, scaleY) static int32_t apiSpriteRotateAndScale(lua_State *L) { int32_t n = lua_gettop(L); SpriteT *sprite = NULL; double angle = 0.0; double scaleX = 1.0; double scaleY = 1.0; _argCheck(L, "spriteRotateAndScale", 3, 4); sprite = _argSprite(L, "spriteRotateAndScale", 1); angle = fmod(_argNumber(L, "spriteRotateAndScale", 2), DEGREES_PER_CIRCLE); scaleX = _argNumber(L, "spriteRotateAndScale", 3); scaleY = (n == 4) ? _argNumber(L, "spriteRotateAndScale", 4) : scaleX; if ((angle != sprite->angle) || (scaleX != sprite->scaleX) || (scaleY != sprite->scaleY)) { sprite->angle = angle; sprite->scaleX = scaleX; sprite->scaleY = scaleY; _spriteRebuildSurface(sprite); } _luaTrace(L, "spriteRotateAndScale", "%d %f %f %f", sprite->id, sprite->angle, sprite->scaleX, sprite->scaleY); return 0; } // spriteScale(id, scale) or spriteScale(id, scaleX, scaleY) static int32_t apiSpriteScale(lua_State *L) { int32_t n = lua_gettop(L); SpriteT *sprite = NULL; double scaleX = 1.0; double scaleY = 1.0; _argCheck(L, "spriteScale", 2, 3); sprite = _argSprite(L, "spriteScale", 1); scaleX = _argNumber(L, "spriteScale", 2); scaleY = (n == 3) ? _argNumber(L, "spriteScale", 3) : scaleX; if ((scaleX != sprite->scaleX) || (scaleY != sprite->scaleY)) { sprite->scaleX = scaleX; sprite->scaleY = scaleY; _spriteRebuildSurface(sprite); } _luaTrace(L, "spriteScale", "%d %f %f", sprite->id, sprite->scaleX, sprite->scaleY); return 0; } // spriteSetFrame(id, frame) Ignored for still images and out of range frames. static int32_t apiSpriteSetFrame(lua_State *L) { SpriteT *sprite = NULL; int32_t frame = 0; _argCheck(L, "spriteSetFrame", 2, 2); sprite = _argSprite(L, "spriteSetFrame", 1); frame = _argInteger(L, "spriteSetFrame", 2); if ((sprite->animation != NULL) && (frame >= 0) && (frame < sprite->animation->count) && (frame != sprite->currentFrame)) { sprite->currentFrame = frame; sprite->ticks = 0; sprite->originalSurface = sprite->animation->frames[frame]; _spriteRebuildSurface(sprite); } _luaTrace(L, "spriteSetFrame", "%d %d", sprite->id, sprite->currentFrame); return 0; } // spriteUnload(id) static int32_t apiSpriteUnload(lua_State *L) { SpriteT *sprite = NULL; _argCheck(L, "spriteUnload", 1, 1); sprite = _argSprite(L, "spriteUnload", 1); _luaTrace(L, "spriteUnload", "%d", sprite->id); _spriteDestroy(sprite); return 0; } // height = terrainGetHeight(node, x, z): the terrain's height at a world x, z, or nil off it static int32_t apiTerrainGetHeight(lua_State *L) { float height = 0.0f; _argCheck(L, "terrainGetHeight", 3, 3); if (!terrainGetHeight(_argNode(L, "terrainGetHeight", 1), (float)_argNumber(L, "terrainGetHeight", 2), (float)_argNumber(L, "terrainGetHeight", 3), &height)) { lua_pushnil(L); return 1; } lua_pushnumber(L, height); return 1; } // index = vehicleAddWheel(vehicle, wheelNode, radius, width, suspensionLength) static int32_t apiVehicleAddWheel(lua_State *L) { int32_t index = 0; _argCheck(L, "vehicleAddWheel", 5, 5); index = vehicleAddWheel(_argVehicle(L, "vehicleAddWheel", 1), _argNode(L, "vehicleAddWheel", 2), (float)_argNumber(L, "vehicleAddWheel", 3), (float)_argNumber(L, "vehicleAddWheel", 4), (float)_argNumber(L, "vehicleAddWheel", 5)); if (index < 0) { _luaDie(L, "vehicleAddWheel", "Unable to add the wheel (too many?)."); } lua_pushinteger(L, index); return 1; } static int32_t apiVehicleDelete(lua_State *L) { _argCheck(L, "vehicleDelete", 1, 1); vehicleDelete(_argVehicle(L, "vehicleDelete", 1)); return 0; } // vehicleDrive(vehicle, forward, right, brake, handBrake) static int32_t apiVehicleDrive(lua_State *L) { int32_t node = 0; float in[VEHICLE_DRIVE_INPUTS] = { 0.0f, 0.0f, 0.0f, 0.0f }; int32_t x = 0; _argCheck(L, "vehicleDrive", 3, 5); node = _argVehicle(L, "vehicleDrive", 1); for (x = 0; x < VEHICLE_DRIVE_INPUTS; x++) { if (lua_gettop(L) >= 2 + x) { in[x] = (float)_argNumber(L, "vehicleDrive", 2 + x); } } vehicleDrive(node, in[0], in[1], in[2], in[3]); return 0; } static int32_t apiVehicleGetGear(lua_State *L) { _argCheck(L, "vehicleGetGear", 1, 1); lua_pushinteger(L, vehicleGetGear(_argVehicle(L, "vehicleGetGear", 1))); return 1; } static int32_t apiVehicleGetRpm(lua_State *L) { _argCheck(L, "vehicleGetRpm", 1, 1); lua_pushnumber(L, (lua_Number)vehicleGetRpm(_argVehicle(L, "vehicleGetRpm", 1))); return 1; } static int32_t apiVehicleGetSpeed(lua_State *L) { _argCheck(L, "vehicleGetSpeed", 1, 1); lua_pushnumber(L, (lua_Number)vehicleGetSpeed(_argVehicle(L, "vehicleGetSpeed", 1))); return 1; } static int32_t apiVehicleGetWheelSlip(lua_State *L) { _argCheck(L, "vehicleGetWheelSlip", 2, 2); lua_pushnumber(L, (lua_Number)vehicleGetWheelSlip(_argVehicle(L, "vehicleGetWheelSlip", 1), _argInteger(L, "vehicleGetWheelSlip", 2))); return 1; } static int32_t apiVehicleIsWheelOnGround(lua_State *L) { _argCheck(L, "vehicleIsWheelOnGround", 2, 2); lua_pushboolean(L, vehicleIsWheelOnGround(_argVehicle(L, "vehicleIsWheelOnGround", 1), _argInteger(L, "vehicleIsWheelOnGround", 2))); return 1; } // vehicleNew(node, VEHICLE_CAR | VEHICLE_MOTORCYCLE | VEHICLE_TANK | VEHICLE_BOAT): the node must carry a dynamic body static int32_t apiVehicleNew(lua_State *L) { int32_t node = 0; int32_t kind = 0; _argCheck(L, "vehicleNew", 2, 2); node = _argBody(L, "vehicleNew", 1); kind = _argInteger(L, "vehicleNew", 2); if ((kind < VEHICLE_CAR) || (kind > VEHICLE_BOAT)) { _luaDie(L, "vehicleNew", "Unknown vehicle kind %d.", kind); } if (!vehicleNew(node, (VehicleKindE)kind)) { _luaDie(L, "vehicleNew", "Unable to create the vehicle: the node needs a dynamic body, in a 3D world."); } _luaTrace(L, "vehicleNew", "node %d kind %d", node, kind); return 0; } static int32_t apiVehicleSetAntiRoll(lua_State *L) { _argCheck(L, "vehicleSetAntiRoll", 2, 2); vehicleSetAntiRoll(_argVehicle(L, "vehicleSetAntiRoll", 1), (float)_argNumber(L, "vehicleSetAntiRoll", 2)); return 0; } static int32_t apiVehicleSetBrakes(lua_State *L) { _argCheck(L, "vehicleSetBrakes", 3, 3); vehicleSetBrakes(_argVehicle(L, "vehicleSetBrakes", 1), (float)_argNumber(L, "vehicleSetBrakes", 2), (float)_argNumber(L, "vehicleSetBrakes", 3)); return 0; } // vehicleSetEngine(vehicle, maxTorque, maxRpm[, minRpm]) static int32_t apiVehicleSetEngine(lua_State *L) { _argCheck(L, "vehicleSetEngine", 3, 4); vehicleSetEngine(_argVehicle(L, "vehicleSetEngine", 1), (float)_argNumber(L, "vehicleSetEngine", 2), (float)_argNumber(L, "vehicleSetEngine", 3), (lua_gettop(L) == 4) ? (float)_argNumber(L, "vehicleSetEngine", 4) : VEHICLE_DEFAULT_MIN_RPM); return 0; } // vehicleSetGears(vehicle, {ratio, ...}[, reverseRatio][, automatic]) static int32_t apiVehicleSetGears(lua_State *L) { int32_t node = 0; int32_t count = 0; float *ratios = NULL; float reverse = VEHICLE_DEFAULT_REVERSE_GEAR; bool automatic = true; _argCheck(L, "vehicleSetGears", 2, 4); node = _argVehicle(L, "vehicleSetGears", 1); ratios = _argFloatTable(L, "vehicleSetGears", 2, &count); if (lua_gettop(L) >= 3) { reverse = (float)_argNumber(L, "vehicleSetGears", 3); } if (lua_gettop(L) == 4) { automatic = _argBoolean(L, "vehicleSetGears", 4); } if ((count < 1) || (count > VEHICLE_MAX_GEARS) || !vehicleSetGears(node, ratios, count, reverse, automatic)) { SDL_free(ratios); _luaDie(L, "vehicleSetGears", "Give one to %d forward gear ratios.", VEHICLE_MAX_GEARS); } SDL_free(ratios); return 0; } static int32_t apiVehicleSetSteering(lua_State *L) { _argCheck(L, "vehicleSetSteering", 2, 2); vehicleSetSteering(_argVehicle(L, "vehicleSetSteering", 1), (float)_argNumber(L, "vehicleSetSteering", 2)); return 0; } // vehicleSetRudder(boat, maxTorque) static int32_t apiVehicleSetRudder(lua_State *L) { _argCheck(L, "vehicleSetRudder", 2, 2); vehicleSetRudder(_argVehicle(L, "vehicleSetRudder", 1), (float)_argNumber(L, "vehicleSetRudder", 2)); return 0; } static int32_t apiVehicleSetSuspension(lua_State *L) { _argCheck(L, "vehicleSetSuspension", 3, 3); vehicleSetSuspension(_argVehicle(L, "vehicleSetSuspension", 1), (float)_argNumber(L, "vehicleSetSuspension", 2), (float)_argNumber(L, "vehicleSetSuspension", 3)); return 0; } // vehicleSetThrust(boat, maxForce, x, y, z): the propeller's push and where it pushes, in the hull static int32_t apiVehicleSetThrust(lua_State *L) { _argCheck(L, "vehicleSetThrust", 5, 5); vehicleSetThrust(_argVehicle(L, "vehicleSetThrust", 1), (float)_argNumber(L, "vehicleSetThrust", 2), _argVec3(L, "vehicleSetThrust", 3)); return 0; } // vehicleSetWheel(vehicle, index, steered, driven) static int32_t apiVehicleSetWheel(lua_State *L) { _argCheck(L, "vehicleSetWheel", 4, 4); if (!vehicleSetWheel(_argVehicle(L, "vehicleSetWheel", 1), _argInteger(L, "vehicleSetWheel", 2), _argBoolean(L, "vehicleSetWheel", 3), _argBoolean(L, "vehicleSetWheel", 4))) { _luaDie(L, "vehicleSetWheel", "No such wheel."); } return 0; } // videoDraw(id, x, y, x2, y2) - Stretch the frame into the rectangle // videoDraw(id, x, y, centered) - Draw with the video's rotation and scale static int32_t apiVideoDraw(lua_State *L) { int32_t n = lua_gettop(L); VideoT *video = NULL; bool center = false; bool newFrame = false; int64_t frame = 0; const uint8_t *pixels = NULL; int32_t pitch = 0; SDL_Surface *source = NULL; SDL_Surface *rgba = NULL; SDL_Rect dest; _argCheck(L, "videoDraw", 4, 5); video = _argVideo(L, "videoDraw", 1); dest.x = _argInteger(L, "videoDraw", 2); dest.y = _argInteger(L, "videoDraw", 3); dest.w = 0; dest.h = 0; if (n == 5) { dest.w = _argInteger(L, "videoDraw", 4) - dest.x + 1; dest.h = _argInteger(L, "videoDraw", 5) - dest.y + 1; } else { center = _argBoolean(L, "videoDraw", 4); } // Advance the video and wrap its decoded frame without copying it. frame = videoUpdate(video->handle, &video->texture); if (!videoGetPixels(video->handle, &pixels, &pitch)) { _luaTrace(L, "videoDraw", "%d no frame yet", video->id); return 0; } newFrame = (frame != video->lastFrame); video->lastFrame = frame; source = SDL_CreateSurfaceFrom(videoGetWidth(video->handle), videoGetHeight(video->handle), VIDEO_SURFACE_FORMAT, (void *)pixels, pitch); if (source == NULL) { utilDie("%s", SDL_GetError()); } if (n == 5) { // Simple/Stretched draw SDL_BlitSurfaceScaled(source, NULL, _global.overlay, &dest, SDL_SCALEMODE_NEAREST); } else { if ((video->angle == 0.0) && (video->scaleX == 1.0) && (video->scaleY == 1.0)) { // Untransformed: draw the frame directly. dest.w = source->w; dest.h = source->h; if (center) { dest.x -= dest.w / 2; dest.y -= dest.h / 2; } SDL_BlitSurface(source, NULL, _global.overlay, &dest); } else { // Rebuild the transformed frame only when something changed. if (newFrame || video->transformChanged || (video->transformedSurface == NULL)) { SDL_DestroySurface(video->transformedSurface); // Give the frame an alpha channel so rotated corners come out transparent. rgba = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32); if (rgba == NULL) { utilDie("%s", SDL_GetError()); } video->transformedSurface = rotoZoomSurface(rgba, -video->angle, video->scaleX, video->scaleY, video->smooth != 0); SDL_DestroySurface(rgba); if (video->transformedSurface == NULL) { utilDie("Unable to transform video %d.", video->id); } video->transformChanged = false; } dest.w = video->transformedSurface->w; dest.h = video->transformedSurface->h; if (center) { dest.x -= dest.w / 2; dest.y -= dest.h / 2; } SDL_BlitSurface(video->transformedSurface, NULL, _global.overlay, &dest); } } SDL_DestroySurface(source); _overlayTouched(); _luaTrace(L, "videoDraw", "%d %d %d %d %d %" PRId64, video->id, dest.x, dest.y, dest.w, dest.h, frame); return 0; } // track = videoGetAudioTrack(id) static int32_t apiVideoGetAudioTrack(lua_State *L) { VideoT *video = NULL; int32_t track = 0; _argCheck(L, "videoGetAudioTrack", 1, 1); video = _argVideo(L, "videoGetAudioTrack", 1); track = videoGetAudioTrack(video->handle); _luaTrace(L, "videoGetAudioTrack", "%d %d", video->id, track); lua_pushinteger(L, track); return 1; } // count = videoGetAudioTracks(id) static int32_t apiVideoGetAudioTracks(lua_State *L) { VideoT *video = NULL; int32_t count = 0; _argCheck(L, "videoGetAudioTracks", 1, 1); video = _argVideo(L, "videoGetAudioTracks", 1); count = videoGetAudioTracks(video->handle); _luaTrace(L, "videoGetAudioTracks", "%d %d", video->id, count); lua_pushinteger(L, count); return 1; } // frame = videoGetFrame(id) static int32_t apiVideoGetFrame(lua_State *L) { VideoT *video = NULL; int64_t frame = 0; _argCheck(L, "videoGetFrame", 1, 1); video = _argVideo(L, "videoGetFrame", 1); frame = videoGetFrame(video->handle); _luaTrace(L, "videoGetFrame", "%d %" PRId64, video->id, frame); lua_pushinteger(L, frame); return 1; } // count = videoGetFrameCount(id) static int32_t apiVideoGetFrameCount(lua_State *L) { VideoT *video = NULL; int64_t count = 0; _argCheck(L, "videoGetFrameCount", 1, 1); video = _argVideo(L, "videoGetFrameCount", 1); count = videoGetFrameCount(video->handle); _luaTrace(L, "videoGetFrameCount", "%d %" PRId64, video->id, count); lua_pushinteger(L, count); return 1; } // height = videoGetHeight(id) static int32_t apiVideoGetHeight(lua_State *L) { VideoT *video = NULL; int32_t height = 0; _argCheck(L, "videoGetHeight", 1, 1); video = _argVideo(L, "videoGetHeight", 1); height = videoGetHeight(video->handle); _luaTrace(L, "videoGetHeight", "%d %d", video->id, height); lua_pushinteger(L, height); return 1; } // code = videoGetLanguage(id, track) static int32_t apiVideoGetLanguage(lua_State *L) { VideoT *video = NULL; int32_t track = 0; const char *language = NULL; _argCheck(L, "videoGetLanguage", 2, 2); video = _argVideo(L, "videoGetLanguage", 1); track = _argInteger(L, "videoGetLanguage", 2); if ((track < 0) || (track >= videoGetAudioTracks(video->handle))) { _luaDie(L, "videoGetLanguage", "Invalid audio track %d in video %d.", track, video->id); } language = videoGetLanguage(video->handle, track); _luaTrace(L, "videoGetLanguage", "%d %d %s", video->id, track, language); lua_pushstring(L, language); return 1; } // name = videoGetLanguageDescription(code) English name for an ISO 639 code. static int32_t apiVideoGetLanguageDescription(lua_State *L) { const char *code = NULL; const char *description = NULL; _argCheck(L, "videoGetLanguageDescription", 1, 1); code = _argString(L, "videoGetLanguageDescription", 1); description = videoGetLanguageDescription(code); _luaTrace(L, "videoGetLanguageDescription", "%s %s", code, description); lua_pushstring(L, description); return 1; } // left, right = videoGetVolume(id) static int32_t apiVideoGetVolume(lua_State *L) { VideoT *video = NULL; int32_t left = 0; int32_t right = 0; _argCheck(L, "videoGetVolume", 1, 1); video = _argVideo(L, "videoGetVolume", 1); videoGetVolume(video->handle, &left, &right); _luaTrace(L, "videoGetVolume", "%d %d %d", video->id, left, right); lua_pushinteger(L, left); lua_pushinteger(L, right); return 2; } // width = videoGetWidth(id) static int32_t apiVideoGetWidth(lua_State *L) { VideoT *video = NULL; int32_t width = 0; _argCheck(L, "videoGetWidth", 1, 1); video = _argVideo(L, "videoGetWidth", 1); width = videoGetWidth(video->handle); _luaTrace(L, "videoGetWidth", "%d %d", video->id, width); lua_pushinteger(L, width); return 1; } // playing = videoIsPlaying(id) static int32_t apiVideoIsPlaying(lua_State *L) { VideoT *video = NULL; bool playing = false; _argCheck(L, "videoIsPlaying", 1, 1); video = _argVideo(L, "videoIsPlaying", 1); playing = videoIsPlaying(video->handle); _luaTrace(L, "videoIsPlaying", "%d %d", video->id, playing); lua_pushboolean(L, playing); return 1; } // id = videoLoad(filename) static int32_t apiVideoLoad(lua_State *L) { const char *name = NULL; char *dataDir = NULL; VideoT *video = NULL; _argCheck(L, "videoLoad", 1, 1); name = _argString(L, "videoLoad", 1); // The index file lives in a data directory named for the video's directory. dataDir = createDataDir(_global.conf->dataDirBase, name); if (dataDir == NULL) { _luaDie(L, "videoLoad", "Unable to create data directory for %s.", name); } video = (VideoT *)calloc(1, sizeof(VideoT)); if (!video) { _luaDie(L, "videoLoad", "Unable to allocate new video."); } video->handle = videoLoad(name, NULL, dataDir, _global.renderer, true); video->id = _global.nextVideoId++; video->lastFrame = -1; video->scaleX = 1.0; video->scaleY = 1.0; HASH_ADD_INT(_global.videoList, id, video); _selectDefaultAudioTrack(video->handle); videoSetVolume(video->handle, _global.conf->volumeNonVldp, _global.conf->volumeNonVldp); _luaTrace(L, "videoLoad", "%s %s %d", name, dataDir, video->id); free(dataDir); lua_pushinteger(L, video->id); return 1; } // videoPause(id) static int32_t apiVideoPause(lua_State *L) { VideoT *video = NULL; _argCheck(L, "videoPause", 1, 1); video = _argVideo(L, "videoPause", 1); videoPause(video->handle); _luaTrace(L, "videoPause", "%d", video->id); return 0; } // videoPlay(id) static int32_t apiVideoPlay(lua_State *L) { VideoT *video = NULL; _argCheck(L, "videoPlay", 1, 1); video = _argVideo(L, "videoPlay", 1); videoPlay(video->handle); _luaTrace(L, "videoPlay", "%d", video->id); return 0; } // videoQuality(id, RENDER_PIXELATED | RENDER_SMOOTH) static int32_t apiVideoQuality(lua_State *L) { VideoT *video = NULL; int32_t smooth = 0; _argCheck(L, "videoQuality", 2, 2); video = _argVideo(L, "videoQuality", 1); smooth = _argInteger(L, "videoQuality", 2) ? RENDER_SMOOTH : RENDER_PIXELATED; if (smooth != video->smooth) { video->smooth = smooth; video->transformChanged = true; } _luaTrace(L, "videoQuality", "%d %d", video->id, video->smooth); return 0; } // videoRotate(id, degrees) static int32_t apiVideoRotate(lua_State *L) { VideoT *video = NULL; double angle = 0.0; _argCheck(L, "videoRotate", 2, 2); video = _argVideo(L, "videoRotate", 1); angle = fmod(_argNumber(L, "videoRotate", 2), DEGREES_PER_CIRCLE); if (angle != video->angle) { video->angle = angle; video->transformChanged = true; } _luaTrace(L, "videoRotate", "%d %f", video->id, video->angle); return 0; } // videoRotateAndScale(id, degrees, scale) or videoRotateAndScale(id, degrees, scaleX, scaleY) static int32_t apiVideoRotateAndScale(lua_State *L) { int32_t n = lua_gettop(L); VideoT *video = NULL; double angle = 0.0; double scaleX = 1.0; double scaleY = 1.0; _argCheck(L, "videoRotateAndScale", 3, 4); video = _argVideo(L, "videoRotateAndScale", 1); angle = fmod(_argNumber(L, "videoRotateAndScale", 2), DEGREES_PER_CIRCLE); scaleX = _argNumber(L, "videoRotateAndScale", 3); scaleY = (n == 4) ? _argNumber(L, "videoRotateAndScale", 4) : scaleX; if ((angle != video->angle) || (scaleX != video->scaleX) || (scaleY != video->scaleY)) { video->angle = angle; video->scaleX = scaleX; video->scaleY = scaleY; video->transformChanged = true; } _luaTrace(L, "videoRotateAndScale", "%d %f %f %f", video->id, video->angle, video->scaleX, video->scaleY); return 0; } // videoScale(id, scale) or videoScale(id, scaleX, scaleY) static int32_t apiVideoScale(lua_State *L) { int32_t n = lua_gettop(L); VideoT *video = NULL; double scaleX = 1.0; double scaleY = 1.0; _argCheck(L, "videoScale", 2, 3); video = _argVideo(L, "videoScale", 1); scaleX = _argNumber(L, "videoScale", 2); scaleY = (n == 3) ? _argNumber(L, "videoScale", 3) : scaleX; if ((scaleX != video->scaleX) || (scaleY != video->scaleY)) { video->scaleX = scaleX; video->scaleY = scaleY; video->transformChanged = true; } _luaTrace(L, "videoScale", "%d %f %f", video->id, video->scaleX, video->scaleY); return 0; } // videoSeek(id, frame) static int32_t apiVideoSeek(lua_State *L) { VideoT *video = NULL; int64_t frame = 0; _argCheck(L, "videoSeek", 2, 2); video = _argVideo(L, "videoSeek", 1); frame = _argInteger64(L, "videoSeek", 2); videoSeek(video->handle, frame); _luaTrace(L, "videoSeek", "%d %" PRId64, video->id, frame); return 0; } // videoSetAudioTrack(id, track) static int32_t apiVideoSetAudioTrack(lua_State *L) { VideoT *video = NULL; int32_t track = 0; _argCheck(L, "videoSetAudioTrack", 2, 2); video = _argVideo(L, "videoSetAudioTrack", 1); track = _argInteger(L, "videoSetAudioTrack", 2); if ((track < 0) || (track >= videoGetAudioTracks(video->handle))) { _luaDie(L, "videoSetAudioTrack", "Invalid audio track %d in video %d.", track, video->id); } videoSetAudioTrack(video->handle, track); _luaTrace(L, "videoSetAudioTrack", "%d %d", video->id, track); return 0; } // videoSetVolume(id, left, right) Percent, clamped to 0..100. static int32_t apiVideoSetVolume(lua_State *L) { VideoT *video = NULL; int32_t left = 0; int32_t right = 0; _argCheck(L, "videoSetVolume", 3, 3); video = _argVideo(L, "videoSetVolume", 1); left = SDL_clamp(_argInteger(L, "videoSetVolume", 2), 0, VIDEO_VOLUME_MAX); right = SDL_clamp(_argInteger(L, "videoSetVolume", 3), 0, VIDEO_VOLUME_MAX); videoSetVolume(video->handle, left, right); _luaTrace(L, "videoSetVolume", "%d %d %d", video->id, left, right); return 0; } // videoUnload(id) static int32_t apiVideoUnload(lua_State *L) { VideoT *video = NULL; _argCheck(L, "videoUnload", 1, 1); video = _argVideo(L, "videoUnload", 1); _luaTrace(L, "videoUnload", "%d", video->id); _videoDestroy(video); return 0; } // viewDelete(view) static int32_t apiViewDelete(lua_State *L) { _argCheck(L, "viewDelete", 1, 1); viewDelete(_argView(L, "viewDelete", 1)); return 0; } // view = viewNew(width, height): a camera rendered to a texture every frame, for materialSetView static int32_t apiViewNew(lua_State *L) { int32_t view; _argCheck(L, "viewNew", 2, 2); view = viewNew(_argInteger(L, "viewNew", 1), _argInteger(L, "viewNew", 2)); if (view < 0) { _luaDie(L, "viewNew", "No view available (is 3D available, and are fewer than %d in use?).", MAX_VIEWS); } lua_pushinteger(L, view); return 1; } // viewSetCamera(view [, node]): the node the view looks from; none for the default view static int32_t apiViewSetCamera(lua_State *L) { int32_t view; int32_t camera = -1; _argCheck(L, "viewSetCamera", 1, 2); view = _argView(L, "viewSetCamera", 1); if ((lua_gettop(L) >= 2) && !lua_isnil(L, 2)) { camera = _argNode(L, "viewSetCamera", 2); } viewSetCamera(view, camera); return 0; } // r, g, b = vldpGetPixel(x, y) Overlay coordinates; reads the current laserdisc frame. static int32_t apiVldpGetPixel(lua_State *L) { int32_t x = 0; int32_t y = 0; uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; _argCheck(L, "vldpGetPixel", 2, 2); x = (int32_t)(_argNumber(L, "vldpGetPixel", 1) / _global.overlayScaleX); y = (int32_t)(_argNumber(L, "vldpGetPixel", 2) / _global.overlayScaleY); if (_global.videoHandle >= 0) { videoGetPixel(_global.videoHandle, x, y, &r, &g, &b); } _luaTrace(L, "vldpGetPixel", "%d %d %d %d %d", x, y, r, g, b); lua_pushinteger(L, r); lua_pushinteger(L, g); lua_pushinteger(L, b); return 3; } static int32_t apiVldpSetVerbose(lua_State *L) { return _apiUnimplemented(L, "vldpSetVerbose"); } // ===== Engine entry point ===== // A game database named on the command line runs one entry of its games.dat (--entry, default the first). ConfigT *confFromDatabase(const ConfigT *conf) { lua_State *L = luaL_newstate(); ConfigT *base = cloneConf(conf); ConfigT *result = NULL; base->container = strdup(conf->scriptFile); free(base->scriptFile); base->scriptFile = NULL; vfsInit(base->container, base->dataDirBase, NULL); luaL_openlibs(L); if ((_luaLoadFile(L, VFS_GAMES_DAT, NULL, false) != LUA_OK) || (lua_pcall(L, 0, 0, 0) != LUA_OK)) { utilDie("%s: %s", base->container, lua_tostring(L, -1)); } lua_getglobal(L, "GAMES"); if (!lua_istable(L, -1) || (lua_rawgeti(L, -1, conf->entry) != LUA_TTABLE)) { utilDie("%s: games.dat has no entry %d.", base->container, conf->entry); } lua_replace(L, 1); lua_settop(L, 1); result = _buildConfFromTable(L, base); if (result->scriptFile == NULL) { utilDie("%s: games.dat entry %d has no SCRIPT.", base->container, conf->entry); } free(result->container); result->container = strdup(base->container); lua_close(L); destroyConf(&base); return result; } void singe(SDL_Window *window, SDL_Renderer *renderer, SDL_GPUDevice *device, ConfigT *conf) { int32_t x = 0; int32_t y = 0; int32_t xr = 0; int32_t yr = 0; int32_t slot = 0; int32_t axisIndex = 0; int32_t code = 0; int32_t intReturn = 0; int32_t videoWidth = 0; int32_t videoHeight = 0; int64_t thisFrame = -1; int64_t lastFrame = -1; uint64_t frameClock = 0; SDL_FRect windowTarget; SDL_FRect scaledTarget; SDL_FRect sindenWhite; SDL_FRect sindenBlack; SDL_Texture *sceneTexture = NULL; SDL_Color sindenWhiteColor = { COLOR_BYTE_MAX, COLOR_BYTE_MAX, COLOR_BYTE_MAX, SDL_ALPHA_OPAQUE }; SDL_Color sindenBlackColor = { 0, 0, 0, SDL_ALPHA_OPAQUE }; SDL_Event event; ManyMouseEvent mouseEvent; MouseT *mouse = NULL; int32_t finished[SOUND_QUEUE_SIZE]; int32_t finishedCount = 0; // Set up globals memset(&_global, 0, sizeof(GlobalT)); _global.frameFileHandle = -1; _global.videoHandle = -1; _global.mouseMode = MOUSE_SINGLE; _global.controllerDeadZone = CONTROLLER_DEAD_ZONE_DEFAULT; _global.running = true; _global.discStopped = true; _global.mouseEnabled = true; _global.window = window; _global.renderer = renderer; _global.device = device; _subsystemsInit(); // Local copy of config _global.conf = cloneConf(conf); vfsInit(_global.conf->container, _global.conf->dataDirBase, _global.conf->dataDir); videoSetAudioDelay(_global.conf->audioDelayMs); videoSetAudioCalibration(_loadAudioCalibration()); utilTrace("Audio delay: device queue %d ms, calibration %d ms, game %d ms", videoGetAudioLatency(), videoGetAudioCalibration(), videoGetAudioDelay()); _loadControlMappings(); // Show splash screens if (!_global.conf->noLogos) { _progTrace("Showing splash screens"); _doLogos(); } // Start Lua for game _createScriptContext(); // Open main video file, if this is a laserdisc game. Otherwise the canvas is the world. if (_global.conf->disc) { _progTrace("Opening main video file"); if (_global.conf->isFrameFile) { _global.frameFileHandle = frameFileLoad(_global.conf->videoFile, _global.conf->dataDir, _global.renderer, _global.conf->showCalculated); frameFileSeek(_global.frameFileHandle, 0, &_global.videoHandle, &thisFrame); // Fills in _global.videoHandle } else { _global.videoHandle = videoLoad(_global.conf->videoFile, NULL, _global.conf->dataDir, _global.renderer, false); } videoSetVolume(_global.videoHandle, _global.conf->volumeVldp, _global.conf->volumeVldp); _global.canvasWidth = videoGetWidth(_global.videoHandle); _global.canvasHeight = videoGetHeight(_global.videoHandle); } else { _progTrace("No disc; canvas is %dx%d", _global.conf->canvasWidth, _global.conf->canvasHeight); _global.canvasWidth = _global.conf->canvasWidth; _global.canvasHeight = _global.conf->canvasHeight; } videoWidth = _global.canvasWidth; videoHeight = _global.canvasHeight; // Should we resize the window to the video's shape? if (_global.conf->resolutionWasCalculated && !_global.conf->fullScreen && !_global.conf->fullScreenWindow) { if (videoWidth * _global.conf->yResolution > videoHeight * _global.conf->xResolution) { // Video is wider than the window: keep the width, shrink the height. _global.conf->yResolution = _global.conf->xResolution * videoHeight / videoWidth; } else { // Video is taller: keep the height, shrink the width. _global.conf->xResolution = _global.conf->yResolution * videoWidth / videoHeight; } _progTrace("Resizing window to %dx%d based on main video file", _global.conf->xResolution, _global.conf->yResolution); SDL_SetWindowSize(_global.window, _global.conf->xResolution, _global.conf->yResolution); SDL_SyncWindow(_global.window); SDL_SetRenderDrawColor(_global.renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(_global.renderer); } // Everything renders in video coordinates, letterboxed unless the user wants it stretched, and // either way mouse positions convert back into those coordinates. SDL_SetRenderLogicalPresentation(_global.renderer, videoWidth, videoHeight, _global.conf->stretchVideo ? SDL_LOGICAL_PRESENTATION_STRETCH : SDL_LOGICAL_PRESENTATION_LETTERBOX); // Default render location is the entire window windowTarget.x = 0; windowTarget.y = 0; windowTarget.w = videoWidth; windowTarget.h = videoHeight; sindenWhite.x = -1; sindenBlack.x = -1; // Sinden Light Gun Border Setup if (_global.conf->sindenArgc > 0) { //***TODO*** ADD MOUSE SCALING TO COMPENSATE FOR BORDER switch (_global.conf->sindenArgc) { // WW - Just the width of the white border case SINDEN_WHITE: sindenWhite.x = _global.conf->sindenArgv[0]; break; // WW WB - Width of white border and then black border case SINDEN_WHITE_BLACK: sindenWhite.x = _global.conf->sindenArgv[0]; sindenBlack.x = _global.conf->sindenArgv[1]; break; // RW GW BW WW - Custom color "white" border and width case SINDEN_CUSTOM_WHITE: sindenWhiteColor.r = (uint8_t)_global.conf->sindenArgv[0]; sindenWhiteColor.g = (uint8_t)_global.conf->sindenArgv[1]; sindenWhiteColor.b = (uint8_t)_global.conf->sindenArgv[2]; sindenWhite.x = _global.conf->sindenArgv[3]; break; // RW GW BW WW WB - Custom color "white" border and width then width of black border case SINDEN_CUSTOM_WHITE_BLACK: sindenWhiteColor.r = (uint8_t)_global.conf->sindenArgv[0]; sindenWhiteColor.g = (uint8_t)_global.conf->sindenArgv[1]; sindenWhiteColor.b = (uint8_t)_global.conf->sindenArgv[2]; sindenWhite.x = _global.conf->sindenArgv[3]; sindenBlack.x = _global.conf->sindenArgv[4]; break; // RW GW BW WW RB GB BB WB - Custom color "white" border and width then custom color "black" border and width case SINDEN_CUSTOM_WHITE_CUSTOM_BLACK: sindenWhiteColor.r = (uint8_t)_global.conf->sindenArgv[0]; sindenWhiteColor.g = (uint8_t)_global.conf->sindenArgv[1]; sindenWhiteColor.b = (uint8_t)_global.conf->sindenArgv[2]; sindenWhite.x = _global.conf->sindenArgv[3]; sindenBlackColor.r = (uint8_t)_global.conf->sindenArgv[4]; sindenBlackColor.g = (uint8_t)_global.conf->sindenArgv[5]; sindenBlackColor.b = (uint8_t)_global.conf->sindenArgv[6]; sindenBlack.x = _global.conf->sindenArgv[7]; break; default: utilDie("Bad Sinden argument count: %d", _global.conf->sindenArgc); } // The white border is the inner one; the black border (if any) surrounds it. sindenWhite.y = sindenWhite.x; sindenWhite.w = videoWidth - (sindenWhite.x * 2); sindenWhite.h = videoHeight - (sindenWhite.y * 2); if (sindenBlack.x >= 0) { sindenBlack.y = sindenBlack.x; sindenBlack.w = videoWidth - (sindenBlack.x * 2); sindenBlack.h = videoHeight - (sindenBlack.y * 2); sindenWhite.x += sindenBlack.x; sindenWhite.y += sindenBlack.y; sindenWhite.w -= (sindenBlack.x * 2); sindenWhite.h -= (sindenBlack.y * 2); } windowTarget = sindenWhite; } // Overscan compensation shrinks whatever the game is drawn into (the whole window, or the inside // of the Sinden border) about its centre. if (_global.conf->scaleFactor < SCALE_FACTOR_MAX) { scaledTarget.w = windowTarget.w * (float)_global.conf->scaleFactor / (float)SCALE_FACTOR_MAX; scaledTarget.h = windowTarget.h * (float)_global.conf->scaleFactor / (float)SCALE_FACTOR_MAX; scaledTarget.x = windowTarget.x + (windowTarget.w - scaledTarget.w) / 2.0f; scaledTarget.y = windowTarget.y + (windowTarget.h - scaledTarget.h) / 2.0f; windowTarget = scaledTarget; } // Create overlay surface and its texture x = (int32_t)(videoWidth * OVERLAY_SCALE_DEFAULT); y = (int32_t)(videoHeight * OVERLAY_SCALE_DEFAULT); _progTrace("Creating overlay of %dx%d", x, y); _overlayResize(x, y); // Mouse setup _global.mouseEnabled = !_global.conf->noMouse; _progTrace("Initializing ManyMouse"); _global.mouseCount = ManyMouse_Init(); _progTrace("Mouse Driver: %s", ManyMouse_DriverName()); _progTrace("Mice Found: %d", _global.mouseCount); if (_global.mouseCount < 0) { _global.mouseCount = 0; } if (_global.mouseCount > MAX_MICE) { _global.mouseCount = MAX_MICE; } if ((_global.mouseCount < 1) && _global.mouseEnabled) { utilSay("Warning: No mice detected. Mouse input disabled."); _global.mouseEnabled = false; } for (x = 0; x < _global.mouseCount; x++) { strncpy(_global.mice[x].name, ManyMouse_DeviceName((unsigned)x), sizeof(_global.mice[x].name) - 1); _global.mice[x].x = videoWidth / 2; _global.mice[x].y = videoHeight / 2; _progTrace("Mouse %d: %s", x, _global.mice[x].name); } // Grab mouse _progTrace("Grabbing mouse"); _setMouseCaptured(true); // Controllers are started by the event loop only for the first script in // the queue - so kick 'em here to be sure they're going. _startControllers(); // Sound effect tracks: a fixed pool so scripts keep getting small channel numbers. _global.effectsVolume = AUDIO_MAX_VOLUME * _global.conf->volumeNonVldp / VOLUME_MAX; _progTrace("Setting up sound effects mixer"); for (x = 0; x < EFFECT_TRACKS; x++) { _effectTracks[x] = MIX_CreateTrack(videoGetMixer()); if (_effectTracks[x] == NULL) { utilDie("%s", SDL_GetError()); } MIX_TagTrack(_effectTracks[x], EFFECT_TAG); MIX_SetTrackGain(_effectTracks[x], _mixerGain(_global.effectsVolume)); // Let us know when sounds end MIX_SetTrackStoppedCallback(_effectTracks[x], _effectStopped, (void *)(intptr_t)x); } // The script's own defaults, now that everything they touch exists. _resetScriptState(); // Load overlay font _progTrace("Loading console font"); _global.consoleFontSurface = _loadEmbeddedPng(font_png, font_png_len); _global.consoleFontWidth = _global.consoleFontSurface->w / CONSOLE_FONT_GLYPHS; _global.consoleFontHeight = _global.consoleFontSurface->h; _progTrace("Console font is %dx%d, %s", _global.consoleFontSurface->w, _global.consoleFontSurface->h, SDL_GetPixelFormatName(_global.consoleFontSurface->format)); // The glyph background is whatever colour the top left pixel has (magenta in the shipped font). SDL_SetSurfaceColorKey(_global.consoleFontSurface, true, _surfaceCornerKey(_global.consoleFontSurface)); // The disc always starts parked on frame 1, paused, like discSearch(1). if (_global.videoHandle >= 0) { _progTrace("Parking laserdisc on frame 1"); _discSeek(1); videoPause(_global.videoHandle); _global.discStopped = false; _selectDefaultAudioTrack(_global.videoHandle); } // Start script _runScript(true); // Game Loop _progTrace("Script is running"); while (_global.running) { // A reload asked for by the script, F5 or a changed file happens here, between frames. if (_global.conf->reload && _watchedChanged()) { _global.reloadRequested = true; } if (_global.reloadRequested) { _reloadScript(); } // SDL Event Loop while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_GAMEPAD_AXIS_MOTION: slot = _controllerSlot(event.gaxis.which); if ((slot < 0) || (event.gaxis.axis >= CONTROLLER_AXIS_COUNT)) { break; } axisIndex = AXIS_INDEX_CONTROLLER(slot, event.gaxis.axis); // Each axis direction is a "key" so it can be mapped in controls.cfg. code = CODE_GAMEPAD_BASE + slot * CODE_GAMEPAD_STRIDE + event.gaxis.axis * CODE_AXIS_STRIDE; code += (event.gaxis.value < 0) ? CODE_AXIS_NEGATIVE : CODE_AXIS_POSITIVE; if (abs(event.gaxis.value) > _global.controllerDeadZone) { if (_global.axisCode[axisIndex] != code) { _releaseAxis(axisIndex); _processKey(true, 0, code); _global.axisCode[axisIndex] = code; } } else { _releaseAxis(axisIndex); } _global.axisCache[axisIndex] = event.gaxis.value; if (!_global.frozen) { _callLua("onControllerMoved", "iii", event.gaxis.axis, event.gaxis.value, slot); } break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: slot = _controllerSlot(event.gbutton.which); if (slot < 0) { break; } if (event.gbutton.button < CONTROLLER_BUTTON_COUNT) { if ((event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) && (SDL_GetTicks() < _global.inputGraceUntil)) { _global.buttonSuppressed[slot][event.gbutton.button] = true; } if (_global.buttonSuppressed[slot][event.gbutton.button]) { // Held since before this script: swallow it, and its release. if (event.type == SDL_EVENT_GAMEPAD_BUTTON_UP) { _global.buttonSuppressed[slot][event.gbutton.button] = false; } break; } } code = CODE_GAMEPAD_BASE + slot * CODE_GAMEPAD_STRIDE + CODE_GAMEPAD_BUTTON_OFFSET + event.gbutton.button; _processKey(event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN, 0, code); break; case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_REMOVED: _startControllers(); break; case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: // Mapped switches want one press per key; full mode keeps repeats for text entry. if (event.key.repeat && (_global.keyboardMode == KEYBOARD_NORMAL)) { break; } if (event.key.scancode < SDL_SCANCODE_COUNT) { if ((event.type == SDL_EVENT_KEY_DOWN) && (SDL_GetTicks() < _global.inputGraceUntil)) { _global.keySuppressed[event.key.scancode] = true; } if (_global.keySuppressed[event.key.scancode]) { // Held since before this script: swallow it, and its release. if (event.type == SDL_EVENT_KEY_UP) { _global.keySuppressed[event.key.scancode] = false; } break; } } _processKey(event.type == SDL_EVENT_KEY_DOWN, event.key.key, event.key.scancode); break; case SDL_EVENT_WINDOW_FOCUS_GAINED: // SDL re-reports every held key as a press when a window gains focus. _suppressHeldInput(); break; case SDL_EVENT_MOUSE_MOTION: if (_global.mouseEnabled && (_global.mouseMode == MOUSE_SINGLE)) { // Positions arrive in window pixels; the game works in the video's coordinates. SDL_ConvertEventToRenderCoordinates(_global.renderer, &event); x = (int32_t)(event.motion.x * _global.overlayScaleX); y = (int32_t)(event.motion.y * _global.overlayScaleY); xr = (int32_t)(event.motion.xrel * _global.overlayScaleX); yr = (int32_t)(event.motion.yrel * _global.overlayScaleY); _fireMouseMoved(0, x, y, xr, yr); } break; case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: if (_global.mouseEnabled && (_global.mouseMode == MOUSE_SINGLE) && (event.button.button >= SDL_BUTTON_LEFT) && (event.button.button <= SDL_BUTTON_X2)) { _processKey(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN, 0, _mouseCode(0, _sdlMouseButtonToCode[event.button.button])); } break; case SDL_EVENT_MOUSE_WHEEL: if (_global.mouseEnabled && (_global.mouseMode == MOUSE_SINGLE) && (event.wheel.y != 0.0f)) { code = _mouseCode(0, (event.wheel.y > 0.0f) ? CODE_MOUSE_WHEEL_UP : CODE_MOUSE_WHEEL_DOWN); _processKey(true, 0, code); _processKey(false, 0, code); } break; case SDL_EVENT_QUIT: _progTrace("Quit requested"); _global.running = false; break; default: break; } } // Mouse Event Loop - drained even when unused so the queue never fills. while (ManyMouse_PollEvent(&mouseEvent)) { if (!_global.mouseEnabled || (_global.mouseMode != MOUSE_MANY) || (mouseEvent.device >= (unsigned)_global.mouseCount)) { continue; } mouse = &_global.mice[mouseEvent.device]; switch (mouseEvent.type) { case MANYMOUSE_EVENT_RELMOTION: // Integrate the motion into an absolute position clamped to the video. xr = 0; yr = 0; if (mouseEvent.item == 0) { xr = mouseEvent.value; mouse->x += xr; } else { yr = mouseEvent.value; mouse->y += yr; } if (mouse->x < 0) { mouse->x = 0; } if (mouse->x >= videoWidth) { mouse->x = videoWidth - 1; } if (mouse->y < 0) { mouse->y = 0; } if (mouse->y >= videoHeight) { mouse->y = videoHeight - 1; } x = (int32_t)(mouse->x * _global.overlayScaleX); y = (int32_t)(mouse->y * _global.overlayScaleY); xr = (int32_t)(xr * _global.overlayScaleX); yr = (int32_t)(yr * _global.overlayScaleY); _fireMouseMoved((int32_t)mouseEvent.device, x, y, xr, yr); break; case MANYMOUSE_EVENT_ABSMOTION: // Absolute devices (tablets, some guns) report a position within a range. if (mouseEvent.maxval > mouseEvent.minval) { if (mouseEvent.item == 0) { mouse->x = (int32_t)((int64_t)(mouseEvent.value - mouseEvent.minval) * videoWidth / (mouseEvent.maxval - mouseEvent.minval)); } else { mouse->y = (int32_t)((int64_t)(mouseEvent.value - mouseEvent.minval) * videoHeight / (mouseEvent.maxval - mouseEvent.minval)); } x = (int32_t)(mouse->x * _global.overlayScaleX); y = (int32_t)(mouse->y * _global.overlayScaleY); _fireMouseMoved((int32_t)mouseEvent.device, x, y, 0, 0); } break; case MANYMOUSE_EVENT_BUTTON: // Limited to the same five buttons as single-mouse mode. if (mouseEvent.item < CODE_MOUSE_BUTTON_COUNT) { _processKey(mouseEvent.value == 1, 0, _mouseCode((int32_t)mouseEvent.device, (int32_t)mouseEvent.item)); } break; case MANYMOUSE_EVENT_SCROLL: // Vertical wheel only. if ((mouseEvent.item == 0) && (mouseEvent.value != 0)) { code = _mouseCode((int32_t)mouseEvent.device, (mouseEvent.value > 0) ? CODE_MOUSE_WHEEL_UP : CODE_MOUSE_WHEEL_DOWN); _processKey(true, 0, code); _processKey(false, 0, code); } break; default: break; } } // Deliver sound completions on this thread. They wait out an engine pause. if (!_global.frozen) { finishedCount = _soundQueueDrain(finished); for (x = 0; x < finishedCount; x++) { _callLua("onSoundCompleted", "i", finished[x]); } } // Update the disc video if (_global.videoHandle >= 0) { thisFrame = videoUpdate(_global.videoHandle, &_global.videoTexture); if (_global.conf->isFrameFile) { frameFileUpdate(_global.frameFileHandle, &_global.videoHandle); } // Did we get a new video frame? if (thisFrame != lastFrame) { lastFrame = thisFrame; frameClock = 0; _global.refreshDisplay = true; } } // Call game code, unless the engine has it paused. if (!_global.frozen && (SDL_GetTicks() > frameClock)) { intReturn = OVERLAY_NOT_UPDATED; _callLua("onOverlayUpdate", ">i", &intReturn); if (intReturn == OVERLAY_UPDATED) { _global.refreshDisplay = true; } frameClock = SDL_GetTicks() + FRAME_TICK_MS; // Don't eat all the CPU. // Clear per-frame values. _global.keyboardLastDown = SDL_SCANCODE_UNKNOWN; _global.keyboardLastUp = SDL_SCANCODE_UNKNOWN; } // Update display if (_global.refreshDisplay || _global.overlayDirty || sceneIsEnabled()) { // Clear entire display to black SDL_SetRenderDrawColor(_global.renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(_global.renderer); // Sinden Gun Border if (sindenWhite.x >= 0) { if (sindenBlack.x >= 0) { SDL_SetRenderDrawColor(_global.renderer, sindenBlackColor.r, sindenBlackColor.g, sindenBlackColor.b, sindenBlackColor.a); SDL_RenderFillRect(_global.renderer, &sindenBlack); } SDL_SetRenderDrawColor(_global.renderer, sindenWhiteColor.r, sindenWhiteColor.g, sindenWhiteColor.b, sindenWhiteColor.a); SDL_RenderFillRect(_global.renderer, &sindenWhite); } // Laserdisc Video. Games without a disc draw on black. if (_global.videoHandle >= 0) { if (_global.discStopped) { // Stopped discs display blue like the good old days SDL_SetRenderDrawColor(_global.renderer, 0, 0, BLUE_SCREEN_BLUE, SDL_ALPHA_OPAQUE); SDL_RenderFillRect(_global.renderer, &windowTarget); } else { SDL_RenderTexture(_global.renderer, _global.videoTexture, NULL, &windowTarget); } } // 3D scene modelUpdate(!_global.frozen); physicsUpdate(!_global.frozen); _physicsCallbacks(); navUpdate(!_global.frozen); _navCallbacks(); particlesUpdate(!_global.frozen); sceneUpdateVideo(_sceneVideoSource); sceneTexture = sceneRender(); _updateSounds(); if (sceneTexture != NULL) { SDL_RenderTexture(_global.renderer, sceneTexture, NULL, &windowTarget); } // 2D particles beneath the overlay, then the overlay, then the ones above it _drawParticles2D(PARTICLE_UNDER, &windowTarget); if (_global.overlayDirty) { SDL_UpdateTexture(_global.overlayTexture, NULL, _global.overlay->pixels, _global.overlay->pitch); _global.overlayDirty = false; } SDL_RenderTexture(_global.renderer, _global.overlayTexture, NULL, &windowTarget); _drawParticles2D(PARTICLE_OVER, &windowTarget); particlesClearQueue2D(); if (_global.frozen) { _drawPauseIndicator(&windowTarget); } // Save it? if (_global.requestScreenShot) { _global.requestScreenShot = false; _progTrace("Taking screenshot"); _takeScreenshot(); } // Show it SDL_RenderPresent(_global.renderer); _global.refreshDisplay = false; } SDL_Delay(IDLE_SLEEP_MS); } // End game _progTrace("Script is shutting down"); _callLua("onShutdown", ""); // Stop all sounds _progTrace("Stopping all audio"); for (x = 0; x < EFFECT_TRACKS; x++) { MIX_SetTrackStoppedCallback(_effectTracks[x], NULL, NULL); MIX_DestroyTrack(_effectTracks[x]); _effectTracks[x] = NULL; } // Stop Lua _progTrace("Stopping Lua"); lua_close(_global.luaContext); // Free overlay & overlay font _progTrace("Destroying overlay"); SDL_DestroyTexture(_global.pauseTexture); _subsystemsQuit(); SDL_DestroyTexture(_global.overlayTexture); SDL_DestroySurface(_global.overlay); _progTrace("Destroying console font"); SDL_DestroySurface(_global.consoleFontSurface); // Unload resources the script left behind _unloadScriptResources(); // Unload background video if (_global.conf->isFrameFile) { _progTrace("Unloading framefile"); frameFileUnload(_global.frameFileHandle); } else { if (_global.videoHandle >= 0) { _progTrace("Unloading main video file"); videoUnload(_global.videoHandle); } } // Stop controllers _progTrace("Stopping controllers"); _stopControllers(); // Stop mice _progTrace("Releasing mouse"); _setMouseCaptured(false); _progTrace("Stopping ManyMouse"); ManyMouse_Quit(); // Release global conf memory destroyConf(&_global.conf); // Release control mappings for (x = 0; x < INPUT_COUNT; x++) { free(_global.controlMappings[x].input); } for (x = 0; x < _global.watchedCount; x++) { free(_global.watched[x].name); } free(_global.watched); SDL_free(_global.navDrawVertices); SDL_free(_global.quadVertices); SDL_free(_global.quadIndices); SDL_free(_global.frameStarts); }