10233 lines
343 KiB
C
10233 lines
343 KiB
C
/*
|
|
*
|
|
* Singe 3
|
|
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
|
*
|
|
* 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 <string.h>
|
|
#include <math.h>
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3_image/SDL_image.h>
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
#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);
|
|
}
|