instead-3.1.2/AUTHORS 000644 001751 001751 00000001575 13146577472 014546 0 ustar 00peter peter 000000 000000 INSTEAD was written by:
Peter Kosyh
Initial port to Windows:
Ilya Ryndin
Port to Android:
Mahno Aleksey
Anton Kolosov
Port to Mac OS X:
.dm
Port to Windows CE:
Andrey Afletdinov
Debian package:
Sam Protsenko
FreeBSD port, cmake support and fixes:
Dmitry Marakasov
Ideas, games, advices, testing, bug reports, fixes ...
Vadim Balashoff
Evgeniy Efremov (jhekasoft)
Oleg Gvozdev
Heorhiy Kharvat (zaynyatyi)
Serj Kalichev
Vladimir Podobaev
Alexander Soborov
Vasily Voronkov
Alexander Yakovlev
Vladimir Zhirov
And many others...
Web resources:
http://instead.syscall.ru/talk
http://instead.sourceforge.net
http://instead-games.sourceforge.net
http://github.com/instead-hub/instead
XMPP conference:
instead@conference.jabber.ru
instead-3.1.2/bld.inf 000644 001751 001751 00000000104 13146577472 014720 0 ustar 00peter peter 000000 000000 PRJ_PLATFORMS
DEFAULT
PRJ_MMPFILES
gnumakefile icons.mk
instead.mmp
instead-3.1.2/ChangeLog 000644 001751 001751 00000060432 13146577472 015245 0 ustar 00peter peter 000000 000000 3.1.2
* regression fix in fading and menu;
-- Peter Kosyh Mon, 21 Aug 2017 16:45:00 +0300
3.1.1
* regression fix in fading (Android);
* code cleanups;
* doc cleanups;
-- Peter Kosyh Mon, 21 Aug 2017 08:50:00 +0300
3.1.0
* emscripten port;
* fix div by zero in rnd;
* fix in loadmod (from gamefile);
* fix in path (.walk as function);
* fixes in doc;
* code cleanups (compilation warnings);
* instead.noise1/2/3/4 (Perlin Noise);
* pixels:scale()/rotate();
-- Peter Kosyh Sun, 20 Aug 2017 10:16:00 +0300
3.0.1
* fix segfault while pause/resume;
* stead3: start() logic fix;
-- Peter Kosyh Wed, 24 May 2017 21:03:00 +0300
3.0.0
* refactoring (src/instead);
* tiny-instead;
* bug fixes;
* completely new stead3;
* sound.load_mem;
* sprite.pixels;
-- Peter Kosyh Wed, 19 Apr 2017 17:45:00 +0300
2.4.1
* sprite from sprite load bug fixed with scaling;
* fix in themes pager;
* fix in stead.menu_toggle();
* memory leak in free_font (sprtites) fixed;
* new stead.mouse_show();
* fix in txtnb() escaping;
* fix in stead.busy();
* fixes in fonts,cutscene and keyboard example modules;
* ua -> uk language, fixes in translation;
-- Peter Kosyh Sat, 16 Apr 2016 13:50:00 +0300
2.4.0
* txty (iface.y) -- y position in text;
* menu_toggle ("themes", "settings" added), w/o return to top menu;
* win.ways.mode = top|bottom in themes;
* game themes support (themes/ directory in game);
* set theme vars is not working when not using own themes;
* fix stead.api_atleast/atleast when using with vv.mm format;
* be more strict while loading combined images;
* in -debug mode any "resource not found" messages are errors;
* be more simple with walk from enter/exit (>= 2.4.0);
* About menu shows game information;
* -standalone mode;
* scr.gfx.icon added (SDL2 only, experimental);
* -noconfig parameter;
* profiles support added (-profile parameter);
* -hires parameter and HQ option (enabled by default);
* -modes parameter;
* Android-NG and IOS ports improvements;
* font size scale is displayed in percents;
* -fontscale argument;
-- Peter Kosyh Thu, 18 Feb 2016 15:09:00 +0300
2.3.0
* audio default is 44100 hz;
* fix segfault in windows version;
* visits/visited -> stead.visits, stead.visited;
* -nosound fix (volume 0 problem);
* fix bug in fingers module;
* fixes in Android version;
* -resizable parameter and config option (resizable window mode);
* SDL2: graphics rewrite (better full screen handling, resize, etc.)
* SDL2: fix with alpha blended bg;
-- Peter Kosyh Sun, 18 Oct 2015 13:15:00 +0300
2.2.7
* fix bug with fingers positions (forgotten patch) (SDL2);
-- Peter Kosyh Sun, 20 Sep 2015 13:49:00 +0300
2.2.6
* fix bug with SDL2 and cursor draw;
* fix bug with fingers and click modules;
* fix bug with fingers positions (SDL2);
* fix bug with cursor positions (SDL2);
* fix bug with menu and 32 bit float (S60);
* fix SDL window title on S60;
-- Peter Kosyh Sun, 20 Sep 2015 13:22:00 +0300
2.2.5
* fix bug with SDL2 and idf format (segfault);
* fix bug with SDL2 windows build (cursor coordinates);
* fix some bugs with SDL2;
* fix bug with music paused while run fullscreen mode;
-- Peter Kosyh Sun, 30 Aug 2015 16:15:00 +0300
2.2.4
* fix in prefs module (Windows);
* German language and tutorial;
-- Peter Kosyh Sun, 2 Aug 2015 11:22:00 +0300
2.2.3
* fix in player_save;
* always reset LC_CTYPE to C;
* fallback to software renderer if can not create texture;
* remove game dialog cleanup;
* fix in gamereset() and init() call;
* fix with gtk dialog open and SDL2;
-- Peter Kosyh Fri, 20 Mar 2015 18:56:00 +0300
2.2.2
* last_disp regression fixed;
-- Peter Kosyh Fri, 6 Feb 2015 19:17:00 +0300
2.2.1
* -alsa option removed, use SDL_AUDIODRIVER env instead;
* -vsync added for SDL2 vsync feature;
* *gray* and *grey* colors are both valid;
* fix in sandbox with stead.type;
* fix in sprites dirty screen logic;
* no 48000 hz sound anymore;
* windows build uses SDL2 and luajit;
* debug and vsync options in rc file;
-- Peter Kosyh Sat, 31 Jan 2015 16:27:00 +0300
2.2.0
* fix in debugger;
* fix in links hl with events module;
* fix in fr language;
* fix segfault in fgetsesc;
* space added in fixed gfx theme mode when ways are empty;
* rnd is now Tiny Mersenne Twister algo;
* scr.gfx.scalable & 4 -> disable font scaling;
* stead.life_moved() to detect moves in life;
* stead.last_disp() added;
* stead.nop() added;
* stead.rndseed() added;
* ready for IOS build;
* touch events via input.finger (finger module);
* stead.mouse_pos now returns buttons mask;
* do gfx sync in direct mode only after timer event (speedup);
* SDL2: do not send key repeats in direct mode (speedup);
* SDL2: use scancodes instead keycodes (dvorak fixed);
* updated doc/examples/fonts.lua;
* fix dropf with 2nd parameter;
* added forgotten function dropto;
* updated documentation (en/ru);
-- Peter Kosyh Thu, 1 Jan 2015 10:52:00 +0300
2.1.1
* -debug parameter fixed;
-- Peter Kosyh Wed, 9 Jul 2014 13:30:00 +0400
2.1.0
* fr.ini added;
* fix in highlighting links;
* sprites colorkey function;
* events module (pause, resume, quit events);
* stead.stop_sound/sound.stop second [fadeout] parameter;
-- Peter Kosyh Tue, 3 Jun 2014 17:28:00 +0400
2.0.3
* sandbox fix for win32 systems;
* fix bug while changing HZ;
-- Peter Kosyh Thu, 20 Feb 2014 17:19:00 +0400
2.0.2
* sandbox fixes;
* be more compatible with lua 5.2;
* SDL2.0 gfx mode initialization;
-- Peter Kosyh Thu, 30 Jan 2014 08:30:00 +0400
2.0.1
* lua5.2 compatible;
-- Peter Kosyh Mon, 27 Jan 2014 19:40:00 +0400
2.0.0
* GPL2 code removed. Now INSTEAD is covered by MIT license;
* code in now hosted on github (fixes in Makefiles);
* sandbox (no write access from lua outside game);
* window title contains the game name;
* sdl-instead start variant;
* fixes in Makefiles (PREFIX and DESTDIR);
* experimental cmake support;
* doc/examples addons;
* -lua and -luac parameters;
* in windows do not close -debug console until press return;
* game.gui.hidetitle;
* stead.api_atleast() and stead.atleast();
* stead.tonum, stead.tostr, stead.type, stead.ipairs, stead.pairs, stead.opairs;
* potential bug with non ascii save path and non UTF-8 game codepage;
* bug with language changing;
* bug with anigif rotate and scale;
* bug with anigif and cursor;
* typo in Please, wait message;
-- Peter Kosyh Sun, 26 Jan 2014 13:12:00 +0400
1.9.1
* INSTEAD moved to sourceforge.net;
* pt_BR language added;
* example code fixes;
* -hinting option;
* fixes in Makefiles and configure script;
* lua5.2 fix;
* bug fix in new/delete;
-- Peter Kosyh Sat, 13 Jul 2013 14:30:00 +0400
1.9.0
* music track from svenzzon!!!;
* SDL2.0 ready;
* -owntheme option;
* -noautosave option is not saved;
* -software option;
* bug fix: segfault while change and restart game;
* img_t refactoring for future architecture upgrade;
* fix in anigif scale;
* fix in stead.dialog_rescan;
* mp3 fix in windows binary;
-- Peter Kosyh Fri, 12 Apr 2013 08:00:00 +0400
1.8.3
* bug fix in start() from gamefile;
* bug fix do not remove xwalk after gamereset;
* bug fix in txtnb (lua5.2 compat);
* stead.savename function to redefine saves;
* stead.restart() to restart current game;
* p/pr/pn now do error when called from global context;
* now math. and os. moved to stead;
* debian friendly release;
* ctrl-r is alt-r, ctrl-q is alt-q alias;
* New documentation;
* stead.menu_toggle 'load/save/quit' parameters;
-- Peter Kosyh Sat, 26 Jan 2013 14:50:00 +0400
1.8.2
* bug fix in gfx_chnage_screen (sigsegv);
* bug fix in sound system (sigsegv);
* bug fix in input.lua and lua 5.2;
* bug fix filter scr.w scr.h changing from lua;
* fixes in ua tutorial;
* icon fix in windows;
* cleanup code for clang;
-- Peter Kosyh Wed, 19 Dec 2012 10:34:00 +0400
1.8.1
* bug fix in onact (stop act on non-nil return);
* bug fix in instead_gamespath/instead_themespath/instead_steadpath;
* bug fix in vars, now do not empty [k] indexes in obj;
* bug fix flickering while theme update;
* new theme parameter: win.scroll.mode = [0|1|2|3]
* new iface.anchor feature;
* faster working with layouts;
-- Peter Kosyh Sat, 17 Nov 2012 10:32:00 +0400
1.8.0
* bug fix in stead.need_scene() with parameter;
* bug fix in click on sprite;
* bug fix in save logic (table keys);
* bug fixes in dbg;
* bug fix in proxy_menu - possible to work with nam = true objects;
* bug fix much more faster text output;
* bug fix if use returns nil and used returns true;
* bug fix in change theme on the fly via theme module (segfault);
* bug fix in restore_snapshot (extra call to main.enter);
* internal functions are moved in stead. table;
* dbg module: use f7 key again to exit debugger;
* enable and disable methods in list;
* sprite.rotate, sprite.scale smooth parameter;
* much better scr.gfx.scalable handling, uses /2^x scaler;
* CPPFLAGS added in Makefile;
* scaler is updated from SDL_gfx 2.0.24;
-- Peter Kosyh Sat, 1 Sep 2012 12:08:00 +0400
1.7.0
* bug fix in phrase handling w/o parameter;
* bug fix in zip unpack;
* fix in doc/menu;
* svg logo added;
* busy dialog;
* opt_fading;
* normal fonts in MAEMO build;
* wroom module added;
* nouse module added;
* nolife module added;
* hideinv module is better now, noinv added;
* new dlg module:
* - better dlg syntax;
* - psub/pret/pjump/pstart;
* - stead.phrase_prefix;
* compose in sprite;
* game.onact/onwalk/onuse/oninv handlers;
* counters module;
* proxymenu module;
* configure.sh now checks gtk+-3.0;
* sprite.box and sprite.blank;
* stead.add_var internal command;
* stead.gui.hideways added;
* xact now searches recursive;
* xwalk xact added;
* lifeon with order;
* correct output from life methods while moving;
* stead.need_scene() added;
* stead.last_act() added;
* move/purge/remove/replace now works with lists too;
* dialog:visible added;
-- Peter Kosyh Thu, 17 Apr 2012 13:40:00 +0400
1.6.2
* bugfix in build with SDL_mixer 1.2.12;
* configure.sh script is now compatible with BSD;
-- Peter Kosyh Thu, 08 Feb 2012 19:52:00 +0400
1.6.1
* bugfix in prefs;
* justify text option;
* new track in tutorial3 (svenzzon - The Titan Turrican, CC BY-NC-SA 3.0);
* SDL_mixer 1.2.12 ready;
* gentoo build fix;
* fix build on systems w/o PATH_MAX;
* fix SDL icon;
-- Peter Kosyh Thu, 19 Jan 2012 09:43:00 +0400
1.6.0
* CJK support;
* set_music_fading(out, in);
* 1 bit transparent icon added for SDL;
* stead.space_delim added;
* get_themespath, get_gamespath;
* goto -> walk, goXX -> walkXX;
* lua5.2 ready;
-- Peter Kosyh Tue, 20 Dec 2011 08:37:00 +0400
1.5.2
* bug fix in release kbd event;
* improved motion mode;
* align in float gfx mode;
-- Peter Kosyh Sun, 10 Oct 2011 09:49:00 +0400
1.5.1
* bug fix in game_cmd logic;
* bug fix in highlight logic;
* bug fix in click sound logic;
* bug fix in RAW_TEXT;
* INSTEAD SDL -> INSTEAD;
* languages are sorted now;
* set_timer moved to stead;
* code cleanup (no warnings);
-- Peter Kosyh Mon, 05 Sep 2011 16:11:00 +0400
1.5.0
* bug fix in gamefile;
* bug fix in lifes output;
* bug fix in PLAYER_MOVED and lifes;
* bug fix in stop_sound;
* bug fix in set_music with parameter;
* bug fix in highlighting;
-- Peter Kosyh Mon, 17 Aug 2011 15:40:00 +0400
1.4.5
* bug fix in rnd() w/o parameter;
* bug fix in for_each;
* bug fix in txtnb and \;
* bug fix in original theme picture scaling;
* bug fix in left/right image alignment;
* bug fix in change_pl;
* callpush/callpop/cctx/strip do_ini and some others moved to stead;
* win.align added to theme;
* theme_name added;
* now fading first value is new scene flag;
* more information in error messages while checking lists;
* fixes in input module;
* added PLATFORM variable;
-- Peter Kosyh Mon, 01 Aug 2011 12:24:00 +0400
1.4.4
* bug fix in direct mode (memory leak);
-- Peter Kosyh Thu, 21 Jun 2011 08:57:00 +0400
1.4.3
* bug fix in nopara logic;
* ukranian translation and tutorial;
* multilang tutorial3;
* sprites small fix (predefined handles);
* cursor changing from direct mode support;
-- Peter Kosyh Thu, 19 Jun 2011 10:24:00 +0400
1.4.2
* bug fix in idf gets;
* bug fix in kbd hooking;
-- Peter Kosyh Thu, 19 May 2011 20:14:00 +0400
1.4.1
* bug fix in use;
-- Peter Kosyh Thu, 12 May 2011 23:53:00 +0400
1.4.0
* bug fix in imgl/imgr;
* bug fix in xact;
* bug fix in jump to pos logic;
* bug fix lags in sound;
* bug fix with wince/windows with themes load;
* experemental sprites technology;
* global dir is now readdir;
* multichannal sound system (add_sound, stop_sound);
* -appdata parameter;
* -chunksize parameter;
* show dir to be deleted while remove game;
* theme reset features;
* mouse_pos added;
* start() init function added;
* idf files support (instead data format);
* menu font is now scalable too;
* toggle_menu added;
* get_ticks added;
* bit_xxx bitwise operations added;
* visits() added;
-- Peter Kosyh Mon, 2 May 2011 08:35:00 +0400
1.3.4
* bug fix in text renderer with italic text;
* bug fix with imgl/imgr in inv;
* bug fix in video init;
* bug fix in theme.get 'inv.mode'
* font faces { } syntax;
* new default font;
* set light hinting for font;
* added stead.dir iterator;
* modules doc fix;
* languages dir renamed to lang;
* updated manual.pdf;
-- Peter Kosyh Fri, 11 Mar 2011 12:10:00 +0300
1.3.3
* bug fix (kbd input);
* modules docs;
-- Peter Kosyh Thu, 27 Feb 2011 20:27:00 +0300
1.3.2
* bug fix (dates in save slots);
* bug fix (dbg fixes);
* bug fix (f8 and f9 error msg);
* small bug fixes;
* s60 build;
* SDL 1.3 ready;
* it lang and tutorial;
-- Peter Kosyh Thu, 24 Feb 2011 13:12:00 +0300
1.3.1
* bug fix (imgl and justify);
* bug fix (languages in WinCE version);
* bug fix (prefs do not create save dir);
* bug fix (alt+f4 != f4);
* bug fix ( tag parsing)
* visual.lua moved to doc/;
-- Peter Kosyh Mon, 29 Nov 2010 15:44:00 +0300
1.3.0
* bug fix (resample sounds while HZ change);
* bug fix (empty bg);
* bug fix (cursor center scaling);
* changing themes from game;
* strike ougth text;
* *.fnt.height theme parameter;
* scroller positions in theme;
* escaping ^ and delim;
* box: and blank:;
* pad: ;
* speed up;
* picture flow;
* prefs:purge now removes vars;
* dialog:empty added;
* txttab;
* tutorial update;
-- Peter Kosyh Tue, 12 Nov 2010 22:42:00 +0300
1.2.3
* android build;
* bug in xact;
-- Peter Kosyh Sat, 2 Oct 2010 16:53:00 +0300
1.2.2
* xact now can be used from everywhere;
* much speed improvments;
* get_gamepath, get_steadpath;
* wince port;
* internal cleanups;
-- Peter Kosyh Fri, 25 Sep 2010 13:59:00 +0300
1.2.1
* clearlooks bg;
* fading fix;
* fix in disable autosave;
* disp fix;
* savevars optimization;
-- Peter Kosyh Sat, 28 Aug 2010 19:47:00 +0300
1.2.0
* unpack/install feature;
* browse feature (win32 and gtk);
* clever game/themes sorting;
* time of save slots;
* remove games;
* aligned width for themes/games dialog;
* local appdata mode (portable app);
* clearlooks theme;
* inv align modes;
* prefs object;
* init() function;
* snapshots (not documented?);
* hook and inherit;
* entered/left human friendly actions;
* stead.cmd, stead.args added;
* require added (modules: goto, xact, input, click, vars, dbg, snapshot, prefs, format, kbd, hotkeys);
* instead_version added;
* taketo/takef added;
* disable/enable/disable_all/enable_all added;
* exist() added (seen over disabled objects);
* path() added;
* visited() added;
* live() added;
* nameof() added;
* goback() added;
* goin()/goout() added;
* disp attribute added;
* improved debugger;
* save vars in _G;
* no duplicated games while looking gamespaths;
* code function!!!;
* args in call;
* var and global;
* self() is now self;
* psen/punseen for dlg;
* no return goto needed;
* disable_all/enable_all for lists;
* purge and list_purge;
* undocumented gamefile;
* undocumented LANG;
* many bugfixes...
* mac os x port
-- Peter Kosyh Wed, 04 Aug 2010 20:59:00 +0300
1.1.6
* bugfix (no closed files)
* bugfix in line breaking with gfx
* pr() function added in stead
-- Peter Kosyh Wed, 19 May 2010 17:31:00 +0300
1.1.5
* multiple anigif in one layout fix
* click in picture event
* game.action callback
* bug in parsing
* pause game while minimize (-nopause option added)
* 8 bit scaler fix
-- Peter Kosyh Wed, 10 Mar 2010 15:20:51 +0300
1.1.4
* keyboard navigation fix
* cursor while menu and lost focus
* lower cpu usage (no gif logic if no gifs are drawn)
-- Peter Kosyh Fri, 05 Mar 2010 18:28:40 +0300
1.1.3
* unix_path in get_img
* " in variable names (save)
* \\, in ways and inv
* 32 bit bmp fix
-- Peter Kosyh Wed, 16 Feb 2010 11:10:40 +0300
1.1.2
* bug in saves
-- Peter Kosyh Wed, 10 Feb 2010 13:39:47 +0300
1.1.1
* encode path in save (' in paths);
* mouse focus win problem;
* cleanups in stead.lua;
* doc updates;
* alt-r in debug mode (restart game);
* -version arg
* timer
* kbd input
* mouse input
* bluesteel theme
* nop cmd added in stead.lua
* anigif while menu bug
* add inv.mode disabled
* txtnb added to stead.lua
* bg scaling fix
* broken codepage fix
* esc strings in tag
* themes and games sorting
-- Peter Kosyh Thu, 14 Jan 2010 15:47:50 +0300
1.0.5
* segfault while is_sound with -nosound
* snd volume hack
* arctic theme added
-- Peter Kosyh Thu, 7 Jan 2010 14:34:00 +0300
1.0.4
* xref hl optimization
* now, write save slots in game/saves if game/saves exist
-- Peter Kosyh Tue, 12 Dec 2009 17:15:00 +0300
1.0.3
* autojump to text change
* no cursor flickering
* sdl_path (i18n M$ win sdl paths)
-- Peter Kosyh Tue, 01 Dec 2009 14:12:36 +0300
1.0.2
* fix in box normalize
* fix in scaling
* fixed f5 key
-- Peter Kosyh Tue, 24 Nov 2009 11:00:20 +0300
1.0.1
* bug: no fclose :(
* i18n + esp lang and tutorial
* cat removed
-- Peter Kosyh Fri, 20 Nov 2009 13:33:36 +0300
1.0.0
* another mouse buttons -- escape
* escape as use cancel
* lifeoff from life method
* bug fix in anigif logic
* bug fix in img()
* more clever linebreaking
* kill empty lines at end of layout
* typo in color table (graphics.c) by Alexander
* manual.tex by Alexander and commiters
* default-large theme is default
* combined images!
* page up, page down clever logic;
* f8/f9 - quicksave/quickload
* game.enable_save
* lua stack overflow???
* autosave function
-- Peter Kosyh Tue, 3 Nov 2009 19:43:00 +0400
0.9.3
* have and seen fixes
* -encode option + doencfile
* print reregister
* from() with argument
* new()/delete() (allocator object)
* game:ini() fix
* theme scaling (yahoo!!!)
* scene_use attribute (scene objects using on each other)
-- Peter Kosyh Mon, 12 Oct 2009 11:09:19 +0400
0.9.2
* animated gif support
* graphics everywhere!!! (img cmd)
* sound support (set_sound)
* some cleanups
-- Peter Kosyh Sun, 4 Oct 2009 16:10:09 +0400
0.9.1
* mouse gfx bug
* noautosave option
* make uninstall
* === STEAD FIXES ====
* gui.lua is separated file
* menus in gfx mode!!! :)
* stead.lua fixes:
* ref fix
* phrases saver
* player is object
* disable_all enable_all for object
* inherited inventory
* save game bug
* own stead namespace (table problem fixed!)
* recurse for exit breaking
* wrap into vroom not call exit twice
-- Peter Kosyh Sun, 15 Sep 2009 14:42:44 +0400
0.9
* fixes in localization
* srch method for player
* english tutorial
* separated games?
* font with apache license :)
* debian cleanups
-- Peter Kosyh Tue, 15 Sep 2009 13:29:55 +0400
0.8.9
* -gamespath -game -themespath -theme options
* runtime languages
* disabled() function in stead
* list_zap, list_concat
* remove added
* stead.lua fixes
* full keyboard control!
* alt enter fix
* gfx cursor support!
* no reset video while change game/theme
* tutorial2
-- Peter Kosyh Sat, 5 Aug 2009 11:25:01 +0400
0.8.8
* Makefiles fixes
* cat game fix
* .insteadrc now in .instead/ by default
* small fixes
-- Peter Kosyh Mon, 31 Aug 2009 11:25:01 +0400
0.8.7
* highlight bug in nohl mode with filter
* windows version now looks games and themes in appdir.
-- Peter Kosyh Fri, 28 Aug 2009 12:51:08 +0400
0.8.6
* cat game fixes
* seen extra parameter
-- Peter Kosyh Thu, 27 Aug 2009 08:32:00 +0400
0.8.5
* possible segfaults in timer functions.
* ways, objs, drop, take - extra parameters
* put - function
* set_music fixes
-- Peter Kosyh Thu, 24 Aug 2009 08:32:00 +0400
0.8.4
* bug in savevar
* bug in long strings
-- Peter Kosyh Thu, 20 Aug 2009 14:09:14 +0400
0.8.3
* sge forgotten
* set_music with loop argument bug
-- Peter Kosyh Wed, 19 Aug 2009 14:09:14 +0400
0.8.2
* gfx_load_img
-- Peter Kosyh Sat, 15 Aug 2009 14:09:14 +0400
0.8
* key_name attribute!!!!
* do_ini
* version in stead.lua
* opairs!!!
* vroom fix
* typos
* backtrace
* list_set
-- Peter Kosyh Fri, 07 Aug 2009 14:09:14 +0400
0.7.7-4
* Fixes :)
-- Peter Kosyh Wed, 05 Aug 2009 17:26:55 +0400
0.7.7
* typos in games
* local games now can be placed in ~/.instead/games
* console version disabled by default
* -- $Name: tag -- full name of games, not only dirname
* fix of link color for title/ways
* bug in text layout logic (last period in line)
* embedded mode (picture and ways inside text!!!)
* float mode (for books)
* faster text renderer (x2-x5 times)
* free motion mode of scrolling
* click sound support in themes (in default theme too)
* new options (click sound, music)
* default theme changes
* fullscreen with Alt-Enter
* options -alsa, -fullscreen, -window
* now italic font used for events
* 48000Hz added in sound preferences
* now scroll position is saved in embedded mode
* themes support, one new theme added
* vway and vroom added
* updated tutorial
* set_music now take an loop parameter
* autosave is now default
* now vars saved if they begins from uppercase letter (undescore too);
* save slots !!!
* a lot of bugs fixed (memory leaks, SDL cavities.. )
-- Peter Kosyh Thu, 30 Jul 2009 18:12:19 +0400
0.7.6-1
* debian file fixes
-- Peter Kosyh Fri, 29 May 2009 14:17:01 +0400
0.7.6
* Keyboard scrolling
* Font scaling
* inv.horiz option
-- Peter Kosyh Mon, 25 May 2009 10:58:29 +0400
0.7.5
* Initial release
-- Peter Kosyh Thu, 21 May 2009 11:40:29 +0400
instead-3.1.2/cmake/ 000755 001751 001751 00000000000 13146577472 014546 5 ustar 00peter peter 000000 000000 instead-3.1.2/cmake/FindGTK3.cmake 000644 001751 001751 00000001530 13146577472 017060 0 ustar 00peter peter 000000 000000 # - Try to locate GTK3
# This module defines:
#
# GTK3_INCLUDE_DIRS
# GTK3_LIBRARIES
# GTK3_FOUND
# GTK3_DEFINITIONS
#
FIND_PACKAGE(PkgConfig)
PKG_CHECK_MODULES(PC_GTK3 REQUIRED gtk+-3.0)
SET(GTK3_INCLUDE_DIRS ${PC_GTK3_INCLUDE_DIRS})
FOREACH(LIB ${PC_GTK3_LIBRARIES})
FIND_LIBRARY(FOUND${LIB} HINTS ${PC_GTK3_LIBRARY_DIRS} NAMES ${LIB})
LIST(APPEND GTK3_LIBRARIES ${FOUND${LIB}})
ENDFOREACH(LIB)
IF(GTK3_INCLUDE_DIRS AND GTK3_LIBRARIES)
SET(GTK3_FOUND TRUE)
ENDIF(GTK3_INCLUDE_DIRS AND GTK3_LIBRARIES)
IF(GTK3_FOUND)
IF(NOT GTK3_FIND_QUIETLY)
MESSAGE(STATUS "Found GTK3: -I${GTK3_INCLUDE_DIRS}, ${GTK3_LIBRARIES}")
ENDIF(NOT GTK3_FIND_QUIETLY)
ELSE(GTK3_FOUND)
IF(GTK3_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find GTK3")
ENDIF(GTK3_FIND_REQUIRED)
ENDIF(GTK3_FOUND)
MARK_AS_ADVANCED(GTK3_INCLUDE_DIRS GTK3_LIBRARIES GTK3_FOUND)
instead-3.1.2/cmake/FindIconv.cmake 000644 001751 001751 00000002600 13146577472 017425 0 ustar 00peter peter 000000 000000 # - Locate Iconv
# This module defines:
#
# ICONV_INCLUDE_DIR
# ICONV_LIBRARY
# ICONV_FOUND
#
# Look for header file
FIND_PATH(ICONV_INCLUDE_DIR NAMES iconv.h)
# Check if library is needed
IF(ICONV_INCLUDE_DIR)
SET(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR})
TRY_COMPILE(ICONV_LIBRARY_NOT_NEEDED
${CMAKE_BINARY_DIR}
${CMAKE_MODULE_PATH}/iconv_test.c
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${ICONV_INCLUDE_DIR}"
)
IF(NOT ICONV_LIBRARY_NOT_NEEDED)
# Locate library
FIND_LIBRARY(ICONV_LIBRARY NAMES iconv libiconv)
IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARY)
SET(ICONV_FOUND TRUE)
ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARY)
ELSE(NOT ICONV_LIBRARY_NOT_NEEDED)
# Library not needed
SET(ICONV_LIBRARY)
IF(ICONV_INCLUDE_DIR)
SET(ICONV_FOUND TRUE)
ENDIF(ICONV_INCLUDE_DIR)
ENDIF(NOT ICONV_LIBRARY_NOT_NEEDED)
ENDIF(ICONV_INCLUDE_DIR)
# Show status
IF(ICONV_FOUND)
IF(NOT Iconv_FIND_QUIETLY)
IF(ICONV_LIBRARY_NOT_NEEDED)
MESSAGE(STATUS "Found Iconv: -I${ICONV_INCLUDE_DIR}, library not needed")
ELSE(ICONV_LIBRARY_NOT_NEEDED)
MESSAGE(STATUS "Found Iconv: -I${ICONV_INCLUDE_DIR}, ${ICONV_LIBRARY}")
ENDIF(ICONV_LIBRARY_NOT_NEEDED)
ENDIF(NOT Iconv_FIND_QUIETLY)
ELSE(ICONV_FOUND)
IF(Iconv_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find ICONV")
ENDIF(Iconv_FIND_REQUIRED)
ENDIF(ICONV_FOUND)
MARK_AS_ADVANCED(ICONV_INCLUDE_DIR ICONV_LIBRARY ICONV_FOUND)
instead-3.1.2/cmake/FindSDL2.cmake 000644 001751 001751 00000001174 13146577472 017060 0 ustar 00peter peter 000000 000000 # - Try to locate SDL2
# This module defines:
#
# SDL2_INCLUDE_DIR
# SDL2_LIBRARY
# SDL2_FOUND
#
FIND_PATH(SDL2_INCLUDE_DIR NAMES SDL.h PATH_SUFFIXES SDL2)
FIND_LIBRARY(SDL2_LIBRARY NAMES SDL2)
IF(SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
SET(SDL2_FOUND TRUE)
ENDIF(SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
IF(SDL2_FOUND)
IF(NOT SDL2_FIND_QUIETLY)
MESSAGE(STATUS "Found SDL2: -I${SDL2_INCLUDE_DIR}, ${SDL2_LIBRARY}")
ENDIF(NOT SDL2_FIND_QUIETLY)
ELSE(SDL2_FOUND)
IF(SDL2_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find SDL2")
ENDIF(SDL2_FIND_REQUIRED)
ENDIF(SDL2_FOUND)
MARK_AS_ADVANCED(SDL2_INCLUDE_DIR SDL2_LIBRARY SDL2_FOUND)
instead-3.1.2/cmake/FindSDL2_image.cmake 000644 001751 001751 00000001414 13146577472 020217 0 ustar 00peter peter 000000 000000 # - Try to locate SDL2_image
# This module defines:
#
# SDL2IMAGE_INCLUDE_DIR
# SDL2IMAGE_LIBRARY
# SDL2IMAGE_FOUND
#
FIND_PATH(SDL2IMAGE_INCLUDE_DIR NAMES SDL_image.h PATH_SUFFIXES SDL2)
FIND_LIBRARY(SDL2IMAGE_LIBRARY NAMES SDL2_image)
IF(SDL2IMAGE_INCLUDE_DIR AND SDL2IMAGE_LIBRARY)
SET(SDL2IMAGE_FOUND TRUE)
ENDIF(SDL2IMAGE_INCLUDE_DIR AND SDL2IMAGE_LIBRARY)
IF(SDL2IMAGE_FOUND)
IF(NOT SDL2_image_FIND_QUIETLY)
MESSAGE(STATUS "Found SDL2_image: -I${SDL2IMAGE_INCLUDE_DIR}, ${SDL2IMAGE_LIBRARY}")
ENDIF(NOT SDL2_image_FIND_QUIETLY)
ELSE(SDL2IMAGE_FOUND)
IF(SDL2_image_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find SDL2_image")
ENDIF(SDL2_image_FIND_REQUIRED)
ENDIF(SDL2IMAGE_FOUND)
MARK_AS_ADVANCED(SDL2IMAGE_INCLUDE_DIR SDL2IMAGE_LIBRARY SDL2IMAGE_FOUND)
instead-3.1.2/cmake/FindSDL2_mixer.cmake 000644 001751 001751 00000001414 13146577472 020261 0 ustar 00peter peter 000000 000000 # - Try to locate SDL2_mixer
# This module defines:
#
# SDL2MIXER_INCLUDE_DIR
# SDL2MIXER_LIBRARY
# SDL2MIXER_FOUND
#
FIND_PATH(SDL2MIXER_INCLUDE_DIR NAMES SDL_mixer.h PATH_SUFFIXES SDL2)
FIND_LIBRARY(SDL2MIXER_LIBRARY NAMES SDL2_mixer)
IF(SDL2MIXER_INCLUDE_DIR AND SDL2MIXER_LIBRARY)
SET(SDL2MIXER_FOUND TRUE)
ENDIF(SDL2MIXER_INCLUDE_DIR AND SDL2MIXER_LIBRARY)
IF(SDL2MIXER_FOUND)
IF(NOT SDL2_mixer_FIND_QUIETLY)
MESSAGE(STATUS "Found SDL2_mixer: -I${SDL2MIXER_INCLUDE_DIR}, ${SDL2MIXER_LIBRARY}")
ENDIF(NOT SDL2_mixer_FIND_QUIETLY)
ELSE(SDL2MIXER_FOUND)
IF(SDL2_mixer_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find SDL2_mixer")
ENDIF(SDL2_mixer_FIND_REQUIRED)
ENDIF(SDL2MIXER_FOUND)
MARK_AS_ADVANCED(SDL2MIXER_INCLUDE_DIR SDL2MIXER_LIBRARY SDL2MIXER_FOUND)
instead-3.1.2/cmake/FindSDL2_ttf.cmake 000644 001751 001751 00000001326 13146577472 017734 0 ustar 00peter peter 000000 000000 # - Try to locate SDL2_ttf
# This module defines:
#
# SDL2TTF_INCLUDE_DIR
# SDL2TTF_LIBRARY
# SDL2TTF_FOUND
#
FIND_PATH(SDL2TTF_INCLUDE_DIR NAMES SDL_ttf.h PATH_SUFFIXES SDL2)
FIND_LIBRARY(SDL2TTF_LIBRARY NAMES SDL2_ttf)
IF(SDL2TTF_INCLUDE_DIR AND SDL2TTF_LIBRARY)
SET(SDL2TTF_FOUND TRUE)
ENDIF(SDL2TTF_INCLUDE_DIR AND SDL2TTF_LIBRARY)
IF(SDL2TTF_FOUND)
IF(NOT SDL2_ttf_FIND_QUIETLY)
MESSAGE(STATUS "Found SDL2_ttf: -I${SDL2TTF_INCLUDE_DIR}, ${SDL2TTF_LIBRARY}")
ENDIF(NOT SDL2_ttf_FIND_QUIETLY)
ELSE(SDL2TTF_FOUND)
IF(SDL2_ttf_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find SDL2_ttf")
ENDIF(SDL2_ttf_FIND_REQUIRED)
ENDIF(SDL2TTF_FOUND)
MARK_AS_ADVANCED(SDL2TTF_INCLUDE_DIR SDL2TTF_LIBRARY SDL2TTF_FOUND)
instead-3.1.2/cmake/InstallSymlink.cmake 000644 001751 001751 00000001041 13146577472 020521 0 ustar 00peter peter 000000 000000 MACRO(INSTALL_SYMLINK linktarget linkname destination)
IF(UNIX)
IF(IS_ABSOLUTE ${destination})
SET(FULL_DESTINATION "\$ENV{DESTDIR}${destination}/${linkname}")
ELSE(IS_ABSOLUTE ${destination})
SET(FULL_DESTINATION "\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${destination}/${linkname}")
ENDIF(IS_ABSOLUTE ${destination})
INSTALL(CODE "MESSAGE(STATUS \"Symlink: ${FULL_DESTINATION} -> ${linktarget}\")
EXECUTE_PROCESS(COMMAND \${CMAKE_COMMAND} -E create_symlink ${linktarget} ${FULL_DESTINATION})")
ENDIF()
ENDMACRO()
instead-3.1.2/cmake/iconv_test.c 000644 001751 001751 00000000103 13146577472 017061 0 ustar 00peter peter 000000 000000 #include
int main() {
iconv_open("", "");
return 0;
}
instead-3.1.2/CMakeLists.txt 000644 001751 001751 00000004664 13146577472 016240 0 ustar 00peter peter 000000 000000 PROJECT(instead)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0)
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
IF("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
MESSAGE(FATAL_ERROR "In-source builds are not allowed. Please read ./INSTALL")
ENDIF("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
# global constants
SET(INSTEAD_VERSION "3.1.2")
# options
OPTION(WITH_SDL2 "Use SDL2 instead of SDL 1" OFF)
OPTION(WITH_GTK2 "Use GTK2 file open dialog" ON)
OPTION(WITH_GTK3 "Use GTK3 file open dialog" OFF)
OPTION(WITH_ICONV "Build with iconv support" ON)
# Note: there are three build modes for instead:
# - SYSTEMWIDE
# use it if you want to install it into your (unix) system
# - STANDALONE
# used to generate standalone relocatable package which
# may be run from anywhere. `install' target stages all
# required files in ${STANDALONEDIR} for you
# - neither (both options disabled)
# instead may be run from build directory, useful for
# development
OPTION(SYSTEMWIDE "Build for systemwide install" ON)
OPTION(STANDALONE "Build for standalone package creation" OFF)
IF(WITH_GTK2 AND WITH_GTK3)
MESSAGE(WARNING "WITH_GTK2 and WITH_GTK3 are mutually exclusive, disabling WITH_GTK2")
SET(WITH_GTK2 OFF)
ENDIF(WITH_GTK2 AND WITH_GTK3)
IF(SYSTEMWIDE AND STANDALONE)
MESSAGE(WARNING "Both STANDALONE and SYSTEMWIDE specified, disabling SYSTEMWIDE")
SET(SYSTEMWIDE OFF)
ENDIF(SYSTEMWIDE AND STANDALONE)
SET(BINDIR "bin" CACHE STRING "Where to install binaries")
SET(DATADIR "share/instead" CACHE STRING "Where to install data files")
SET(STEADDIR "${DATADIR}/stead" CACHE STRING "Where to install stead files")
SET(THEMESDIR "${DATADIR}/themes" CACHE STRING "Where to install themes")
SET(GAMESDIR "${DATADIR}/games" CACHE STRING "Where to install games")
SET(ICONDIR "share/pixmaps" CACHE STRING "Where to install icons")
SET(DOCDIR "share/doc/instead" CACHE STRING "Where to install documentation")
SET(LANGDIR "${DATADIR}/lang" CACHE STRING "Where to install language files")
SET(MANDIR "share/man" CACHE STRING "Where to install man pages")
SET(DESKTOPDIR "share/applications" CACHE STRING "Where to install .desktop files")
SET(STANDALONEDIR "${PROJECT_BINARY_DIR}/standalone" CACHE STRING "Where to stage stanalone package")
# subdirectories
ADD_SUBDIRECTORY(desktop)
ADD_SUBDIRECTORY(doc)
ADD_SUBDIRECTORY(games)
ADD_SUBDIRECTORY(icon)
ADD_SUBDIRECTORY(lang)
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(stead)
ADD_SUBDIRECTORY(themes)
instead-3.1.2/config.make 000644 001751 001751 00000000040 13146577535 015564 0 ustar 00peter peter 000000 000000 # you can define own flags here
instead-3.1.2/configure-mingw32.sh 000755 001751 001751 00000000337 13146577472 017275 0 ustar 00peter peter 000000 000000 #!/bin/sh
rm -f config.make
rm -f Rules.make
ln -sf Rules.mingw Rules.make
rm -f sdl-instead
ln -sf src/sdl-instead sdl-instead
echo "Ok. We are ready to build. Use this command:"
echo " \$ make -f Makefile.mingw setup"
instead-3.1.2/configure.sh 000755 001751 001751 00000015205 13146577472 016011 0 ustar 00peter peter 000000 000000 #!/bin/sh
echo -n "Checking pkg-config..."
if ! pkg-config --version >/dev/null 2>&1; then
echo "error: no pkg-config in \$PATH."
exit 1
fi
echo "ok"
echo -n "Checking pkg-config --cflags zlib..."
if ! pkg-config --cflags zlib >/dev/null 2>&1; then
if [ -f /usr/include/zlib.h ]; then
echo "not found, but /usr/include/zlib.h exists..."
zlib_cflags="x"
zlib_libs=
else
echo "internal"
zlib_cflags=
zlib_libs=
fi
else
echo "system"
zlib_cflags="pkg-config --cflags zlib"
zlib_libs="pkg-config --libs zlib"
fi
echo -n "Checking pkg-config --cflags gtk+-2.0..."
if ! pkg-config --cflags gtk+-2.0 >/dev/null 2>&1; then
echo "no"
echo -n "Checking pkg-config --cflags gtk+-3.0..."
if ! pkg-config --cflags gtk+-3.0 >/dev/null 2>&1; then
echo "no open file dialog"
gtk_cflags=
gtk_libs=
else
echo "yes"
gtk_cflags="pkg-config --cflags gtk+-3.0"
gtk_libs="pkg-config --libs gtk+-3.0"
fi
else
echo "yes"
gtk_cflags="pkg-config --cflags gtk+-2.0"
gtk_libs="pkg-config --libs gtk+-2.0"
fi
echo -n "Checking pkg-config --cflags lua[5.1|5.2|51|52|jit]..."
if [ "x$LUA" = "x" ]; then
lua_ver="lua5.1 lua5.2 lua lua-5.1 lua-5.2 lua51 lua52 luajit"
else
lua_ver=$LUA
fi
for v in $lua_ver; do
if pkg-config --cflags "$v" >/dev/null 2>&1; then
echo "$v"
lua_cflags="pkg-config --cflags $v"
lua_libs="pkg-config --libs $v"
break
fi
done
if test "x$lua_libs" = "x"; then
echo "failed: no package lua/lua5.1/lua5.2/lua-5.1/lua-5.2/lua51/lua52"
echo "Please install lua development package."
exit 1
fi
echo -n "Checking sdl2-config..."
if ! sdl2-config --version >/dev/null 2>&1; then
if ! sdl-config --version >/dev/null 2>&1; then
echo "error: no sdl-config/sdl2-config in \$PATH."
echo "Please install sdl, sdl_ttf, sdl_mixer and sdl_image development packages."
exit 1
fi
echo "no, using SDL 1.xx"
sdl_config="sdl-config"
sdl_libs="-lSDL_ttf -lSDL_mixer -lSDL_image -lm"
else
echo "ok"
sdl_config="sdl2-config"
sdl_libs="-lSDL2_ttf -lSDL2_mixer -lSDL2_image -lm"
fi
echo -n "Checking sdl-config --cflags..."
if ! $sdl_config --cflags >/dev/null 2>&1; then
echo "failed."
exit 1
fi
echo "ok"
ops="$CPPFLAGS $CFLAGS $LDFLAGS"
ops=$ops" "`$lua_cflags`
ops=$ops" "`$lua_libs`
echo -n "Looking for compiler..."
if ! $CC --version >/dev/null 2>&1; then
if ! cc --version >/dev/null 2>&1; then
if ! gcc --version >/dev/null 2>&1; then
echo "cc, gcc, \$(CC) are not valid compilers... Please export CC for valid one...";
exit 1;
else
cc="gcc";
fi
else
cc="cc"
fi
else
cc=$CC
fi
cat << EOF >/tmp/sdl-test.c
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
return 0;
}
EOF
echo $cc
echo -n "Checking test build...("
echo -n $cc /tmp/sdl-test.c $ops `$sdl_config --cflags` `$sdl_config --libs` $sdl_libs -o /tmp/sdl-test ")..."
if ! $cc /tmp/sdl-test.c $ops `$sdl_config --cflags` `$sdl_config --libs` $sdl_libs -o /tmp/sdl-test; then
echo "failed".
echo "Please sure if these development packages are installed: sdl, sdl_ttf, sdl_mixer, sdl_image."
rm -f /tmp/sdl-test.c /tmp/sdl-test
exit 1
fi
echo "ok"
rm -f /tmp/sdl-test.c /tmp/sdl-test
cat << EOF >/tmp/iconv-test.c
#include
int main(int argc, char **argv)
{
iconv_open("","");
}
EOF
echo $cc
echo -n "Checking iconv...("
echo -n "$cc /tmp/iconv-test.c -o iconv-test)..."
if $cc /tmp/iconv-test.c -o /tmp/iconv-test >/dev/null 2>&1; then
CFLAGS="$CFLAGS -D_HAVE_ICONV -DLIBICONV_PLUG" # force FreeBSD to use iconv.h from base
echo "ok"
elif $cc /tmp/iconv-test.c -liconv -o /tmp/iconv-test >/dev/null 2>&1; then
CFLAGS="$CFLAGS -D_HAVE_ICONV"
LDFLAGS="$LDFLAGS -liconv"
echo "ok, with -liconv"
elif $cc /tmp/iconv-test.c -I/usr/local/include -L/usr/local/lib -liconv -o /tmp/iconv-test >/dev/null 2>&1; then
CFLAGS="$CFLAGS -I/usr/local/include -D_HAVE_ICONV"
LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv"
echo "ok, with -liconv and -L/usr/local/lib"
else
echo -n "failed. Build without iconv.".
fi
rm -f /tmp/iconv-test.c /tmp/iconv-test
if ! make clean >/dev/null 2>&1; then
echo " * Warning!!! Can not do make clean..."
fi
echo -n "Generating config.make..."
echo "# autamatically generated by configure.sh" >config.make
if [ ! -z "$CFLAGS" ]; then
echo "EXTRA_CFLAGS+=$CFLAGS" >> config.make
fi
if [ ! -z "$LDFLAGS" ]; then
echo "EXTRA_LDFLAGS+=$LDFLAGS" >> config.make
fi
if [ ! -z "$gtk_cflags" ]; then
echo "EXTRA_CFLAGS+=-D_USE_GTK -D_USE_BROWSE" >> config.make
echo "EXTRA_CFLAGS+=\$(shell $gtk_cflags)" >> config.make
echo "EXTRA_LDFLAGS+=\$(shell $gtk_libs)" >> config.make
fi
if [ -z "$zlib_cflags" ]; then
echo "SUBDIRS=src/zlib" >> config.make
echo "ZLIB_CFLAGS=-I../zlib" >> config.make
echo "ZLIB_LFLAGS=../zlib/libz.a" >> config.make
elif [ "$zlib_cflags" = "x" ]; then
echo "ZLIB_CFLAGS=" >> config.make
echo "ZLIB_LFLAGS=-lz" >> config.make
else
echo "ZLIB_CFLAGS=\$(shell $zlib_cflags)" >> config.make
echo "ZLIB_LFLAGS=\$(shell $zlib_libs)" >> config.make
fi
echo "LUA_CFLAGS=\$(shell $lua_cflags)" >> config.make
echo "LUA_LFLAGS=\$(shell $lua_libs)" >> config.make
echo "SDL_CFLAGS=\$(shell $sdl_config --cflags)" >> config.make
echo "SDL_LFLAGS=\$(shell $sdl_config --libs) $sdl_libs" >> config.make
echo "ok"
if [ "x$PREFIX" = "x" ]; then
echo -n "Choose installation mode. Standalone(1) or system(2) [1]: "
read ans
else
ans="2"
fi
if [ "x$ans" = "x1" -o "x$ans" = "x" ]; then
echo " * Standalone version"
rm -f Rules.make
ln -sf Rules.make.standalone Rules.make
rm -f sdl-instead
ln -sf src/sdl-instead sdl-instead
echo "Ok. We are ready to build. Use these commands:"
echo " \$ make"
echo " \$ ./sdl-instead"
elif [ "x$ans" = "x2" ]; then
if [ "x$PREFIX" = "x" ]; then
echo -n "Enter prefix path [/usr/local]: "
read ans
else
ans="$PREFIX"
fi
if [ "x$ans" = "x" ]; then
prefix="/usr/local"
else
prefix="$ans"
fi
rm -f Rules.make
ln -s Rules.make.system Rules.make
echo " * System version with prefix: $prefix"
echo "PREFIX=$prefix" >> config.make
echo "BIN=\$(PREFIX)/bin/" >> config.make
echo "DATAPATH=\$(PREFIX)/share/instead" >> config.make
echo "STEADPATH=\$(DATAPATH)/stead" >> config.make
echo "THEMESPATH=\$(DATAPATH)/themes" >> config.make
echo "GAMESPATH=\$(DATAPATH)/games" >> config.make
echo "ICONPATH=\$(PREFIX)/share/pixmaps" >> config.make
echo "DOCPATH=\$(PREFIX)/share/doc/instead" >> config.make
echo "LANGPATH=\$(DATAPATH)/lang" >> config.make
echo "MANPATH=\$(PREFIX)/share/man/man6" >> config.make
echo "Ok. We are ready to build and install. Use these commands:"
echo " \$ make"
echo " \$ sudo make install"
echo " \$ sdl-instead"
else
echo "Huh!!! Wrong answer."
exit 1
fi
echo " Enjoy..."
instead-3.1.2/contrib/ 000755 001751 001751 00000000000 13146577472 015126 5 ustar 00peter peter 000000 000000 instead-3.1.2/contrib/caanoo.patch 000644 001751 001751 00000011551 13146577472 017412 0 ustar 00peter peter 000000 000000 diff -Nur instead-2.5/Rules.caanoo instead-2.5-caanoo/Rules.caanoo
--- instead-2.5/Rules.caanoo 1970-01-01 03:00:00.000000000 +0300
+++ instead-2.5-caanoo/Rules.caanoo 2017-03-10 21:49:22.942070000 +0300
@@ -0,0 +1,45 @@
+VERSION := 3.1.2
+
+PREFIX = /home/peter/Devel/GPH_SDK/tools/gcc-4.2.4-glibc-2.7-eabi
+CC = $(PREFIX)/bin/arm-gph-linux-gnueabi-gcc
+STRIP = $(PREFIX)/bin/arm-gph-linux-gnueabi-strip
+
+CFLAGS += -DARM_ARCH -DGP2X_BUILD
+CFLAGS += -DWIZ_BUILD -DCAANOO
+CFLAGS += -O3 -std=c99 -msoft-float -funsigned-char -fno-common -fno-builtin -Wl,--allow-multiple-definition
+CFLAGS += -I../SDL-1.2.13/include `$(PREFIX)/arm-gph-linux-gnueabi/sys-root/usr/bin/sdl-config --cflags` -I$(PREFIX)/include
+
+DESTDIR=
+BIN=
+DATAPATH=.
+STEADPATH=$(DATAPATH)/stead
+THEMESPATH=$(DATAPATH)/themes
+GAMESPATH=$(DATAPATH)/games
+ICONPATH=$(DATAPATH)/icon
+LANGPATH=$(DATAPATH)/lang
+DOCPATH=
+MANPATH=
+SYSTEMSETUP=no
+
+ZLIB_CFLAGS=
+ZLIB_LFLAGS=-lz
+
+LUA_CFLAGS=-I./caanoo/lua/
+LUA_LFLAGS=./caanoo/lua/liblua.a
+
+SDL_CFLAGS=
+SDL_LFLAGS=-lSDL -lSDL_mixer -lSDL_image -lSDL_ttf -lm -ldl -lpthread -lz
+
+CFLAGS += -g -Wall -Dunix -D_USE_UNPACK -D_LOCAL_APPDATA -D_HAVE_ICONV -D_SDL_MOD_BUG
+
+INSTALLD=echo "Do not install standalone version!"
+INSTALLB=echo "Do not install standalone version!"
+INSTALL=echo "Do not install standalone version!"
+LN=echo "Do not install standalone version!"
+
+EXE=
+PLATFORM=unix.c
+RESOURCES=
+RM=rm
+AR=ar rc
+RANLIB=ranlib
diff -Nur instead-2.5/src/game.c instead-2.5-caanoo/src/game.c
--- instead-2.5/src/game.c 2017-03-02 08:58:05.000000000 +0300
+++ instead-2.5-caanoo/src/game.c 2017-03-10 21:49:48.207850000 +0300
@@ -3200,7 +3200,7 @@
#else
game_menu(menu_askquit);
#endif
- } else if ((alt_pressed | control_pressed) && (!is_key(ev, "q") || !is_key(ev, "f4"))) {
+ } else if (!is_key(ev, "quit") || ((alt_pressed | control_pressed) && (!is_key(ev, "q") || !is_key(ev, "f4")))) {
game_running = 0;
return -1;
} else if (alt_pressed &&
diff -Nur instead-2.5/src/graphics.c instead-2.5-caanoo/src/graphics.c
--- instead-2.5/src/graphics.c 2017-02-24 19:25:41.000000000 +0300
+++ instead-2.5-caanoo/src/graphics.c 2017-03-10 21:49:22.945278000 +0300
@@ -2104,17 +2104,24 @@
#else
int gfx_set_mode(int w, int h, int fs)
{
+ int maxbpp = 32;
int hw = (software_sw)?0:SDL_HWSURFACE;
SDL_Surface *scr;
game_reset_name();
if (gfx_width == w && gfx_height == h && gfx_fs == fs) {
return 0; /* already done */
}
+#ifdef CAANOO
+ maxbpp = 16;
+#endif
vid_modes = NULL;
gfx_fs = fs;
gfx_width = w;
gfx_height = h;
SDL_ShowCursor(SDL_DISABLE);
+#ifdef CAANOO
+ scr = SDL_SetVideoMode(gfx_width, gfx_height, maxbpp, ((gfx_fs)?SDL_HWSURFACE:0) | ( ( fs ) ? SDL_FULLSCREEN : 0 ) );
+#else
#ifdef S60
scr = SDL_SetVideoMode(gfx_width, gfx_height, 0, SDL_ANYFORMAT | hw | ( ( fs ) ? SDL_FULLSCREEN : 0 ) );
#else
@@ -2142,6 +2149,7 @@
#endif
#endif
#endif
+#endif
screen = GFX_IMG_REL(scr);
if (scr == NULL || screen == NULL) {
fprintf(stderr, "Unable to set %dx%d video: %s\n", w, h, SDL_GetError());
@@ -5442,7 +5450,7 @@
int gfx_init(void)
{
- if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
return -1;
}
diff -Nur instead-2.5/src/input.c instead-2.5-caanoo/src/input.c
--- instead-2.5/src/input.c 2017-02-24 19:25:41.000000000 +0300
+++ instead-2.5-caanoo/src/input.c 2017-03-10 21:49:22.945694000 +0300
@@ -54,6 +54,41 @@
}
#endif
+#ifdef CAANOO
+static const char *joy_map(int button)
+{
+ switch(button) {
+ case 2:
+ return "left";
+ case 6:
+ return "right";
+ case 0:
+ return "up";
+ case 4:
+ return "down";
+ case 15:
+ return "space"; //y
+ case 14:
+ return "tab"; //x
+ case 12:
+ return "escape"; // a
+ case 18:
+ case 13:
+ return "return"; // b
+ case 8:
+ return "quit";
+ case 10:
+ return "page up";
+ case 11:
+ return "page down";
+ case 9:
+ case 20:
+ return "f1";
+ }
+ return "";
+}
+#endif
+
int minimized(void)
{
if (nopause_sw)
@@ -156,6 +191,10 @@
#ifdef IOS
SDL_SetEventFilter(HandleAppEvents, NULL);
#endif
+ if(SDL_NumJoysticks() > 0) {
+ SDL_JoystickOpen(0);
+ SDL_JoystickEventState(SDL_ENABLE);
+ }
return 0;
}
@@ -347,6 +386,20 @@
case SDL_QUIT:
game_running = 0;
return -1;
+#ifdef CAANOO
+ case SDL_JOYBUTTONDOWN:
+ inp->type = KEY_DOWN;
+ inp->code = event.jbutton.button;
+ strncpy(inp->sym, joy_map(event.jbutton.button), sizeof(inp->sym));
+ inp->sym[sizeof(inp->sym) - 1] = 0;
+ break;
+ case SDL_JOYBUTTONUP:
+ inp->type = KEY_UP;
+ inp->code = event.jbutton.button;
+ strncpy(inp->sym, joy_map(event.jbutton.button), sizeof(inp->sym));
+ inp->sym[sizeof(inp->sym) - 1] = 0;
+ break;
+#endif
case SDL_KEYDOWN: /* A key has been pressed */
#if SDL_VERSION_ATLEAST(2,0,0)
if (event.key.repeat) {
instead-3.1.2/contrib/instead-em-build.sh 000755 001751 001751 00000015451 13146577472 020616 0 ustar 00peter peter 000000 000000 #!/usr/bin/env bash
# build INSTEAD with emscripten
set -e
export WORKSPACE="" #"/home/peter/Devel/emsdk-portable/env"
if [ ! -f ./emsdk_set_env.sh ]; then
echo "Run this script in emsdk directory"
exit 1
fi
if [ -z "$WORKSPACE" ]; then
echo "Define WORKSPACE path in $0"
exit 1
fi
if [ ! -d "$WORKSPACE" ]; then
echo "Please, create build directory $WORKSPACE"
exit 1
fi
. ./emsdk_set_env.sh
# some general flags
export CFLAGS="-g0 -O2"
export CXXFLAGS="$CFLAGS"
export EM_CFLAGS="-Wno-warn-absolute-paths"
export EMMAKEN_CFLAGS="$EM_CFLAGS"
export PKG_CONFIG_PATH="$WORKSPACE/lib/pkgconfig"
export MAKEFLAGS="-j2"
# flags to fake emconfigure and emmake
export CC="emcc"
export CXX="em++"
export LD="$CC"
export LDSHARED="$LD"
export RANLIB="emranlib"
export AR="emar"
# Lua
cd $WORKSPACE
rm -rf lua-5.1.5
[ -f lua-5.1.5.tar.gz ] || wget -nv 'https://www.lua.org/ftp/lua-5.1.5.tar.gz'
tar xf lua-5.1.5.tar.gz
cd lua-5.1.5
cat src/luaconf.h | sed -e 's/#define LUA_USE_POPEN//g' -e 's/#define LUA_USE_ULONGJMP//g'>src/luaconf.h.new
mv src/luaconf.h.new src/luaconf.h
emmake make posix CC=emcc
emmake make install INSTALL_TOP=$WORKSPACE
# libiconv
cd $WORKSPACE
rm -rf libiconv-1.15
[ -f libiconv-1.15.tar.gz ] || wget -nv 'https://ftp.gnu.org/gnu/libiconv/libiconv-1.15.tar.gz'
tar xf libiconv-1.15.tar.gz
cd libiconv-1.15
emconfigure ./configure --prefix=$WORKSPACE
emmake make install
# zlib
cd $WORKSPACE
rm -rf zlib-1.2.11/
[ -f zlib-1.2.11.tar.gz ] || wget -nv 'http://zlib.net/zlib-1.2.11.tar.gz'
tar xf zlib-1.2.11.tar.gz
cd zlib-1.2.11
emconfigure ./configure --prefix=$WORKSPACE
emmake make install
# libmikmod
cd $WORKSPACE
rm -rf libmikmod-3.1.12/
[ -f SDL2_mixer-2.0.1.tar.gz ] || wget -nv https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.1.tar.gz
tar xf SDL2_mixer-2.0.1.tar.gz
mv SDL2_mixer-2.0.1/external/libmikmod-3.1.12/ libmikmod-3.1.12/
cd libmikmod-3.1.12/
emconfigure ./configure --prefix=$WORKSPACE --disable-shared --enable-static
emmake make install SHELL="${SHELL}"
# SDL2_mixer
cd $WORKSPACE
rm -rf SDL_mixer
[ -d SDL_mixer/.hg ] || hg clone https://hg.libsdl.org/SDL_mixer
cd SDL_mixer
hg pull -u
hg up -C
hg --config "extensions.purge=" purge --all
cat configure.in | sed -e 's/AC_CHECK_LIB(\[modplug\], /AC_CHECK_LIB(\[modplug\], \[ModPlug_Load\], /' -e 's/have_libmikmod=no/have_libmikmod=yes/g' > configure.in.new
mv -f configure.in.new configure.in
./autogen.sh
emconfigure ./configure --prefix=$WORKSPACE CPPFLAGS="-I$WORKSPACE/include -s USE_VORBIS=1 -s USE_OGG=1" LDFLAGS="-L$WORKSPACE/lib" --disable-sdltest --disable-shared \
--disable-music-mp3-mad-gpl --enable-music-ogg --disable-music-ogg-shared --enable-music-mod-mikmod --disable-music-mod-mikmod-shared \
--disable-music-midi-fluidsynth --disable-music-midi-fluidsynth-shared \
--disable-music-mp3-smpeg --disable-music-mp3-smpeg-shared
cat Makefile | sed -e 's| \$(objects)/playwave\$(EXE) \$(objects)/playmus\$(EXE)||g' > Makefile.new
mv -f Makefile.new Makefile
emmake make install
# jpeg lib
cd $WORKSPACE
rm -rf jpeg-9b
[ -f jpegsrc.v9b.tar.gz ] || wget -nv 'http://www.ijg.org/files/jpegsrc.v9b.tar.gz'
tar xf jpegsrc.v9b.tar.gz
cd jpeg-9b
emconfigure ./configure --prefix=$WORKSPACE
emmake make install
# SDL_image
cd $WORKSPACE
rm -rf SDL2_image
[ -d SDL2_image/.git ] || git clone https://github.com/emscripten-ports/SDL2_image.git SDL2_image
cd SDL2_image
./autogen.sh
emconfigure ./configure --host=asmjs-unknown-linux --prefix=$WORKSPACE CPPFLAGS="-I$WORKSPACE/include -s USE_SDL=2 -s USE_LIBPNG=1 " LDFLAGS="-L$WORKSPACE/lib -lpng -ljpeg" --disable-sdltest --disable-shared --enable-static --enable-png --disable-png-shared --enable-jpg --disable-jpg-shared
emmake make install
# INSTEAD
echo "INSTEAD"
cd $WORKSPACE
[ -d instead-em ] || git clone https://github.com/instead-hub/instead.git instead-em
cd instead-em
git pull
[ -e Rules.make ] || ln -s Rules.standalone Rules.make
cat < config.make
EXTRA_CFLAGS+= -DNOMAIN -D_HAVE_ICONV -I../../include
SDL_CFLAGS=-I../../include/SDL2 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s SDL2_IMAGE_FORMATS='["png","jpeg","gif"]'
SDL_LFLAGS=
LUA_CFLAGS=
LUA_LFLAGS=
ZLIB_LFLAGS=
EOF
emmake make clean
emmake make
cd $WORKSPACE
[ -d instead-em-js ] || mkdir instead-em-js
[ -d instead-em-js/fs ] || mkdir instead-em-js/fs
cp -R instead-em/icon instead-em-js/fs/
cp -R instead-em/stead instead-em-js/fs/
cp -R instead-em/themes instead-em-js/fs/
cp -R instead-em/lang instead-em-js/fs/
cp -R instead-em/games instead-em-js/fs/
find instead-em-js/fs/ \( -name '*.svg' -o -name Makefile -o -name CMakeLists.txt \) -exec rm {} \;
cd instead-em-js
ln -f -s ../instead-em/src/sdl-instead sdl-instead.bc
ln -f -s ../lib lib
cat < post.js
var Module;
FS.mkdir('/appdata');
FS.mount(IDBFS,{},'/appdata');
Module['postRun'].push(function() {
var argv = []
var req
if (typeof window === "object") {
argv = window.location.search.substr(1).trim().split('&');
if (!argv[0])
argv = [];
}
var url = argv[0];
if (!url) {
FS.syncfs(true, function (error) {
if (error) {
console.log("Error while syncing: ", error);
};
console.log("Running...");
Module.ccall('instead_main', 'number');
});
return;
}
req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "arraybuffer";
console.log("Get: ", url);
setTimeout(function() {
var spinnerElement = document.getElementById('spinner');
spinnerElement.style.display = 'inline-block';
Module['setStatus']('Downloading data file...');
}, 3);
req.onload = function() {
var basename = function(path) {
parts = path.split( '/' );
return parts[parts.length - 1];
}
var data = req.response;
console.log("Data loaded...");
FS.syncfs(true, function (error) {
if (error) {
console.log("Error while syncing: ", error);
}
url = basename(url);
console.log("Writing: ", url);
FS.writeFile(url, new Int8Array(data), { encoding: 'binary' }, "w");
console.log("Running...");
var args = [];
[ "instead-em", url, "-standalone" ].forEach(function(item) {
args.push(allocate(intArrayFromString(item), 'i8', ALLOC_NORMAL));
args.push(0); args.push(0); args.push(0);
})
args = allocate(args, 'i32', ALLOC_NORMAL);
setTimeout(function() {
Module.setStatus('');
}, 3);
Module.ccall('instead_main', 'number', ["number", "number"], [3, args ]);
});
}
req.send(null);
});
EOF
emcc -O2 sdl-instead.bc lib/libz.a lib/libiconv.so lib/liblua.a lib/libSDL2_mixer.a lib/libmikmod.a lib/libSDL2_image.a lib/libjpeg.a \
-s EXPORTED_FUNCTIONS="['_instead_main']" \
-s 'SDL2_IMAGE_FORMATS=["png","jpeg","gif"]' \
-s QUANTUM_SIZE=4 \
-s USE_OGG=1 -s USE_VORBIS=1 -s USE_SDL=2 -s USE_SDL_TTF=2 -s USE_LIBPNG=1 \
-o instead-em.html -s SAFE_HEAP=0 -s TOTAL_MEMORY=167772160 -s ALLOW_MEMORY_GROWTH=0 \
--post-js post.js \
--preload-file fs@/
echo "Happy hacking"
python2.7 -m SimpleHTTPServer 8000
instead-3.1.2/contrib/maemo.patch 000644 001751 001751 00000012566 13146577472 017257 0 ustar 00peter peter 000000 000000 diff -Nur instead-2.5.0/debian/compat instead-2.5.0-maemo/debian/compat
--- instead-2.5.0/debian/compat 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/debian/compat 2017-03-11 13:04:27.792981000 +0300
@@ -1 +1 @@
-7
+4
diff -Nur instead-2.5.0/debian/control instead-2.5.0-maemo/debian/control
--- instead-2.5.0/debian/control 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/debian/control 2017-03-11 13:04:27.793261000 +0300
@@ -1,8 +1,8 @@
Source: instead
-Section: games
+Section: user/games
Priority: optional
Maintainer: Peter Kosyh
-Build-Depends: debhelper (>= 7), pkg-config, liblua5.1-dev, libsdl1.2-dev, libsdl-ttf2.0-dev, libsdl-image1.2-dev, libsdl-mixer1.2-dev, zlib1g-dev
+Build-Depends: debhelper (>= 5), pkg-config, liblua5.1-dev, libsdl1.2-dev, libsdl-ttf2.0-dev, libsdl-image1.2-dev, libsdl-mixer1.2-dev, zlib1g-dev
Standards-Version: 3.8.3
Homepage: http://instead.sourceforge.net
@@ -11,4 +11,3 @@
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: simple text adventures/visual novels engine
Visual novell/text quest-like engine.
-
diff -Nur instead-2.5.0/debian/rules instead-2.5.0-maemo/debian/rules
--- instead-2.5.0/debian/rules 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/debian/rules 2017-03-11 13:04:27.793529000 +0300
@@ -61,7 +61,7 @@
install: build
dh_testdir
dh_testroot
- dh_prep
+# dh_prep
dh_installdirs
# Add here commands to install the package into debian/instead.
diff -Nur instead-2.5.0/desktop/instead.desktop.in instead-2.5.0-maemo/desktop/instead.desktop.in
--- instead-2.5.0/desktop/instead.desktop.in 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/desktop/instead.desktop.in 2017-03-11 13:04:27.793840000 +0300
@@ -13,4 +13,8 @@
Exec=@BIN@/sdl-instead
Icon=sdl_instead
Terminal=false
+X-Window-Icon=sdl_instead
+X-Window-Icon-Dimmed=sdl_instead
+X-HildonDesk-ShowInToolbar=true
+StartupWMClass=instead
Categories=Game;LogicGame;
diff -Nur instead-2.5.0/desktop/Makefile instead-2.5.0-maemo/desktop/Makefile
--- instead-2.5.0/desktop/Makefile 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/desktop/Makefile 2017-03-11 13:04:27.794105000 +0300
@@ -10,9 +10,9 @@
cat instead.desktop.in | sed -e "s|@BIN@|$(BIN)|g" > instead.desktop
install:
- $(INSTALLD) $(DESTDIR)$(PREFIX)/share/applications
- $(INSTALL) instead.desktop $(DESTDIR)$(PREFIX)/share/applications/instead.desktop
+ $(INSTALLD) $(DESTDIR)$(PREFIX)/share/applications/hildon
+ $(INSTALL) instead.desktop $(DESTDIR)$(PREFIX)/share/applications/hildon/instead.desktop
uninstall:
- $(RM) $(DESTDIR)$(PREFIX)/share/applications/instead.desktop
+ $(RM) $(DESTDIR)$(PREFIX)/share/applications/hildon/instead.desktop
diff -Nur instead-2.5.0/patches/maemo.patch instead-2.5.0-maemo/patches/maemo.patch
diff -Nur instead-2.5.0/Rules.make.system instead-2.5.0-maemo/Rules.make.system
--- instead-2.5.0/Rules.make.system 2017-03-11 10:34:42.000000000 +0300
+++ instead-2.5.0-maemo/Rules.make.system 2017-03-11 13:04:27.794345000 +0300
@@ -28,7 +28,7 @@
SDL_CFLAGS=$(shell sdl-config --cflags)
SDL_LFLAGS=$(shell sdl-config --libs) -lSDL_ttf -lSDL_mixer -lSDL_image -lm
-CFLAGS += -Wall -Dunix -D_USE_UNPACK # -D_SDL_MOD_BUG
+CFLAGS += -Wall -Dunix -D_USE_UNPACK -DMAEMO # -D_SDL_MOD_BUG
INSTALLD=install -d -m 0755
INSTALLB=install -m 0755
diff -Nur instead-2.5.0/src/instead/util.h instead-2.5.0-maemo/src/instead/util.h
--- instead-2.5.0/src/instead/util.h 2017-03-11 10:34:44.000000000 +0300
+++ instead-2.5.0-maemo/src/instead/util.h 2017-03-11 13:04:46.785519000 +0300
@@ -52,7 +52,7 @@
#define FREE(v) do { if ((v)) free((v)); v = NULL; } while(0)
#ifdef _USE_SDL
- #include
+/* #include */
#include
#ifdef _USE_SDL_ICONV
#include
diff -Nur instead-2.5.0/src/sound.c instead-2.5.0-maemo/src/sound.c
--- instead-2.5.0/src/sound.c 2017-03-11 10:34:44.000000000 +0300
+++ instead-2.5.0-maemo/src/sound.c 2017-03-11 14:28:39.065602000 +0300
@@ -27,7 +27,9 @@
#include
#include
-
+#define SDL_memcpy memcpy
+#define SDL_calloc calloc
+#define SDL_free free
#ifdef S60
int audio_rate = 11025;
#else
diff -Nur instead-2.5.0/themes/default/theme.ini instead-2.5.0-maemo/themes/default/theme.ini
--- instead-2.5.0/themes/default/theme.ini 2017-03-11 10:34:45.000000000 +0300
+++ instead-2.5.0-maemo/themes/default/theme.ini 2017-03-11 13:04:27.794651000 +0300
@@ -5,7 +5,7 @@
; $Name(it): Predefinito$
scr.w = 800
-scr.h = 600
+scr.h = 480
scr.gfx.scalable = 1
scr.gfx.h = -1
@@ -23,7 +23,7 @@
win.x = 48
win.y = 8
win.w = 500
-win.h = 568
+win.h = 448
win.fnt.name = {sans,sans-b,sans-i,sans-bi}.ttf
win.fnt.size = 16
@@ -43,7 +43,8 @@
inv.x = 620
inv.y = 8
inv.w = 144
-inv.h = 564
+inv.h = 448
+
inv.fnt.name = {sans,sans-b,sans-i,sans-bi}.ttf
inv.fnt.size = 16
@@ -71,8 +72,8 @@
menu.fnt.height = 1.0
menu.gfx.button = menu.png
menu.button.x = 776
-menu.button.y = 576
+menu.button.y = 456
snd.click = click.wav ; click.ogg
diff -Nur instead-2.5.0/themes/wide/theme.ini instead-2.5.0-maemo/themes/wide/theme.ini
--- instead-2.5.0/themes/wide/theme.ini 2017-03-11 10:34:46.000000000 +0300
+++ instead-2.5.0-maemo/themes/wide/theme.ini 2017-03-11 13:04:27.794891000 +0300
@@ -3,10 +3,3 @@
; $Name(uk):Широка$
; $Name(es):Amplio$
; $Name(it):Ampio$
-
-scr.w = 800
-scr.h = 480
-win.h = 448
-inv.h = 448
-
-menu.button.y = 456
instead-3.1.2/COPYING 000644 001751 001751 00000003335 13146577472 014525 0 ustar 00peter peter 000000 000000 Copyright 2009-2017 Peter Kosyh
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Please see each individual source file for a list of they copyright holders.
INSTEAD links with the following libraries/sources:
Lua (MIT)
SDL (LGPL)
SDL_image (LGPL)
SDL_ttf (LGPL)
SDL_mixer (LGPL)
SDL_gfx (Zlib)
SDL_anigif.c (LGPL)
SDL2 (Zlib)
SDL2_image (Zlib)
SDL2_ttf (Zlib)
SDL2_mixer (Zlib)
SDL2_gfx (Zlib)
zlib (Zlib)
Freetype (LGPL)
FFmpeg (LGPL)
jpeg (JPEG License)
libpng (PNG license)
zlib (Zlib License)
list routines (MIT) (http://ccodearchive.net/info/list.html)
LuaFileSystem (http://keplerproject.github.io/luafilesystem/license.html)
tinymt32 (The 3-clause BSD License)
instead-3.1.2/debian/ 000755 001751 001751 00000000000 13146577472 014710 5 ustar 00peter peter 000000 000000 instead-3.1.2/debian/changelog 000644 001751 001751 00000006204 13146577472 016564 0 ustar 00peter peter 000000 000000 instead (3.1.2) unstable; urgency=low
* 3.1.2 release;
-- Peter Kosyh Mon, 21 Aug 2017 16:45:00 +0300
instead (3.1.1) unstable; urgency=low
* 3.1.1 release;
-- Peter Kosyh Mon, 21 Aug 2017 08:50:00 +0300
instead (3.1.0) unstable; urgency=low
* 3.1.0 release;
-- Peter Kosyh Sun, 20 Aug 2017 10:16:00 +0300
instead (3.0.1) unstable; urgency=low
* 3.0.1 release;
-- Peter Kosyh Wed, 24 May 2017 21:03:00 +0300
instead (3.0.0) unstable; urgency=low
* 3.0.0 release;
-- Peter Kosyh Wed, 19 Apr 2017 17:45:00 +0300
instead (2.4.1) unstable; urgency=low
* 2.4.1 release;
-- Peter Kosyh Sat, 16 Apr 2016 13:50:00 +0300
instead (2.4.0) unstable; urgency=low
* 2.4.0 release;
-- Peter Kosyh Thu, 18 Feb 2016 15:09:00 +0300
instead (2.3.0) unstable; urgency=low
* 2.3.0 release;
-- Peter Kosyh Sun, 18 Oct 2015 13:15:00 +0300
instead (2.2.7) unstable; urgency=low
* 2.2.7 release;
-- Peter Kosyh Sun, 20 Sep 2015 13:49:00 +0300
instead (2.2.6) unstable; urgency=low
* 2.2.6 release;
-- Peter Kosyh Sun, 20 Sep 2015 13:22:00 +0300
instead (2.2.5) unstable; urgency=low
* 2.2.5 release;
-- Peter Kosyh Sun, 30 Aug 2015 16:15:00 +0300
instead (2.2.4) unstable; urgency=low
* 2.2.4 release;
-- Peter Kosyh Sun, 2 Aug 2015 11:22:00 +0300
instead (2.2.3) unstable; urgency=low
* 2.2.3 release;
-- Peter Kosyh Fri, 20 Mar 2015 18:56:00 +0300
instead (2.2.2) unstable; urgency=low
* 2.2.2 release;
-- Peter Kosyh Fri, 6 Feb 2015 19:17:00 +0300
instead (2.2.1) unstable; urgency=low
* 2.2.1 release;
-- Peter Kosyh Sat, 31 Jan 2015 16:27:00 +0300
instead (2.2.0) unstable; urgency=low
* 2.2.0 release;
-- Peter Kosyh Thu, 1 Jan 2015 10:52:00 +0300
instead (2.1.1) unstable; urgency=low
* 2.1.1 release;
-- Peter Kosyh Wed, 9 Jul 2014 13:30:00 +0400
instead (2.1.0) unstable; urgency=low
* 2.1.0 release;
-- Peter Kosyh Tue, 3 Jun 2014 17:28:00 +0400
instead (2.0.3) unstable; urgency=low
* 2.0.3 release;
-- Peter Kosyh Thu, 20 Feb 2014 17:20:00 +0400
instead (2.0.2) unstable; urgency=low
* 2.0.2 release;
-- Peter Kosyh Thu, 30 Jan 2014 08:30:00 +0400
instead (2.0.1) unstable; urgency=low
* 2.0.1 release;
-- Peter Kosyh Mon, 27 Jan 2014 19:40:00 +0400
instead (2.0.0) unstable; urgency=low
* 2.0.0 release;
-- Peter Kosyh Sun, 26 Jan 2014 13:12:00 +0400
instead (1.9.1) unstable; urgency=low
* 1.9.1 release;
-- Peter Kosyh Sat, 13 Jul 2013 14:30:00 +0400
instead (1.9.0) unstable; urgency=low
* 1.9.0 release;
-- Peter Kosyh Fri, 12 Apr 2013 08:00:00 +0400
instead (1.8.3) unstable; urgency=low
* debian friendly release;
-- Peter Kosyh Sat, 26 Jan 2013 14:52:00 +0400
instead-3.1.2/debian/compat 000644 001751 001751 00000000002 13146577472 016106 0 ustar 00peter peter 000000 000000 7
instead-3.1.2/debian/control 000644 001751 001751 00000000744 13146577472 016320 0 ustar 00peter peter 000000 000000 Source: instead
Section: games
Priority: optional
Maintainer: Peter Kosyh
Build-Depends: debhelper (>= 7), pkg-config, liblua5.1-dev, libsdl1.2-dev, libsdl-ttf2.0-dev, libsdl-image1.2-dev, libsdl-mixer1.2-dev, zlib1g-dev
Standards-Version: 3.8.3
Homepage: http://instead.sourceforge.net
Package: instead
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: simple text adventures/visual novels engine
Visual novell/text quest-like engine.
instead-3.1.2/debian/copyright 000644 001751 001751 00000000613 13146577472 016643 0 ustar 00peter peter 000000 000000 This package was debianized by Peter Kosyh p.kosyh@gmail.com on
Thu, 21 May 2009 11:40:29 +0400.
It was downloaded from http://instead.sourceforge.net
Upstream Author:
p.kosyh@gmail.com
Copyright:
Copyright (C) 2009 Peter Kosyh
License:
MIT
The Debian packaging is (C) 2009, Peter Kosyh p.kosyh@gmail.com and
is licensed under the MIT, see `/usr/share/common-licenses/MIT'.
instead-3.1.2/debian/rules 000755 001751 001751 00000004532 13146577472 015774 0 ustar 00peter peter 000000 000000 #!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
configure: configure-stamp
configure-stamp:
dh_testdir
# Add here commands to configure the package.
touch configure-stamp
build: build-arch build-indep
build-arch: build-stamp
build-indep: build-stamp
build-stamp: configure-stamp
dh_testdir
# Add here commands to compile the package.
ln -s instead.6 doc/sdl-instead.6
ln -s instead.txt doc/sdl-instead.txt
rm -rf Rules.make
ln -s Rules.make.system Rules.make
echo "2" | ./configure.sh
$(MAKE) PREFIX=/usr BIN=/usr/games DATAPATH=/usr/share/games/instead
rm -rf Rules.make
#docbook-to-man debian/instead.sgml > instead.1
touch $@
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
# Add here commands to clean up after the build process.
rm -rf doc/sdl-instead.6
rm -rf doc/sdl-instead.txt
rm -rf Rules.make
ln -s Rules.make.system Rules.make
$(MAKE) clean
rm -rf Rules.make
ln -s Rules.make.standalone Rules.make
dh_clean
install: build
dh_testdir
dh_testroot
dh_prep
dh_installdirs
# Add here commands to install the package into debian/instead.
rm -rf Rules.make
ln -s Rules.make.system Rules.make
$(MAKE) install DESTDIR=$(CURDIR)/debian/instead PREFIX=/usr DATAPATH=/usr/share/games/instead BIN=/usr/games
rm -rf Rules.make
# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installexamples
# dh_install
# dh_installmenu
# dh_installdebconf
# dh_installlogrotate
# dh_installemacsen
# dh_installpam
# dh_installmime
# dh_python
# dh_installinit
# dh_installcron
# dh_installinfo
dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure
instead-3.1.2/desktop/ 000755 001751 001751 00000000000 13146577472 015137 5 ustar 00peter peter 000000 000000 instead-3.1.2/desktop/CMakeLists.txt 000644 001751 001751 00000000366 13146577472 017704 0 ustar 00peter peter 000000 000000 IF(SYSTEMWIDE)
SET(BIN ${CMAKE_INSTALL_PREFIX}/${BINDIR}) # for following CONFIGURE_FILE
CONFIGURE_FILE(instead.desktop.in instead.desktop)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/instead.desktop DESTINATION ${DESKTOPDIR})
ENDIF(SYSTEMWIDE)
instead-3.1.2/desktop/Makefile 000644 001751 001751 00000000652 13146577472 016602 0 ustar 00peter peter 000000 000000 include ../Rules.make
include ../config.make
clean:
rm -rf instead.desktop
all: instead.desktop
instead.desktop: instead.desktop.in
cat instead.desktop.in | sed -e "s|@BIN@|$(BIN)|g" > instead.desktop
install:
$(INSTALLD) $(DESTDIR)$(PREFIX)/share/applications
$(INSTALL) instead.desktop $(DESTDIR)$(PREFIX)/share/applications/instead.desktop
uninstall:
$(RM) $(DESTDIR)$(PREFIX)/share/applications/instead.desktop
instead-3.1.2/desktop/instead.desktop.in 000644 001751 001751 00000000757 13146577472 020577 0 ustar 00peter peter 000000 000000 [Desktop Entry]
Version=1.0
Type=Application
Name=INSTEAD
Name[ru]=INSTEAD
Name[uk]=INSTEAD
Comment=Simple Text Adventures Interpreter
Comment[ru]=Интерпретатор простых приключений
Comment[uk]=Iнтерпретатор простих пригод
Keywords=game;adventure;quest;
Keywords[ru]=игра;приключения;квест;
Keywords[uk]=гра;пригоди;квест;
Exec=@BIN@/sdl-instead
Icon=sdl_instead
Terminal=false
Categories=Game;LogicGame;
instead-3.1.2/doc/ 000755 001751 001751 00000000000 13146577535 014233 5 ustar 00peter peter 000000 000000 instead-3.1.2/doc/examples/ 000755 001751 001751 00000000000 13146577472 016051 5 ustar 00peter peter 000000 000000 instead-3.1.2/doc/CMakeLists.txt 000644 001751 001751 00000001770 13146577472 017000 0 ustar 00peter peter 000000 000000 SET(DOC_FILES
index.html
)
SET(GENERATED_DOC_FILES
stead3.pdf
stead2.pdf
stead2-modules.pdf
stead2-en.html
stead2.html
instead.txt
)
FOREACH(FILE ${GENERATED_DOC_FILES})
IF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE})
SET(DOC_FILES ${DOC_FILES} ${FILE})
ELSE(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE})
SET(MISSING_FILES ${MISSING_FILES} ${FILE})
ENDIF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE})
ENDFOREACH(FILE ${GENERATED_DOC_FILES})
IF(NOT "${MISSING_FILES}" STREQUAL "")
MESSAGE(WARNING "This distribution lacks some documentation files: ${MISSING_FILES}. These may be generated (network access and additional tools required, see doc/makepdfs), or you may use instead source tarball from sourceforge which has these files included")
ENDIF(NOT "${MISSING_FILES}" STREQUAL "")
IF(SYSTEMWIDE)
INSTALL(FILES ${DOC_FILES} DESTINATION ${DOCDIR})
INSTALL(FILES instead.6 DESTINATION ${MANDIR}/man6)
ELSEIF(STANDALONE)
INSTALL(FILES ${DOC_FILES} DESTINATION ${STANDALONEDIR}/doc)
ENDIF(SYSTEMWIDE)
instead-3.1.2/doc/Makefile 000644 001751 001751 00000001657 13146577472 015704 0 ustar 00peter peter 000000 000000 include ../Rules.make
include ../config.make
all:
clean:
rm -f *.pdf *.aux *.log *.out *.toc stead3-manual.tex stead2.html stead2-en.html instead.txt
stead3-manual.tex: stead3.md
multimarkdown stead3.md -t latex -o stead3-manual.tex
stead3.pdf: stead3.tex stead3-manual.tex
xelatex stead3.tex
xelatex stead3.tex
xelatex stead3.tex
pdf: stead3.pdf stead2-pdfs
stead2-pdfs:
./makepdfs2
man:
man ./instead.6 | col -b > instead.txt
wiki:
wget "http://instead.syscall.ru/wiki/doku.php?id=ru:gamedev:documentation&do=export_xhtml" -O stead2.html
wget "http://instead.syscall.ru/wiki/doku.php?id=en:gamedev:documentation&do=export_xhtml" -O stead2-en.html
docs: wiki man pdf
install:
$(INSTALLD) $(DESTDIR)$(DOCPATH)
-$(INSTALL) instead.txt *.html *.pdf $(DESTDIR)$(DOCPATH)
$(INSTALLD) $(DESTDIR)$(MANPATH)
$(INSTALL) *.6 $(DESTDIR)$(MANPATH)
uninstall:
$(RM) -rf $(DESTDIR)$(DOCPATH)
$(RM) $(DESTDIR)$(MANPATH)/instead.*
instead-3.1.2/doc/index.html 000644 001751 001751 00000003526 13146577473 016237 0 ustar 00peter peter 000000 000000
INSTEAD -- interpreter of simple text adventures for Unix and Windows
INSTEAD 3.1.2
INSTEAD -- interpreter of simple text adventures for Unix and Windows.
INSTEAD was designed to interpret the games that are the mix of visual novels, text quests and classical 90'ss quests.
(c)by Peter Kosyh, '2009-2017 [gl00my(dog)mail.ru, jabber:gl00my@jabber.ru]
instead-3.1.2/doc/instead.6 000644 001751 001751 00000007077 13146577473 015765 0 ustar 00peter peter 000000 000000 .TH INSTEAD 6 "Version 3.1.2" Instead GAMES
.SH NAME
INSTEAD - Simple Text Adventure Engine, The Interpreter
.SH DESCRIPTION
The interpreter of STEAD (Simple Text Adventures) allows one to play games that combine visual novels, text adventures and classic quests of 1990-s.
The STEAD games features are:
.TP
* very simple source code of the stories. (based on LUA);
.TP
* graphical game interface;
.TP
* supports music and images;
.TP
* theme support \- a game may change the interface look;
.TP
* portability (originally written for Linux, depends on SDL and lua).
.SH SYNOPSIS
.B sdl-instead
.I [options] [game.zip or game.idf or path to game]
.SH OPTIONS
.TP
.B -debug
Debug mode (for game developers).
.TP
.B -nosound
Run the game without sound.
.TP
.B -vsync
Vsync output. (SDL2 only)
.TP
.B -gamespath
Add path with games.
.TP
.B -themespath
Add path with themes.
.TP
.B -nostdgames
Do not use default games.
.TP
.B -nostdthemes
Do not use default themes.
.TP
.B -game
Select game in gamespath.
.TP
.B -theme
Select theme in themespath.
.TP
.B -owntheme
Force game to use own theme.
.TP
.B -fullscreen
Run the game in fullscreen mode.
.TP
.B -hires / -nohires
Set/unset the high resolution when run in fullscreen mode.
.TP
.B -window
Run the game in windowed mode.
.TP
.B -resizable
Resizable window (SDL2 only)
.TP
.B -scale
Scale window (SDL2 only)
.TP
.B -noautosave
Disable autosave load.
.TP
.B -encode [encoded file]
Encode lua file. Encoded file can be executed with doencfile("encoded file").
.TP
.B -idf
Build idf file [INSTEAD data file] from directory.
.TP
.B -mode [WxH]
Use WxH resolution.
.TP
.B -modes [,[,]] ...
Define available resolutions.
.TP
.B -software
Force software rendering
.TP
.B -fontscale
Font scaling.
.TP
.B -nopause
Do not pause the game on window minimize.
.TP
.B -hinting 0|1|2|3
Set the font hinting mode (helpful with infinality).
.TP
.B -install [game in zip]
Install game from zip archive
.TP
.B -quit
Quit :)
.TP
.B -appdata [fullpath]
Store saves and settings in appdata path. Path must exist!
.TP
.B -chunksize [size in bytes]
Size for audio buffer. Try this if sound lags.
.TP
.B -version
Show version and exit.
.TP
.B -lua [arguments]
Exec lua script (for game developers).
.TP
.B -luac
Check lua script (for game developers).
.TP
.B -standalone
Run game in standalone mode (no sandbox, no games selection ...).
.TP
.B -noconfig
Do not load saved config (run with defaults).
.TP
.B -profile
Using configuration profile. Profile file is the ini-styled file with parameter = value pairs. Where is parameters
are arguments without -.
.PP
Configuration file name is 'insteadrc' and it's located in ~/.instead/.
Games can be downloaded from http://instead.sourceforge.net. Unzip them
in ~/.instead/games.
.SH CONTROLS
Please, see tutorial.
.SH LICENCE
This software is covered by the terms of the MIT license.
.SH WARRANTY
None.
.SH COPYRIGHT AND CREDITS
.TP
.B Peter Kosyh
INSTEAD author.
.TP
.B Ilya Ryndin
Initial port to Windows.
.TP
.B Mahno Aleksey, Anton Kolosov
Port to Android.
.TP
.B .dm
Port to Mac OS X.
.TP
.B Andrey Afletdinov
Port to Windows CE.
.TP
.B Vadim Balashoff
Advices.
.TP
.B Vladimir Zhirov
Advices.
.TP
.B Alexander Yakovlev
manual.tex support.
.TP
.B Serj Kalichev
Advices and default theme correction.
.TP
.B Vladimir Podobaev, Oleg Gvozdev
Testers.
.TP
.B Alexander Soborov
Wiki support.
.TP
Many others for comments, bug reports, games and ideas.
.SH SEE ALSO
.BR http://instead.sourceforge.net
instead-3.1.2/doc/makepdfs2 000755 001751 001751 00000005223 13146577473 016040 0 ustar 00peter peter 000000 000000 #!/usr/bin/env bash
function do_pandoc()
{
pandoc --toc --highlight-style=tango -f html $@ -t latex --latex-engine=xelatex -V sansfont="Verdana" -V mainfont="Verdana" -V monofont="Liberation Mono" --variable fontsize=12pt --template=template.tex -V documentclass=extarticle
}
function build_html()
{
wget "http://instead.syscall.ru/wiki/doku.php?id=$1&do=export_xhtml" -O "$2".html.1
cat "$2.html.1" | awk 'BEGIN {
toc = 0;
wrap=0
}
{
if ($0 ~ "") {
toc=1;
} else if ($0 ~ "") {
toc=0;
}
# if (wrap == 1 && $0 ~ "^") {
# wrap=-1
# print "WRAPCENTERMAGICEND"
# }
# if ($0 ~ "
= 0) {
print;
}
# if (wrap == -1) { wrap = 0 };
# if (wrap == -2) { wrap = 1 };
}' > "$2.html.2";
mv -f "$2.html.2" "$2.html.1"
# cp "$2.html.1" "$2.html.z"
}
function build_manual()
{
build_html "ru:gamedev:documentation" stead2
local d=`date +%d.%m.%y`
do_pandoc "stead2.html.1" | sed -e 's/\\date{}/\\date{'$d'}/' -e 's/\\author{}/\\author{Петр Косых}/' -e 's/{\\char13}/'"'"'/g' -e 's/ //g' -e 's/\\title{.*}/\\title{Руководство INSTEAD}/' -e 's/WRAPCENTERMAGICSTART/\\begin{Info}/g' -e 's/WRAPCENTERMAGICEND/\\end{Info}/g' > "stead2.tex"
rm -f stead2.html.1
rm -f *.toc *.aux *.out *.log
xelatex stead2.tex -o stead2.pdf
xelatex stead2.tex -o stead2.pdf
xelatex stead2.tex -o stead2.pdf
rm -f *.toc *.aux *.out *.log stead2.tex
}
function build_modules()
{
local mod="click format hideinv kbd prefs timer xact sprites sound nouse counters wroom nolife proxymenu dash hotkeys para quotes theme snapshots dbg trigger keyboard cutscene fonts"
local mod2=""
for m in $mod; do
build_html ru:gamedev:modules:$m $m #--template=template.tex
mod2="$mod2$m.html.1 "
done
local d=`date +%d.%m.%y`
do_pandoc $mod2 | sed -e 's/ //g' | awk 'BEGIN {
scr = 0;
}
{
if ($0 ~ "\subsection{Скриншоты}") {
scr=1;
} else if (scr == 1 && $0 ~ "\end{document}" ) {
scr=0
} else if (scr == 1 && ($0 ~ "\section" || $0 ~ "\subsection") ) {
scr=0;
}
if (scr == 0) {
print;
}
}' | sed -e 's/\\date{}/\\date{'$d'}/' -e 's/\\title{.*}/\\title{Модули INSTEAD}/' -e 's/{\\char13}/'"'"'/g' -e 's/WRAPCENTERMAGICSTART/\\begin{Info}/g' -e 's/WRAPCENTERMAGICEND/\\end{Info}/g' > stead2-modules.tex
rm -f $mod2
rm -f *.toc *.aux *.out *.log
xelatex stead2-modules.tex -o stead2.pdf
xelatex stead2-modules.tex -o stead2.pdf
xelatex stead2-modules.tex -o stead2-modules.pdf
rm -f *.toc *.aux *.out *.log stead2-modules.tex
}
build_manual
build_modules
instead-3.1.2/doc/stead3.md 000644 001751 001751 00000747016 13146577473 015760 0 ustar 00peter peter 000000 000000 ## Общие сведения
Код игр под INSTEAD пишется на языке Lua (5.1), поэтому, знание этого
языка полезно, хотя и не необходимо. Ядро движка также написано на
lua, поэтому знание Lua может быть полезным для углубленного понимания
принципов его работы, конечно, при условии, если вам интересно этим
заниматься.
За время своего развития, INSTEAD получил множество новых
функций. Теперь с его помощью можно делать игры разных жанров (от
аркад, до игр с текстовым вводом). А также, в INSTEAD можно запускать
игры, написанные на некоторых других движках, но основой INSTEAD
остается первоначальное ядро, которое ориентировано на создание
текстографических приключенческих игр. В данной документации описана
именно эта базовая часть, изучение которой необходимо даже в том
случае, если вы хотите написать что-то другое... Начните свое изучение
INSTEAD с написания простой игры!
В феврале 2017 года, после 8 лет разработки, INSTEAD (версия 3.0)
вышел с поддержкой нового ядра STEAD3. Старое ядро получило название
STEAD2. INSTEAD поддерживает работу игр написанных как на STEAD2, так
и на STEAD3. Это руководство описывает STEAD3.
Если у вас возникают вопросы, вы можете зарегистрироваться на форуме
INSTEAD и задавать их там:
http://instead.syscall.ru/talk
Также, вы можете посетить Jabber конференцию:
instead@conference.jabber.ru
### История создания
Когда мы говорим "текстовое приключение" у большинства людей возникает
один из двух привычных образов. Это либо текст с кнопками действий,
например:
Вы видите перед собой стол. На столе лежит яблоко. Что делать?
1) Взять яблоко
2) Отойти от стола
Или, гораздо реже, это классические игры с текстовым вводом, где для
управления игрой необходимо было вводить действия с клавиатуры.
Вы на кухне. Тут есть стол.
> осмотреть стол.
На столе есть яблоко.
У обоих подходов есть свои преимущества и недостатки.
Если говорить про первый подход, то он близок к жанру книг-игр и
удобен больше для литературных текстов, которые описывают _события_,
происходящие с главным героем, и не очень удобен для создания
классических квестов, где главный герой исследует _смоделированный_ в
игре _мир_, свободно перемещаясь по нему и взаимодействуя с объектами
этого мира.
Второй подход моделирует мир, но требует значительных усилий от автора
игры, и, что более важно, более подготовленного игрока. Особенно,
когда мы имеем дело с русским языком.
Проект INSTEAD был создан для написания другого типа игр, которые
совмещают преимущества обоих подходов, одновременно пытаясь избежать
их недостатков.
Мир игры на INSTEAD моделируется как при втором подходе, то есть в
игре есть места (сцены или комнаты) которые может посещать главный
герой и объекты, с которыми он взаимодействует (включая живых
персонажей). Игрок свободно изучает мир и манипулирует
объектами. Причем, действия с объектами не прописаны в виде явных
пунктов меню, а скорее напоминают классические графические квесты в
стиле 90-х.
На самом деле, в INSTEAD есть множество незаметных на первый взгляд
вещей, которые направлены на развитие выбранного подхода, и который
делает процесс игры максимально динамичным и непохожим на привычные
"текстовые квесты". Это подтверждается в том числе и тем, что на
движке было выпущено множество замечательных игр, интерес к которым
проявляют не только любители текстовых игр как таковых, но и люди не
знакомые с данным жанром.
Перед изучением данного руководства, я рекомендую поиграть в
классические игры INSTEAD, чтобы понять о чем идет речь. С другой
стороны, раз вы здесь, то наверное вы уже сделали это.
Правда, не стоит пока изучать код этих игр, так как старые игры очень
часто написаны неоптимально, с использованием устаревших
конструкций. Текущая версия INSTEAD позволяет реализовывать код
лаконичнее, проще и понятнее. Об этом и рассказывается в данном
документе.
Если вас интересует история создания движка, то вы можете прочитать
статью о том, как все начиналось: http://instead.syscall.ru/2010/05/history
### Как выглядит классическая INSTEAD игра
Итак, как выглядит классическая INSTEAD игра?
_Главное окно_ игры содержит описательную часть, информацию о
статической и динамической части сцены, активные события и картинку
сцены (в графическом интерпретаторе) с возможными переходами в другие
сцены.
_Описательная часть сцены_ отображается только один раз, при показе
сцены, или при явном осмотре сцены (в графическом интерпретаторе --
_Статическая часть сцены_ содержит информацию о статических объектах
сцены (обычно, это декорации) и отображается всегда. Эта часть
написана автором игры.
_Динамическая часть сцены_ составлена из описаний объектов сцены,
которые присутствуют в данный момент на сцене. Эта часть генерируется
автоматически и отображается всегда. Обычно, в ней представлены
объекты, которые могут менять свое местоположение.
Игроку доступны объекты, доступные на любой сцене --
_инвентарь_. Игрок может взаимодействовать с объектами инвентаря и
действовать объектами инвентаря на другие объекты сцены или инвентаря.
> Следует отметить, что понятие инвентаря является условным. Например,
> в "инвентаре" могут находиться такие объекты как "открыть",
> "осмотреть", "использовать" и т.д.
_Действиями_ игрока могут быть:
- осмотр сцены;
- действие на объект сцены;
- действие на объект инвентаря;
- действие объектом инвентаря на объект инвентаря;
- действие объектом инвентаря на объект сцены;
- переход в другую сцену.
### Как создавать игру
Игра представляет из себя каталог, в котором должен находиться скрипт
(текстовый файл) main3.lua. (Обратите внимание, наличие файла
main3.lua означает, что вы пишите игру на STEAD3!) Другие ресурсы игры
(скрипты на lua, графика и музыка) должны находиться в рамках этого
каталога. Все ссылки на ресурсы делаются относительно текущего
каталога -- каталога игры.
В начале файла main3.lua может быть определен заголовок, состоящий из
тегов (строк специального вида). Теги должны начинаться с символов:
два минуса.
--
Два минуса это комментарий с точки зрения Lua. На данный момент
существуют следующие теги.
Тег $Name: содержит название игры в кодировке UTF-8. Пример
использования тега:
-- $Name: Самая интересная игра!$
Затем следует (желательно) задать версию игры:
-- $Version: 0.5$
И указать авторство:
-- $Author: Анонимный любитель текстовых приключений$
Дополнительную информацию об игре, можно задать тегом Info:
-- $Info: Это ремейк старой игры\nС ZX specturm$
Обратите внимание на \n в Info, это станет переводом строки, когда вы
выберете пункт "Информация" в INSTEAD.
Если вы разрабатываете игру в Windows, то убедитесь, что ваш редактор
поддерживает кодировку UTF-8 _без BOM_. Именно эту кодировку следует
использовать при написании игры!
Далее, обычно следует указать модули, которые требуются игре. О
модулях будет рассказано отдельно.
require "fmt" -- некоторые функции форматирования
fmt.para = true -- включить режим параграфов (отступы)
Кроме того, обычно стоит определить обработчики по-умолчанию:
game.act, game.use, game.inv, о которых также будет рассказано ниже.
game.act = 'Не работает.';
game.use = 'Это не поможет.';
game.inv = 'Зачем мне это?';
_Инициализацию_ игры следует описывать в функции init, которая
вызывается движком в самом начале. В этой функции удобно
инициализировать состояние игрока на начало игры, или какие-то другие
действия, нужные для первоначальной настройки мира игры. Впрочем,
функция ''init'' может быть и не нужна.
function init() -- добавим в инвентарь нож и бумагу
take 'нож'
take 'бумага'
end
После того как игра проинициализирована, выполняется запуск игры. Вы
можете определить функцию start(), которая запускается непосредственно
перед запуском игры. Это значит, например, что в случае загрузки
сохраненной игры, start() вызовется после того, как сохранение будет
прочитано,
function start(load) -- восстановить состояние?
if load then
dprint "Это загрузка состояния."
else
dprint "Это старт игры."
end
-- нам сейчас не нужно ничего делать
end
Графический интерпретатор ищет доступные игры в каталоге
games. Unix-версия интерпретатора кроме этого каталога просматривает
также игры в каталоге ~/.instead/games. Windows-версия: Documents and
Settings/USER/Local Settings/Application Data/instead/games. В
Windows- и standalone-Unix-версии игры ищутся в каталоге
./appdata/games, если он существует.
В некоторых сборках INSTEAD (в Windows, в Linux если проект собран с
gtk и др.) можно открывать игру по любому пути из меню "Выбор игры".
Либо, нажать f4. Если в каталоге с играми присутствует только одна
игра, INSTEAD запустит ее автоматически, это удобно, если вы хотите
распространять свою игру вместе с движком.
Таким образом, вы кладете игру в свой каталог и запускаете INSTEAD.
__Важно!__
При написании игры, настоятельно рекомендуется использовать отступы
для оформления кода игры, как это сделано в примере из данного
руководства, этим самым вы сократите количество ошибок и сделаете свой
код наглядней!
Ниже приводится минимальный шаблон для вашей первой игры:
```
-- $Name: Моя первая игра$
-- $Version: 0.1$
-- $Author: Анонимный автор$
require "fmt"
fmt.para = true
game.act = 'Гм...';
game.use = 'Не сработает.';
game.inv = 'Зачем это мне?';
function init()
-- инициализация, если она нужна
end
```
### Основы отладки
Во время отладки (проверки работоспособности вашей игры) удобно, чтобы
INSTEAD был запущен с параметром -debug, тогда в случае ошибок будет
показана более подробная информация о проблеме в виде стека
вызовов. Параметр -debug можно задать в ярлыке (если вы работаете в
Windows), а для других систем, я думаю вы и так знаете как передавать
параметры командной строки.
Кроме того, в режиме -debug автоматически подключается отладчик. Вы
можете активировать его с помощью клавиш ctrl-d или f7. Вы можете
подключить отладчик и явно указав:
require "dbg"
В коде вашей игры.
При отладке игры обычно нужно часто сохранять игру и загружать
состояние игры. Вы можете использовать стандартный механизм сохранений
через меню (или по клавишам f2/f3), или воспользоваться быстрым
сохранением/загрузкой (клавиши f8/f9).
В режиме '-debug' вы можете перезапускать игру клавишами
'alt-r'. В комбинации с f8/f9 это дает возможность быстро посмотреть
изменения в игре после ее правки.
В режиме '-debug' Windows-версия INSTEAD создает консольное окно (в
Unix версии, если вы запускаете INSTEAD из консоли, вывод будет
направлен в нее) в которое будет осуществляться вывод ошибок. Кроме
того, используя функцию 'print()' вы сможете порождать свои
сообщения с отладочным выводом. Например:
```
act = function(s)
print ("Act is here! ");
...
end;
```
Не пугайтесь, когда вы прочитаете все руководство и начнете писать
свою игру, вы, скорее всего, взглянете на этот пример с большим
воодушевлением.
Вы также можете использовать функцию dprint(), которая посылает вывод
в окно отладчика, и вы сможете посмотреть его при входе в режим
отладки.
```
act = function(s)
dprint ("Act is here! ");
...
end;
```
Во время отладки бывает удобно изучать файлы сохранений, которые
содержат состояние переменных игры. Чтобы не искать каждый раз файлы
сохранений, создайте каталог saves в директории с вашей игрой (в том
каталоге, где содержится main3.lua) и игра будет сохраняться в
saves. Этот механизм также будет удобен для переноса игры на другие
компьютеры.
Возможно (особенно, если вы пользуетесь Unix системами) вам понравится
идея проверки синтаксиса ваших скриптов через запуск компилятора
''luac''. В Windows это тоже возможно, нужно только установить
выполняемые файлы lua для Windows
(http://luabinaries.sourceforge.net)/ и воспользоваться luac52.exe.
Вы можете проверить синтаксис и с помощью INSTEAD, для этого
воспользуйтесь параметром -luac:
sdl-instead -debug -luac <пусть к скрипту.lua>
## Сцена
_Сцена_ (или комната) -- это единица игры, в рамках которой игрок
может изучать все объекты сцены и взаимодействовать с ними. Например,
сценой может быть комната, в которой находится герой. Или участок
леса, доступный для наблюдения.
В любой игре должна быть сцена с именем ''main''. Именно с нее
начнется и ваша игра!
```
room {
nam = 'main';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
```
Запись означает создание объекта (так как почти все сущности в INSTEAD
это объекты) main типа room (комната). Атрибут объекта nam хранит имя
комнаты 'main', по которому можно обращаться к комнате из кода. Каждый
объект имеет свое уникальное имя. Если вы попробуете создать два
объекта с одинаковыми именами, вы получите сообщение об ошибке.
Для обращения к объекту по имени, вы можете использовать следующую
запись:
```
dprint("Объект: ", _'main')
```
У каждого объекта игры есть _атрибуты_ и _обработчики событий_. В
данном примере есть два атрибута: nam и dsc. Атрибуты разделяются
разделителем (в данном примере -- символом точка с запятой ';').
Обычно, атрибуты могут быть текстовыми строками,
функциями-обработчиками и булевыми значениями. Однако, атрибут nam
всегда должен быть текстовой строкой, если он задан.
На самом деле, вы можете не указывать имя при создании объекта:
```
room {
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
```
В таком случае, движок сам даст имя объекту, и это имя будет неким
числом. Так как вы не знаете это число, вы не можете обратиться к
объекту явно. Иногда удобно создавать безымянные объекты, например,
для декораций. При создании объекта, даже если он "безымянный", вы
можете создать переменную - ссылку на объект, например:
```
myroom = room {
disp = "Чулан";
dsc = [[Вы в чулане.]];
}
```
Переменная myroom в таком случае становится синонимом объекта (ссылкой
на сам объект).
```
dprint("Объект: ", myroom)
```
Вы можете придерживаться какого-то одного способа, или применять
оба. Например, вы можете задать и имя и переменную-ссылку:
```
main_room = room {
nam = 'main';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
```
Важно понять, что движок в любом случае работает с именами объектов, а
переменные-ссылки -- это просто способ упростить доступ к часто
используемым объектам. Поэтому, для нашей первой игры мы обязаны
указать атрибут nam = 'main', чтобы создать комнату main с которой и
начнется наше приключение!
В нашем примере, при показе сцены, в качестве заголовка сцены будет
использован атрибут 'disp'. На самом деле, если бы мы его не задали,
то в заголовке мы бы увидели 'nam'. Но nam не всегда удобно делать
заголовком сцены, особенно если это строка вроде 'main', или если это
числовой идентификатор, который движок присвоил объекту автоматически.
Есть еще более понятный атрибут 'title'. Если он задан, то при
отображении комнаты в качестве заголовка будет указан именно он.
title используется тогда, когда игрок находится _внутри_ комнаты. Во
всех остальных случаях (при показе переходов в эту комнату)
используется 'disp' или 'nam'.
```
mroom = room {
nam = 'main';
title = 'Начало приключения';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
```
Атрибут 'dsc' -- это описание сцены, которое выводится один раз при
входе в сцену или при явном осмотре сцены. В нем нет описаний
объектов, присутствующих в сцене.
Вы можете использовать символ ',' вместо ';' для разделения
атрибутов. Например:
```
room {
nam = 'main',
disp = 'Главная комната',
dsc = 'Вы в большой комнате.',
}
```
В данном примере все атрибуты -- строковые. Строка может быть записана
в одинарных или двойных кавычках:
```
room {
nam = 'main';
disp = 'Главная комната';
dsc = "Вы в большой комнате.";
}
```
Для длинных описаний удобно использовать запись вида:
```
dsc = [[ Очень длинное описание... ]];
```
При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе
описания сцены присутствовали абзацы -- используйте символ '^'.
```
dsc = [[ Первый абзац. ^^
Второй Абзац.^^
Третий абзац.^
На новой строке.]];
```
Я рекомендую всегда использовать [[ и ]] для 'dsc'.
Напомню еще раз, что имя 'nam' объекта и его отображение (в данном
случае то, как сцена будет выглядеть для игрока в виде надписи сверху)
можно (и, часто, нужно!) разделять. Для этого существуют атрибуты
'disp' и 'title'. 'title' бывает только у комнат и работает как
описатель, когда игрок находится внутри данной комнаты. В остальных
случаях используется 'disp' (если он есть).
Если 'disp' и 'title' не заданы, то считается, что отображение
равняется имени.
'disp' и 'title' могут принимать значение false, в таком случае,
отображения не будет.
## Объекты
_Объекты_ -- это единицы сцены, с которыми взаимодействует игрок.
```
obj {
nam = 'стол';
dsc = 'В комнате стоит {стол}.';
act = 'Гм... Просто стол...';
};
```
Имя объекта ''nam'' используется при попадании его в инвентарь. Хотя,
в нашем случае, стол вряд ли туда попадет. Если у объекта определен
'disp', то при попадании в инвентарь для его отображения будет
использоваться именно этот атрибут. Например:
```
obj {
nam = 'стол';
disp = 'угол стола';
dsc = 'В комнате стоит {стол}.';
tak = 'Я взялся за угол стола';
inv = 'Я держусь за угол стола.';
};
```
Все-таки стол попал к нам в инвентарь.
Вы можете скрывать отображение предмета в инвентаре, если 'disp'
атрибут будет равен 'false'.
'dsc' -- описание объекта. Оно будет выведено в динамической части
сцены, при наличии объекта в сцене. Фигурными скобками отображается
фрагмент текста, который будет являться ссылкой в окне INSTEAD. Если
объектов в сцене много, то все описания выводятся одно за другим,
через пробел,
'act' -- это обработчик события, который вызывается при действии
пользователя (действие на объект сцены, обычно -- клик мышкой по
ссылке). Его основная задача -- вывод (возвращение) строки текста,
которая станет частью событий сцены, и изменение состояния игрового
мира.
## Добавляем объекты в сцену
Для того, чтобы поместить в сцену объекты, существует несколько путей.
Во-первых, при создании комнаты можно определить список 'obj',
состоящий из имен объектов:
```
obj { -- объект с именем, но без переменной
nam = 'ящик';
dsc = [[На полу я вижу {ящик}.]];
act = [[Тяжелый!]];
}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
obj = { 'ящик' };
};
```
Теперь, при отображении сцены мы увидим объект "ящик" в динамической
части.
Вместо имени объекта, вы можете использовать переменную-ссылку, если
только она была определена заранее:
```
apple = obj { -- объект с переменной, но без имени
dsc = [[Тут есть {яблоко}.]];
act = [[Красное!!]];
}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
obj = { apple };
};
```
Альтернативной формой записи является конструкция with:
```
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
}:with {
'ящик',
}
```
Конструкция with позволяет избавиться от лишнего уровня вложенности в
коде игры.
Во-вторых, вы можете объявлять объекты прямо внутри obj или with,
описывая их определение:
```
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
}:with {
obj {
nam = 'ящик';
dsc = [[На полу я вижу {ящик}.]];
act = [[Тяжелый!]];
}
};
```
Это удобно делать для объектов - декораций. Но в таком случае, вы не
сможете создавать объекты с переменной-ссылкой. К счастью, для
декораций это и не нужно.
Если в комнату помещаются несколько объектов, разделяйте их ссылки
запятыми, например:
obj = { 'ящик', apple };
Вы можете вставлять переводы строк для наглядности, когда объектов
много, например, так:
```
obj = {
'tabl',
'apple',
'knife',
};
```
Еще один способ размещения предметов заключается в вызове функций,
которые поместят объекты в требуемые комнаты. Он будет рассмотрен в
дальнейшем.
## Декорации
Объекты, которые могут быть перенесены из одной сцены в другую (или
попадать в инвентарь), обычно имеют имя и/или переменную-ссылку. Так
как таким образом вы всегда можете найти объект где угодно и работать
с ним.
Но немалую часть игрового мира составляют объекты, которые занимают
конкретную локацию и служат в качестве декораций.
Таких объектов может быть очень много, и более того, обычно это
однотипные объекты вроде деревьев и тому подобных объектов.
Для создания декораций можно использовать различные подходы.
### Один и тот же объект в нескольких комнатах
Вы можете создать один объект, например, 'дерево' и помещать их в
разные комнаты.
```
obj {
nam = 'дерево';
dsc = [[Тут стоит {дерево}.]];
act = [[Все деревья выглядят похожими.]];
}
room {
nam = 'Лес';
obj = { 'дерево' };
}
room {
nam = 'Улица';
obj = { 'дерево' };
}
```
### Использование тегов вместо имен
Если вам не нравится придумывать уникальные имена для однотипных
декоративных объектов, вы можете использовать для таких объектов
теги. Теги задаются атрибутом tag и всегда начинаются с символа '#':
```
obj {
tag = '#цветы';
dsc = [[Тут есть {цветы}.]]
}
```
В данном примере, имя у объекта будет сформировано автоматически, но
обращаться к объекту вы сможете по тегу. При этом объект будет
искаться в текущей комнате. Например:
```
dprint(_'#цветы') -- ищем в текущей комнате первый объект с тегом '#цветы'
```
Теги, это в каком то смысле, синоним локальных имен, поэтому
существует альтернативная запись создания предмета с тегом:
```
obj {
nam = '#цветы';
dsc = [[Тут есть {цветы}.]]
}
```
Если имя у объекта начинается с символа '#', то такой объект получает
тег и автоматически сгенерированное числовое имя.
### Использование атрибута сцены decor
Так как декорации не меняют свое место-положение, есть смысл сделать
их частью описания сцены, а не динамической области. Это делается с
помощью атрибута сцены 'decor'. decor показывается всегда и его
основная функция -- описание декораций сцены.
```
room {
nam = 'Дом';
dsc = [[Я у себя дома.]];
decor = [[Тут я вижу много интересных вещей. Например, на {#стена|стене}
висит {#картина|картина}.]];
}: with {
obj {
nam = '#стена';
act = [[Стена как стена!]];
};
obj {
nam = '#картина';
act = [[Ван-Гог?]];
}
}
```
Здесь мы видим сразу несколько приемов:
1. В decor в виде связанного текста описаны декорации;
2. В качестве ссылок используются конструкции с явным заданием
объектов, к которым они относятся {имя объекта|текст};
3. В качестве имен объектов используются теги, чтобы не думать над их
уникальностью;
4. У объектов-декораций в сцене отсутствуют атрибуты dsc, так как их
роль играет decor.
Конечно, вы можете комбинировать все описанные приемы между собой в
любых пропорциях.
## Объекты, связанные с другими объектами
Объекты тоже могут содержать в себе атрибут 'obj' (или конструкцию
'with'). При этом, при выводе объектов, INSTEAD будет разворачивать
списки последовательно. Такая техника может использоваться для
создания объектов-контейнеров или просто для связывания нескольких
описаний вместе. Например, поместим на стол яблоко.
```
obj {
nam = 'яблоко';
dsc = [[На столе лежит {яблоко}.]];
act = 'Взять что-ли?';
};
obj {
nam = 'стол';
dsc = [[В комнате стоит {стол}.]];
act = 'Гм... Просто стол...';
obj = { 'яблоко' };
};
room {
nam = 'Дом';
obj = { 'стол' };
}
```
При этом, в описании сцены мы увидим описание объектов 'стол' и
'яблоко', так как 'яблоко' -- связанный со столом объект и движок при
выводе объекта 'стол' вслед за его 'dsc' выведет последовательно
''dsc'' всех вложенных в него объектов.
Также, следует отметить, что оперируя объектом 'стол' (например,
перемещая его из комнаты в комнату) мы автоматически будем перемещать
и вложенный в него объект 'яблоко'.
Конечно, данный пример мог бы быть написан и по другому, например,
так:
```
room {
nam = 'Дом';
obj = { 'стол' };
}:with {
obj {
nam = 'стол';
dsc = [[В комнате стоит {стол}.]];
act = 'Гм... Просто стол...';
}: with {
obj {
nam = 'яблоко';
dsc = [[На столе лежит {яблоко}.]];
act = 'Взять что-ли?';
};
}
}
```
Выбирайте тот способ, который для вас понятней.
## Атрибуты и обработчики как функции
Большинство атрибутов и обработчиков могут быть _функциями_. Так,
например:
```
disp = function()
p 'яблоко';
end
```
Пример не очень удачен, так как проще было бы написать disp =
'яблоко', но показывает синтаксис записи функции.
Основная задача такой функции -- это возврат строки или булевого
значения. Сейчас мы рассматриваем возврат строки. Для возврата строки
вы можете использовать явную запись в виде:
```
return "яблоко";
```
При этом ход выполнения кода функции прекращается и она возвращает
движку строку. В данном случае "яблоко".
Более привычным способом вывода являются функции:
- p ("текст") -- вывод текста и пробела;
- pn ("текст") -- вывод текста с переводом строки;
- pr ("текст") -- вывод текста "как есть".
> Если ''p''/''pn''/''pr'' вызывается с одним текстовым параметром, то скобки можно опускать.
pn "Нет скобкам!"
Все эти функции дописывают текст в буфер и при возврате из функции
возвращают его движку. Таким образом, вы можете постепенно
формировать вывод за счет последовательного выполнения p/pn/pr. Имейте
в виду, что автору крайне редко необходимо явно форматировать текст,
особенно, если это описание объектов, движок сам расставляет
необходимые переводы строк и пробелы для разделения информации разного
рода и делает это унифицированным способом.
Вы можете использовать '..' или ',' для склейки строк. Тогда '(' и ')'
обязательны. Например:
```
pn ("Строка 1".." Строка 2");
pn ("Строка 1", "Строка 2");
```
> Основное отличие атрибутов от обработчиков событий состоит в том,
> что обработчики событий могут менять состояние игрового мира, а
> атрибуты нет. Поэтому, если вы оформляете атрибут (например, 'dsc')
> в виде функции, помните, что задача атрибута это возврат значения, а
> не изменение состояния игры! Дело в том, что движок обращается к
> атрибутам в те моменты времени, которые обычно четко не определены,
> и не связаны явно с какими-то игровыми процессами!
__Важно!__
> Еще одной особенностью обработчиков является тот факт, что вы не
> должны ждать каких то событий внутри обработчика. То есть, не должно
> быть каких-то циклов ожидания, или организации задержек (пауз). Дело
> в том, что задача обработчика -- изменить игровое состояние и отдать
> управление INSTEAD, который визуализирует эти изменения и снова
> перейдет в ожидание действий пользователя. Если вам требуется
> организовать задержки вывода, вам придется воспользоваться модулем
> "timer".
Функции практически всегда содержат условия и работу с
переменными. Например:
```
obj {
nam = 'яблоко';
seen = false;
dsc = function(s)
if not s.seen then
p 'На столе {что-то} лежит.';
else
p 'На столе лежит {яблоко}.';
end
end;
act = function(s)
if s.seen then
p 'Это яблоко!';
else
s.seen = true;
p 'Гм... Это же яблоко!';
end
end;
};
```
Если атрибут или обработчик оформлен как функция, то всегда _первый
аргумент_ функции (s) -- сам объект. То-есть, в данном примере, 's'
это синоним _'яблоко'. Когда вы работаете с самим объектом в функции,
удобнее использовать параметр, а не явное обращение к объекту по
имени, так как при переименовании объекта вам не придется переписывать
вашу игру. Да и запись будет короче.
В данном примере при показе сцены в динамической части сцены будет
выведен текст: 'На столе что-то лежит'. При взаимодействии с 'что-то',
переменная 'seen' объекта 'яблоко' будет установлена в true -- истина,
и мы увидим, что это было яблоко.
Как видим, синтаксис оператора 'if' довольно очевиден. Для
наглядности, несколько примеров.
if <выражение> then <действия> end
if have 'яблоко' then
p 'У меня есть яблоко!'
end
if <выражение> then <действия> else <действия иначе> end
if have 'яблоко' then
p 'У меня есть яблоко!'
else
p 'У меня нет яблока!'
end
if <выражение> then <действия> elseif <выражение 2> then <действия 2> else <иначе> end -- и т.д.
if have 'яблоко' then
p 'У меня есть яблоко!'
elseif have 'вилка' then
p 'У меня нет яблока, но есть вилка!'
else
p 'У меня нет ни яблока, ни вилки!'
end
Выражение в операторе if может содержать логическое "и" (and), "или"
(or), "отрицание" (not) и скобки ( ) для задания приоритетов. Запись
вида if <переменная> then означает, что переменная не равна
false. Равенство описывается как '==', неравенство '~='.
```
if not have 'яблоко' and not have 'вилка' then
p 'У меня нет ни яблока, ни вилки!'
end
...
if w ~= apple then
p 'Это не яблоко.';
end
...
if time() == 10 then
p '10 й ход настал!'
end
```
__Важно!__
В ситуации когда переменная не была определена, но используется в
условии, INSTEAD даст ошибку. Вам придется заранее определять
переменные, которые вы используете.
### Переменные объекта
Запись 's.seen' означает, что переменная 'seen' размещена в объекте
's' (то есть 'яблоко'). Помните, мы назвали первый параметр функции
's' (от self), а первый параметр -- это сам текущий объект.
Переменные объекта должны быть определены заранее, если вы собираетесь
их модифицировать. Примерно так, как мы поступили с seen. Но
переменных может быть много.
```
obj {
nam = 'яблоко';
seen = false;
eaten = false;
color = 'красный';
weight = 10;
...
};
```
Все переменные объекта, при их изменении, попадают в файл сохранения
игры.
Если вы не хотите, чтобы переменная попала в файл сохранения, вы
можете объявить такие переменные в специальном блоке:
```
obj {
nam = 'яблоко';
{
t = 1; -- эта переменная не попадет в сохранения
x = false; -- и эта тоже
}
};
```
Обычно, вам не стоит так делать. Однако есть ситуация, при которой
этот прием будет полезным. Дело в том, что массивы и таблицы объекта
всегда сохраняются. Если вы используете массивы для хранения
неизменяемых значений, вы можете написать так:
```
obj {
nam = 'яблоко';
{
text = { "раз", "два", "три" }; -- никогда не попадет в файл сохранения
}
...
};
```
Вы можете обращаться к переменным объекта через s -- если это сам
объект. или по переменной - ссылке, например:
```
apple = obj {
color = 'красный';
}
...
-- где-то в другом месте
apple.color = 'зеленый'
```
Или по имени:
```
obj {
nam = 'яблоко';
color = 'красный';
}
...
-- где-то в другом месте
_'яблоко'.color = 'зеленый'
```
На самом деле, вы можете создавать переменные-объекта на лету (без
предварительного их определения), хотя обычно в этом нет смысла.
Например:
```
apple 'xxx' (10) -- создали переменную xxx у объекта apple по ссылке
(_'яблоко') 'xxx' (10) -- то же самое, но по имени объекта
```
### Локальные переменные
Кроме переменных объекта вы можете использовать локальные и глобальные
переменные.
Локальные переменные создаются с помощью служебного слова local:
```
act = function(s)
local w = _'лампочка'
w.light = true
p [[Я нажал на кнопку и лампочка загорелась.]]
end
```
В данном примере, переменная w существует только в теле
функции-обработчика act. Мы создали временную ссылку-переменную w,
которая ссылается на объект 'лампочка', чтобы изменить
свойство-переменную light у этого объекта.
Конечно, мы могли написать и:
_'лампочка'.light = true
Но представьте себе, если нам нужно произвести несколько действий с
объектом, в таких случаях проще воспользоваться временной переменной.
Локальные переменные никогда не попадают в файл-сохранение и играют
роль временных вспомогательных переменных.
Локальные переменные можно создавать и вне функций, тогда данная
переменная видима только в пределах данного lua файла и не попадает в
файл сохранения.
Еще один пример использования локальных переменных:
```
obj {
nam = 'котенок';
state = 1;
act = function(s)
s.state = s.state + 1
if s.state > 3 then
s.state = 1
end
p [[Муррр!]]
end;
dsc = function(s)
local dsc = {
"{Котенок} мурлычет.",
"{Котенок} играет.",
"{Котенок} облизывается.",
};
p(dsc[s.state])
end;
end
```
Как видим, в функции dsc мы определили массив dsc. 'local' указывает
на то, что он действует в пределах функции dsc. Конечно, данный
пример можно было написать и так:
```
dsc = function(s)
if s.state == 1 then
p "{Котенок} мурлычет."
elseif s.state == 2 then
p "{Котенок} играет."
else
p "{Котенок} облизывается.",
end
end
```
### Глобальные переменные
Вы также можете создать глобальную переменную:
```
global { -- определение глобальных переменных
global_var = 1; -- число
some_number = 1.2; -- число
some_string = 'строка';
know_truth = false; -- булево значение
array = {1, 2, 3, 4}; -- массив
}
```
Еще одна форма записи, удобная для одиночных определений:
```
global 'global_var' (1)
```
Глобальные переменные всегда попадают в файл-сохранение.
Кроме глобальных переменных вы можете задавать константы. Синтаксис
аналогичен глобальным переменным:
```
const {
A = 1;
B = 2;
}
const 'Aflag' (false)
```
Движок будет контролировать неизменность констант. Константы не
попадают в файл-сохранение.
Иногда вам нужно работать с переменной, которая не определена как
local (и видима во всех ваших lua файлах игры), но не должна попадать
в файл сохранения. Для таких переменных вы можете использовать
декларации:
```
declare {
A = 1;
B = 2;
}
declare 'Z' (false)
```
Декларации не попадают в файл сохранения. Одно из важных свойств
деклараций состоит в том, что вы можете декларировать функции,
например:
```
declare 'test' (function()
p "Hello world!"
end)
global 'f' (test)
```
В таком случае, вы можете присваивать значение функции 'test' другим
переменным и состояние этих переменных может быть сохранено в файле
сохранения. То-есть, декларированную функцию можно использовать как
значение переменной!
Вы можете декларировать ранее определенные функции, например:
```
declare 'dprint' (dprint)
```
Тем самым делая такие недекларированные функции -- декларированными.
Декларация функции, по сути, это присвоение функции имени, благодаря
чему мы можем сохранить эту функцию как ссылку.
### Вспомогательные функции
Вы можете писать свои вспомогательные функции и использовать их из
своей игры, например:
```
function mprint(n, ...)
local a = {...}; -- временный массив с аргументами к функции
p(a[n]) -- выведем n-й элемент массива
end
...
dsc = function(s)
mprint(s.state, {
"{Котенок} мурлычет.",
"{Котенок} играет.",
"{Котенок} облизывается." });
end;
```
Пока не обращайте внимания на данный пример, если он кажется вам сложным.
### Возвращаемые значения обработчиков
Если необходимо показать, что действие не выполнено (обработчик не
сделал ничего полезного), возвращайте значение false. Например:
```
act = function(s)
if broken_leg then
return false
end
p [[Я ударил ногой по мячу.]]
end
```
При этом будет отображено описание по умолчанию, заданное с помощью
обработчика 'game.act'. Обычно описание по умолчанию содержит
описание невыполнимых действий. Что-то вроде:
game.act = 'Гм... Не получается...';
Итак, если вы не задали обработчик act или вернули из него false --
считается, что реакции нет и движок выполнит аналогичный обработчик у
объекта 'game'.
Обычно, нет никакого смысла возвращать false из act, но существуют
другие обработчики, о которых будет рассказано дальше, для которых
описанное поведение точно такое же.
На самом деле, кроме 'game.act' и 'act' -- атрибута объекта существует
обработчик 'onact' у объекта game, который может прервать выполнение
обработчика 'act'.
Перед тем как вызвать обработчик 'act' у объекта, вызывается onact у
game. Если обработчик вернет false, выполнение 'act' обрывается.
'onact' удобно использовать, для контроля
событий в комнате или игре, например:
```
-- вызываем onact комнат, если они есть
-- для действий на любой объект
game.onact = function(s, ...)
local r, v = std.call(here(), 'onact', ...)
if v == false then -- если false, обрубаем цепочку
return r, v
end
return
end
room {
nam = 'shop';
disp = 'Магазин';
onact = function(s, w)
p [[В магазине нельзя воровать!]]
p ([[Даже, если это всего-лишь ]], w, '.')
return false
end;
obj = { 'мороженное', 'хлеб' };
}
```
В данном примере, при попытке "потрогать" любой предмет, будет
выведено сообщение о запрете данного действия.
Все, что описано выше на примере 'act' действует и для других
обработчиков: tak, inv, use, а также при переходах, о чем будет
рассказано далее.
> Иногда возникает необходимость вызвать функцию - обработчик вручную.
> Для этого используется синтаксис вызова метода
> объекта. 'Объект:метод(параметры)'. Например:
apple:act() -- вызовем обработчик 'act' у объекта 'apple' (если он
определен как функция!). _'яблоко':act() -- то же самое, но по
имени, а не по переменной-ссылке
Такой метод работает только в том случае, если вызываемый метод
оформлен как функция. Вы можете воспользоваться 'std.call()' для
вызова обработчика тем способом, каким это делает сам INSTEAD. (Будет
описано в дальнейшем).
## Инвентарь
Простейший вариант сделать объект, который можно брать -- определить
обработчик 'tak'.
Например:
```
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
inv = function(s)
p 'Я съел яблоко.'
remove(s); -- удалить яблоко из инвентаря
tak = 'Вы взяли яблоко.';
};
```
При этом, при действии игрока на объект "яблоко" (щелчок мыши на
ссылку в сцене) -- яблоко будет убрано из сцены и добавлено в
инвентарь. При действии игрока на инвентарь (двойной щелчок мыши на
названии объекта) -- вызывается обработчик 'inv'.
В нашем примере, при действии игроком на яблоко в инвентаре -- яблоко
будет съедено.
Конечно, мы могли бы реализовать код взятия объекта в ''act'',
например, так:
```
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
inv = function(s)
p 'Я съел яблоко.'
remove(s); -- удалить яблоко из инвентаря
end;
act = function(s)
take(s)
p 'Вы взяли яблоко.';
end
};
```
Если у объекта в инвентаре не объявлен обработчик 'inv', будет вызван 'game.inv'.
Если обработчик 'tak' вернет false, то предмет не будет взят, например:
```
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
tak = function(s)
p "Оно же червивое!"
return false
end;
};
```
## Переходы между сценами
Традиционные переходы в INSTEAD выглядят как ссылки над описанием
сцены. Для определения таких переходов между сценами используется
атрибут сцены -- список 'way'. В списке определяются комнаты, в виде
имен комнат или переменных-ссылок, аналогично списку 'obj'. Например:
```
room {
nam = 'room2';
disp = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
};
room {
nam = 'main';
disp = 'Главная комната';
dsc = 'Вы в большой комнате.';
way = { 'room2' };
};
```
При этом, вы сможете переходить между сценами 'main' и 'room2'. Как вы
помните, 'disp' может быть функцией, и вы можете генерировать имена
переходов на лету. Или использовать title, для разделения имени сцены
как заголовка и как имени перехода:
```
room {
nam = 'room2';
disp = 'В зал';
title = 'В зале';
dsc = 'Вы в огромном зале.';
way = { 'main' };
};
room {
nam = 'main';
title = 'В главной комнате';
disp = 'В главную комнату';
dsc = 'Вы в большой комнате.';
way = { 'room2' };
};
```
При переходе между сценами движок вызывает обработчик 'onexit' из
текущей сцены и 'onenter' в той сцене, куда идет игрок. Например:
```
room {
onenter = 'Вы заходите в зал.';
nam = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
onexit = 'Вы выходите из зала.';
};
```
Конечно, как и все обработчики, 'onexit' и 'onenter' могут быть
функциями. Тогда первый параметр это (как всегда) сам объект -
комната, а второй -- это комната куда игрок собирается идти (для
'onexit') или из которой собирается уйти (для 'onenter'). Например:
```
room {
onenter = function(s, f)
if f^'main' then
p 'Вы идете из комнаты main.';
end
end;
nam = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
onexit = function(s, t)
if t^'main' then
p 'Я не хочу назад!'
return false
end
end;
};
```
Запись вида:
if f^'main' then
Это сопоставление объекта с именем. Это альтернатива записям:
if f == _'main' then
Или:
if f.nam == 'main' then
Или:
if std.nameof(f) == 'main' then
Как видим на примере onexit, эти обработчики, кроме строки могут
возвращать булевое значение статуса. Аналогично обработчику onact, мы
может отменить переход, вернув false из onexit/onenter.
Вы можете сделать возврат статуса и другим способом, если это кажется
вам удобным:
```
return "Я не хочу назад", false
```
Если же вы используете функции 'p'/'pn'/'pr', то просто возвращайте
статус операции с помощью завершающего 'return', как показано в
примере выше.
__Важно!__
> Следует отметить, что при вызове обработчика 'onenter' указатель на
> текущую сцену (here()) **еще не изменен**!!! В INSTEAD есть
> обработчики 'exit' (уход из комнаты) и 'enter' (заход в комнату),
> которые вызываются уже _после_ того, как переход произошел. Эти
> обработчики рекомендованы к использованию всегда, когда нет
> необходимости запрещать переход.
Иногда есть необходимость, чтобы название перехода отличалось от
названия комнаты, в которую ведет этот переход. Существует несколько
способов сделать это. Например, с помощью 'path'.
```
room {
nam = 'room2';
title = 'Зал';
dsc = 'Вы в огромном зале.';
way = { path { 'В главную комнату', 'main'} };
};
room {
nam = 'main';
title = 'Главная комната';
dsc = 'Вы в большой комнате.';
way = { path {'В зал', 'room2'} };
};
```
На самом деле, 'path' создает комнату с атрибутом 'disp', который
равен первому параметру, и специальной функцией 'onenter', которая
перенаправляет игрока в комнату заданную вторым параметром 'path'.
Если вы укажете три параметра:
way = { path {'#взал', 'В зал', 'room2'} };
То первый параметр станет именем (или тегом, как в приведенном
примере) такой комнаты.
Альтернативная форма записи с явным заданием атрибута nam:
way = { path { nam = '#взал', 'В зал', 'room2'} };
Вы можете менять название перехода, после того, как переход происходил
хотя бы раз, и вы узнали, что же это за комната:
way = { path {'#вдверь', 'В дверь', after = 'В гостиную', 'room2'} };
Все параметры, кроме имени перехода, могут быть функциями.
Таким образом, 'path' позволяет именовать переходы удобным способом.
Иногда вам может потребоваться включать и выключать переходы. На самом
деле это требуется не часто. Идея переходов состоит в том, что переход
виден даже тогда, когда он невозможен. Например, представим себе сцену
перед домом у входной двери. Войти в дом нельзя, так как дверь
закрыта.
Нет особого смысла прятать переход "дверь". Просто в функции 'onenter'
сцены внутри дома мы проверяем, а есть ли у героя ключ? И если ключа
нет, говорим о том, что дверь закрыта и запрещаем переход. Это
повышает интерактивность и упрощает код. Если же вы хотите сделать
дверь объектом сцены, поместите ее в комнату, но в 'act' обработчике
сделайте осмотр двери, или дайте возможность игроку открыть ее ключом
(как это сделать - мы рассмотрим позже), но сам переход дайте сделать
игроку привычным способом через строку переходов.
Тем не менее, бывают ситуации, когда переход не очевиден и он
появляется в результате каких-то событий. Например, мы осмотрели часы
и увидели там секретный лаз.
```
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
enable '#часы'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { '#часы', 'В часы', 'inclock' }:disable() };
};
```
В данном примере, мы создали _отключенный_ переход, за счет вызова
метода 'disable' у комнаты созданной с помощью 'path'. Метод 'disable'
есть у всех объектов (не только комнат), он переводит объект в
отключенное состояние, которое означает, что объект перестает быть
доступным игроку. Замечательным свойством отключенного объекта
является то, что его можно _включить_ с помощью 'enable()';
Далее, когда игрок нажимает на ссылку, описывающую часы, вызывается
обработчик 'act', который с помощью функции 'enable()' делает переход
видимым.
Альтернативный вариант заключается не в выключении, а 'закрытии'
объекта:
```
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
open '#часы'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { '#часы', 'В часы', 'inclock' }:close() };
};
```
В чем разница? Выключение объекта означает то, что объект перестает
быть доступным для игрока. Если в объекте вложены другие объекты, то
и эти объекты становятся недоступными.
Закрытие объекта делает недоступным содержимое данного объекта, но не
сам объект.
Однако, в случае комнат, и закрытие комнаты и отключенные комнаты
приводят к одному результату -- переход на них становится недоступным.
Еще один вариант:
```
room {
nam = 'inclock';
dsc = [[Я в часах.]];
}:close()
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
open 'inclock'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { 'В часы', 'inclock' } };
};
```
Здесь мы закрываем и открываем не переход, а комнату, в которую ведет
переход. path не показывает себя, если комната в которую он ведет
отключена или закрыта.
## Действие объектов друг на друга
Игрок может действовать объектом инвентаря на другие объекты. Для
этого он щелкает мышью на предмет инвентаря, а затем, на предмет
сцены. При этом вызывается обработчик 'used' у объекта, на который
действуют, и обработчик 'use' объекта, которым действуют.
Например:
```
obj {
nam = 'нож';
dsc = 'На столе лежит {нож}';
inv = 'Острый!';
tak = 'Я взял нож!';
use = 'Вы пытаетесь использовать нож.';
};
obj {
nam = 'стол';
dsc = 'В комнате стоит {стол}.';
act = 'Гм... Просто стол...';
obj = { 'нож' };
used = function(s)
p 'Вы пытаетесь сделать что-то со столом...';
return false
end;
};
```
В данном примере, обработчик used возвращает false. Зачем? Если вы
помните, возврат false означает, что обработчик сообщает движку о том,
что событие он не обработал. Если бы мы не вернули бы false, очередь
до обработчика 'use' объекта 'нож' просто бы не дошла. На самом деле,
в реальности обычно вы будете пользоваться или use или used, вряд ли
имеет смысл выполнять оба обработчика во время действия предмета
на предмет.
Еще один пример, когда удобно вернуть false:
```
use = function(s, w)
if w^'яблоко' then
p [[Я почистил яблоко.]]
w.cut = true
return
end
return false;
end
```
В данном случае use у яблока обрабатывает только одну ситуацию --
действие на яблоко. В остальных случаях, обработчик возвращает false и
движок вызовет метод по-умолчанию: game.use.
Но лучше, если вы пропишете действие по-умолчанию для ножа:
```
use = function(s, w)
if w^'яблоко' then
p [[Я почистил яблоко.]]
w.cut = true
return
end
p [[Не стоит размахивать ножом!]]
end
```
Этот пример также демонстрирует тот факт, что вторым параметром у use
является предмет на который мы действуем. У метода 'used',
соответственно, второй параметр -- это объект, который действует на
нас:
```
obj {
nam = 'мусорка';
dsc = [[В углу стоит {мусорка}.]];
used = function(s, w)
if w^'яблоко' then
p [[Я выбросил яблоко в мусорку.]]
remove(w)
return
end
return false;
end
}
```
Как вы помните, перед вызовом use вызывается обработчик onuse у
объекта game, потом у объекта 'игрок', а потом у текущей комнаты. Вы
можете блокировать 'use', вернув из любого из перечисленных методов
'onuse' -- false.
Использовать 'use' или 'used' (или оба) это вопрос личных
предпочтений, однако, метод used вызывается раньше и, следовательно,
имеет больший приоритет.
## Объект "Игрок"
Игрок в мире INSTEAD представлен объектом типа 'player'. Вы можете
создавать несколько игроков, но один игрок присутствует по-умолчанию.
Имя этого объекта -- 'player'. Существует переменная-ссылка pl,
которая указывает на этот объект.
Обычно, вам не нужно работать с этим объектом напрямую. Но иногда это
может быть необходимым.
По умолчанию, атрибут 'obj' у игрока представляет собой инвентарь.
Обычно, нет смысла переопределять объект типа player, однако, вы
можете это сделать:
```
game.player = player {
nam = "Василий";
room = 'кухня'; -- стартовая комната игрока
power = 100;
obj = { 'яблоко' }; -- заодно добавим яблоко в инвентарь
};
```
В INSTEAD есть возможность создавать нескольких игроков и
переключаться между ними. Для этого служит функция 'change_pl()'. В
качестве параметра передайте функции требуемый объект типа 'player'
(или его имя). Функция переключит текущего игрока, и при
необходимости, осуществит переход в комнату, где находится новый
игрок.
Функция 'me()' всегда возвращает текущего игрока. Следовательно, в
большинстве игр me() == pl.
## Объект "Мир"
Игровой мир представлена объектом типа world. Имя такого объекта
'game'. Существует ссылка-переменная, которая также называется game.
Обычно вы не работаете с этим объектом напрямую, однако иногда вы
можете вызывать его методы, или менять значения переменных этого
объекта.
Например, переменная game.codepage содержит кодировку исходного кода
игры, и по-умолчанию равна "UTF-8". Я не рекомендую использовать
другие кодировки, но иногда, выбор кодировки может стать
необходимостью.
Переменная game.player -- содержит текущего игрока.
Кроме того, как вы уже знаете, объект 'game' может содержать
обработчики по умолчанию: 'act', 'inv', 'use', 'tak', которые будут
вызваны, если в результате действий пользователя не будут найдены
никакие другие обработчики (или все они вернули false). Например, вы
можете написать в начале игры:
```
game.act = 'Не получается.';
game.inv = 'Гм.. Странная штука..';
game.use = 'Не сработает...';
game.tak = 'Не нужно мне это...';
```
Конечно, все они могут быть функциями.
Также, объект game может содержать обработчики: onact, ontak, onuse,
oninv, onwalk -- которые могут прерывать действия, в случае возврата
false.
Еще, у объекта game можно задать обработчики: afteract, afterinv,
afteruse, afterwalk -- которые вызываются в случае успешного
выполнения соответствующего действия.
## Атрибуты-списки
Атрибуты-списки (такие как 'way' или 'obj') позволяют работать со
своим содержимым с помощью набора методов. Атрибуты-списки призваны
сохранять в себе списки объектов. На самом деле, вы можете создавать
списки для собственных нужд, и размещать их в объектах, например:
```
room {
nam = 'холодильник';
frost = std.list { 'мороженное' };
}
```
Хотя, обычно, это не требуется.
Ниже перечислены методы объектов типа 'список'. Вы можете вызывать их
для любых списков, хотя обычно это будут way и obj, например:
ways():disable() -- отключить все переходы
- disable() - отключает все объекты списка;
- enable() - включает все объекты списка;
- close() - закрыть все объекты списка;
- open() - открыть все объекты списка;
- add(объект|имя, [позиция]) - добавить объект;
- for_each(функция, аргументы) - вызвать для каждого объекта функцию с
аргументами;
- lookup(имя/тег или объект) - поиск объекта в списке. Возвращает
объект и индекс;
- srch(имя/тег или объект) - поиск видимого объекта в списке;
- empty() - вернет true, если список пуст;
- zap() - очистить список;
- replace(что, на что) - заменить объект в списке;
- cat(список, [позиция]) - добавить содержимое списка в текущий список
по позиции;
- del(имя/объект) - удалить объект из списка.
Существуют функции, возвращающие объекты-списки:
- inv([игрок]) - вернуть инвентарь игрока;
- objs([комната]) - вернуть объекты комнаты;
- ways([комната]) - вернуть переходы комнаты.
Конечно, вы можете обращаться к спискам и напрямую:
```
pl.obj:add 'нож'
```
Объекты в списках хранятся в том порядке, в котором вы их
добавите. Однако, если у объекта присутствует числовой атрибут pri, то
он играет роль _приоритета_ в списке. Если pri не задан, значением
приоритета считается 0. Таким образом, если вы хотите, чтобы какой-то
объект был первым в списке, давайте ему приоритет pri < 0. Если в
конце списка -- > 0.
```
obj {
pri = -100;
nam = 'штука';
disp = 'Очень важный предмет инвентаря';
inv = [[Осторожней с этим предметом.]];
}
```
## Функции, которые возвращают объекты
В INSTEAD определены некоторые функции, которые возвращают различные объекты.
При описании функции используются следующие соглашения о параметрах.
- в символах [ ] описаны необязательные параметры;
- 'что' или 'где' - означает объект (в том числе комнату), заданный тегом,
именем или переменной-ссылкой;
Итак, основные функции:
- '_(что)' - получить объект;
- 'me()' возвращает текущего объекта-игрока;
- 'here()' возвращает текущую сцену;
- 'where(что)' возвращает комнату или объект в котором находится
заданный объект, если объект находится в нескольких местах, то можно
передать второй параметр -- таблицу Lua, в которую будут добавлены
эти объекты;
- 'inroom(что)' аналогично where(), но вернет комнату, в которой
расположен объект (это важно для объектов в объектах);
- 'from([где])' возвращает прошлую комнату, из которой игрок перешел в
заданную комнату. Необязательный параметр -- получить прошлую комнату
не для текущей комнаты, а для заданной;
- 'seen(что, [где])' возвращает объект или переход, если он
присутствует и видим, есть второй необязательный параметр -- выбрать
сцену или объект/список в котором искать;
- 'lookup(что, [где])' возвращает объект или переход, если он
существует в сцене или объекте/списке;
- 'inspect(что)' возвращает объект, если он виден/доступен на
сцене. Поиск производится по переходам и объектам, в том числе, в
объектах игрока;
- 'have(что)' возвращает объект, если он есть в инвентаре и не
отключен;
- 'live(что)' возвращает объект, если он присутствует среди живых
объектов (описано далее);
Эти функции в основном используются в условиях, либо для поиска
объекта с последующей модификацией. Например, вы можете использовать
'seen' для написания условия:
```
onexit = function(s)
if seen 'монстр' then -- если у функции 1 параметр,
--- скобки писать не обязательно
p 'Монстр загораживает проход!'
return false
end
end
```
А также, для нахождения объекта в сцене:
```
use = function(s, w)
if w^'окно' then
local ww = lookup 'собака'
if not ww then
p [[А где моя собака?]]
return
end
place(ww, 'улица')
p 'Я разбил окно! Моя собака выпрыгнула на улицу.'
return
end
return false
end
```
Пример с функцией 'have':
```
...
act = function(s)
if have 'нож' then
p 'Но у меня же есть нож!';
return
end
take 'нож'
end
...
```
> Может возникнуть вопрос, в чем разница между функциями lookup и _ ()?
> Дело в том, что lookup() ищет объект, и в случае, если объект не найден
> -- просто ничего не вернет. А запись _ () предполагает, что вы точно
> знаете, что за предмет вы получаете. Другими словами, _ () это
> безусловное получение объекта по имени. Эта функция в общем случае не
> занимается _поиском_. Только если в качестве параметра задан тег,
> будет осуществлен поиск среди доступных объектов. Если вы используете
> _ () на несуществующий объект или недоступный тег -- вы получите ошибку!
## Другие функции стандартной библиотеки
В INSTEAD в модуле stdlib, который всегда подключается автоматически,
определены функции, которые предлагаются автору как основной рабочий
инструмент по работе с миром игры. Рассмотрим их в этой главе.
При описании функции в большинстве функций под параметром 'w'
понимается объект или комната, заданная именем, тегом или по
переменной-ссылке. [ wh ] - означает необязательный параметр.
- include(файл) - включить файл в игру;
include "lib" -- включит файл lib.lua из текущего каталога с игрой;
- loadmod(модуль) - подключить модуль игры;
loadmod "module" -- включит модуль module.lua из текущего каталога;
- rnd(m) - случайное целочисленное значение от '1' до 'm';
- rnd(a, b) - случайное целочисленное значение от 'a' до 'b', где 'a'
и 'b' целые >= 0;
- rnd\_seed(что) - задать зерно генератора случайных чисел;
- p(...) - вывод строки в буфер обработчика/атрибута (с пробелом в конце);
- pr(...) - вывод строки в буфер обработчика/атрибута "как есть";
- pn(...) - вывод строки в буфер обработчика/атрибута (с переводом строки в конце);
- pf(fmt, ...) - вывод форматной строки в буфер обработчика/атрибута;
local text = 'hello';
pf("Строка: %q Число: %d\n", text, 10);
- pfn(...)(...)... "строка" - формирование простого обработчика;
Данная функция упрощает создание простых обработчиков:
act = pfn(walk, 'ванная') "Я решил зайти в ванную.";
act = pfn(enable, '#переход') "Я заметил отверстие в стене!";
- obj {} - создание объекта;
- stat {} - создание статуса;
- room {} - создание комнаты;
- menu {} - создание меню;
- dlg {} - создание диалога;
- me() - возвращает текущего игрока;
- here() - возвращает текущую сцену;
- from([w]) - возвращает комнату из которой осуществлен переход в
текущую сцену;
- new(конструктор, аргументы) - создание нового _динамического_
объекта (будет описано далее);
- delete(w) - удаление динамического объекта;
- gamefile(файл, [сбросить состояние?]) - подгрузить динамически файл
с игрой;
gamefile("part2.lua", true) -- сбросить состояние игры (удалить
объекты и переменные), подгрузить part2.lua и начать с main комнаты.
- player {} - создать игрока;
- dprint(...) - отладочный вывод;
- visits([w]) - число визитов в данную комнату (или 0, если визитов не было);
- visited([w]) - число визитов в комнату или false, если визитов не
было;
if not visited() then
p [[Я тут первый раз.]]
end
- walk(w, [булевое exit], [булевое enter]) - переход в сцену;
walk('конец', false, false) -- безусловный переход (игнорировать
onexit/onenter/exit/enter);
- walkin(w) - переход в под-сцену (без вызова exit/onexit текущей комнаты);
- walkout([w]) - возврат из под-сцены (без вызова enter/onenter);
- \_(w) - получение объекта;
- for_all(fn, ....) - выполнить функцию для всех аргументов;
for_all(enable, 'окно', 'дверь');
- seen(w, [где]) - поиск видимого объекта;
- lookup(w, [где]) - поиск объекта;
- ways([где]) - получить список переходов;
- objs([где]) - получить список объектов;
- search(w) - поиск доступного игроку объекта;
- have(w) - поиск предмета в инвентаре;
- inroom(w) - возврат комнаты/комнат, в которой находится объект;
- where(w, [таблица]) - возврат объекта/объектов, в котором находится объект;
local list = {}
local w = where('яблоко', list)
-- если яблоко находится в более, чем одном месте, то
-- list будет содержать массив этих мест.
-- Если вам достаточно одного местоположения, то:
where 'яблоко' -- будет достаточно
- closed(w) - true если объект закрыт;
- disabled(w) - true если объект выключен;
- enable(w) - включить объект;
- disable(w) - выключить объект;
- open(w) - открыть объект;
- close(w) - закрыть объект;
- actions(w, строка, [значение]) - возвращает (или устанавливает)
число действий типа t для объекта w.
if actions(w, 'tak') > 0 then -- предмет w был взят хотя бы 1 раз;
if actions(w) == 1 then -- act у предмета w был вызван 1 раз;
- pop(тег) - возврат в прошлую ветвь диалога;
- push(тег) - переход в следующую ветвь диалога
- empty([w]) - пуста ли ветвь диалога? (или объект)
- lifeon(w) - добавить объект в список живых;
- lifeoff(w) - убрать объект из списка живых;
- live(w) - объект жив?;
- change\_pl(w) - смена игрока;
- player\_moved([pl]) - текущий игрок перемещался в этом такте?;
- inv([pl]) - получить список-инвентарь;
- remove(w, [wh]) - удалить объект из объекта или комнаты; Удаляет
объект из списков obj и way (оставляя во всех остальных, например,
game.lifes);
- purge(w) - уничтожить объект (из всех списков); Удаляет объект из
_всех_ списков, в которых он присутствует;
- replace(w, ww, [wh]) - заменить один объект на другой;
- place(w, [wh]) - поместить объект в объект/комнату (удалив его из
старого объекта/комнаты);
- put(w, [wh]) - поместить объект без удаления из старого местоположения;
- take(w) - забрать объект;
- drop(w, [wh]) - выбросить объект;
- path {} - создать переход;
- time() - число ходов от начала игры.
__Важно!__
На самом деле, многие из этих функций также умеют работать не только с
комнатами и объектами, но и со списками. То есть 'remove(apple,
inv())' сработает также как и 'remove(apple, me())''; Впрочем,
remove(apple) тоже сработает и удалит объект из тех мест, где он
присутствует.
Рассмотрим несколько примеров.
```
act = function()
pn "Я иду в следующую комнату..."
walk (nextroom);
end
obj {
nam = 'моя машина';
dsc = 'Перед хижиной стоит мой старенький {пикап} Toyota.';
act = function(s)
walk 'inmycar';
end
};
```
__Важно!__
> После вызова 'walk' выполнение обработчика продолжится до его
> завершения. Поэтому обычно, после 'walk' всегда следует
> 'return', если только это не последняя строка функции, хотя и в
> этом случае безопасно поставить 'return'.
```
act = function()
pn "Я иду в следующую комнату..."
walk (nextroom);
return
end
```
Не забывайте также, что при вызове 'walk' вызовутся обработчики
'onexit/onenter/exit/enter'' и если они запрещают переход, то он не
произойдет.
## Диалоги
Диалоги -- это сцены специального типа 'dlg', содержащие объекты --
фразы. При входе в диалог игрок видит перечень фраз, которые может
выбирать, получая какую-то реакцию игры. По умолчанию, уже выбранные
фразы скрываются. При исчерпании всех вариантов, диалог завершается
выходом в предыдущую комнату (конечно, если в диалоге нет постоянно
видимых фраз, среди которых обычно встречается что-то типа 'Завершить
разговор' или 'Спросить еще раз'). При повторном входе в диалог, все
скрытые фразы снова становятся видимыми и диалог сбрасывается в
начальное состояние (если, конечно, автор игры специально не
прикладывал усилия по изменению вида диалога).
Переход в диалог в игре осуществляется как переход на сцену:
```
obj {
nam = 'повар';
dsc = 'Я вижу {повара}.';
act = function()
walk 'povardlg'
end,
};
```
Хотя я рекомендую использовать 'walkin', так как в случае 'walkin' не
вызываются 'onexit/exit' текущей комнаты, а персонаж, с которым мы
можем поговорить, обычно находиться в этой же комнате, где и главный
герой. То есть:
```
obj {
nam = 'повар';
dsc = 'Я вижу {повара}.';
act = function()
walkin 'povardlg'
end,
};
```
Если вам не нравится префикс у фраз в виде дефиса, вы можете определить строковую переменную:
```
std.phrase_prefix = '+';
```
И получить префикс в виде '+' перед каждой фразой. Вы также можете
сделать префикс функцией. На вход функции в таком случае будет
поступать в виде параметра номер фразы. Задача функции -- вернуть
строковый префикс.
Обратите внимание, что 'std.phrase_prefix' не сохраняется, если вам
нужно переопределять ее на лету, вам придется восстанавливать ее
состояние в 'start()' функции вручную!
__Важно!__
> Я рекомендую использовать модуль 'noinv' и задавать свойство 'noinv'
> в диалогах. Диалоги будут выглядеть красивей и вы обезопасите свою
> игру от ошибок и непредсказуемых реакций при использовании инвентаря
> внутри диалога (так как обычно автор не подразумевает такие
> вещи). Например:
```
require "noinv"
...
dlg {
nam = 'Охранник';
-- в диалогах обычно не нужен инвентарь
noinv = true;
...
}
```
### Фразы
Центральным понятием в диалогах является _фраза_. Фразы это не просто
вопрос-ответ, как можно подумать. Фраза является деревом, и в этом
смысле, весь диалог может быть реализован единственной
фразой. Например:
```
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = {
{ 'У вас есть бобы?', '-- Нет.'},
{ 'У вас есть шоколад?', '-- Нет.'},
{ 'У вас есть квас?', '-- Да',
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
```
Как видно из примера, фраза задается атрибутом phr и может содержать
разветвленный диалог. Фраза содержит в себе выборы, каждый из которых
тоже может содержать в себе выборы и так далее...
Фраза имеет формат пары: описатель -- реакция. В простейшем случае,
это строки. Но это могут быть функциями. Обычно, функцией бывает
реакция, которая может содержать код по изменению игрового мира.
Пара может быть простой:
{'Вопрос', 'Ответ }
А может содержать в себе массив пар:
{'Вопрос', 'Ответ',
{'Под-вопрос1', 'Под-ответ1' },
{'Под-вопрос2', 'Под-ответ2' },
}
На самом деле, если вы посмотрите внимательно на атрибут phr, то вы
заметите, что массив выборов тоже является вложенным в главную фразу
phr, но только первоначальная пара отсутствует:
```
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = {
-- тут мог бы быть вопрос ответ 1-го уровня!
-- 'Главный вопрос', 'Главный ответ',
{ 'У вас есть бобы?', '-- Нет.'},
{ 'У вас есть шоколад?', '-- Нет.'},
{ 'У вас есть квас?', '-- Да',
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
```
На самом деле, так и есть. И вы можете добавить 'Главный вопрос' и
'Главный ответ', но только вы не увидите этот главный вопрос. Дело в
том, что при входе в диалог фраза phr автоматически раскрывается, так
как обычно нет никакого смысла в диалогах из одной единственной фразы.
И гораздо проще понять диалог как набор выборов, чем как единственную
древовидную фразу. Так что у phr никогда нет первоначальной пары
вопрос-ответ, но мы сразу попадаем в массив вариантов, что более
понятно.
Когда мы говорим о том, что диалог на самом деле реализован одной
фразой, мы не совсем правы. Дело в том, что мы имеем дело с фразой,
внутри которой находятся другие фразы... Это напоминает нам ситуацию с
объектами. Действительно, фразы -- это объекты! Которые могут
находиться внутри друг-друга. Итак, взглянем на диалог свежим взглядом:
```
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = { -- это объект типа фраза, без dsc и act
-- это 1-я фраза, внутри фразы с dsc и act
{ 'У вас есть бобы?', '-- Нет.'},
-- это 2-я фраза, внутри фразы с dsc и act
{ 'У вас есть шоколад?', '-- Нет.'},
-- это 3-я фраза, внутри фразы с dsc и act
{ 'У вас есть квас?', '-- Да',
-- это 1-я фраза внутри 3й фразы с dsc и act
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
-- здесь act в виде функции
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
```
Как видим, диалог -- это комната, а фразы -- специальные объекты!
Теперь вам станет понятным дальнейшее изложение.
> Внимание! По умолчанию, когда игрок нажимает на один из вопросов в
> списке, движок повторяет его в выводе и только потом выводит
> ответ. Это сделано для того, чтобы диалог выглядел связанным. Если
> вы хотите отключить такое поведение, используйте настройку
> std.phrase_show:
```
std.phrase_show = false -- не выводить фразу-вопрос при выборе
```
Эта настройка действует на все диалоги, устанавливайте ее в init() или
start() функции.
### Атрибуты фраз
Рассмотрим вариант фразы:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
}
}
```
Если запустить этот диалог, то в после выбора, скажем, красной
таблетки, у нас останется еще один выбор синей таблетки. Но наш
замысел, явно не в этом! Существует несколько способов сделать диалог
правильным.
Во первых, вы можете воспользоваться pop() -- возвратом на предыдущий
уровень диалога:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'Красную', function() p 'Держите!'; pop() end; },
{'Синюю', function() p 'Вот!'; pop() end; },
}
}
```
Или, в другой записи:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'Красную', pfn(pop) 'Держите!' },
{'Синюю', pfn(pop) 'Вот!' },
}
}
```
Но это не слишком удобно, кроме того, что если эти фразы содержат в
себе новые фразы? В случаях, когда вариант предлагает выбор, и этот
выбор должен быть единственным, вы можете задать у фразы атрибут only:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
}
}
```
В таком случае, после выбора фразы, все фразы текущего контекста будут
закрыты.
Еще одна частая ситуация, вы хотите, чтобы фраза не пряталась после ее
активации. Это делается заданием флага true:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
}
}
```
Альтернативная запись, с явным заданием атрибута always:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ always = true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
}
}
```
Еще один пример. Что-если мы хотим, чтобы фраза была показана(или
спрятана) по какому-либо условию? Для этого есть функция-обработчик
cond.
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
```
В данном примере, только при наличии у игрока яблока, покажется ветка
диалога 'А хотите яблоко?'.
Иногда бывает удобно выполнить действие в тот момент, когда варианты
текущего уровня(контекста) диалога исчерпаны. Для этого служит
функция-обработчик onempty.
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
onempty = function()
p [[Ты сделал свой выбор.]]
pop()
end;
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
```
Обратите внимание, что когда есть метод onempty, автоматический
возврат в предыдущую ветку не производится, предполагается, что метод
onempty сделает все, что нужно.
Все описанные атрибуты могут быть установлены у любой фразы. В том
числе и на 1-м уровне:
```
phr = {
onempty = function()
p [[Вот и поговорили.]]
walkout()
end;
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
onempty = function()
p [[Ты сделал свой выбор.]]
pop()
end;
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
```
### Теги
Только что мы рассмотрели механизмы диалогов, которые уже позволяют
создавать довольно сложные диалоги. Однако, и этих средств может не
хватить. Иногда нам нужно уметь обращаться к фразам из других мест
диалога. Например, выборочно включать их, или анализировать их
состояние. А также делать переходы из одних ветвей диалога в другие.
Все это возможно для фраз, у которых есть тег. Создать фразу с тегом
очень просто:
```
phr = {
{ '#что?', 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'#красная', 'Красную', 'Держите!' },
{'#синяя', 'Синюю', 'Вот!' },
},
}
```
Как видим, наличие в начале фразы строки, которая начинается на символ
'#' - означает наличие тега.
Для таких фраз работают стандартные методы, такие как seen или
enable/disable. Например, мы могли бы обойтись без атрибута only
следующим образом:
```
phr = {
{ '#что?', 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'#красная', 'Красную', 'Держите!'
cond = function(s)
return opened '#синяя'
end
},
{'#синяя', 'Синюю', 'Вот!',
cond = function(s)
return opened '#красная'
end
},
},
}
```
Теги, кроме того, что позволяют узнавать и менять состояние конкретных
фраз, делают возможным переходы между фразами. Для этого используются
функции push и pop.
push(куда) -- делает переход на фразу с запоминанием позиции в стеке.
pop([куда]) -- вызванная без параметра, поднимается на 1 позицию в
стеке истории. Можно указать конкретный тег фразы, которая должна быть
в истории, в таком случае возврат будет осуществлен на нее.
Нужно отметить, что при переходе по push, мы переходим не на одну
фразу, а на список фраз этой фразы. То-есть раскрываем ее, также как
это сделано для главной фразы phr. Например:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
},
{ false, '#отаблетке',
{'Я сделал верный выбор?', 'Время покажет.'}
},
}
```
Тут мы видим сразу несколько приемов:
- атрибут next, вместо явного описания реакции в виде функции с
push. next -- это простой способ записать push.
- false в начале фразы, делает фразу выключенной. Она находится в
состоянии выключена, пока не сделать явный enable. Однако внутрь фразы
мы можем перейти, и показать содержимое выборов. Альтернативная запись
возможна с использованием атрибута hidden:
```
{ hidden = true, '#отаблетке',
{'Я сделал верный выбор?', 'Время покажет.'}
},
```
Таким образом можно записывать диалоги не древовидно, а линейно. Еще
одна особенность переходов состоит в том, что если у фразы не описана
реакция, то при переходе будет вызван заголовок фразы:
```
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
},
{ false, '#отаблетке', [[Я взял таблетку и мастер хитро улыбнулся.]],
{'Я сделал верный выбор?', 'Время покажет.'},
{'Что делать дальше?', 'Ты свободен.'},
},
}
```
При выборе таблетки, будет вызван заголовочный метод фразы
'#отаблетке', а уже потом будет представлен выбор.
Если вам нравится линейная запись, вы можете предпочесть следующий
вариант:
```
dlg {
nam = 'диалог';
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
}
}
}:with {
{ '#отаблетке', [[Я взял таблетку и мастер хитро улыбнулся.]],
{'Я сделал верный выбор?', 'Время покажет.'},
{'Что делать дальше?', 'Ты свободен.'},
},
}
```
Дело в том, что атрибут phr диалога задает первый объект комнаты. Но
вы можете заполнить объекты комнаты обычным образом: задав obj или
with. Так как при входе в диалог раскрывается 1-я фраза, то остальные
фразы вы не увидите (обратите внимания, у фразы '#отаблетке' не стоит
false), но вы сможете делать переходы на эти фразы.
### Методы
Как вы уже знаете, объекты в INSTEAD могут находиться в состоянии
открыт/закрыт и выключен/включен. Как это соответствует фразам
диалога?
Для обычных фраз, после активации выбора фраза _закрывается_. При
повторном входе в диалог все фразы _открываются_.
Для фраз с always = true (или true в начале определения) -- такого
закрытия не происходит.
Для фраз с hidden = true (или false в начале определения) -- фраза
будет создана как выключенная. Она не будет видима до тех пор, пока не
будет явно включена.
Для фраз с cond(), каждый раз при просмотре фраз вызывается этот
метод, и в зависимости от возвращаемого значения (true/не true) фраза
включается или выключается.
Зная это поведение, вы можете прятать/показывать и анализировать фразы
обычными функциями вида: disable / enable / empty / open / close /
closed / disabled и так далее...
Однако, делать вы это можете только в самом диалоге, так как все фразы
идентифицируются по тегам. Если вы хотите модифицировать
состояние/анализировать фразы из других комнат вы можете:
- дать фразе имя { nam = 'имя' }...
- искать фразу по тегу в другой комнате: local ph = lookup('#тег',
'диалог') и потом работать с ней;
Что касается функций push/pop, то вы можете вызывать их явно как
методы диалога, например:
```
_'диалог':push '#новая'
```
Но лучше это делать в самом диалоге, например, в enter.
Кроме того есть метод :reset, который сбрасывает стек переходов и
устанавливает стартовую фразу, например:
```
enter = function(s)
s:reset '#начало'
end
```
> Следует отметить, что когда вы делаете enable/disable/open/close
> фразы, то вы выполняете действие именно над этой фразой, а не над
> фразами включенными внутрь. Но так как при показе фраз движок
> остановится на выключенном/закрытом объекте-фразе и не войдет
> внутрь, этого достаточно.
## Специальные объекты
В STEAD3 существуют специальные объекты, которые выполняют
специфические функции. Все такие объекты можно разделить на два
класса:
1. Системные объекты @;
2. Подстановки $.
Системные объекты, это объекты, чье имя начинается с символа
'@' или '$'. Такие объекты обычно создаются в _модулях_. Они не уничтожаются
при смерти игрового мира (например, при подгрузке gamefile, при
загрузке игры из сохранения, и так далее). Примеры объектов: @timer,
@prefs, @snd.
Такие объекты, кроме своих специальных функций, могут быть
использованы по ссылке, без явного помещения объекта в сцену или
инвентарь, но механизм действия таких объектов -- особенный.
### Объект '@'
Обычно, вам не нужно работать с такими объектами, но в качестве
примера рассмотрим реализацию 'ссылок'.
Пусть мы хотим сделать ссылку, при нажатии на которую мы перейдем в
другую комнату. Конечно, мы могли бы добавить объект в сцену, но стоит
ли это делать в таком простом случае?
Как нам может помочь системный объект?
```
obj {
nam = '@walk';
act = function(s, w)
walk(w, false, false)
end;
}
room {
nam = 'main';
title = 'Начало';
decor = [[Начать {@walk старт|приключение}]];
}
```
При нажатии на ссылку "приключение" будет вызван метод act объекта
'@walk' с параметром "старт".
На самом деле, в стандартной библиотеке stdlib уже есть объект, с
именем '@', который позволяет делать свои обработчики ссылок следующим
образом:
```
xact.walk = walk
room {
nam = 'main';
title = 'Начало';
decor = [[Начать {@ walk старт|приключение}]];
}
```
Обратите внимание, на пробел после @. Данная запись делает следующее:
- берет объект '@' (такой объект создан библиотекой stdlib);
- берет его act;
- вызывает act с параметрами walk и старт;
- act объекта '@' смотрит в массив xact;
- walk определяет метод, который будет вызван из массива xact;
- старт -- параметр этого метода.
Другой пример:
```
xact.myprint = function(w)
p (w)
end
room {
nam = 'main';
title = 'Начало';
decor = [[Нажми {@ myprint "hello world"|на кнопку}]];
}
```
### Подстановки
Объекты, чье имя начинается на символ '$' тоже считаются системными
объектами, но работают они по-другому.
Если в выводе текста встречается "ссылка" вида:
```
{$my a b c|текст}
```
То происходит следующее:
1. Берется объект $my;
2. Берется act объекта $my;
3. Вызывается act: _'$my':(a, b, c, текст);
4. Возвращаемая строка заменяет собой всю конструкцию {...}.
Таким образом, объекты играют роль подстановки.
Зачем это нужно? Представьте себе, что вы разработали модуль, который
превращает записи формул из текстового вида в графические. Вы пишете
объект $math который в своем act методе превращает текст в графическое
изображение (спрайт) и возвращает его в текстовый поток. Тогда
пользоваться таким модулем крайне просто, например:
```
{$math|(2+3*x)/y^2}
```
## Динамические события
Вы можете определять обработчики, которые выполняются каждый раз,
когда время игры увеличивается на 1. Обычно, это имеет смысл для живых
персонажей, или каких-то фоновых процессов игры. Алгоритм шага игры
выглядит примерно так:
- Игрок нажимает на ссылку;
- Реакция 'act', 'use'', 'inv', 'tak', осмотр сцены (клик по названию
сцены) или переход в другую сцену;
- Динамические события;
- Вывод нового состояния сцены.
Например, сделаем Барсика живым:
```
obj {
nam = 'Барсик';
{ -- не сохранять массив lf
lf = {
[1] = 'Барсик шевелится у меня за пазухой.',
[2] = 'Барсик выглядывает из-за пазухи.',
[3] = 'Барсик мурлычит у меня за пазухой.',
[4] = 'Барсик дрожит у меня за пазухой.',
[5] = 'Я чувствую тепло Барсика у себя за пазухой.',
[6] = 'Барсик высовывает голову из-за пазухи и осматривает местность.',
};
};
life = function(s)
local r = rnd(5);
if r > 2 then -- делать это не всегда
return;
end
r = rnd(#s.lf); -- символ # -- число элементов в массиве
p(s.lf[r]); -- выводим одно из 6 состояний Барсика
end;
....
```
И вот момент в игре, когда Барсик попадает к нам за пазуху!
```
take 'Барсик' -- добавить в инвентарь
lifeon 'Барсик' -- оживить Барсика!
```
Любой объект (в том числе и сцена) могут иметь свой обработчик 'life',
который вызывается каждый такт игры, если объект был добавлен в список
живых объектов с помощью 'lifeon'. Не забывайте удалять живые объекты
из списка с помощью 'lifeoff', когда они больше не нужны. Это можно
сделать, например, в обработчике 'exit', или любым другим способом.
> Если в вашей игре много "живых" объектов, вы можете задавать им
> явную позицию в списке, при добавлении. Для этого, воспользуйтесь
> вторым числовым параметром (целое неотрицательное число) 'lifeon',
> чем меньше число, тем выше приоритет. 1 -- самый высокий. Или вы
> можете использовать атрибут pri у объекта. Правда, этот атрибут
> будет влиять на приоритет объекта в любом списке.
Если вам нужен фоновый процесс в какой-то комнате, запускайте его в
'enter' и удаляйте в 'exit', например:
```
room {
nam = 'В подвале';
dsc = [[Тут темно!]];
enter = function(s)
lifeon(s);
end;
exit = function(s)
lifeoff(s);
end;
life = function(s)
if rnd(10) > 8 then
p [[Я слышу какие-то шорохи!]];
-- изредка пугать игрока шорохами
end
end;
way = { 'Дом' };
}
```
Если вам нужно определить, был ли переход игрока из одной сцены в
другую, воспользуйтесь 'player\_moved()'.
```
obj {
nam = 'фонарик';
on = false;
life = function(s)
if player_moved() then -- гасить фонарик при переходах
s.on = false
p "Я выключил фонарик."
return
end
end;
...
}
```
Для отслеживания протекающих во времени событий, используйте 'time()'
или вспомогательную переменную-счетчик. Для определения местоположения
игрока -- 'here()'. Для определения факта, что объект "живой" --
'live()'.
```
obj {
nam = 'динамит';
timer = 0;
used = function(s, w)
if w^'спичка' then -- спичка?
if live(s) then
return "Уже горит!"
end
p "Я поджег динамит."
lifeon(s)
return
end
return false -- если не спичка
end;
life = function(s)
s.timer = s.timer + 1
if s.timer == 5 then
lifeoff(s)
if here() == where(s) then
p [[Динамит взорвался рядом со мной!]]
else
p [[Я услышал, как взорвался динамит.]];
end
end
end;
...
}
```
Если 'life' обработчик возвращает текст события, он печатается после
описания сцены.
Вы можете вернуть из обработчика 'life' второй код возврата, ('true'
или 'false'). Если вы вернете true -- то это будет признаком важного
события, которое выведется до описания объектов сцены, например:
```
p 'В комнату вошел охранник.'
return true
```
Или:
```
return 'В комнату вошел охранник.', true
```
Если вы вернете false, то цепочка life методов прервется на вас. Это
удобно делать при выполнении walk из метода life, например:
```
life = function()
walk 'theend'
return false -- это последний life
end
```
Если вы хотите блокировать 'life' обработчики в какой-то из комнат,
воспользуйтесь модулем 'nolife'. Например:
```
require "noinv"
require "nolife"
dlg {
nam = 'Охранник';
noinv = true;
nolife = true;
...
}
```
Отдельно стоит рассмотреть вопрос перехода игрока из 'life'
обработчика. Если вы собираетесь использовать функции 'walk' внутри
'life', то вам следует учитывать следующее поведение.
Если 'life' переносит игрока в новую локацию, то обычно предполагается
что вы:
1. Очищаете вывод реакций: game:reaction(false);
2. Очищаете вывод живых методов на данный момент: game:events(false,
false)
3. Делаете walk.
4. Останавливаете цепочку life вызовов с помощью return false;
Некоторые моменты требуют пояснений.
game:reaction() -- позволяет взять/изменить вывод реакции
пользователя, если задать его в false это означает сбросить реакцию.
game:events() -- позволяет взять/изменить вывод life методов. В
качестве параметров принимаются приоритетные и не приоритетные
сообщения, задав false, false мы отменили весь вывод предыдущих life
методов.
В стандартной библиотеке уже есть функция life_walk(), которая делает
описанные действия. Вам остается только вернуть false.
## Графика
Графический интерпретатор INSTEAD анализирует атрибут сцены 'pic', и
воспринимает его как путь к картинке, например:
```
room {
pic = 'gfx/home.png';
nam = 'Дома';
dsc = 'Я у себя дома';
};
```
__Важно!__
Используйте в путях только прямые '/'. Также, настоятельно
рекомендуется использовать в именах каталогов и файлов только
латинские строчные символы. Этим самым вы обезопасите свою игру от
проблем с совместимостью и она будет работать на всех архитектурных
платформах, куда портирован INSTEAD.
Конечно, 'pic' может быть функцией, расширяя возможности разработчика.
Если в текущей сцене не определен атрибут 'pic', то берется атрибут
'game.pic'. Если не определен и он, то картинка не отображается.
Поддерживаются все наиболее распространенные форматы изображений, но я
рекомендую вам использовать 'png' и (когда важен размер) 'jpg'.
Вы можете использовать в качестве картинок анимированные gif файлы.
Вы можете встраивать графические изображения прямо в текст, в том
числе в инвентарь, переходы, заглавия комнат и 'dsc' с помощью
функции 'fmt.img' (Для этого включите модуль fmt).
Например:
```
require "fmt"
obj {
nam = 'яблоко'
disp = 'Яблоко'..fmt.img('img/apple.png');
}
```
Тем-не менее, картинку сцены всегда следует оформлять в виде 'pic'
атрибута, а не вставки 'fmt.img' в 'dsc' комнаты.
Дело в том, что картинка сцены масштабируется по другому алгоритму.
Картинки 'fmt.img' масштабируются в соответствии с настройками INSTEAD
(масштаб темы), а 'pic' -- учитывает также размер картинки.
Кроме того, картинки 'pic' обладают и другими свойствами, например,
возможностью отслеживания координат кликов мышью.
Если вы поместите 'fmt.img' внутрь { и }, то получите графическую ссылку.
```
obj {
nam = 'яблоко';
disp = 'яблоко ' ..img('img/apple.png');
dsc = function(s)
p ("На полу лежит {яблоко",fmt.img 'img/apple.png', "}");
-- другие варианты:
-- return "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
-- p "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
-- или dsc = "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
end;
}
```
INSTEAD поддерживает обтекание картинок текстом. Если картинка
вставляется с помощью функции 'fmt.imgl'/'fmt.imgr', она будет
расположена у левого/правого края.
__Важно!__
> Картинки, вставленные в текст с помощью 'fmt.imgl/fmt.imgr' не могут быть
> ссылками!!! Используйте их только в декоративных целях.
Для задания отступов вокруг изображения используйте 'pad', например:
```
fmt.imgl 'pad:16,picture.png' -- отступы по 16 от каждого края
fmt.imgl 'pad:0 16 16 4,picture.png' -- отступы: вверху 0, справа 16, внизу 16, слева 4
fmt.imgl 'pad:0 16,picture.png' -- отступы: вверху 0, справа 16, внизу 0, слева 16
```
Вы можете использовать псевдо-файлы для изображений прямоугольников и
пустых областей:
```
dsc = fmt.img 'blank:32x32'..[[Строка с пустым изображением.]];
dsc = fmt.img 'box:32x32,red,128'..[[Строка красным полупрозрачным квадратом.]];
```
INSTEAD может обрабатывать составные картинки, например:
```
pic = 'gfx/mycat.png;gfx/milk.png@120,25;gfx/fish.png@32,32';
```
Таким образом, составная картинка представляет собой набор путей к
изображениям, разделенных символом ';'. Вторая и последующие
компоненты могут содержать постфикс в виде
@x\_координата,y\_координата%, где координате 0,0 соответствует левый
верхний угол всего изображения. Общий размер картинки считается равным
общему размеру первой компоненте составной картинки, то есть, первый
компонент (в нашем примере -- gfx/mycat.png) играет роль холста, а
последующие компоненты накладываются на этот холст.
Наложение происходит для левого верхнего угла накладываемой
картинки. Если вам нужно, чтобы наложение происходило относительно
центра накладываемой картинки, используйте перед координатами префикс
''c'', например:
```
pic = 'gfx/galaxy.png;gfx/star.png@c128,132';
```
Оформив в виде функции формирование пути составной картинки, вы можете
генерировать изображение на основе игрового состояния.
Если вы в своей игре привязываетесь к каким-то координатам
изображений, или к их размерам, делайте это относительно оригинальных
размеров изображений. При масштабировании темы под заданное игроком
разрешение, INSTEAD сам будет осуществлять пересчёт координат (при
этом координаты для игры выглядят так, как будто игра запущена без
масштабирования). Однако, возможны небольшие погрешности вычислений.
Если вам не хватает функций, описанных в этой главе, изучите модуль
"sprite", который предоставляет более широкие возможности по
графическому оформлению. Но я крайне не рекомендую делать это в своей
первой игре.
## Музыка
Для работы с музыкой и звуками вам понадобится модуль snd.
```
require "snd"
```
Интерпретатор проигрывает в цикле текущую музыку, которая задается
с помощью функции: 'snd.music(путь к музыкальному файлу)'.
__Важно!__
Используйте в путях только прямые '/'. Также, настоятельно
рекомендуется использовать в именах каталогов и файлов только
латинские строчные символы. Этим самым вы обезопасите свою игру от
проблем с совместимостью и она будет работать на всех архитектурных
платформах, куда портирован INSTEAD.
Поддерживается большинство музыкальных форматов, но настоятельно
рекомендуется использовать формат 'ogg', так как именно он
поддерживается наилучшим образом во всех версиях INSTEAD (для
различных платформ).
__Важно!__
Следует проявлять осторожность при использовании трекерной музыки, так
как в некоторых дистрибутивах Linux могут быть проблемы при
проигрывании определенных файлов (ошибки в связке библиотек SDL_mixer
и libmikmod).
Также, если вы используете 'mid' файлы, будьте готовы к тому, что
игрок услышит их только в Windows версии INSTEAD (так как в
большинстве случаев, Unix версии SDL_mixer собраны без поддержки
''timidity'').
В качестве частоты музыкальных файлов используйте частоты кратные
11025.
```
room {
pic = 'gfx/street.png';
enter = function()
snd.music 'mus/rain.ogg'
end;
nam = 'на улице';
dsc = 'На улице идет дождь.';
};
```
'snd.music()' без параметра возвращает текущее имя трека.
В функцию 'snd.music()' можно передавать второй параметр -- количество
проигрываний (циклов). Получить текущий счетчик можно с помощью
'snd.music()' без параметров -- второе возвращаемое значение. 0 --
означает вечный цикл. 1..n -- количество проигрываний. -1 --
проигрывание текущего трека закончено.
Для того, чтобы отменить проигрывание музыки, вы можете использовать
'snd.stop\_music()'
Для того, чтобы узнать, играет ли музыка:
```
snd.music_playing()
```
Вы можете задавать время нарастания и затухания музыки, с помощью вызова:
```
snd.music_fading(o, [i])
```
Здесь o - время в мс. для затухания и i - время в мс. для нарастания
музыки. Если задан только один параметр -- оба времени считаются
одинаковыми. После вызова, установленные параметры будут влиять на
проигрывание всех музыкальных файлов.
Для проигрывания звуков используйте 'snd.play()'. Настоятельно
рекомендуется использовать формат 'ogg', хотя большинство
распространенных звуковых форматов также будет работать.
Различие между музыкой и звуковым файлом заключается в том, что движок
следит за процессом проигрывания музыки и сохраняет/восстанавливает
текущий проигрываемый трек. Выйдя из игры и загрузив ее снова, игрок
услышит то же музыкальное оформление, что слышал при выходе. Звуки
обычно означают кратковременные эффекты, и движок не сохраняет и не
восстанавливает звуковые события. Так, если игрок не успел дослушать
звук выстрела и вышел из игры, после загрузки файла сохранения он не
услышит звук (или его окончание) снова.
Тем не менее, если учесть то, что 'snd.play()' позволяет запускать
зацикленные звуки, то различие между музыкой и звуками становится уже
не таким однозначным.
Итак, определение функции: 'snd.play(файл, [канал], [цикл])', где:
- файл -- путь и\или имя звукового файла;
- канал -- номер канала [0..7]; Если не указан, то выберется первый свободный.
- цикл -- количество проигрываний 1..n, 0 -- зацикливание.
Для остановки проигрывания звука можно использовать 'snd.stop()'. Для
остановки звука в определенном канале 'snd.stop(канал)'.
__Важно!__
> Если вы используете зацикленные звуки, вам придется самим
> восстанавливать их состояние (запускать снова с помощью
> 'snd.sound()') в функции 'start()'
Например:
```
global 'wind_blow' (false)
...
function start()
if wind_blow then
snd.play('snd/wind.ogg', 0)
end
end
```
Если вам не достаточно описанных здесь функций по работе со звуком,
используйте полное описание модуля "snd".
## Форматирование и оформление вывода
Обычно INSTEAD сам занимается форматированием и оформлением
вывода. Например, отделяет статическую сцену от динамической. Выделяет
курсивом действия игрока. Переводит фокус на изменение в тексте и
т.д. Модули вроде "fmt" улучшают качество вывода игры без
дополнительных усилий со стороны автора.
Например:
```
require 'fmt'
fmt.para = true -- включить отступы параграфов
```
И ваша игра будет выглядеть гораздо лучше. Если вам нужна какая-то
автоматическая обработка выводимого текста, вы можете включить модуль
"fmt" и определить функцию 'fmt.filter'. Например:
```
require "fmt"
fmt.filter = function(s, state)
-- s -- вывод
-- state -- true, если это вывод сцены
return s..'\nЭта строка будет добавлена к выводу';
end
end
```
Многие хорошие игры на INSTEAD никак не занимаются своим оформлением,
кроме разбиения текста 'dsc' на параграфы с помощью символов '^^',
поэтому подумайте, а так ли вам хочется заниматься оформлением своей
игры вручную?
Тем не менее, иногда это все-таки необходимо.
> Внимание! По умолчанию, все конечные и начальные переводы строк,
> пробелы и символы табуляции вырезаются из вывода обработчиков. Так
> как обычно они не имеют смысла и даже вредны. В редких случаях,
> автору может понадобиться более полный контроль над выводом, тогда он
> может задать std.strip\_call как false в init() или start(),
> например:
```
std.strip_call = false
obj {
dsc = [[Тут лежит {яблоко}.^^^^]] -- теперь переводы строк
-- не будут вырезаны, хотя это странное желание
}
```
> Но обычно такое ручное форматирование свидетельствует о плохом
> стиле. Для оформления сцены лучше использовать decor и/или
> подстановки $.
### Форматирование
Вы можете делать простое форматирование текста с помощью функций:
- fmt.c(строка) - разместить по центру;
- fmt.r(строка) - разместить справа;
- fmt.l(строка) - разместить слева;
- fmt.top(строка) - сверху строки;
- fmt.bottom(строка) - снизу строки;
- fmt.middle(строка) - середина строки (по умолчанию).
Например:
```
room {
nam = 'main';
title = 'Добро пожаловать';
dsc = fmt.c 'Добро пожаловать!'; -- если у функции только 1 параметр,
-- скобки можно опускать;
}
```
Вышеописанные функции влияют не только на текст, но и на изображения,
вставленные с помощью ''fmt.img()'.
Следует отметить, что если вы используете несколько функций
форматирования, то предполагается, что они относятся к разным строкам
(параграфам). В противном случае, результат не определен. Разбивайте
текст на абзацы символами '^' или 'pn()'.
INSTEAD при выводе удаляет лишние пробелы. Это значит, что неважно
сколько пробелов вы вставляете между словами, все равно при выводе они
не будут учитываться для расчета расстояния между словами. Иногда это
может стать проблемой.
Вы можете создавать _неразрывные строки_ с помощью:
fmt.nb(строка). Например, модуль "fmt" использует неразрывные строки
для создания отступов в начале параграфов. Также, 'fmt.nb' может
оказаться удобной для вывода служебных символов. Можно сказать, что
вся строка-параметр 'fmt.nb' воспринимается движком как одно большое
слово.
Еще один пример. Если вы используете подчеркивание текста, то
промежутки между словами не будут подчеркнуты. При использовании
'fmt.nb' промежутки также будут подчеркнуты.
INSTEAD не поддерживает отображение таблиц, однако для вывода простых
табличных данных можно воспользоваться 'fmt.tab()'. Эта функция
используется для абсолютного позиционирования в строке (табулятор).
fmt.tab(позиция, [центр])
_Позиция_, это текстовый или числовой параметр. Если задан числовой
параметр, он воспринимается как позиция в пикселях. Если он задан в
виде строкового параметра 'число%', то он воспринимается как
позиция, выраженная в процентах от ширины окна вывода сцены.
Необязательный строковой параметр _центр_ задает позицию в следующем
за 'fmt.tab' слове, которая будет размещена по указанному смещению в
строке. Позиции могут быть следующими:
- left;
- right;
- center.
По-умолчанию считается что задан параметр "left".
Так, например:
```
room {
nam = 'main';
disp = 'Начало';
-- размещение 'Начало!' по центру строки
dsc = fmt.tab('50%', 'center')..'Начало!';
}
```
Конечно, не очень удачный пример, так как то же самое можно было
сделать с помощью 'fmt.c()'. Более удачный пример.
dsc = function(s)
p(fmt.tab '0%')
p "Слева";
p(fmt.tab '100%', 'right')
p "Справа";
end
На самом деле, единственная ситуация, когда применение 'fmt.tab()'
оправдано -- это вывод табличных данных.
Следует отметить, что в ситуации, когда мы пишем что-то вроде:
```
-- размещение 'Раз' по центру строки
dsc = fmt.tab('50%', 'center')..'Раз два три!';
```
Только слово 'Раз' будет помещено в центр строки, остальные слова
будут дописаны справа от этого слова. Если вы хотите центрировать 'Раз
два три!' как одно целое, воспользуйтесь 'fmt.nb()'.
```
-- размещение 'Раз два три!' по центру строки
dsc = fmt.tab('50%', 'center')..fmt.nb ('Раз два три!');
```
В INSTEAD также существует выполнять простое вертикальное
форматирование. Для этого используйте вертикальный табулятор:
fmt.y(позиция, [центр])
Как и в случае с fmt.tab _позиция_, это текстовый или числовой
параметр. Здесь он воспринимается как позиция строки, выраженная в
пикселях или процентах от высоты области сцены. Например, 100% --
соответствует нижней границе области сцены. 200% -- соответствует
нижней границе второй страницы вывода (две высоты области вывода
сцены).
Необязательный строковой параметр _центр_ задает позицию внутри
строки, относительно которой выполняется позиционирование:
- top (по верхнему краю);
- middle (по центру);
- bottom (по нижнему краю -- значение по умолчанию).
Следует отметить, что 'fmt.y' работает целиком для строки. Если в
строке встретится несколько fmt.y, действовать будет последний из
табуляторов.
```
-- размещение 'ГЛАВА I' - в центре сцены
dsc = fmt.y('100%').."ГЛАВА I";
```
_Если позиция, указанная табулятором, уже занята другой строкой, табулятор игнорируется._
По умолчанию, статическая часть сцены отделяется от динамической двойным переводом строки. Если вам это не подходит, вы можете переопределить 'std.scene_delim', например:
std.scene_delim = '^' -- одинарный перевод строки
Вы не можете менять эту переменную в обработчиках, так как она не
сохраняется, но вы можете задать ее для игры целиком, или
восстанавливать ее вручную в функции 'start()'.
Если вас категорически не устраивает то, как INSTEAD формирует вывод
(последовательность абзацов текста), вы можете переопределить функцию
'game.display()', которая по умолчанию выглядит следующим образом:
```
game.display = function(s, state)
local r, l, av, pv
local reaction = s:reaction() or nil -- реакция
r = std.here()
if state then -- такт игры?
reaction = iface:em(reaction) -- курсив
av, pv = s:events()
av = iface:em(av) -- вывод "важных" life
pv = iface:em(pv) -- вывод фоновых life
l = s.player:look() -- objects [and scene] -- объекты и сцена
end
l = std.par(std.scene_delim,
reaction or false, av or false, l or false,
pv or false) or ''
return l
end;
```
Тот факт, что я привел здесь этот код, не означает, что я рекомендую
переопределять эту функцию. Напротив, я категорически против такой
сильной привязки к форматированию текста. Тем не менее, иногда
возникает ситуация, когда полный контроль за последовательностью
вывода необходим. Если вы пишите свою первую игру, просто пропустите
этот текст.
### Оформление
Вы можете менять начертание текста с помощью комбинаций функций:
- fmt.b(строка) - жирный текст;
- fmt.em(строка) - курсив;
- fmt.u(строка) - подчеркнутый текст;
- fmt.st(строка) - перечеркнутый текст.
Например:
```
room {
nam = 'Intro';
title = false;
dsc = function(s)
p ('Вы находитесь в комнате: ')
p (fmt.b(s))
end;
}
```
> Используя функции 'fmt.u' и 'fmt.st' на строках, содержащих пробелы,
> вы получите разрывы линий в этих местах. Что избежать этого, можно
> превратить текст в _неразрывную строку_:
fmt.u(fmt.nb "теперь текст без пропусков" )
Строго говоря, INSTEAD не поддерживает одновременный вывод разными шрифтами в окно сцены (если не считать разное начертание), поэтому если вам все-таки требуется более гибкий контроль вывода, вы можете сделать следующее:
- Использовать графические вставки 'fmt.img()';
- Использовать модуль 'fonts', в котором реализована отрисовка
разными шрифтами за счет модуля 'sprite';
- Использовать другой движок, так как скорее всего вы используете INSTEAD не по назначению.
## Конструкторы и наследование
__Внимание!__
Если вы пишите свою первую игру, было бы лучше, если бы она была
простая. Для простой игры информация из этой главы не
понадобится. Более того, 90% игр на INSTEAD не использует вещей,
описанных в этой главе!
Если вы пишите игру, в которой много однотипных объектов, возможно,
вам захочется упростить их создание. Это можно сделать одним из
следующих способов:
- Создать свой конструктор;
- Создать новый класс объектов.
### Конструкторы
Конструктор -- это функция, которая создает за вас объект и заполняет
его атрибуты так, как вам это нужно. Рассмотрим пример. Допустим, в
вашей игре будет много окон. Нужно создавать окна, любое окно можно
разбить. Мы можем написать конструктор 'window'.
```
window = function(v)
v.window = true
v.broken = false
if v.dsc == nil then
v.dsc = 'Здесь есть {окно}.'
end
v.act = function(s)
if s.broken then
p [[Окно разбито.]]
else
p [[За окном темно.]]
end
end
if v.used == nil then
v.used = function(s, w)
if w^'молоток' then
if s.broken then
p [[Окно уже разбито.]]
else
p [[Я разбил окно.]]
s.broken = true;
end
return
end
return false
end
end
return obj(v)
end
```
Как видим, идея конструкторов проста. Вы просто создаете функцию,
которая получает на вход таблицу с атрибутами {}, которую конструктор
может дозаполнить нужными атрибутами. Затем эта таблица передается
конструктору obj/room/dlg и возвращается полученный объект.
Теперь, создавать окна стало легко:
```
window {
dsc = [[Тут есть {окно}.]];
}
```
Или, так как окно это обычно статический объект, можно создавать его
прямо в 'obj'.
```
obj = { window {
dsc = 'В восточной стене есть {окно}.';
}
};
```
У нашего окна будет готовый used метод и act метод. Вы можете
проверить тот факт, что объект окно -- просто проверив признак window:
```
use = function(s, w)
if w.window then
p [[Действие на окно.]]
return
end
return false
end
```
Состояние "разбитости" окна, это атрибут broken.
Как реализовать наследование в конструкторах?
На самом деле, в примере выше уже используется
наследование. Действительно, ведь конструктор 'window'' вызывает
другой конструктор 'obj', тем самым получая все свойства обычного
объекта. Также, 'window' определяет переменную признак 'window',
чтобы в игре мы могли понять, что мы имеем дело с окном. Например:
Для иллюстрации механизма наследования создадим класс объектов
'treasure', те. сокровищ.
```
global { score = 0 }
treasure = function()
local v = {}
v.disp = 'сокровище'
v.treasure = true
v.points = 100
v.dsc = function(s)
p ('Здесь есть {', std.dispof(s), '}.')
end;
v.inv = function(s)
p ('Это же ', std.dispof(s), '.');
end;
v.tak = function(s)
score = score + s.points; -- увеличим счет
p [[Дрожащими руками я забрал сокровища.]];
end
return obj(v)
end
```
А теперь, на его основе создадим золото, алмаз и сундук.
```
gold = function(dsc)
local v = treasure();
v.disp = 'золото';
v.gold = true;
v.points = 50;
v.dsc = dsc;
return v
end
diamond = function(dsc)
local v = treasure();
v.disp = 'алмаз';
v.diamond = true;
v.points = 200;
v.dsc = dsc;
return v
end
chest = function(dsc)
local v = treasure();
v.disp = 'сундук';
v.chest = true
v.points = 1000;
v.dsc = dsc;
return v
end
```
Теперь, в игре можно создавать сокровища через конструкторы:
```
diamond1 = diamond("В грязи я заметил {алмаз}.")
diamond2 = diamond(); -- тут будет стандартное описание алмаза
gold1 = gold("В углу я заметил блеск {золота}.");
room {
nam = 'пещера';
obj = {
diamond1,
gold1,
chest("А еще я вижу {сундук}!")
};
}
```
На самом деле, как именно писать функции-конструкторы и реализовывать
принцип наследования, зависит только от вас. Выберете наиболее простой
и понятный способ.
При написании конструкторов иногда бывает полезным сделать вызов
обработчика так, как это делает INSTEAD. Для этого используется
'std.call(объект, метод, параметры)', при этом эта функция вернет
реакцию атрибута в виде строки. Например, рассмотрим модификацию
'window', которая заключается в том, что можно определять свою
реакцию на осмотр окна, которая будет выполнена после стандартного
сообщения о том, что это разбитое окно (если оно разбито).
```
window = function(nam, dsc, what)
local v = {} -- создаем пустую таблицу
-- заполняем ее
v.window = true
v.what = what
v.broken = false
if dsc == nil then
v.dsc = 'Здесь есть {окно}'
end
v.act = function(s)
if s.broken then
p [[Окно разбито.]]
end
local r, v = stead.call(s, 'what')
if v then -- обработчик выполнился?
p(r)
else
p [[За окном темно.]]
end
end
return obj(v)
end
```
Таким образом, мы можем при создании окна задать третий параметр, в
котором определить функцию или строку, которая будет реакцией во время
осмотра окна. При этом сообщение о том, что окно разбито (если оно
действительно разбито), будет выведено перед этой реакцией.
### Класс объектов
Конструкторы объектов широко использовались в STEAD2. В STEAD3
obj/dlg/room реализованы как классы объектов. Класс объектов удобно
создавать для тех случаев, когда поведение создаваемого объекта не
укладывается в стандартные объекты obj/room/dlg и вы хотите поменять
методы класса. Изменив метод класса, например, вы можете вообще
изменить то, как выглядит предмет в сцене. В качестве примера,
рассмотрим создание класса "контейнер". Контейнер может хранить в себе
другие объекты, быть закрытым и открытым.
```
-- create own class container
cont = std.class({ -- создаем класс cont
display = function(s) -- переопределяем метод показа предмета
local d = std.obj.display(s)
if s:closed() or #s.obj == 0 then
return d
end
local c = s.cont or 'Внутри: ' -- описатель содержимого
local empty = true
for i = 1, #s.obj do
local o = s.obj[i]
if o:visible() then
empty = false
if i > 1 then c = c .. ', ' end
c = c..'{'..std.nameof(o)..'|'..std.dispof(o)..'}'
end
end
if empty then
return d
end
c = c .. '.'
return std.par(std.space_delim, d, c)
end;
}, std.obj) -- мы наследуемся от стандартного объекта
```
После этого, вы можете создавать контейнеры так:
```
cont {
nam = 'ящик';
dsc = [[Тут есть {ящик}.]];
cont = 'В ящике: ';
}: with {
'яблоко', 'груша';
}
```
Когда контейнер будет открыт, вы увидите описание ящика, а также
содержимое ящика в виде строки ссылок: В ящике: яблоко, груша.
dsc объектов яблоко и груша будут скрыты.
К сожалению, подробное описание классов выходит за рамки данного
руководства, эти вещи будут описаны в другом руководстве для
разработчиков модулей. А пока, для вашей первой игры, вам не стоит
писать свои классы объектов.
## Полезные советы
### Разбиение на файлы
Когда ваша игра становится большой, размещение ее кода целиком в 'main3.lua' -- плохая идея.
Для разбиения текста игры на файлы вы можете использовать
'include'. Вы должны использовать 'include' в глобальном контексте
таким образом, чтобы во время загрузки 'main3.lua' загрузились и все
остальные фрагменты игры, например.
```
-- main3.lua
include "episode1" -- .lua можно опускать
include "npc"
include "start"
room {
nam = 'main';
....
```
Как именно разбивать исходный текст на файлы зависит только от вас. Я
использую файлы в соответствии с эпизодами игры (которые обычно слабо
связаны между собой), но можно создавать файлы, хранящие отдельно
комнаты, объекты, диалоги и т.д. Это вопрос личного удобства.
Также есть возможность динамически подгружать части игры (с
возможностью доопределять объекты). Для этого вы
можете воспользоваться функцией 'gamefile':
```
...
act = function() gamefile ("episode2") end -- .lua можно опускать
...
```
> Внимание! Если в вашей игре определена функция init(), то в
> подгружаемых частях она также должна присутствовать! В противном
> случае, после подгрузки файла, будет вызвана текущая функция init(),
> что обычно не является желательным.
'gamefile()' также позволяет загрузить новый файл и забыть стек
предыдущих загрузок, запустив этот новый файл как самостоятельную
игру. Для этого, задайте второй параметр функции как 'true'. Имейте в
виду, что существующие модули остаются и переживают операцию gamefile
в обоих случаях. 'gamefile()' можно использовать только в
обработчиках.
```
act = function() gamefile ("episode3.lua", true); end;
```
Во втором варианте 'gamefile()' можно использовать для оформления
мультиязычных игр или игр-сборников, где фактически из оболочки
выполняется запуск самостоятельной игры.
### Модули
Дополнительная функциональность часто реализована в INSTEAD в виде
модулей. Для использования модуля необходимо
написать:
```
require "имя модуля"
```
Или:
```
loadmod "имя модуля"
```
Если модуль поставляется вместе с игрой.
Часть модулей входит в поставку INSTEAD, но есть и такие, которые вы
можете скачать отдельно и положить в каталог с игрой. Вы можете
заменить любой стандартный модуль своим, если положите его в каталог с
игрой под тем-же именем файла, что и стандартный.
Модуль, это фактически 'lua' файл с именем: 'имя_модуля.lua'.
Ниже перечислены основные стандартные модули, с указанием
функциональности, которые они предоставляют.
- 'dbg' — модуль отладки;
- 'click' — модуль перехвата кликов мыши по картинке сцены;
- 'prefs' — модуль настроек (хранилище данных настроек);
- 'snapshots' — модуль поддержки снапшотов (для откатов игровых ситуаций);
- 'fmt' — модуль оформления вывода;
- 'theme' — управление темой на лету;
- 'noinv' - модуль работы с инвентарем;
- 'key'' - модуль обработки событий срабатывания клавиш;
- 'timer' - таймер;
- 'sprite' — модуль для работы со спрайтами;
- 'snd' — модуль работы со звуком;
- 'nolife' – модуль блокировки методов life;
Пример загрузки модулей:
```
--$Name: Моя игра!$
require "fmt"
require "click"
```
### Меню
Стандартное поведение предмета инвентаря состоит в том, что игрок
должен сделать два щелчка мышью. Это необходимо потому, что каждый
предмет инвентаря может быть использован на другой предмет сцены или
инвентаря. После второго щелчка происходит игровой такт игры. Иногда
такое поведение может быть нежелательным. Возможно, вы захотите
сделать игру в которой игровая механика отличается от классических
INSTEAD игр. Тогда вам может понадобится меню.
Меню -- это элемент инвентаря, который срабатывает на первый клик. При
этом меню может сообщить движку, что действие не является игровым
тактом. Таким образом, используя меню вы можете создать в зоне
инвентаря управление игрой любой сложности. Например, существует
модуль "proxymenu", который реализует управление игрой в стиле квестов
на ZX-"Спектрум". В игре "Особняк" свое управление, которое вводит
несколько модификаторов действий, и т.д.
Итак, вы можете делать меню в области инвентаря, определяя объекты с
типом 'menu'. При этом, обработчик меню ('act') будет вызван
после одного клика мыши. Если обработчик возвращает false, то
состояние игры не изменяется. Например, реализация кармана:
```
menu {
state = false;
nam = 'карман';
disp = function(s)
if s.state then
return fmt.u('карман'); -- подчеркиваем активный карман
end
return 'карман';
end;
gen = function(s)
if s.state then
s:open(); -- показать все предметы в кармане
else
s:close(); -- спрятать все предметы в кармане
end
return s
end;
act = function(s)
s.state = not s.state -- изменить состояние
s:gen(); -- открыть или закрыть карман
end;
}: with {
obj {
nam = 'нож';
inv = 'Это нож';
};
}
function init()
take 'карман':gen()
end
```
### Статус игрока
Иногда возникает желание выводит какой-нибудь статус. Например,
количество игровых очков, состояние героя или, наконец, время
суток. INSTEAD не предоставляет каких-то других областей вывода, кроме
сцены и инвентаря, поэтому, самым простым способом вывода статуса
является вывод его в зону инвентаря.
Ниже представлена реализация статуса игрока в виде текста, который
появляется в инвентаре, но не может быть выбран, то есть, выглядит
просто как текст.
```
global {
life = 10;
power = 10;
}
stat { -- stat -- объект "статус"
nam = 'статус';
disp = function(s)
pn ('Жизнь: ', life)
pn ('Сила: ', power)
end
};
function init()
take 'статус'
end
```
### walk из обработчиков onexit и onenter
Вы можете делать 'walk' из обработчиков 'onenter' и
'onexit'. Например, 'path' реализован как комната с обработчиком
'onenter', который переносит игрока в другую комнату.
Рекомендуется возвращать из onexit/onenter false в случае, если вы
делаете walk из этих обработчиков.
### Кодирование исходного кода игры
Если вы не хотите показывать исходный код своих игр, вы можете
закодировать исходный код с помощью параметра командной строки
'-encode':
sdl-instead -encode <путь к файлу> [выходной путь]
И использовать закодированный файл с помощью обычных include/gamefile.
Однако, для этого вы должны написать в начале main3.lua:
```
std.dofile = std.doencfile
```
При этом главный файл 'main3.lua' необходимо оставлять открытым. Таким
образом, схема выглядит следующим образом ('game.lua' -- закодированный
файл):
```
-- $Name: Моя закрытая игра!$
std.dofile = std.doencfile
include "game"; -- никто не узнает, как ее пройти!
```
__Важно!__
> Не используйте компиляцию игр с помощью 'luac', так как 'luac' создает
> платформозависимый код! Однако, компиляция игр может быть
> использована для поиска ошибок в коде.
### Запаковка ресурсов
Вы можете упаковать ресурсы игры (графику, музыку, темы) в файл
ресурсов '.idf', для этого поместите все ресурсы в каталог 'data'
и запустите INSTEAD:
sdl-instead -idf <путь к data>
При этом, в текущем каталоге должен будет создастся файл
'data.idf'. Поместите его в каталог с игрой. Теперь ресурсы игры в
виде отдельных файлов можно удалить (конечно, оставив себе
оригинальные файлы).
Вы можете запаковать в формат '.idf' всю игру:
sdl-instead -idf <путь к игре>
Игры в формате 'idf' можно запускать как обычные игры 'instead' (как
если бы это были каталоги) а также из командной строки:
sdl-instead game.idf
### Переключение между игроками
Вы можете создать игру с несколькими персонажами и время от времени
переключаться между ними (см. 'change_pl()'). Но вы можете также
использовать этот трюк для того, чтобы иметь возможность переключаться
между разными типами инвентаря.
### Использование параметров обработчика
Пример кода.
```
obj {
nam = 'камень';
dsc = 'На краю лежит {камень}.';
act = function()
remove 'камень';
p 'Я толкнул камень, он сорвался и улетел вниз...';
end
```
Обработчик act мог бы выглядеть проще:
```
act = function(s)
remove(s);
p 'Я толкнул камень, он сорвался и улетел вниз...';
end
```
### Таймер
Для асинхронных событий, привязанных к реальному времени, в INSTEAD
есть возможность использовать таймер. На самом деле, вам следует
хорошо подумать, стоит ли в приключенческой игре использовать
таймер. Обычно, игроком это воспринимается не слишком благосклонно. С
другой стороны, таймер вполне можно использовать для управления
музыкой или в оформительских целях.
Для использования таймера, вам следует подключить модуль "timer".
require "timer"
Таймер программируется с помощью объекта 'timer'.
- timer:set(мс) -- задать интервал таймера в миллисекундах;
- timer:stop() -- остановить таймер.
При срабатывании таймера, вызывается обработчик game.timer. Если
game.timer возвращает false, сцена не перерисовывается. В
противном случае, возвращаемое значение выводится как реакция.
Вы можете делать локальные для комнаты обработчики 'timer'. Если в
комнате объявлен обработчик 'timer', он вызывается вместо
'game.timer'. Если он возвращает false -- вызывается game.timer.
Например:
```
game.timer = function(s)
if time() > 10 then
return false
end
snd.play 'gfx/beep.ogg';
p ("Timer:", time())
end
function init()
timer:set(1000) -- раз в секунду
end
```
```
room {
enter = function(s)
timer:set(1000);
end;
timer = function(s)
timer:stop();
walk 'комната2';
end;
nam = 'Проверка таймера';
dsc = [[Ждите.]];
}
```
Состояние таймера попадает в файл сохранения, таким образом, вам не
нужно заботиться о его восстановлении.
Вы можете вернуть из таймера специальный статус:
return true, false
В этом режиме перерисуется только область инвентаря. Это можно
использовать для статусов вроде часов.
Кроме того, в INSTEAD существует возможность отслеживать интервалы
времени в миллисекундах. Для этого используйте функцию
instead.ticks(). Функция возвращает число миллисекунд, прошедшее с
момента старта игры.
### Музыкальный плеер
Вы можете написать для игры свой проигрыватель музыки, создав его на основе живого объекта, например:
```
-- играет треки в случайном порядке
require "snd"
obj {
{
tracks = {"mus/astro2.mod",
"mus/aws_chas.xm",
"mus/dmageofd.xm",
"mus/doomsday.s3m"};
};
nam = 'плеер';
life = function(s)
if not snd.music_playing() then
local n = s.tracks[rnd(#s.tracks)]
snd.music(n, 1);
end
end;
}:lifeon();
```
Ниже приводится пример более сложного плеера. Меняем трек только если
он закончился или прошло более 2 минут и игрок перешел из комнаты в
комнату. В каждом треке можно указать число проигрываний (0 -
зацикленный трек):
```
require "timer"
global { track_time = 0 };
obj {
nam = 'player';
pos = 0;
{
playlist = { '01 Frozen sun.ogg', 0,
'02 Thinking.ogg', 0,
'03 Melancholy.ogg', 0,
'04 Everyday happiness.ogg', 0,
'10 Good morning again.ogg', 1,
'15 [Bonus track] The end (demo cover).ogg', 1
};
};
tick = function(s)
if snd.music_playing() and ( track_time < 120 or not player_moved() ) then
return
end
track_time = 0
if s.pos == 0 then
s.pos = 1
else
s.pos = s.pos + 2
end
if s.pos > #s.playlist then
s.pos = 1
end
snd.music('mus/'..s.playlist[s.pos], s.playlist[s.pos + 1]);
end;
}
game.timer = function(s)
track_time = track_time + 1
music_player:tick();
end
function init()
timer:set(1000)
end
```
### Живые объекты
Если вашему герою нужен друг, одним из способов может стать метод
'life' этого персонажа, который всегда переносит объект в локацию
игрока:
```
obj {
nam = 'лошадь';
dsc = 'Рядом со мной стоит {лошадь}.';
act = [[Моя лошадка.]];
life = function(s)
if player_moved() then
place(s);
end
end;
}
function init()
lifeon 'лошадь'; -- сразу оживим лошадь
end
```
### Клавиатура
Вы можете перехватывать события клавиатуры с помощью модуля "keys".
Обычно, перехват клавиш имеет смысл использовать для организации текстового ввода.
За описанием, обращайтесь к документации модуля "keys".
Ниже приводится простой пример:
```
require "keys"
function keys:filter(press, key)
return press -- ловим все нажатия
end
game.onkey = function(s, press, key)
dprint("pressed: ", key)
return false
end
```
Еще пример:
```
require "keys"
game.timer = function(s) -- показываем состояние клавиши курсор вправо
dprint("state of 'right' key: ", keys:state 'right')
end
timer:set(30)
```
### Мышь
Вы можете отслеживать в своей игре клики по картинке сцены, а также по
фону. Для этого, воспользуйтесь модулем "click". Также, вы можете
отслеживать состояние мыши с помощью функции:
```
instead.mouse_pos([x, y])
```
Которая возвращает координаты курсора. Если задать параметры (x, y),
то можно переместить курсор в указанную позицию (все координаты
рассчитываются относительно левого верхнего угла окна INSTEAD).
```
require "click"
function click:filter(press, btn, x, y, px, py)
dprint(press, btn, x, y, px, py)
return press and px -- ловим только нажатия на картинку
end
room {
nam = 'main';
pic = "box:320x200,red";
onclick = function(s, press, btn, x, y, px, py)
pn("Вы нажали на картинку: ", px, ", ", py)
pn("Абсолютные координаты: ", x, ", ", y)
p("Кнопка: ", btn)
end;
}
```
### Вызов меню
Вы можете вызвать из игры меню INSTEAD с помощью функции
'istead.menu()'. Если в качестве параметра задать: 'save', 'load' или
'quit', то будет вызван соответствующий подраздел меню.
### Динамическое создание объектов
Обычные объекты и комнаты нельзя создавать "на лету". Обычно вы
создаете их в глобальном пространстве lua файла. Однако, существуют
игры в которых количество объектов неизвестно заранее, или количество
объектов велико и они добавляются по ходу игры.
В INSTEAD существует способ создания любого объекта на лету. Для этого
вам понадобится написать _конструктор_ вашего объекта и
воспользоваться функцией 'new'.
Конструктор должен быть декларирован.
Итак, вы можете использовать функции 'new' и 'delete' для создания
и удаления динамических объектов. Примеры:
```
declare 'box' (function()
return obj {
dsc = [[Тут лежит {коробка}.]];
tak = [[Я взял коробку.]];
}
end)
local o = new (box);
take(o);
```
```
declare 'box' (function(dsc)
return obj {
dsc = dsc;
tak = [[Я взял коробку.]];
}
end)
take(new(box, 'В углу стоит {коробка}'))
```
'new' воспринимает первый аргумент -- как задекларированную
функцию-конструктор, а все остальные параметры -- как аргументы
конструктору. Результатом выполнения конструктора должен быть
объект.
```
function myconstructor()
local v = {}
v.disp = 'тестовый объект'
v.act = 'Тестовая реакция'
return obj(v)
end
```
Если вы хотите уничтожить объект по его имени или ссылке-переменной,
воспользуйтесь:
```
purge(o) -- удалить из всех списков
delete(o) -- освободить объект
```
При этом, delete это именно удаление объекта из INSTEAD, а не аналог
remove() или purge(). Обычно, нет особого смысла делать delete. Только
если предмет больше никогда не понадобится в игре, или вы собираетесь
пересоздать объект с тем же именем, имеет смысл освободить его с
помощью delete().
Более практически-полезный пример:
```
-- декларируем существующую
-- функцию
declare 'path' (path)
-- динамический переход
-- создали новый объект
-- и положили его в ways()
put( new (path, { 'переход', 'комната2'}, ways())
```
### Запрет на сохранение игры
Иногда может понадобиться запретить игроку делать сохранения в
игре. Например, если речь идет о сценах, где важный элемент составляет
случай, или для коротких игр, в которых проигрыш должен быть фатальным
и требовать перезапуска игры.
Для управлением функции сохранения используется атрибут 'instead.nosave'.
Например:
instead.nosave = true -- запретить сохранения
Если вы хотите запрещать сохранения не везде, а в некоторых сценах,
оформите 'instead.nosave' в виде функции, или же меняйте состояние
атрибута на лету -- он попадает в файл сохранений.
```
-- запретить
-- сохранения в комнатах, которые содержат атрибут nosave.
instead.nosave = function()
return here().nosave
end
```
Следует отметить, что запрет на сохранения не означает запрета на
автосохранение. Для управления автосохранением воспользуйтесь
аналогичным атрибутом 'instead.noautosave'.
> Вы можете явно сохранять игру с помощью вызова:
> 'instead.autosave([номер слота])'; Если номер слота не задан, то
> игра будет сохранена под слотом 'автосохранение'. Имейте в виду, что
> сохраняется состояние __после__ завершение текущего такта игры.
### Определение типа объекта
В INSTEAD существует два способа определить тип объекта.
Первый - с помощью функции std.is_obj(переменная, [тип]).
Например:
```
a = room {
nam = 'объект';
};
dprint(std.is_obj(a)) -- выведет true
dprint(std.is_obj('объект')) -- выведет false
dprint(std.is_obj(a, 'room')) -- выведет true
dprint(std.is_obj(a.obj, 'list')) -- выведет true
```
std.is_obj() удобная для определения типа пременной или аргумента
функции.
Второй способ связан с использованием метода type:
```
a = room {
nam = 'объект';
};
dprint(a:type 'room') -- выведет true
```
## Темы для sdl-instead
Графический интерпретатор поддерживает механизм тем. _Тема_
представляет из себя каталог, с файлом 'theme.ini' внутри.
Тема, которая является минимально необходимой -- это тема
'default'. Эта тема всегда загружается первой. Все остальные темы
наследуются от нее и могут частично или полностью заменять ее
параметры. Выбор темы осуществляется пользователем через меню
настроек, однако конкретная игра может содержать собственную тему и
таким образом влиять на свой внешний вид. В этом случае в каталоге с
игрой должен находиться свой файл 'theme.ini'. Тем не менее,
пользователь свободен отключить данный механизм, при этом
интерпретатор будет предупреждать о нарушении творческого замысла
автора игры.
Синтаксис 'theme.ini' очень прост.
<параметр> = <значение>
или
; комментарий
Значения могут быть следующих типов: строка, цвет, число.
Цвет задается в форме #rgb, где r g и b компоненты цвета в
шестнадцатеричном виде. Кроме того некоторые основные цвета
распознаются по своим именам. Например: yellowgreen, или
violet.
Параметры могут принимать значения:
- scr.w = ширина игрового пространства в пикселях (число)
- scr.h = высота игрового пространства в пикселях (число)
- scr.col.bg = цвет фона
- scr.gfx.scalable = [0|1|2] (0 - не масштабируемая тема, 1 -
масштабируемая, 2 - кратно-масштабируемая), начиная с версии 2.2.0
доступны дополнительно [4|5|6]: 4 - полностью не масштабируемая (с
не масштабируемыми шрифтами), 5 - масштабируемая, с не
масштабируемыми шрифтами, 6 - кратно-масштабируемая, с не
масштабируемыми шрифтами
- scr.gfx.bg = путь к картинке фонового изображения (строка)
- scr.gfx.cursor.x = x координата центра курсора (число)
- scr.gfx.cursor.y = y координата центра курсора (число)
- scr.gfx.cursor.normal = путь к картинке-курсору (строка)
- scr.gfx.cursor.use = путь к картинке-курсору режима использования (строка)
- scr.gfx.use = путь к картинке-индикатору режима использования (строка)
- scr.gfx.pad = размер отступов к скролл-барам и краям меню (число)
- scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h = координаты, ширина и
высота окна изображений. Области в которой располагается картинка
сцены. Интерпретация зависит от режима расположения (числа)
- win.gfx.h - синоним scr.gfx.h (для совместимости)
- scr.gfx.icon = пусть к файлу-иконке игры (ОС зависимая опция, может
работать некорректно в некоторых случаях)
- scr.gfx.mode = режим расположения (строка fixed, embedded или
float). Задает режим изображения. embedded -- картинка является
частью содержимого главного окна, параметры scr.gfx.x, scr.gfx.y,
scr.gfx.w игнорируются. float -- картинка расположена по указанным
координатам (scr.gfx.x, scr.gfx.y) и масштабируется к размеру
scr.gfx.w x scr.gfx.h если превышает его. fixed -- картинка является
частью сцены как в режиме embedded, но не скроллируется вместе с
текстом а расположена непосредственно над ним. Доступны модификации
режима float с модификаторами 'left/right/center/middle/bottom/top',
указывающими как именно размещать картинку в области
scr.gfx. Например: float-top-left;
- win.scroll.mode = [0|1|2|3] режим прокрутки области сцены. 0 - нет
автоматической прокрутки, 1 - прокрутка на изменение в тексте, 2
прокрутка на изменение, только если изменение не видно, 3 - всегда
в конец;
- win.x, win.y, win.w, win.h = координаты, ширина и высота главного
окна. Области в которой располагается описание сцены (числа)
- win.fnt.name = путь к файлу-шрифту (строка). Здесь и далее, шрифт
может содержать описание всех начертаний, например:
{sans,sans-b,sans-i,sans-bi}.ttf (заданы начертания для regular,
bold, italic и bold-italic). Вы можете опускать какие-то начертания,
и движок сам сгенерирует их на основе обычного начертания, например:
{sans,,sans-i}.ttf (заданы только regular и italic);
- win.align = center/left/right/justify (выравнивание текста в окне
сцены);
- win.fnt.size = размер шрифта главного окна (размер)
- win.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
- win.gfx.up, win.gfx.down = пути к файлам-изображениям скорллеров
вверх/вниз для главного окна (строка)
- win.up.x, win.up.y, win.down.x, win.down.y = координаты скроллеров
(координата или -1)
- win.col.fg = цвет текста главного окна (цвет)
- win.col.link = цвет ссылок главного окна (цвет)
- win.col.alink = цвет активных ссылок главного окна (цвет)
- win.ways.mode = top/bottom (задать расположение списка переходов, по
умолчанию top -- сверху сцены)
- inv.x, inv.y, inv.w, inv.h = координаты, высота и ширина области
инвентаря. (числа)
- inv.mode = строка режима инвентаря (horizontal или vertical). В
горизонтальном режиме инвентаря в одной строке могут быть несколько
предметов. В вертикальном режиме, в каждой строке инвентаря содержится
только один предмет. (число) Существует модификации
(-left/right/center). Вы можете задать режим disabled если в вашей
игре не нужен инвентарь;
- inv.col.fg = цвет текста инвентаря (цвет)
- inv.col.link = цвет ссылок инвентаря (цвет)
- inv.col.alink = цвет активных ссылок инвентаря (цвет)
- inv.fnt.name = путь к файлу-шрифту инвентаря (строка)
- inv.fnt.size = размер шрифта инвентаря (размер)
- inv.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
- inv.gfx.up, inv.gfx.down = пути к файлам-изображениям скорллеров
вверх/вниз для инвентаря (строка)
- inv.up.x, inv.up.y, inv.down.x, inv.down.y = координаты скроллеров
(координата или -1)
- menu.col.bg = фон меню (цвет)
- menu.col.fg = цвет текста меню (цвет)
- menu.col.link = цвет ссылок меню (цвет)
- menu.col.alink = цвет активных ссылок меню (цвет)
- menu.col.alpha = прозрачность меню 0-255 (число)
- menu.col.border = цвет бордюра меню (цвет)
- menu.bw = толщина бордюра меню (число)
- menu.fnt.name = путь к файлу-шрифту меню (строка)
- menu.fnt.size = размер шрифта меню (размер)
- menu.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
- menu.gfx.button = путь к файлу изображению значка меню (строка)
- menu.button.x, menu.button.y = координаты кнопки меню (числа)
- snd.click = путь к звуковому файлу щелчка (строка)
- include = имя темы (последний компонент в пути каталога) (строка)
Кроме того, заголовок темы может включать в себя комментарии с
тегами. На данный момент существует только один тег: $Name:,
содержащий UTF-8 строку с именем темы. Например:
```
; $Name:Новая тема$
; модификация темы book
include = book -- использовать тему Книга
scr.gfx.h = 500 -- заменить в ней один параметр
```
> Интерпретатор выполняет поиск тем в каталоге themes. Unix версия
> кроме этого каталога, просматривает также каталог ~/.instead/themes/
> Windows версия -- Documents and Settings/USER/Local
> Settings/Application Data/instead/themes
Кроме этого, новые версии INSTEAD поддерживают механизм множественных
тем в одной игре. Давая возможность игроку через стандартное меню
INSTEAD выбрать подходящее оформление, из предусмотренных автором
игры. Для этого, все темы должны располагаться в игре в подкаталоге
themes. В свою очередь, каждая тема -- это подкаталог в каталоге
themes. В каждом таком подкаталоге должен находится свой файл
theme.ini и ресурсы темы (картинки, шрифты, звуки). При этом
обязательно наличие темы-каталога themes/default - эта тема будет
загружена по умолчанию. Формат файлов theme.ini мы только что
рассмотрели. Однако, пути к файлам с ресурсами в theme.ini пишутся не
относительно корневого каталога игры, а относительно текущего каталога
темы. Это означает, что обычно они содержат только имя самого файла,
без пути к каталогу. Например:
```
mygame/
themes/
default/
theme.ini
bg.png
widescreen/
theme.ini
main3.lua
```
theme.ini
```
scr.gfx.bg = bg.png
; ...
```
При этом, все игровые темы наследуются от темы
themes/default. Поддерживается механизм include. При этом, INSTEAD
сначала пытается найти одноименную тему игры, и если такой темы не
находится, будет загружена тема из стандартных тем INSTEAD (если она
существует). Далее, в theme.ini можно изменять только те параметры,
которые требуют изменения.
## Модуль theme
Модуль theme позволяет изменять параметры темы на лету.
> Имейте в виду, что изменение параметров темы на лету для игр,
> которые не содержат собственную тему -- источник потенциальных
> проблем! Дело в том, что ваша игра в таком случае должна быть готова
> работать с любыми разрешениями и параметрами тем, что крайне сложно
> добиться. Поэтому, если вы собираетесь менять параметры темы из кода
> -- создайте свою тему и включите ее в игру!
При этом, сохранение изменений темы в файле сохранения не
поддерживается. Автор игры должен сам восстановить параметры темы в
функции start(), как это делается при работе с модулем спрайтов.
Для изменения параметров действующей темы, используются следующие
функции:
```
-- настройка окна вывода
theme.win.geom(x, y, w, h)
theme.win.color(fg, link, alink)
theme.win.font(name, size, height)
theme.win.gfx.up(pic, x, y)
theme.win.gfx.down(pic, x, y)
-- настройка инвентаря
theme.inv.geom(x, y, w, h)
theme.inv.color(fg, link, alink)
theme.inv.font(name, size, height)
theme.inv.gfx.up(pic, x, y)
theme.inv.gfx.down(pic, x, y)
theme.inv.mode(mode)
-- настройка меню
theme.menu.bw(w)
theme.menu.color(fg, link, alink)
theme.menu.font(name, size, height)
theme.menu.gfx.button(pic, x, y)
-- настройка графики
theme.gfx.cursor(norm, use, x, y)
theme.gfx.mode(mode)
theme.gfx.pad(pad)
theme.gfx.bg(bg)
-- настройка звука
theme.snd.click(name);
```
Существует возможность чтения текущих параметров тем:
theme.get 'имя переменной темы';
Возвращаемое значение всегда в текстовой форме.
theme.set ('имя переменной темы', значение);
Вы можете сбросить значение параметра темы на то, которое было
установлено во встроенной теме игры:
```
theme.reset 'имя переменной';
theme.win.reset();
```
Существует функция, для того, чтобы узнать текущую выбранную тему.
```
theme.name()
```
Функция возвращает строку -- имя каталога темы. Если игра использует
собственный файл 'theme.ini', функция вернет точку. Это удобно, для
определения того, включен ли механизм собственных тем игр:
```
if theme.name() ~= '.' then
error "Please, enable own theme mode in menu!"
end
```
Если в игре используется механизм множественных тем, то имя темы
начинается с точки, например:
```
if theme.name() ~= '.default' then
-- наша встроенная тема default
elseif theme.name() ~= 'default' then
-- стандартная тема default в INSTEAD
end
```
Пример использования:
```
theme.gfx.bg "dramatic_bg.png";
theme.win.geom (0,0, theme.get 'scr.w', theme.get 'scr.h');
theme.inv.mode 'disabled'
```
## Модуль sprite
Модуль sprite позволяет работать с графическими изображениями.
Для включения модуля напишите:
require "sprite"
Спрайты не могут попасть в файл сохранения, поэтому восстановление
состояния спрайтов -- задача автора игры. Обычно, для этого
используются функции init() и/или start(). start() вызывается после
загрузки игры, поэтому в этой функции вы можете использовать
переменные игры.
На самом деле в модуле спрайт реализованы два модуля: спрайты и
пиксели. Но так как они используются совместно, они размещены в одном
модуле. Начнем со спрайтов:
### Спрайты
Для создания спрайта используйте метод sprite.new, например:
```
declare 'my_spr' (sprite.new 'gfx/bird.png')
local heart = sprite.new 'heart.png'
local blank = sprite.new (320, 200) -- пустой спрайт 320x200
```
У созданного спрайтового объекта существуют следующие методы:
- :alpha(alpha) - создает новый спрайт с заданной прозрачностью alpha
(255 - не прозрачно);
- :dup(spr) - создает копию спрайта;
- :scale(spr, xs, ys, [smooth]) -- масштабирует спрайт, для отражений
используйте масштаб -1.0 (медленно! не для реального времени). Создает
новый спрайт.
- :rotate(angle, [smooth]) -- поворот спрайта на заданный угол в
градусах (медленно! не для реального времени). Создает новый спрайт.
- :size() -- Возвращает ширину и высоту спрайта в пикселях.
- :draw(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Рисование
области src спрайта в область dst спрайта (задание alpha сильно
замедляет выполнение функции).
- :draw(dst_spr, x, y, [alpha]) -- Рисование спрайта, укороченный
вариант; (задание alpha сильно замедляет выполнение функции).
- :copy(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Копирование
прямоугольника fw-на-fh из спрайта в спрайт
dst\_spr по координатам [x,y] (рисование - замещение). Существует
укороченный вариант (как :draw).
- :compose(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Рисование - с
учетом прозрачности обоих спрайтов). Существует укороченный вариант
(как :draw).
- :fill(x, y, w, h, [col]) -- Заполнение спрайта цветом.
- :fill([col]) -- Заполнение спрайта цветом.
- :pixel(x, y, col, [alpha]) -- Заполнение пикселя спрайта.
- :pixel(spr, x, y) -- Взятие пикселя спрайта (возвращает четыре
компонента цвета).
- :colorkey(spr, color) -- Задает в спрайте цвет, который выступает в
роли прозрачного фона. При этом, при последующем выполнении операции
:copy, из рассматриваемого спрайта будут скопированы только те
пиксели, цвет которых не совпадает с цветом прозрачного фона.
В качестве "цвета" методы получают строки вида: 'green', 'red',
'yellow' или '#333333', '#2d80ff'...
Пример:
```
local spr = sprite.new(320, 200)
spr:fill 'blue'
local spr2 = sprite.new 'fish.png'
spr2:draw(spr, 0, 0)
```
Кроме того, существует возможность работы с шрифтами. Шрифт создается
с помощью sprite.fnt(), например:
local font = sprite.fnt('sans.ttf', 32)
У созданного объекта определены следующие методы:
- :height() -- высота шрифта в пикселях;
- :text(text, col, [style]) -- создание спрайта из текста, col - здесь
и далее - цвет в текстовом формате (в формате '#rrggbb' или
'текстовое название цвета').
- :size(text) -- вычисляет размер, который будет занимать текстовый
спрайт, без создания спрайта.
Вам также может пригодиться функция:
sprite.font_scaled_size(size)
Она возвращает размер шрифта с учетом масштабирование, которое
выставил игрок в настройках INSTEAD. Если вы в своей игре хотите
учитывать такую настройку, используйте эту функцию для определения
размера шрифта.
Пример:
```
local f = sprite.fnt('sans.ttf', 32)
local spr = sprite.new('box:320x200,black')
f:text("HELLO!", 'white'):draw(spr, 0, 0)
```
Теперь, рассмотрим варианты применения модуля sprite.
### Функция pic
Функция pic может вернуть спрайт. Вы можете формировать каждый раз
новый спрайт (что будет не очень эффективно), или можете возвращать
заранее выделенный спрайт. Если в такой спрайт вносятся изменения, то
эти изменения будут отражены в следующем кадре игры. Так, меняя спрайт
по таймеру, можно делать анимацию:
```
require "sprite"
require "timer"
local spr = sprite.new(320, 200)
function game:timer()
local col = { 'red', 'green', 'blue'}
col = col[rnd(3)]
spr:fill(col)
return false
end
game.pic = spr
function start()
timer:set(30)
end
room {
nam = 'main';
decor = [[ГИПНОЗ!]];
}
```
### Подстановки
Вы можете создать свой системный объект - подстановку, и формировать
графику в выводе игры с помощью img, например:
```
require "sprite"
require "timer"
require "fmt"
obj {
nam = '$spr';
{
["квадрат"] = sprite.new 'box:32x32,red';
};
act = function(s, w)
return fmt.img(s[w])
end
}
room {
nam = 'main';
decor = [[Сейчас мы вставим спрайт: {$spr|квадрат}.]];
}
```
### direct режим
В INSTEAD существует режим прямого доступа к графике. В теме он
задается с помощью параметра:
scr.gfx.mode = direct
Этот параметр можно заранее выставить в theme.ini, или воспользоваться
модулем theme. Или, специальной функцией:
sprite.direct(true)
Если режим удалось включить -- функция вернет true. sprite.direct()
без параметра -- возвращает текущий режим (true -- если direct
включен.)
В этом режиме игра имеет прямой доступ ко всему окну и может выполнять
отрисовку в процедуре таймера. Экран представлен специальным спрайтом:
sprite.scr()
Например:
```
require "sprite"
require "timer"
require "theme"
sprite.direct(true)
local stars = {}
local w, h
local colors = {
"red",
"green",
"blue",
"white",
"yellow",
"cyan",
"gray",
"#002233",
}
function game:timer()
local scr = sprite.scr()
scr:fill 'black'
for i = 1, #stars do
local s = stars[i]
scr:pixel(s.x, s.y, colors[s.dy])
s.y = s.y + s.dy
if s.y >= h then
s.y = 0
s.x = rnd(w) - 1
s.dy = rnd(8)
end
end
end
function start()
w, h = theme.get 'scr.w', theme.get 'scr.h'
w = std.tonum(w)
h = std.tonum(h)
for i = 1, 100 do
table.insert(stars, { x = rnd(w) - 1, y = rnd(h) - 1, dy = rnd(8) })
end
timer:set(30)
end
```
Еще один пример:
```
require "timer"
require "sprite"
require "theme"
local spr = sprite
declare {
fnt = false, ball = false, ballw = 0,
ballh = 0, bg = false, line = false,
G = false, by = false, bv = false,
bx = false, t1 = false,
}
function init()
fnt = spr.fnt(theme.get 'win.fnt.name', 32);
ball = fnt:text("INSTEAD 3.0", 'white', 1);
ballw, ballh = ball:size();
bg = spr.new 'box:640x480,black';
line = spr.new 'box:320x8,lightblue';
spr.direct(true)
end
function start()
timer:set(20)
G = 9.81
by = -ballh
bv = 0
bx = 320
t1 = instead.ticks()
end
function phys()
local t = timer:get() / 1000;
bv = bv + G * t;
by = by + bv * t;
if by > 400 then
bv = - bv
end
end
function game:timer(s)
local i
for i = 1, 10 do
phys()
end
if instead.ticks() - t1 >= 20 then
bg:copy(spr.scr(), 0, 0);
ball:draw(spr.scr(), (640 - ballw) / 2, by - ballh/2);
line:draw(spr.scr(), 320/2, 400 + ballh / 2);
t1 = instead.ticks()
end
end
```
### Использование sprite совместно с модулем theme
В функции start и в обработчиках вы можете менять параметры темы, в
том числе, используя в качестве графики спрайты, например:
```
require "sprite"
require "theme"
function start() -- заменим фон на спрайт
local spr = sprite.new(800, 600)
spr:fill 'blue'
spr:fill (100, 100, 32, 60, 'red')
theme.set('scr.gfx.bg', spr)
end
```
Используя эту технику, вы можете наносить на фоновое изображение
статусы, элементы управления или просто менять подложку.
### Пиксели
Модуль спрайтов поддерживает также работу с пиксельной графикой. Вы
можете создавать объекты -- наборы пикселей, модифицировать их и
рисовать в спрайты.
Создание пикселей осуществляется функцией pixels.new().
Примеры:
local p1 = pixels.new(320, 200) -- создали пиксели 320x200
local p2 = pixels.new 'gfx/apple.png' -- создали пиксели из
-- изображения
local p3 = pixels.new(320, 200, 2) -- создали пиксели 320x200,
-- которые при отрисовке их в спрайт -- будут смасштабированы до
-- 640x400
Объект пиксели имеет следующие методы:
> при описании использованы обозначения: r, g, b, a --
> компоненты пикселя: красный, зеленый, синий, и прозрачность. Все
> значения от 0 до 255). x, y - координаты левого верхнего угла, w, h
> -- ширина и высота области.
- :size() -- вернуть размер и масштаб (как 3 значения);
- :clear(r, g, b, [a]) -- быстрая очистка пикселей;
- :fill(r, g, b, [a]) -- заливка (с учетом прозрачности);
- :fill(x, y, w, h, r, g, b, [a]) -- заливка области (с учетом прозрачности);
- :val(x, y, r, g, b, a) - задать значение пикселя
- :val(x, y) -- получить компоненты r, g, b, a
- :pixel(x, y, r, g, b, a) -- нарисовать пиксель (с учетом
прозрачности существующего пикселя);
- :line(x1, y1, x2, y2, r, g, b, a) -- линия;
- :lineAA(x1, y1, x2, y2, r, g, b, a) -- линия с AA;
- :circle(x, y, radius, r, g, b, a) -- окружность;
- :circleAA(x, y, radius, r, g, b, a) -- окружность с AA;
- :poly({x1, y1, x2, y2, ...}, r, g, b, a) -- полигон;
- :polyAA({x1, y1, x2, y2, ...}, r, g, b, a) -- полигон с AA;
- :blend(x1, y1, w1, h1, pixels2, x, y) -- рисовать область пикселей в
другой объект пиксели, полная форма;
- :blend(pixels2, x, y) -- короткая форма;
- :fill\_circle(x, y, radius, r, g, b, a) -- залитый круг;
- :fill\_triangle(x1, y1, x2, y2, x3, y3, r, g, b, a) -- залитый
треугольник;
- :fill\_poly({x1, y1, x2, y2, ...}, r, g, b, a) -- залитый полигон;
- :copy(...) -- как blend, но не рисовать, а копировать (быстро);
- :scale(xscale, yscale, [smooth]) -- масштабирование в новый объект
pixels;
- :rotate(angle, [smooth]) -- поворот в новый объект pixels;
- :draw_spr(...) -- как draw, но в спрайт, а не пиксели;
- :copy_spr(...) -- как copy, но в спрайт, а не пиксели;
- :compose_spr(...) -- то же самое, но в режиме compose;
- :dup() -- создать копию пикселей;
- :sprite() -- создать спрайт из пикселей.
Также, есть возможность работы со шрифтами:
- pixels.fnt(fnt(шрифт.ttf, размер) -- создать шрифт;
При этом, у созданного объекта "шрифт" существует метод text:
- :text(текст, цвет(как в спрайтах), стиль) -- создать пиксели с текстом;
Например:
```
local fnt = pixels.fnt("sans.ttf", 64)
local t = fnt:text("HELLO, INSTEAD!", 'black')
pxl:copy_spr(sprite.scr())
pxl2:draw_spr(sprite.scr(), 100, 200);
t:draw_spr(sprite.scr(), 200, 400)
```
Еще один пример (автор примера, Андрей Лобанов):
```
require "sprite"
require "timer"
sprite.direct(true)
declare 'pxl' (false)
declare 't' (0)
function game:timer()
local x, y, i
t = t + 1
for x = 0, 199 do
for y = 0, 149 do
i = (x * x + y * y + t)
pxl:val(x, y, 0, i, i / 2)
end
end
pxl:copy_spr(sprite.scr())
end
function start(load)
pxl = pixels.new(200, 150, 4)
timer:set(20)
end
```
При процедурной генерации с помощью pixels удобно использовать шумы
Перлина. В INSTEAD существуют функции:
- instead.noise1(x) - 1D шум Перлина;
- instead.noise2(x, y) - 2D шум Перлина;
- instead.noise3(x, y, z) - 3D шум Перлина;
- instead.noise4(x, y, z, w) - 4D шум Перлина;
Все эти функции возвращают значение в диапазоне [-1; 1] а на вход
получают координаты с плавающей точкой.
## Модуль snd
Мы уже рассматривали базовые возможности по работе со звуком. Модуль
snd имеет еще некоторые функции по работе со звуком.
Вы можете подгрузить звук и держать его в памяти до тех пор, пока он
вам нужен.
```
require 'snd'
local wav = snd.new 'bark.ogg'
```
Кроме подгрузки файлов, вы можете загрузить звук из массива lua:
```
local wav = {}
for i = 1, 10000 do
table.insert(wav, rnd() * 2 - 1) -- случайные значения от -1 до 1
end
function start()
local p = snd.new(22050, 1, wave) -- частота, число каналов и звук
p:play()
end
```
Звук задается в нормированном формате: [-1 .. 1]
Звук можно проиграть методом :play([chan], [loop]), где chan -- канал
(0 - 7), loop - циклы (0 - бесконечность).
Остальные функции модуля:
- snd.stop([channel]) – остановить проигрывание выбранного канала или
всех каналов. Вторым параметром можно задавать время затухания звука
в мс. при его приглушении;
- snd.playing([channel]) – узнать проигрывается ли звук на любом
канале или на выбранном канале; если выбран конкретный канал,
функция вернет хандл проигрываемого в данный момент звука или
nil. Внимание! Звук клика не учитывается и обычно занимает 0 канал;
- snd.pan(chan, l, r) – задание паннинга. Канал, громкость
левого[0-255], громкость правого[0-255] каналов. Необходимо вызывать
перед проигрыванием звука, чтобы имело эффект;
- snd.vol(vol) – задание громкости звука (и музыки и эффектов) от 0 до 127.
Еще одна интересная возможность -- генерирование звука на лету (пока
находится в экспериментальном статусе):
```
require "snd"
function cb(hz, len, data)
for i = 1, len do
data[i] = rnd() * 2 - 1
end
end
function start()
snd.music_callback(cb)
end
```
## Модуль prefs
Этот модуль позволяет сохранять настройки игры. Другими словами,
сохраненная информация не зависит от состояния игры. Такой механизм
можно использовать, например, для реализации системы достижений или
счетчика количества прохождений игры.
По своей сути prefs это объект, все переменные которого будут
сохранены.
Cохранить настройки:
```
prefs:store()
```
Настройки сохраняются автоматически при сохранении игры, но вы можете
контролировать этот процесс, вызывая prefs:store().
Уничтожить файл с настройками:
```
prefs:purge()
```
Загрузка настроек выполняется автоматически при запуске игры
(перед вызовом функции start()), но вы можете инициировать загрузку и
вручную:
```
prefs:load()
```
Пример использования:
```
-- $Name: Тест модуля prefs$
-- $Version: 0.1$
-- $Author: instead$
-- подключаем модуль click
require "click"
-- подключаем модуль prefs
require "prefs"
-- устанавливаем начальное значение счетчика
prefs.counter = 0;
-- определяем функцию отслеживания количества "кликов"
game.onclick = function(s)
-- увеличиваем счетчик
prefs.counter = prefs.counter + 1;
-- сохраняем счетчик
prefs:store();
-- выводим сообщение
p("На данный момент сделано ", prefs.counter ," кликов");
end;
-- добавляем изображение, по которому можно производить клики
game.pic = 'box:320x200,black';
room {
nam = 'main',
title = "Комната кликов",
-- делаем фиксацию статичной части описания
-- добавляем описание для сцены
decor = [[ Этот тест был написан специально
для проверки работы модуля <>.
]];
};
```
Обратите внимание, что после запуска игры заново, число
выполненных кликов не обнулится!
## Методы объектов
У всех объектов STEAD3 существуют методы, которые используются при
реализации стандартной библиотеке и, обычно, не используются автором
игры напрямую. Однако, иногда полезно знать состав этих методов, хотя
бы для того, чтобы не называть свои переменные и методы именами уже
существующих методов. Ниже представлен список методов с кратким
описанием.
### Объект (obj)
- :with({...}) - задание списка obj;
- :new(...) - конструктор;
- :actions(тип, [значение]) - задать/прочитать число событий объекта
заданного типа;
- :inroom([{}]) - в какой комнате (комнатах) находится объект;
- :where([{}]) - в каком объекте (объектах) находится объект;
- :purge() - удалить объект из всех списков;
- :remove() - удалить объект из всех объектов/комнат/инвентаря;
- :close()/:open() - закрыть/открыть;
- :disable()/:enable() - выключить/включить;
- :closed() -- вернет true, если закрыт;
- :disabled() -- вернет true, если выключен;
- :empty() -- вернет true, если пуст;
- :save(fp, n) -- функция сохранения;
- :display() -- функция отображения в сцене;
- :visible() -- вернет true если считается видимым;
- :srch(w) -- поиск видимого объекта;
- :lookup(w) -- поиск любого объекта;
- :for_each(fn, ...) -- итератор по объектам;
- :lifeon()/:lifeoff() -- добавить/удалить из списка живых;
- :live() -- вернет true, если в списке живых.
### Комната (room)
Кроме методов obj, добавлены следующие методы:
- :from() -- откуда пришли в комнату;
- :visited() -- была ли комната посещена ранее?;
- :visits() -- число визитов (0 -- если не было);
- :scene() -- отображение сцены (не объектов);
- :display() -- отображение объектов сцены;
### Диалоги (dlg)
Кроме методов room, добавлены следующие методы:
- :push(фраза) - перейти к фразе с запоминанием ее в стеке;
- :reset(фраза) -- то же самое, но со сбросом стека;
- :pop([фраза]) -- возврат по стеку;
- :select([фраза]) -- выбор текущей фразы;
- :ph\_display() -- отображение выбранной фразы;
### Игровой мир (объект game)
Кроме методов obj, добавлены следующие методы:
- :time([v]) -- установить/взять число игровых тактов;
- :lifeon([v])/:lifeoff([v]) -- добавить/удалить объект из списка
живых, или включить/выключить живой список глобально (если не задан
аргумент);
- :live([v]) -- проверить активность живого объекта;
- :set\_pl(pl) -- переключить игрока;
- :life() -- итерация живых объектов;
- :step() -- такт игры;
- :lastdisp([v]) -- установить/взять последний вывод;
- :display(state) -- отобразить вывод;
- :lastreact([v]) -- установить/взять последнюю реакцию;
- :reaction([v]) -- установить/взять текущую реакцию;
- :events(pre, bg) -- установить/взять события живых объектов;
- :cmd(cmd) -- выполнение команды INSTEAD;
### Игрок (player)
Кроме методов obj, добавлены следующие методы:
- :moved() -- игрок сделал перемещение в текущем такте игры;
- :need_scene([v]) -- нужна отрисовка сцены в данном такте;
- :inspect(w) -- найти объект (видимый) в текущей сцене или себе
самом;
- :have(w) -- поиск в инвентаре;
- :useit(w) -- использовать предмет;
- :useon(w, ww) -- использовать предмет на предмет;
- :call(m, ...) -- вызов метода игрока;
- :action(w) -- действие на предмет (act);
- :inventory() -- вернуть инвентарь (список, по умолчанию это obj);
- :take(w) -- взять объект;
- :walk/walkin/walkout -- переходы;
- :go(w) -- команда идти (проверяет доступность переходов);
## Послесловие
Вот и все, здесь документация заканчивается. Но, возможно, начинается
самое интересное -- ваша история!
Я сделал первую версию INSTEAD в 2009 году. В тот момент я никогда бы
не подумал, что моя игрушка (и движок) переживут столько
изменений. Сейчас, когда я пишу это послесловие, на дворе 2017 год и
текстовые приключения все еще существуют. Правда, их влияние на
культуру по прежнему минимально. И хороших приключений -- по прежнему
очень мало.
На мой взгляд, у текстографических игр огромный потенциал. Они не
такие интерактивные, они не отбирают вашу жизнь взамен на вечно
неудовлетворенное желание, не вынуждают вас сутками просиживать за
монитором в раздражении или нездоровой нервозности... Они могут взять
лучшее из мира литературы и компьютерных игр. И то, что жанр по
большей части некоммерческий -- даже плюс.
История INSTEAD, на мой взгляд, хорошее тому подтверждение. Было
выпущено множество игр, которые можно с уверенностью назвать
отличными! Их авторы могут отойти от дел, но созданные ими
произведения уже живут своей жизнью, отражаясь в сознании людей,
которые в них играют или помнят. Пусть "тираж" этих игр не так велик,
но то что я увидел, полностью "оправдало" все потраченные усилия на
движок. Я знаю, что это время потрачено не зря. Так что я нашел в себе
силы, и сделал движок еще лучше, выпустив STEAD3. Я надеюсь, он
понравится и вам.
Так что если вы дочитали до этого места, я могу только пожелать вам
дописать вашу первую историю. Творчество -- это и есть свобода. :)
Спасибо и удачи. Петр Косых, март 2017.
instead-3.1.2/doc/stead3.tex 000644 001751 001751 00000002620 13146577473 016141 0 ustar 00peter peter 000000 000000 \documentclass[12pt,]{book}
\usepackage{cmap}
\usepackage[T2A]{fontenc}
\usepackage[english,russian]{babel}
\usepackage{indentfirst}
\usepackage[russian]{datetime}
\usepackage{upquote}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
% \usepackage[utf8]{inputenc}
\else % if luatex or xelatex
\usepackage{fontspec}
\ifxetex
\usepackage{xltxtra,xunicode}
\fi
\defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
\newcommand{\euro}{€}
\setmainfont{Verdana}
\setsansfont{Verdana}
\setmonofont{Liberation Mono}
\fi
\ifxetex
\usepackage[setpagesize=false, % page size defined by xetex
unicode=false, % unicode breaks when used with xetex
xetex]{hyperref}
\else
\usepackage[unicode=true]{hyperref}
\fi
\hypersetup{breaklinks=true,
bookmarks=true,
pdfauthor={},
pdftitle={ru:gamedev:documentation},
colorlinks=true,
urlcolor=blue,
linkcolor=magenta,
pdfborder={0 0 0}}
\usepackage{tocstyle}
\setcounter{tocdepth}{3}
\setcounter{secnumdepth}{3}
\title{STEAD3}
\author{Петр Косых}
\date{20.08.2017}
\sloppy
\begin{document}
\maketitle
{
\hypersetup{linkcolor=black}
\renewcommand{\contentsname}{Оглавление}
\tableofcontents
\clearpage
}
\input{stead3-manual.tex}
\end{document}
instead-3.1.2/doc/template.tex 000644 001751 001751 00000011707 13146577473 016577 0 ustar 00peter peter 000000 000000 \documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$lang$,$endif$]{$documentclass$}
\usepackage[utf8]{inputenc}
\usepackage{cmap}
\usepackage[T2A]{fontenc}
\usepackage[english,russian]{babel}
\usepackage{indentfirst}
\usepackage{geometry}
\usepackage{longtable}
\usepackage[russian]{datetime}
\usepackage{upquote}
\usepackage{framed}
\usepackage{color}
\definecolor{shadecolor}{gray}{0.9}
% \geometry{verbose,tmargin=1cm,bmargin=1cm,lmargin=1cm,rmargin=1cm,headheight=1cm,headsep=1cm,footskip=0.7cm}
\geometry{verbose,tmargin=2cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm,headheight=1cm,headsep=1cm,footskip=0.7cm}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
% use microtype if available
\IfFileExists{microtype.sty}{\usepackage{microtype}}{}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
\usepackage[utf8]{inputenc}
$if(euro)$
\usepackage{eurosym}
$endif$
\else % if luatex or xelatex
\usepackage{fontspec}
\ifxetex
\usepackage{xltxtra,xunicode}
\fi
\defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
\newcommand{\euro}{€}
$if(mainfont)$
\setmainfont{$mainfont$}
$endif$
$if(sansfont)$
\setsansfont{$sansfont$}
$endif$
$if(monofont)$
\setmonofont{$monofont$}
$endif$
$if(mathfont)$
\setmathfont{$mathfont$}
$endif$
\fi
$if(geometry)$
\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry}
$endif$
$if(natbib)$
\usepackage{natbib}
\bibliographystyle{plainnat}
$endif$
$if(biblatex)$
\usepackage{biblatex}
$if(biblio-files)$
\bibliography{$biblio-files$}
$endif$
$endif$
$if(listings)$
\usepackage{listings}
$endif$
$if(lhs)$
\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{}
$endif$
$if(highlighting-macros)$
$highlighting-macros$
$endif$
$if(verbatim-in-note)$
\usepackage{fancyvrb}
$endif$
$if(fancy-enums)$
% Redefine labelwidth for lists; otherwise, the enumerate package will cause
% markers to extend beyond the left margin.
\makeatletter\AtBeginDocument{%
\renewcommand{\@listi}
{\setlength{\labelwidth}{4em}}
}\makeatother
\usepackage{enumerate}
$endif$
$if(tables)$
\usepackage{ctable}
\usepackage{float} % provides the H option for float placement
$endif$
$if(graphics)$
\usepackage{graphicx}
% We will generate all images so they have a width \maxwidth. This means
% that they will get their normal width if they fit onto the page, but
% are scaled down if they would overflow the margins.
\makeatletter
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth
\else\Gin@nat@width\fi}
\makeatother
\let\Oldincludegraphics\includegraphics
\renewcommand{\includegraphics}[1]{\Oldincludegraphics[width=\maxwidth]{#1}}
$endif$
\ifxetex
\usepackage[setpagesize=false, % page size defined by xetex
unicode=false, % unicode breaks when used with xetex
xetex]{hyperref}
\else
\usepackage[unicode=true]{hyperref}
\fi
\hypersetup{breaklinks=true,
bookmarks=true,
pdfauthor={$author-meta$},
pdftitle={$title-meta$},
colorlinks=true,
urlcolor=$if(urlcolor)$$urlcolor$$else$blue$endif$,
linkcolor=$if(linkcolor)$$linkcolor$$else$magenta$endif$,
pdfborder={0 0 0}}
$if(links-as-notes)$
% Make links footnotes instead of hotlinks:
\renewcommand{\href}[2]{#2\footnote{\url{#1}}}
$endif$
$if(strikeout)$
\usepackage[normalem]{ulem}
% avoid problems with \sout in headers with hyperref:
\pdfstringdefDisableCommands{\renewcommand{\sout}{}}
$endif$
% \setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}
\setlength{\emergencystretch}{3em} % prevent overfull lines
$if(numbersections)$
$else$
\setcounter{secnumdepth}{0}
$endif$
$if(verbatim-in-note)$
\VerbatimFootnotes % allows verbatim text in footnotes
$endif$
$if(lang)$
\ifxetex
\usepackage{polyglossia}
\setmainlanguage{$mainlang$}
\else
\usepackage[$lang$]{babel}
\fi
$endif$
$for(header-includes)$
$header-includes$
$endfor$
\newenvironment{fshaded}{%
\def\FrameCommand{\fcolorbox{framecolor}{shadecolor}}%
\MakeFramed {\FrameRestore}}%
{\endMakeFramed}
\renewenvironment{Shaded}{\definecolor{shadecolor}{gray}{0.9}\definecolor{framecolor}{gray}{0.5}\begin{shaded}\noindent}{\end{shaded}}
% \newenvironment{Info}{\definecolor{shadecolor}{rgb}{0.9,0.9,1}\definecolor{framecolor}{rgb}{.1,.0,.7}\begin{fshaded}\noindent}{\end{fshaded}}
$if(title)$
\title{$title$}
$endif$
\author{$for(author)$$author$$sep$ \and $endfor$}
\date{}
\sloppy
\begin{document}
$if(title)$
\maketitle
$endif$
$for(include-before)$
$include-before$
$endfor$
$if(toc)$
{
\hypersetup{linkcolor=black}
\renewcommand{\contentsname}{Оглавление}
\tableofcontents
\clearpage
}
$endif$
$body$
$if(natbib)$
$if(biblio-files)$
$if(biblio-title)$
$if(book-class)$
\renewcommand\bibname{$biblio-title$}
$else$
\renewcommand\refname{$biblio-title$}
$endif$
$endif$
\bibliography{$biblio-files$}
$endif$
$endif$
$if(biblatex)$
\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$
$endif$
$for(include-after)$
$include-after$
$endfor$
\end{document}
instead-3.1.2/doc/stead2.html 000644 001751 001751 00001416613 13146577477 016324 0 ustar 00peter peter 000000 000000
ru:gamedev:documentation
Код игр под INSTEAD пишется на lua (5.1 или 5.2), поэтому, знание этого языка полезно, хотя и не необходимо. Ядро движка также написано на lua, поэтому знание lua может быть полезным для углубленного понимания принципов его работы, конечно, при условии, если вам интересно этим заниматься.
За время своего развития, INSTEAD получил множество новых функций. Теперь с его помощью можно делать игры разных жанров (от аркад, до игр с текстовым вводом). А также, в INSTEAD можно запускать игры, написанные на некоторых других движках, но основой INSTEAD остается первоначальное ядро, которое ориентировано на создание текстографических приключенческих игр. В данной документации описана именно эта базовая часть, изучение которой необходимо даже в том случае, если вы хотите написать что-то другое… Начните свое изучение INSTEAD с написания простой игры!
Когда мы говорим «текстовое приключение» у большинства людей возникает один из двух привычных образов. Это либо текст с кнопками действий, например:
Вы видите перед собой стол. На столе лежит яблоко. Что делать?
1) Взять яблоко
2) Отойти от стола
Или, гораздо реже, это классические игры с текстовым вводом, где для управления игрой необходимо было вводить действия с клавиатуры.
Вы на кухне. Тут есть стол.
> осмотреть стол.
На столе есть яблоко.
У обоих подходов есть свои преимущества и недостатки.
Если говорить про первый подход, то он близок к жанру книг-игр и удобен больше для литературных текстов, которые описывают события, происходящие с главным героем, и не очень удобен для создания классических квестов, где главный герой исследует смоделированный в игре мир, свободно перемещаясь по нему и взаимодействуя с объектами этого мира.
Второй подход моделирует мир, но требует значительных усилий от автора игры, и, что более важно, более подготовленного игрока. Особенно, когда мы имеем дело с русским языком.
Проект INSTEAD был создан для написания другого типа игр, которые совмещают преимущества обоих подходов, одновременно пытаясь избежать их недостатков.
Мир игры на INSTEAD моделируется как при втором подходе, то есть в игре есть места (сцены) которые может посещать главный герой и объекты, с которыми он взаимодействует (включая живых персонажей). Игрок свободно изучает мир и манипулирует объектами. Причем, действия с объектами не прописаны в виде явных пунктов меню, а скорее напоминают классические графические квесты в стиле 90-х.
На самом деле, в INSTEAD есть множество незаметных на первый взгляд вещей, которые направлены на развитие выбранного подхода, и который делает процесс игры максимально динамичным и непохожим на привычные «текстовые квесты». Это подтверждается в том числе и тем, что на движке было выпущено множество замечательных игр, интерес к которым проявляют не только любители текстовых игр как таковых, но и люди не знакомые с данным жанром.
Перед изучением данного руководства, я рекомендую поиграть в классические игры INSTEAD, чтобы понять о чем идет речь. С другой стороны, раз вы здесь, то наверное вы уже сделали это.
Правда, не стоит пока изучать код этих игр, так как старые игры очень часто написаны неоптимально,
с использованием устаревших конструкций. Текущая версия INSTEAD позволяет реализовывать код
лаконичнее, проще и понятнее. Об этом и рассказывается в данном документе.
Главное окно игры содержит информацию о статической и динамической части сцены, активные события и картинку сцены с возможными переходами в другие сцены (в графическом интерпретаторе).
Статическая часть сцены отображается только один раз, при показе сцены, или при повторении команды look (в графическом интерпретаторе – клик на названии сцены).
Динамическая часть сцены составлена из описаний объектов сцены, она отображается всегда.
Игроку доступны объекты, доступные на любой сцене – инвентарь. Игрок может взаимодействовать с объектами инвентаря и действовать объектами инвентаря на другие объекты сцены или инвентаря.
Следует отметить, что понятие инвентаря является условным. Например, в «инвентаре» могут находиться такие объекты как «открыть», «осмотреть», «использовать» и т.д.
Действиями игрока могут быть:
осмотр сцены;
действие на объект сцены;
действие на объект инвентаря;
действие объектом инвентаря на объект сцены;
действие объектом инвентаря на объект инвентаря;
действие объектом сцены на объект сцены (режим scene_use);
действие объектом сцены на инвентарь (режим scene_use);
переход в другую сцену.
Как создавать игру
Игра представляет из себя каталог, в котором должен находиться скрипт (текстовый файл) main.lua. Другие ресурсы игры (скрипты на lua, графика и музыка) должны находиться в рамках этого каталога. Все ссылки на ресурсы делаются относительно текущего каталога – каталога игры.
В начале файла main.lua может быть определен заголовок, состоящий из тегов (строк специального вида). Теги должны начинаться с символов --: комментарий с точки зрения lua. На данный момент существует три тега.
Тег $Name: содержит название игры в кодировке UTF-8. Пример использования тега:
Если вы разрабатываете игру в Windows, то убедитесь, что ваш редактор поддерживает кодировку UTF-8 без BOM. Именно эту кодировку следует использовать при написании игры!
Сразу после заголовков вам необходимо указать версию STEAD API, которая требуется игре. На данный момент последняя версия 2.4.0.
instead_version "2.4.0"
Важно!
Если instead_version отсутствует, то STEAD API будет работать в режиме совместимости (устаревшее API).
Дело в том, что начиная с версии 1.2.0, в INSTEAD для автора появились новые возможности. При этом в
движок была заложена идея постепенного развития API.
Как это работает?
Если вы пишете в начале игры:
instead_version "версия api"
То вы даете знать движку, что ваш код написан для INSTEAD >= 1.2.0
и при этом вам доступна та версия API, которую вы вписали. Например:
instead_version "1.8.1"
Сейчас версии API совпадают с версиями INSTEAD.
Но есть уже много игр, написанных до версии INSTEAD 1.2.0, и они работают на новых версиях INSTEAD. В этих играх нет instead_version и движок понимает, что игра написана для старой версии API.
Новое API дает множество преимуществ автору (снапшоты, глобальные переменные, изменяемые на лету функции, автоматическое форматирование, модули и пр..), а перенос старого кода на новое API тривиален. Поэтому нет большого смысла писать новые игры на старом API, что распространено из-за того, что для изучения INSTEAD были выбраны старые игры.
Важно!
Поэтому, не изучайте код игр прежде чтения этого руководства!
Далее, обычно следует указать модули, которые требуются игре. О модулях будет рассказано отдельно.
require"para"-- красивые отступы;require"dash"-- замена символов два минуса на тире;require"quotes"-- замена простых кавычек "" на типографские «»;
Кроме того, обычно стоит определить обработчики по-умолчанию: game.act, game.use, game.inv, о которых также будет рассказано ниже.
game.act ='Не работает.';
game.use ='Это не поможет.';
game.inv ='Зачем мне это?';
Инициализацию игры следует описывать в функции init, которая вызывается движком в самом начале.
В этой функции удобно инициализировать состояние игрока на начало игры, или какие-то другие действия, нужные для первоначальной настройки мира игры. Впрочем, функция init может быть и не нужна.
function init()-- добавим в инвентарь нож и бумагу
take(knife);
take(paper);end
Графический интерпретатор ищет доступные игры в каталоге games. Unix-версия интерпретатора кроме этого каталога просматривает также игры в каталоге ~/.instead/games.
Windows-версия: Documents and Settings/USER/Local Settings/Application Data/instead/games.
В Windows- и standalone-Unix-версии игры ищутся в каталоге ./appdata/games, если он существует.
В некоторых сборках INSTEAD (в Windows, в Linux если проект собран с gtk и др.) можно открывать игру по любому пути из меню «Выбор игры». Либо, нажать f4. Если в каталоге с играми присутствует только одна игра, INSTEAD запустит ее автоматически, это удобно, если вы хотите распространять свою игру вместе с движком.
Таким образом, вы кладете игру в свой каталог и запускаете INSTEAD.
Важно!
При написании игры, настоятельно рекомендуется использовать отступы для оформления кода игры, как это сделано в примере из данного руководства, этим самым вы сократите количество ошибок и сделаете свой код наглядней!
Ниже приводится минимальный шаблон для вашей первой игры:
-- $Name: Моя первая игра$-- $Version: 0.1$-- $Author: Анонимный автор$
instead_version "1.9.1"require"para"-- для оформленияrequire"dash"require"quotes"require"dbg"-- для отладки
game.act ='Гм...';
game.use ='Не сработает.';
game.inv ='Зачем это мне?';function init()-- инициализация, если она нужнаend
Основы отладки
Во время отладки (проверки работоспособности вашей игры) удобно, чтобы INSTEAD был запущен с параметром -debug, тогда в случае ошибок будет показана более подробная информация о проблеме в виде стека вызовов. Параметр -debug можно задать в ярлыке (если вы работаете в Windows), а для других систем, я думаю вы и так знаете как передавать параметры командной строки.
При отладке игры обычно нужно часто сохранять игру и загружать состояние игры. Вы можете использовать стандартный механизм сохранений через меню (или по клавишам f2/f3), или воспользоваться быстрым сохранением/загрузкой (клавиши f8/f9).
В режиме -debug вы можете перезапускать игру клавишами alt-r. В комбинации с f8/f9 это дает возможность быстро посмотреть изменения в игре после ее правки.
В режиме -debug Windows-версия INSTEAD создает консольное окно (в Unix версии, если вы запускаете INSTEAD из консоли, вывод будет направлен в нее) в которое будет осуществляться вывод ошибок. Кроме того, используя функцию print() вы сможете порождать свои сообщения с отладочным выводом. Например:
act =function(s)print("Act is here! "..stead.deref(s));...end;
Не пугайтесь, когда вы прочитаете все руководство и начнете писать свою игру, вы, скорее всего, взглянете на этот пример с большим воодушевлением.
Также удобно воспользоваться модулем отладчика, для этого после записи instead_version добавьте:
require"dbg"
Отладчик доступен по клавише f7.
Во время отладки бывает удобно изучать файлы сохранений, которые содержат состояние переменных игры. Чтобы не искать каждый раз файлы сохранений, создайте каталог saves в директории с вашей игрой (в том каталоге, где содержится main.lua) и игра будет сохраняться в saves. Этот механизм также будет удобен для переноса игры на другие компьютеры.
Возможно (особенно, если вы пользуетесь Unix системами) вам понравится идея проверки синтаксиса ваших скриптов через запуск компилятора luac. В Windows это тоже возможно, нужно только установить выполняемые файлы lua для Windows (http://luabinaries.sourceforge.net)/ и воспользоваться luac52.exe.
Вы можете проверить синтаксис и с помощью INSTEAD (для версий не меньше 1.9.2), для этого воспользуйтесь параметром -luac:
sdl-instead -debug-luac <пусть к скрипту.lua>
1. Сцена
Сцена (или комната) – это единица игры, в рамках которой игрок может изучать все объекты сцены и взаимодействовать с ними. Например, сценой может быть комната, в которой находится герой. Или участок леса, доступный для наблюдения.
В любой игре должна быть сцена с именем main. Именно с нее начнется и ваша игра!
main = room {
nam ="Главная комната";
dsc =[[Вы в большой комнате.]];}
Запись означает создание объекта (так как почти все сущности в INSTEAD это объекты) main типа room (комната). Текстовый идентификатор main, по которому можно обращаться к комнате, будем в дальнейшем называть идентификатором комнаты.
У каждого объекта игры есть атрибуты и обработчики событий. В данном примере есть два атрибута: nam и dsc. Атрибуты разделяются разделителем (в данном примере – символом точка с запятой ;).
Обычно, атрибуты могут быть текстовыми строками, функциями-обработчиками и булевыми значениями.
Например, обязательный атрибут nam для сцены это то, что будет заголовком сцены при ее отображении. Имя сцены также используется для ее идентификации при переходах. Вообще, nam это обязательный атрибут любого объекта. Часто вы можете работать с объектом по его имени, а не по идентификатору.
Атрибут dsc – это описание статической части сцены, которое выводится один раз при входе в сцену или при явном осмотре сцены.
Вы можете использовать символ , вместо ; для разделения атрибутов. Например:
main = room {
nam ='Главная комната',
dsc ='Вы в большой комнате.',}
Как вы помните, INSTEAD спрячет статическое описание сцены, после того, как игрок его прочитает при входе в сцену.
Если для вашего творческого замысла необходимо, чтобы описание статической части сцены выводилось каждый раз, вы можете определить для своей игры параметр forcedsc (в начале игры).
game.forcedsc =true;
Или, аналогично, задать атрибут forcedsc для конкретных сцен.
main = room {
forcedsc =true;
nam ='Главная комната';
dsc =[[Вы в большой комнате.]];}
Но лучше всего на данном этапе не использовать эту технику, так как классические игры INSTEAD не используют ее, а движок по-умолчанию оптимизирован именно на стандартное поведение.
В данном примере все атрибуты – строковые. Строка может быть записана в одинарных или двойных кавычках:
main = room {
nam ='Главная комната';
dsc ="Вы в большой комнате.";}
Для длинных описаний удобно использовать запись вида:
dsc =[[ Очень длинное описание... ]];
При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе описания сцены присутствовали абзацы – используйте символ ^.
dsc =[[ Первый абзац. ^^
Второй Абзац.^^
Третий абзац.^
На новой строке.]];
Я рекомендую всегда использовать [[ и ]] для dsc.
На самом деле, имя nam объекта и его отображение (в данном случае то, как сцена будет выглядеть для игрока в виде надписи сверху окна) можно разделять. Для этого существует атрибут disp. Если он не задан, то считается, что отображение равняется имени, но если это не так, то для функций отображения используется disp, например:
main = room {
nam ='Начало';
disp ='Моя комната';
dsc =[[Я нахожусь в своей комнате.]];}
2. Объекты
Объекты – это единицы сцены, с которыми взаимодействует игрок.
tabl = obj {
nam ='стол';
dsc ='В комнате стоит {стол}.';
act ='Гм... Просто стол...';};
Обязательное имя объекта nam используется при попадании его в инвентарь, а также в текстовом интерпретаторе для адресации объекта. Если у объекта определен disp, то при попадании в инвентарь для его отображения будет использоваться именно этот атрибут. Например:
tabl = obj {
nam ='стол';
disp ='угол стола';
dsc ='В комнате стоит {стол}.';
tak ='Я взялся за угол стола';
inv ='Я держусь за угол стола.';};
Вы можете скрывать отображение предмета в инвентаре, если disp атрибут будет равен false.
dsc – описание объекта. Оно будет выведено в динамической части сцены. Фигурными скобками отображается фрагмент текста, который будет являться ссылкой в окне INSTEAD. Если объектов в сцене много, то все описания выводятся одно за другим, через пробел,
act – это обработчик события, который вызывается при действии пользователя (действие на объект сцены, обычно – клик мышкой по ссылке). Его задача – вывод (возвращение) строки текста, которая станет частью событий сцены, или логического значения (см. раздел 5).
Важно!
ВНИМАНИЕ: в пространстве имен lua уже существуют некоторые объекты (таблицы), например: table, io, string… Будьте внимательны при создании объекта. Например, в приведенном примере традиционно используется tabl, а не table. Хотя в новых версиях INSTEAD эта проблема практически полностью решена, и в данном примере можно было бы использовать и идентификатор table.
В любом случае, вы не можете использовать дескрипторы объектов, совпадающие с именами конструкторов INSTEAD, такими как: obj, game, player, list, room, dlg.
3. Добавляем объекты в сцену
Ссылкой на объект называется текстовая строка, содержащая дескриптор объекта. Например: 'tabl' – ссылка на объект с дескриптором tabl.
Для того, чтобы поместить в сцену объекты, существует два пути.
Во-первых, при создании комнаты можно определить список obj, состоящий из ссылок на объекты:
main = room {
nam ='Главная комната';
dsc =[[Вы в большой комнате.]];
obj ={'tabl'};};
Теперь, при отображении сцены мы увидим объект «стол» в динамической части.
Вы можете использовать дескрипторы объектов (в данном примере, указав tabl без кавычек) но только в том случае, если объект был определен ранее (перед определением комнаты). По этой причине использование кавычек всегда безопасней и рекомендуется как предпочтительное.
Если в комнату помещаются несколько объектов, разделяйте их ссылки запятыми, например:
obj ={'tabl','apple'};
Вы можете вставлять переводы строк для наглядности, когда объектов много, например, так:
obj ={'tabl','apple','knife',};
Второй способ размещения предметов заключается в вызове функций, которые поместят объекты в требуемые комнаты. Он будет рассмотрен в дальнейшем.
4. Объекты, связанные с другими объектами
Объекты тоже могут содержать атрибут obj. При этом, при выводе объектов, INSTEAD будет разворачивать списки последовательно. Такая техника может использоваться для создания объектов-контейнеров или просто для связывания нескольких описаний вместе. Например, поместим на стол яблоко.
apple = obj {
nam ='яблоко';
dsc =[[На столе лежит {яблоко}.]];
act ='Взять что-ли?';};
tabl = obj {
nam ='стол';
dsc =[[В комнате стоит {стол}.]];
act ='Гм... Просто стол...';
obj ={'apple'};};
При этом, в описании сцены мы увидим описание объектов стол и яблоко, так как apple – связанный с tabl объект и движок при выводе объекта tabl вслед за его dsc выведет последовательно dsc всех вложенных в него объектов.
Также, следует отметить, что оперируя объектом стол (например, перемещая его из комнаты в комнату) мы автоматически будем перемещать и вложенный в него объект яблоко.
5. Атрибуты и обработчики как функции
Большинство атрибутов и обработчиков могут быть функциями. Так, например:
nam =function()
p 'яблоко';end;
Пример не очень удачен, так как проще было бы написать nam = 'яблоко', но показывает синтаксис записи функции.
Основная задача такой функции – это возврат строки или булевого значения. Сейчас мы рассматриваем возврат строки. Для возврата строки вы можете использовать явную запись в виде:
return"яблоко";
При этом ход выполнения кода функции прекращается и она возвращает движку строку. В данном случае «яблоко».
Более привычным способом вывода являются функции:
p («текст») – вывод текста и пробела;
pn («текст») – вывод текста с переводом строки;
pr («текст») – вывод текста как есть;
Если p/pn/pr вызывается с одним текстовым параметром, то скобки можно опускать.
pn "Нет скобкам!";
Все эти функции дописывают текст в буфер и при возврате из функции возвращают его движку. Таким образом вы можете постепенно формировать вывод за счет последовательного выполнения p/pn/pr. Имейте в виду, что автору крайне редко необходимо явно форматировать текст, особенно если это описание объектов, движок сам расставляет необходимые переводы строк и пробелы для разделения информации разного рода и делает это унифицированным способом.
Вы можете использовать .. или , для склейки строк. Тогда ( и ) обязательны. Например:
Для очистки буфера (если это нужно), используйте pclr(). Если вам нужно получить содержимое текущего буфера – pget().
Основное отличие атрибутов от обработчиков событий состоит в том, что обработчики событий могут менять состояние игрового мира, а атрибуты нет. Поэтому, если вы оформляете атрибут (например, nam или dsc) в виде функции, помните, что задача атрибута это возврат значения, а не изменение состояния игры! Дело в том, что движок обращается к атрибутам в те моменты времени, которые обычно четко не определены, и не связаны явно с какими-то игровыми процессами!
Важно!
Еще одной особенностью обработчиков является тот факт, что вы не должны ждать каких то событий внутри обработчика. То есть, не должно быть каких-то циклов ожидания, или организации задержек (пауз). Дело в том, что задача обработчика – изменить игровое состояние и отдать управление INSTEAD, который визуализирует эти изменения и снова перейдет в ожидание действий пользователя. Если вам требуется организовать задержки вывода, вам придется воспользоваться модулем «timer» или «cutscene».
Функции практически всегда содержат условия и работу с переменными. Например:
apple = obj {
nam ='яблоко';
dsc =function(s)ifnot s._seen then
p 'На столе {что-то} лежит.';else
p 'На столе лежит {яблоко}.';endend;
act =function(s)if s._seen then
p 'Это яблоко!';else
s._seen =true;
p 'Гм... Это же яблоко!';endend;};
Если атрибут или обработчик оформлен как функция, то всегда первый аргумент функции (s) – сам объект. То есть, в данном примере, s это синоним apple. Когда вы работаете с самим объектом в функции, удобнее использовать параметр, а не дескриптор, так как при переименовании дескриптора вам не придется переписывать вашу игру.
В данном примере при показе сцены в динамической части сцены будет выведен текст: 'На столе что-то лежит'. При взаимодействии с 'что-то', переменная _seen объекта apple будет установлена в true – истина, и мы увидим, что это было яблоко.
Как видим, синтаксис оператора if довольно очевиден. Для наглядности, несколько примеров.
if <выражение> then <действия> end
if have (apple)then
p 'У меня есть яблоко!'end
if <выражение> then <действия> else <действия иначе> end
if have (apple)then
p 'У меня есть яблоко!'else
p 'У меня нет яблока!'end
if <выражение> then <действия> elseif <выражение 2> then <действия 2> else <иначе> end и т.д.
if have (apple)then
p 'У меня есть яблоко!'elseif have (fork)
p 'У меня нет яблока, но есть вилка!'else
p 'У меня нет ни яблока, ни вилки!'end
Выражение в операторе if может содержать логическое «и» (and), «или» (or), «отрицание» (not) и скобки (, ) для задания приоритетов. Запись вида if <переменная> then означает, что переменная не равна false и определена. Равенство описывается как ==, неравенство ~=.
ifnot have (apple)andnot have(fork)then
p 'У меня нет ни яблока, ни вилки!'end...if w ~= apple then
p 'Это не яблоко.';end...iftime()==10then
p '10 й ход настал!'end
Важно!
В ситуации когда переменная не была определена, но используется в условии, считается, что переменная равна пустоте (nil). Так, проверку на существование переменной можно было бы написать следующим образом:
if z ==nilthen
p "Глобальная переменная z не существует."end
В то же время, при анализе несуществующих переменные в условиях, считается, что они дают «ложь». То есть, если z не была определена, то:
ifnot z then
p "Переменная z или не определена, или равна false."end
С другой стороны:
if z ==falsethen
p "Переменная z равна false."end
Учитывайте это при отладке своей игры, так как если вы описались в имени переменной при задании условия, то условие будет работать (без выдачи ошибки), но игровая логика станет некорректной.
Запись s._seen означает, что переменная _seen размещена в объекте s (то есть apple). Помните, мы назвали первый параметр функции s (от self), а первый параметр – это дескриптор текущего объекта. Подчеркивание означает, что эта переменная попадет в файл сохранения игры. Файлы сохранения содержат в себе изменения, которые произошли с игровым миром относительно первоначальной инициализации.
На самом деле, есть два способа определения переменных, попадающих в файл сохранения игры. Либо вы работаете с переменными, имена которых начинаются с символа _, и тогда вы можете создавать такие переменные на лету, либо вы должны явно определять (инициализировать) переменные с помощью конструкций var и global.
global {-- определение глобальных переменных
global_var =1;-- число
some_number =1.2;-- число
some_string ='строка';
know_truth =false;-- булево значение}
main = room {
var {-- определение переменных комнаты
i ="a";
z ="b";};
nam ='Моя первая комната';
var {
new_var =3;};
dsc =function(s)
p ("i == ", s.i);
p ("new_var == ", s.new_var);
p ("global_var == ", global_var);end;
Если вы пользуетесь var или global, обязательно присваивайте переменным первоначальные значения! Вы можете инициализировать переменную значением null, если переменная в дальнейшем должна будет хранить дескриптор объекта, а при инициализации этот объект неизвестен. Другими словами, null – это объект-пустышка.
Важно!
Итак, переменные записываются в файл сохранения, если они размещены в одном из перечисленных типов объектов: комната, объект, игра, игрок, глобальное пространство, при этом начинаются с символа _ или определены с помощью var и global. При разработке пользуйтесь простым правилом: всегда объявляйте переменные в блоках var или global, если они будут меняться. Данный способ наиболее нагляден.
В файл сохранения могут быть записаны переменные следующих типов:
строки;
булевы величины;
числовые величины;
дескрипторы объектов;
конструкции code;
Конструкция code – это другой способ определения функций
dsc = code [[
if not self._seen then
p 'На столе {что-то} лежит.';
else
p 'На столе лежит {яблоко}.';
end
]],
Обратите внимание, что текст функции задан в скобках [[ ]], и это значит, что текстовые параметры внутри скобок придется задавать кавычками или апострофами, либо использовать вложенные скобки ([=[ ]=], [==[ ]==] и т.д.), как указано в документации Lua.
При вызове code автоматически создаются некоторые переменные. При этом в self записан текущий объект, arg1 … arg9 и массив args[] – параметры.
Запись code имеет смысл определять в случае, если функция очень короткая, например:
act = code [[ walk(sea) ]];
Либо, если вы хотите переопределять функции на лету (Конструкции code сохраняются, если присвоены сохраняемым переменным).
Правда, обычно это очень плохой стиль программирования игры.
Иногда при написании функции вам могут понадобиться вспомогательные переменные, которые хранят какие-то промежуточные значения, например:
kitten = obj {
nam ='котенок';
var { state =1};
act =function(s)
s.state = s.state +1if s.state >3then
s.state =1end
p [[Муррр!]]end;
dsc =function(s)local dsc ={"{Котенок} мурлычет.","{Котенок} играет.","{Котенок} облизывается.",};
p(dsc[s.state])end;end
Как видим, в функции dsc мы определили массив dsc. local указывает на то, что он действует в пределах функции dsc. Если вам нужны вспомогательные переменные в функциях, всегда пишите перед их определением local. Конечно, данный пример можно было написать и так:
dsc =function(s)if s.state ==1then
p "{Котенок} мурлычет."elseif s.state ==2then
p "{Котенок} играет."else
p "{Котенок} облизывается.",end
Вы можете писать свои вспомогательные функции и использовать их из своей игры, например:
function mprint(n,...)local a ={...};-- временный массив с аргументами к функции
p(a[n])-- выведем n-й элемент массиваend....
dsc =function(s)
mprint(s.state,{"{Котенок} мурлычет.","{Котенок} играет.","{Котенок} облизывается."});end;
Пока не обращайте внимания на данный пример, если он кажется вам сложным.
Иногда может понадобиться обработчик, который совершал бы некоторое действие, но не выводил никакого описания. Например:
button = obj {
nam ="кнопка";
var {
on =false;};
dsc ="На стене комнаты видна большая красная {кнопка}.";
act =function(s)
s.on =truereturntrueend;}
r12 = room {
nam ='Комната';
forcedsc =true;
dsc =function(s)ifnot button.on then
p [[Я нахожусь в комнате.]];else
p [[Я нахожусь в комнате с нажатой кнопкой.]];endend,
obj ={'button'}}
В данном случае обработчик act нужен для того, чтобы поменять описание комнаты, и не нужно, чтобы он выводил результат действия. Если честно, это плохой пример, и его искусственность показывает, что вам вряд ли понадобится делать обработчики без реакции. Действительно, почему бы в приведенном примере не вывести в act что-то вроде: «Я нажал на кнопку»? Кроме того, нам пришлось еще включить режим forcedsc. Тем не менее, необходимость пустой реакции может возникнуть.
Для отключения результата можно вернуть из обработчика значение true – это будет означать, что действие успешно выполнено, но не требует дополнительного описания.
Если необходимо показать, что действие невыполнимо, ничего не возвращайте. При этом будет отображено описание по умолчанию, заданное с помощью обработчика game.act. Обычно описание по умолчанию содержит описание невыполнимых действий. Что-то вроде:
game.act ='Гм... Не получается...';
Обратите внимание, что для создания динамического описания сцены в рассмотренном выше примере используется функция dsc. Почему бы не менять значение dsc на лету? На самом деле такой вариант сработает только в том случае, если dsc объявить в блоке var. Например, данный пример мог бы выглядеть так:
button = obj {
nam ="кнопка";
dsc ="На стене комнаты видна большая красная {кнопка}.";
act =function(s)
here().dsc =[[Теперь комната выглядит совсем по-другому!!!]];
pn [[После того как я нажал на кнопку, комната преобразилась.
Книжный шкаф куда-то исчез вместе со столом и комодом,
а на его месте появился странного вида аппарат.]];end,}
r12 = room {
nam ='Комната';
var {
dsc =[[Я нахожусь в комнате.]];};
obj ={'button'}}
Правда, такой стиль программирования не рекомендуется. Во-первых, вы запутываете код игры, так как тексты описаний перестают быть локализованными в объектах, которые они описывают. Во-вторых, файлы сохранений будут занимать гораздо больший объем. Я советую всегда использовать функции для изменяющихся атрибутов и реакций, а не менять их значение динамически извне.
Иногда возникает необходимость вызвать обработчик вручную. Для этого используется lua синтаксис вызова метода. Объект:метод(параметры). Например:
apple:act()-- вызовем обработчик ''act'' у объекта ''apple''.
На самом деле, это синоним следующей записи:
apple.act(apple)-- вызовем обработчик ''act'' у объекта ''apple'' -- (явно указав 1-й параметр)
Однако, такой метод работает только в том случае, если вызываемый метод оформлен как функция. Вы можете воспользоваться stead.call() для вызова обработчика тем способом, каким это делает сам INSTEAD. (Будет описано в дальнейшем).
6. Инвентарь
Простейший вариант сделать объект, который можно брать – определить обработчик tak.
Например:
apple = obj {
nam ='яблоко';
dsc ='На столе лежит {яблоко}.';
inv =function(s)remove(s, me());-- удалить яблоко из инвентаря
p 'Я съел яблоко.'end;
tak ='Вы взяли яблоко.';};
При этом, при действии игрока на объект «яблоко» (щелчок мыши на ссылку в сцене) – яблоко будет убрано из сцены и добавлено в инвентарь. При действии игрока на инвентарь (двойной щелчок мыши на названии объекта) – вызывается обработчик inv.
В нашем примере, при действии игроком на яблоко в инвентаре – яблоко будет съедено.
Конечно, мы могли бы реализовать код взятия объекта в act, например, так:
apple = obj {
nam ='яблоко';
dsc ='На столе лежит {яблоко}.';
inv =function(s)remove(s, me());-- удалить яблоко из инвентаря
p 'Я съел яблоко.'end;
act =function(s)
take(s)
p 'Вы взяли яблоко.';end};
Если у объекта в инвентаре не объявлен обработчик inv будет вызван game.inv.
7. Переходы между сценами
Традиционные переходы в INSTEAD выглядят как ссылки над описанием сцены. Для определения таких переходов между сценами используется атрибут сцены – список way. В списке определяются комнаты, в виде ссылок на комнаты или дескрипторов комнат (аналогично списку obj). Например:
room2 = room {
nam ='Зал';
dsc ='Вы в огромном зале.';
way ={'main'};};
main = room {
nam ='Главная комната';
dsc ='Вы в большой комнате.';
obj ={'tabl'};
way ={'room2'};};
При этом, вы сможете переходить между сценами main и room2. Как вы помните, nam (или disp) может быть функцией, и вы можете генерировать имена сцен на лету, например, если вы хотите, чтобы игрок не знал название сцены, пока не попал на нее. Хотя для этой цели есть более удачные средства, вроде модуля wroom, о котором будет рассказано позже.
При переходе между сценами движок вызывает обработчик exit из текущей сцены и enter в той сцене, куда идет игрок. Например:
room2 = room {
enter ='Вы заходите в зал.';
nam ='Зал';
dsc ='Вы в огромном зале.';
way ={'main'};exit='Вы выходите из зала.';};
Конечно, как и все обработчики, exit и enter могут быть функциями. Тогда первый параметр это (как всегда) сам объект, а второй это комната куда игрок хочет идти (для exit) или из которой уходит (для enter). Например:
room2 = room {
enter =function(s, f)if f == main then
p 'Вы пришли из комнаты.';endend;
nam ='Зал';
dsc ='Вы в огромном зале.';
way ={'main'};exit=function(s, t)if t == main then
p 'Я не хочу назад!'returnfalseendend;};
Как видим, эти обработчики могут возвращать два значения: строку и статус. В нашем примере функция exit вернет false, если игрок попытается уйти из зала в комнату main. false означает, что переход не будет выполнен. Такая же логика работает и для enter. Кроме того, она работает и для обработчика tak.
Вы можете сделать возврат статуса и другим способом, если это кажется вам удобным:
return"Я не хочу назад",false
Если же вы используете функции p/pn/pr, то просто возвращайте статус операции с помощью завершающего return, как показано в примере выше.
Важно!
Следует отметить, что при вызове обработчика enter указатель на текущую сцену (here()) может быть еще не изменен!!! В INSTEAD есть обработчики left (уход из комнаты) и entered (заход в комнату), которые вызываются уже после того, как переход произошел. Эти обработчики рекомендованы к использованию всегда, когда нет необходимости запрещать переход.
Иногда есть необходимость, чтобы название перехода отличалось от названия комнаты, в которую ведет этот переход. Существует несколько способов сделать это. Например, с помощью vroom.
room2 = room {
nam ='Зал';
dsc ='Вы в огромном зале.';
way ={ vroom('В главную комнату','main')};};
main = room {
nam ='Главная комната';
dsc ='Вы в большой комнате.';
obj ={'tabl'};
way ={ vroom('В зал','room2')};};
На самом деле, функция vroom возвращает комнату с именем, который равен первому параметру, и специальной функцией enter, которая перенаправляет игрока в комнату заданную вторым параметром vroom.
Таким образом, vroom позволяет именовать переходы удобным способом. Обратите внимание, что в room2 вторая комната задана через ссылку, так как на данный момент main еще не объявлена. Во второй комнате переход также описан в виде ссылки, для общности, хотя в этот момент комната room2 уже объявлена, и мы можем убрать апострофы вокруг room2 во втором параметре vroom.
Если вам не хватает возможностей vroom, вы можете воспользоваться модулем wroom.
Иногда вам может потребоваться включать и выключать переходы. На самом деле это требуется не часто. Идея переходов состоит в том, что переход виден даже тогда, когда он невозможен. Например, представим себе сцену перед домом у входной двери. Войти в дом нельзя, так как дверь закрыта.
Нет никакого смысла прятать переход «дверь». Просто в функции enter сцены внутри дома мы проверяем, а есть ли у героя ключ? И если ключа нет, говорим о том, что дверь закрыта и запрещаем переход. Это повышает интерактивность и упрощает код. Если же вы хотите сделать дверь объектом сцены, поместите ее в комнату, но в act обработчике сделайте осмотр двери, или дайте возможность игроку открыть ее ключом (как это сделать - мы рассмотрим позже), но сам переход дайте сделать игроку привычным способом через строку переходов.
Тем не менее, бывают ситуации, когда переход не очевиден и он появляется в результате каких-то событий. Например, мы осмотрели часы и увидели там секретный лаз.
clock= obj {
nam ='часы';
dsc =[[Тут есть старинные {часы}.]];
act =function(s)
path('В часы'):enable()
p [[Вы видите, что в часах есть потайной ход!]];end;}
hall = room {
nam ='Зал';
dsc ='Вы в огромном зале.';
obj ={'clock'};
way ={ vroom('В часы','inclock'):disable()};};
В данном примере, мы создали отключенный переход, за счет вызова метода disable у комнаты созданной с помощью vroom. Метод disable есть у всех объектов, он переводит объект в отключенное состояние, которое означает, что объект перестает рассматриваться движком как существующий. Кроме того, этот метод снова возвращает объект (уже в выключенном состоянии). Замечательным свойством отключенного объекта является то, что его можно включить с помощью метода :enable();
Кстати, если вам удобнее, вы можете использовать более привычную запись функций:
way ={ disable(vroom('В часы','inclock'))};
Далее, когда игрок нажимает на ссылку, описывающую часы, вызывается обработчик act, который с помощью функции path() находит в way текущей комнаты переход 'В часы' и вызывает для него метод enable(). Альтернативный вариант записи, если он кажется вам удобней:
act =function(s)
enable(path('В часы'))-- вы можете опускать () если у функции 1 параметр, -- таким образом запись enable( path 'В часы' ) -- тоже будет корректной (и более простой)
p [[Вы видите, что в часах есть потайной ход!]];end;
Если бы нам было необходимо проделать такую процедуру с другой комнатой, то мы бы указали второй параметр при вызове path. Например:
path('В часы', room312):enable();
Если вам не нравится, что в вашей игре есть привязка к имени перехода 'В часы', то вы можете использовать переменные:
path_clock = vroom('В часы','inclock');clock= obj {
nam ='часы';
dsc =[[Тут есть старинные {часы}.]];
act =function(s)
path_clock:enable()
p [[Вы видите, что в часах есть потайной ход!]];end;}
hall = room {
nam ='Зал';
dsc ='Вы в огромном зале.';
obj ={'clock'};
way ={ path_clock:disable()};};
Если вы не используете vroom, вы можете включать и выключать сами комнаты:
inclock = room {
nam ='Внутри';
dsc =[[Тут темно.]];}:disable();-- вместо приписки :disable() можно написать-- inclock:disable()-- или disable(inclock)clock= obj {
nam ='часы';
dsc =[[Тут есть старинные {часы}.]];
act =function(s)
inclock:enable()
p [[Вы видите, что в часах есть потайной ход!]];end;}
hall = room {
nam ='Зал';
dsc ='Вы в огромном зале.';
obj ={'clock'};
way ={'inclock'};};
На самом деле, данными вещами не стоит злоупотреблять. Простые игры могут вполне обходиться обычными переходами в виде списка комнат.
8. Действие объектов друг на друга
Игрок может действовать объектом инвентаря на другие объекты. Для этого он щелкает мышью на предмет инвентаря, а затем, на предмет сцены. При этом вызывается обработчик use у объекта, которым действуют, и used – на который действуют.
Например:
knife = obj {
nam ='нож';
dsc ='На столе лежит {нож}';
inv ='Острый!';
tak ='Я взял нож!';
use ='Вы пытаетесь использовать нож.';};
tabl = obj {
nam ='стол';
dsc ='В комнате стоит {стол}.';
act ='Гм... Просто стол...';
obj ={'apple','knife'};
used ='Вы пытаетесь сделать что-то со столом...';};
Если игрок возьмет нож и использует его на стол – то он увидит текст обработчиков use (у объекта knife) и used (у объекта tabl). use и used, очевидно, могут быть функциями.
Тогда, в случае функции use, первый параметр к функции – сам объект, которым осуществляется действие, а второй параметр – объект, на который направлено действие. В случае функции used, первый параметр – сам объект, на который производится действие, а второй параметр – объект, которым осуществляется действие.
То есть, первый параметр это как всегда сам объект, а второй – объект на который направлено действие в случае use и объект, которым действие осуществляется в случае used.
use может вернуть статус false, в этом случае обработчик used не вызовется (если он вообще был). Статус обработчика usedигнорируется.
Пример:
knife = obj {
nam ='нож';
dsc ='На столе лежит {нож}';
inv ='Острый!';
tak ='Я взял нож!';
use =function(s, w)if w ~= tabl then
p 'Не хочу это резать.'returnfalseelse
p 'Вы вырезаете на столе свои инициалы.'endend};
В примере выше нож можно использовать только на стол.
Если и use и used ничего не вернут (или их нет), вызовется обработчик по-умолчанию game.use.
Использовать use или used это вопрос личных предпочтений, однако, всегда следует стараться локализовать код рядом с объектом, к которому он относится. Например, если сделать объект «мусорка» и дать возможность игроку выбрасывать в нее все предметы, то очевидным решением здесь будет обработчик used у мусорки.
trash = obj {
nam ='мусорка';
dsc =[[Я вижу {мусорку}.]];
act ='Не полезу я туда.';
used =function(s, w)remove(w, me())
p [[Мне это не нужно больше.]];end}
При одновременном использовании use и used могут быть проблемы. Например, пусть у игрока есть нож, при использовании которого на все предметы, кроме яблока, выдается сообщение «Я не хочу это резать.».
При применении ножа на мусорку, игроку будет выдано «Я не хочу это резать.», а потом нож навсегда сгинет в ее недрах (так мы написали обработчик used); Конечно, в use ножа можно написать:
p "Я не хочу это резать."returnfalse-- прервать цепочку (не вызывать used)
Но это не очень удобно. Для решения этой проблемы можно воспользоваться модулем nouse.
...require"nouse"...
knife = obj {
nam ='нож',
use =function(s, w)if w ~= apple then-- неравенствоreturnendif w.knife thenreturn"Я уже почистил его."end
w.knife =true
p 'Я почистил яблоко.'end;
nouse =[[Не хочу это резать.]];};
Обработчик nouse объекта вызывается в том случае, если ни use ни used не вернули реакции. Если и nouse объекта не содержит реакции, то вызовется noused страдательного объекта. Если и в таком случае реакция не прописана, вызовется game.nouse;
Конечно, все обработчики могут быть функциями с тремя параметрами. Сам объект (или объект game в случае game.nouse), объект которым действуем и страдательный объект.
Важно!
Модуль «nouse» переопределяет функцию game.use, поэтому используйте game.nouse если вы включаете модуль 'nouse' в свою игру.
Я рекомендую использовать модуль «nouse» всегда, так как код игры становится более наглядным.
На самом деле, в INSTEAD играх можно действовать не только предметом инвентаря на предмет сцены, но и предметом сцены на предмет сцены (и даже предметом сцены на инвентарь). Этот механизм вряд ли действительно стоит использовать, но тем не менее, определив булевый атрибут scene_use в объекте или у игры (game.scene_use), вы можете управлять моментом, когда этот режим доступен.
Например: game.scene_use = true.
Атрибут может быть задан функцией, возвращающей булево значение (true или false).
9. Объект "player"
Игрок в STEAD представлен объектом pl. Тип объекта – player. В движке объект создается следующим образом:
pl = player {
nam ="Incognito";
where ='main';
obj ={};};
Атрибут obj представляет собой инвентарь игрока. Обычно, нет смысла переопределять объект типа player, однако, если вы хотите создать переменные, которые бы хранились в игроке, вы можете сделать это:
pl = player {
nam ="Василий";
where ='main';
var { power =100};
obj ={'apple'};-- заодно добавим яблоко в инвентарь};
В INSTEAD есть возможность создавать нескольких игроков и переключаться между ними.
Для этого служит функция change_pl(). В качестве параметра передайте функции требуемый объект типа player. Функция переключит текущего игрока, и при необходимости, осуществит переход в комнату, где находится новый игрок.
Функция me() всегда возвращает текущего игрока. В большинстве игр me() == pl.
10. Объект ''game''
Игра также представлена объектом game с типом game. В движке он определяется следующим образом:
game = game {
codepage ="UTF-8",
nam ="INSTEAD -- Simple Text Adventure interpreter v"..
stead.version.." '2013 by Peter Kosyh",
dsc =[[
Commands:^
look(or just enter), act <on what> (or just what), use <what> [on what],
go <where>,^
back, inv, way, obj, quit, save <fname>, load <fname>.]],
pl ='pl',
showlast =true,
_scripts ={},};
Как видим, объект хранит в себе указатель на текущего игрока (pl) и некоторые параметры. Не существует возможности пересоздать объект game, к счастью, это и не требуется. Однако, вам придется иногда менять некоторые атрибуты.
Например, вы можете указать в начале своей игры кодировку текста следующим образом:
game.codepage="cp1251";
На самом деле, не стоит этого делать. Лучше настройте свой редактор на использование UTF-8. Переопределение кодировки это экстренная мера, например, для игр написанных URQL, которые могут быть запущены в INSTEAD с помощью соответствующего модуля.
Кроме того, объект game может содержать обработчики по умолчанию act, inv, use, которые будут вызваны, если в результате действий пользователя не будут найдены никакие другие обработчики. Например, вы можете написать в начале игры:
Всегда определяйте эти обработчики в своей игре. В случае, если вы используете модуль «nouse», вместо game.use задавайте game.nouse.
11. Атрибуты-списки
Атрибуты-списки (такие как way или obj) позволяют работать со своим содержимым с помощью набора методов. Атрибуты-списки призваны сохранять в себе списки объектов. На самом деле, вы можете создавать списки для собственных нужд, при этом нет необходимости вставлять список в var или global, например:
treasures = list {'gold','silver'};
Хотя обычно, это не требуется.
Методы списков: add, del, look, srch, purge, replace. Из них наиболее часто используемые: add и del.
add – добавляет в список объект;
cat – cat(b, [pos]) – добавляет в список содержимое списка b в позицию pos;
zap – обнулить список;
del – удаляет объект из списка (если он не выключен);
purge – удаляет даже выключенный объект;
srch – выполняет поиск объекта. Возвращает два значения: элемент списка и индекс (если элемент найден в списке);
replace – замена объекта другим replace(old, new);
enable – включение объекта (если он найден в списке);
disable – выключение объекта (если он найден в списке);
enable_all – включить все объекты в списке;
disable_all – выключить все объекты в списке;
Следует отметить, что параметром add, del, purge, replace и srch и других методов может быть не только сам объект (дескриптор), но и имя объекта (атрибут nam).
Самый часто используемый пример для работы со списками это конструкция вида: inv():del('apple');
inv() – это функция, которая возвращает список-инвентарь. del после : – метод, удаляющий элемент инвентаря.
Аналогично, собственная реализация tak может быть такой:
knife = obj {
nam ='нож',
dsc ='На столе лежит {нож}',
inv ='Острый!',
act =function(s)
objs():del(s);
inv():add(s);end,};
objs() – это функция, которая возвращает список объектов текущей комнаты. Либо любой другой комнаты, если указать ее в качестве параметра к objs().
Для получения списка way используйте функцию ways().
Кроме удаления / добавления объектов из списков вы можете использовать выключение / включение объектов с помощью методов объектаdisable() и enable(). Например: knife:disable(). При этом объект knife пропадает из описания сцены (предполагаем, что он находится в списке obj сцены), но в последствии может быть опять быть включен, с помощью knife:enable(). Для проверки того факта, что объект выключен, можно использовать метод :disabled(). Если вам привычней использовать синтаксис функций, вы можете пользоваться записью вида:
disable(knife)-- или knife:disable()...ifnot disabled(knife)-- или not knife:disabled()...
Методы списковenable и disable работают аналогично одноименным методам объектов, при условии что искомый объект находится в списке. Например, учитывая, что поиск объекта в списке осуществляется в том числе и по имени, можно написать:
inv():disable 'нож';
Важно!
Внимание!!! Для работы с инвентарем, объектами и переходами рекомендуется использовать более высокоуровневые функции: put / take / drop / remove / seen / have и др. функции, которые будут описаны в дальнейшем. Тем не менее, иногда работа со списками оказывается полезной.
12. Функции, которые возвращают объекты
В INSTEAD определены некоторые функции, которые возвращают различные объекты или списки.
При описании функции используются следующие соглашения о параметрах.
в символах [ ] описаны необязательные параметры;
что – означает объект (в том числе комнату), заданный дескриптором, ссылкой или именем;
где – означает объект (в том числе комнату), заданный дескриптором или ссылкой;
комната – означает объект типа room, заданный дескриптором или ссылкой;
объект – означает объект типа obj;
переход – означает текстовое имя перехода в way, его дескриптор или ссылку;
Функции, возвращающие списки:
inv() возвращает список инвентаря;
objs([где]) возвращает список объектов текущей сцены; Второй необязательный параметр – комната или объект, для которой возвращается список obj;
ways([комната]) возвращает список возможных переходов из текущей сцены; Второй необязательный параметр – комната, для которой возвращается список;
На самом деле, функции, которые возвращают списки, редко бывают необходимы при написании игры, так как для работы с объектами и переходами существует набор специальных функций, которые описаны в следующей главе.
Теперь посмотрим на функции, возвращающие объекты:
me() возвращает текущего объекта-игрока;
here() возвращает текущую сцену;
where(объект) возвращает комнату или объект в котором находится заданный объект, если он был помещен туда с помощью функций put/move/drop/replace и т.д.)
from([комната]) возвращает прошлую комнату, из которой игрок перешел в текущую комнату. Второй необязательный параметр – получить прошлую комнату не для текущей комнаты, а для заданной;
seen(что, [где]) возвращает объект, если он присутствует и не отключен на сцене, есть второй необязательный параметр – выбрать сцену или объект в котором искать что;
have(что) возвращает объект, если он есть в инвентаре и не отключен;
exist(что, [где]) возвращает объект, если он присутствует на сцене (даже если он выключен!), есть второй необязательный параметр – выбрать сцену или объект, в котором искать что;
live(что) возвращает объект, если он присутствует среди живых объектов (описано далее);
path(переход, [комната]) – найти элемент в way, даже если он выключен. Второй параметр используется, если интересует не текущая, а заданная комната;
Эти функции в основном используются в условиях, либо для поиска объекта с последующей модификацией.
Например, вы можете использовать seen для написания условия:
exit=function(s)if seen 'монстр'then-- если у функции 1 параметр, --- скобки писать не обязательно
p 'Монстр загораживает проход!'returnfalseendend
А также, для нахождения объекта в сцене:
use =function(s, w)if w == window and path 'В окно':disabled()then-- действие на окно и нет перехода 'В окно'
path 'В окно':enable();
p 'Я разбил окно!'endend
Пример с функцией have:
...
act =function(s)if have('knife')then
p 'Но у меня же есть нож!';returnendend...
Следующие варианты тоже будут работать:
...if have 'knife'then...if have (knife)then...
Еще одна функция, которая получает объект по ссылке:
stead.ref(ссылка).
На самом деле, вам вряд ли понадобится эта функция, по крайней мере для первой игры. Эта функция получает объект по ссылке, другими словами:
stead.ref 'apple'== apple
При условии, конечно, что apple определена.
Функция stead.deref(дескриптор), наоборот, возвращает ссылку-строку для объекта;
act =function(s)
p('Вы кликнули на объект: ', stead.deref(s));end
13. Другие функции INSTEAD
В INSTEAD определено множество функций, которые манипулируют состоянием предмета, или используются для написания игровой логики. Многие из них вы уже встречали по мере чтения данного руководства. Теперь рассмотрим их подробней.
move(что, куда, [где]) переносит объект из текущей сцены в другую сцену или объект; Если задать параметр где, то объект будет искаться не в текущей сцене, а в сцене или объекте где.
move('mycat','inmycar');
Если вы хотите перенести объект из произвольной сцены, вам придется знать о его местоположении. Для создания сложно перемещающихся объектов, вам придется написать свой метод, который будет сохранять текущую позицию объекта в самом объекте и делать удаление объекта из старой сцены. Либо, если для перемещения объектов вы пользуетесь функциями из данной главы, вы можете использовать where. Например:
move(mycat, here(), where(mycat));-- мой кот ходит со мной;
Правда при этом и первоначальное размещение объекта mycat должно производиться методом put (или place).
Существует также функция movef, аналогичная move, но добавляющая объект в начало списка.
drop(что, [где]) – положить объект из инвентаря на сцену:
drop (knife);
Существует также функция dropf, аналогичная drop, но добавляющая объект в начало списка объектов. Второй необязательный параметр – комната, куда помещается предмет.
place(что, [где]) и placef(…) как drop/dropf, но не удалять предмет из инвентаря;
put(что, [где]) и putf(…) устаревшая форма записи place/placef;
replace(что, объект, [где]) – заменить один объект на другой в сцене или объекте где;
remove(что, [где]) удаляет объект из текущей сцены или сцены/объекта где, если он не выключен;
purge (объект, [откуда]) – см. remove, удаляет даже выключенные объекты;
take(что, [где]) – взять объект с текущей сцены или объекта/сцены заданной где. takef – вариант с добавлением в начало инвентаря. На самом деле, если объекта нет на сцене, он все равно будет добавлен в инвентарь, таким образом take, например, часто используется для первоначального наполнения инвентаря в init функции.
take('knife');
Важно!
На самом деле, эти функции также умеют работать не только с комнатами и объектами, но и со списками. То есть remove(apple, inv()) сработает также как и remove(apple, me());
Кроме того, у некоторых описанных выше функций есть варианты с постфиксом to: placeto, putto, taketo, dropto. У этих функций есть дополнительный параметр – индекс позиции в списке, для точного управления позицией предмета. Вы также можете вписывать позицию прямо в списках, например;
obj ={[1]='apple',[1000]='floor'};
Но я не рекомендую пользоваться такими вещами в своих играх, если вам нужно точное позиционирование, лучше используйте объекты в объектах.
lifeon(объект, [приоритет]) добавить объект в список динамических («живых») объектов (будет описано дальше), приоритет – числовой параметр (положительное число), 1 – наивысший приоритет;
lifeoff(что) удалить объект из списка динамических объектов;
taken(объект) – если объект взят – возвратить true (взят с помощью tak или take());
rnd(m) – случайное целочисленное значение от 1 до m;
rnd(a,b) – случайное целочисленное значение от a до b, где a и b целые >= 0;
walk(куда) – перейти в сцену куда:
act = code [[
pn "Я иду в следующую комнату..."
walk (nextroom);
]]
mycar = obj {
nam ='моя машина';
dsc ='Перед хижиной стоит мой старенький {пикап} Toyota.';
act =function(s)
walk('inmycar');end};
Важно!
После вызова walk выполнение обработчика продолжится до его завершения. Поэтому обычно, после walk всегда следует return, если только это не последняя строка функции, хотя и в этом случае безопасно поставить return.
act = code [[
pn "Я иду в следующую комнату..."
walk (nextroom);
return
]]
Не забывайте также, что при вызове walk вызовутся обработчики exit/enter/left/entered и если они запрещают переход, то он не произойдет.
change_pl(игрок) – переключиться на другого игрока (со своим инвентарем и позицией). При этом функция переносит действие в сцену нового игрока без вызова exit/enter/left/entered. Для указания сцены, на которой находится игрок, вы можете использовать атрибут where:
pupkin.where ='kitchen'
… или явно вызывать функцию walk() после change_pl().
walkback([куда]) – возвращается из сцены в прошлую (если не задано куда). Возврат означает, что свойство from не будет изменено;
back([куда]) – возвращается из сцены в прошлую (если не задано куда). Если это переход из диалога в комнату, то не вызываются: dsc, enter, entered у комнаты. exit/left диалога вызываются. В других случаях аналогична walkback.
walkin(куда) – перейти в сцену, при этом exit/left текущей комнаты не вызывается;
walkout() – вернуться в прошлую сцену, при этом enter/entered этой сцены не вызовется;
time() – возвращает текущее время игры. Время игры считается в активных действиях игрока.
cat(…) – возвращает строку – склейку строк-аргументов. Если первый аргумент nil – пустота, то функция возвращает nil – пустоту.
disable/enable/disable_all/enable_all – аналог одноименных методов у объекта;
visited([комната]) счетчик посещений комнаты или nil - если мы в ней не были, если комната не задана, то проверяется текущая;
visits([комната]) аналогична visited за исключением того, что если посещений не было, возвращает 0, а не пустоту;
player_moved() возвращает true если в данном такте игры был переход игрока между комнатами, предназначена для использования в life методах (описано далее);
life_moved() возвращает true если в данном такте игры был переход игрока между комнатами, выполненный life методом, предназначена для использования в life методах (описано далее);
stead.need_scene() если вам необходимо, чтобы следующий такт игры вывел статическую часть сцены (и вы не хотите использовать forcedsc), вы можете воспользоваться этой функцией. Другим способом перерисовки сцены является переход в эту-же комнату;
stead.nameof(объект) – вернуть имя объекта (nam атрибут);
stead.dispof(объект) – вернуть результат disp, а если его нет – nam;
disabled(объект) – возвращает true, если объект отключен;
stead.call(дескриптор, строка - имя атрибута/обработчика, параметры…) – вызов обработчика или получение значения атрибута (описано далее);
instead_gamepath() – возвращает полный путь к каталогу игры;
instead_savepath() – возвращает полный путь к каталогу с сохранениями игры;
14. Диалоги
Диалоги – это сцены специального типа dlg, содержащие объекты – фразы. В INSTEAD на данный момент существует два способа описания диалогов: расширенный и простой. Простой способ считается устаревшим, и не рекомендуется для новых игр. Рассмотрим оба способа.
Общим для обоих способов является сама архитектура, по которой работает диалог.
При входе в диалог игрок видит перечень фраз (по-умолчанию, пронумерованных), которые может выбирать, получая какую-то реакцию игры. По умолчанию, уже выбранные фразы скрываются. При исчерпании всех вариантов, диалог завершается выходом в предыдущую комнату (конечно, если в диалоге нет постоянно видимых фраз, среди которых обычно встречается что-то типа Завершить разговор или Спросить еще раз).
Переход в диалог в игре осуществляется как переход на сцену:
povar = obj {
nam ='повар';
dsc ='Я вижу {повара}.';
act =function()
walk 'povardlg'end,};
Хотя я рекомендую использовать walkin, так как в случае walkin не вызываются exit/left текущей комнаты, а персонаж, с которым мы можем поговорить, обычно находиться в этой же комнате, где и главный герой. То есть:
Вы можете делать переход из одного диалога в другой, организуя иерархичность диалогов (при необходимости делая возврат на предыдущий уровень с помощью back()), впрочем, в расширенных диалогах иерархичность организована более просто.
Если вам не нравится префикс у фразы в виде цифры, вы можете определить строковую переменную:
stead.phrase_prefix ='--';
И получить префикс в виде тире перед каждой фразой.
Обратите внимание, что stead.phrase_prefix не сохраняется, если вам нужно переопределять ее на лету, вам придется восстанавливать ее состояние в start функции вручную!
Важно!
Если в диалоге нет ни одной фразы, движок по умолчанию блокирует переход в такой диалог (ведь из него нет выхода!). Имейте это в виду при отладке своей игры.
Важно!
Я настоятельно рекомендую использовать модуль hideinv и задавать свойство hideinv в диалогах. Диалоги будут выглядеть красивей и вы обезопасите свою игру от ошибок и непредсказуемых реакций при использовании инвентаря внутри диалога (так как обычно автор не подразумевает такие вещи). Например:
instead_version "1.8.2"require"hideinv"...
guarddlg = dlg {
nam ='Охранник';-- в диалогах обычно не нужен инвентарь
hideinv =true;...}
Наиболее частой ошибкой является неправильно написанный переход из обработчика inv в диалог. Например, реализация мобильного телефона, которая позволяет игроку в любой момент перейти в диалог звонка. Обычно, диалог завершается функцией back(). Однако, если инвентарь не спрятан, то игрок может повторно зайти в диалог, воспользовавшись инвентарем. При этом back() вернет игрока в предыдущую комнату, то есть снова в этот же диалог. Будьте внимательны! Обычно, чтобы решить подобную проблему достаточно написать код вида:
tel = obj {
nam ='мобильник';
inv =function(s)if here()~= tel_dlg then
walkin(tel_dlg)returnend
p "Я уже держу телефон в руке."end}
Расширенные диалоги
Начиная с версии INSTEAD 1.7.0 поддерживается новый более простой и мощный синтаксис диалогов, по сравнению с устаревшим традиционным. Фразы определяются в атрибуте phr диалога, например:
povardlg = dlg {
nam ='На кухне';
hideinv =true;
entered =[[Передо мной полное лицо женщины -
повара в белом колпаке и усталым взглядом...]];
phr ={{ always =true,'Мне вот-этих зелененьких... Ага -- и бобов!','На здоровье!'};{ always =true,'Картошку с салом, пожалуйста!','Приятного аппетита!'};{ always =true,'Две порции чесночного супа!!!','Прекрасный выбор!'};{ always =true,'Мне что-нибудь легонькое, у меня язва...','Овсянка!'};{ always =true,'Спасибо, мне ничего не нужно.','Как пожелаете.',[[ back() ]]};};};
Имейте в виду, что если в диалоге не определен атрибут dsc, то он формируется движком таким образом, чтобы отражать последнюю реакцию диалога, то есть, если игрок нажмет на заголовок сцены он увидит последний ответ на свою реплику еще раз. Если вы рассчитываете на такое поведение диалога, то первоначальную реакцию диалога удобнее всего вписать в entered, как в примере выше. Я не рекомендую переопределять dsc у диалогов расширенного типа.
Каждая фраза имеет вид:
{[номер или tag=тэг,][false если выключена,][always =true],"Вопрос","Ответ",[[ необязательный код - реакция]]},
Фраза содержит вопрос, ответ и реакцию. Когда игрок выбирает одну из фраз (кликнув на нее), выводится ответ, фраза отключается, а затем срабатывает реакция (если она есть). Когда все фразы отключатся, ветвь диалога заканчивается. Реакция – это строка кода на lua, который выполнится после отключения фразы.
В реакции может быть любой lua код, но обычно, он содержит в себе логику по работе с фразами.
INSTEAD предоставляет следующие функции по работе с фразами:
pon(t..) – включить фразы диалога с номерами или тегами t…
poff(t…) – выключить фразы диалога с номерами или тегами t…
prem(t…) – удалить (заблокировать) фразы диалога с номерами или тегами t… (удаление означает невозможность включения фраз. pon(t…) не приведет к включению фраз).
pseen(t…) – вернет true, если все заданные фразы диалога (заданными номерами или тегами) видимы.
punseen(t…) – вернет true, если все заданные фразы диалога (заданные номерами или тегами) невидимы.
Если параметр t не указан, действие относится к текущей фразе (в контексте которой был вызван код).
Если вам необходимо работать с фразами другого диалога (который не является в данный момент текущей комнатой), используйте вызовы в виде методов: комната:метод(), например, guard_dlg:pon('show_card').
Вы можете определить в диалоге выключенную фразу, а затем, включить ее:
povardlg = dlg {
nam ='На кухне';
hideinv =true;
entered =[[Передо мной полное лицо женщины -
повара в белом колпаке и усталым взглядом...]];
phr ={-- выключенная фраза{1,false, always =true,-- для наглядности, вы можете использовать переносы строк[[Дайте мне французских булок!]],[[Конечно...]]};-- знаем про булки, включить фразу{[[А что у вас там, на полке?]],[[Это французские булки.]],[[ pon(1) ]]};{ always =true,'Мне вот-этих зелененьких... Ага -- и бобов!','На здоровье!'};{ always =true,'Картошку с салом, пожалуйста!','Приятного аппетита!'};{ always =true,'Спасибо, мне ничего не нужно.','Как пожелаете.',[[ back() ]]};};};
Итак, как вы уже поняли, для идентификации фразы вы можете использовать номер, например:
{2,"Вопрос?","Ответ!"};
Для сложных диалогов более удобными являются теги, например:
{ tag ='exit',"Ну ладно, я пошел!", code [[ back() ]]};
Если вам не нужно идентифицировать фразу, просто опускайте первое поле:
{"Вопрос?","Ответ!"};
Как видно, тег – это текстовая метка фразы. Как уже было замечено, вы можете делать pon/poff/pseen/punseen как с пронумерованными фразами, так и с фразами, имеющими тег. В случае, если один и тот же тег стоит у нескольких фраз, то действие применяется на все фразы с одинаковым тегом. Для функции pseen, видимость тега означает наличие хотя бы одной фразы с таким тегом, для функции punseen – отсутствие включенных фраз с заданным тегом.
Вы можете присваивать тег и пронумерованной фразе, если это требуется.
Присутствие в фразе always = true означает, что фраза не будет автоматически выключена при ее срабатывании:
{ tag ='exit', always =true,"Ну ладно, я пошел!", code [[ back() ]]}
Если необходимо опустить ответ фразы и всю реакцию описать в параметре «необязательный код», то следующие варианты записи являются допустимыми:
{ tag ='exit', always =true,"Ну ладно, я пошел!",nil,[[ back() ]]},{ tag ='exit', always =true,"Ну ладно, я пошел!", code =[[ back() ]]}
Вы также можете задавать вопрос и ответ в виде функций или code.
{ tag ='exit', code [[ p "Ну ладно, я пошел!" ]],
code [[ p "Может, останешься?"; pon 'really' ]]},{ tag ='really',false,
always =true,"Я точно пошел!",function() back()end}-- эта фраза выключена и включается предыдущей
Вы можете группировать фразы диалога в ветви, тем самым организуя иерархические диалоги без необходимости массового использования pon/poff и перехода между несколькими dlg.
Группа фраз – это набор фраз, отделенная от другой группы фразой, у которой нет реакции (пример такой простейшей фразы это пара скобок {}). Например:
{'Расскажи что-нибудь о погоде?','Хорошо, что тебя интересует?',[[ psub 'погода' ]]},{ always=true,[[Пока!]], code =[[ back() ]]},{ tag ='погода'},{'Какая температура?','25 градусов!'},{'Какая влажность?','80%'},
В диалоге показывается только текущая группа фраз. В примере выше мы видим две группы. При входе в диалог, игрок увидит выбор из двух фраз: «Расскажи что-нибудь…» и «Пока!». Выбрав первую фразу, он попадет в подветку с тегом 'погода', в которой увидит два вопроса (о температуре и влажности). Когда он задаст оба вопроса, то переместится снова на первую ветку, где останется активной только одна фраза: «Пока!».
В данном примере группы разделены фразой: { tag = 'погода' }, но также точно разделителем могла стать пустая фраза без тега:
{'Расскажи что-нибудь о погоде?','Хорошо, что тебя интересует?',[[ psub 'погода' ]]},{ always="true",[[Пока!]], code =[[ back() ]]},{},{ tag ='погода','Какая температура?','25 градусов!'},{'Какая влажность?','80%'},
Переход на ветку осуществляется с помощью команд:
psub – переход с возвратом. Если все ответы ветки были исчерпаны или явно, с помощью pret();
pjump - безусловный переход;
pstart - безусловный переход с обнулением истории переходов по psub.
В качестве аргумента psub/pstart/pjump может быть указан номер или тег. Вы можете использовать эти функции и извне диалога, аналогично pon/poff и т.д, с помощью записи: диалог:метод(), например: shopdlg:pstart(1)
Для того, чтобы узнать текущую подветку, используйте методы диалога диалог:current() и диалог:curtag(). Первый всегда возвращает номер, а второй – тег.
Проверку состояния ветки можно осуществить с помощью функций:
диалог:empty([t]);
диалог:visible([t]);
Обе функции могут получать параметр – номер или тег фразы, с которой начинается анализ группы. :empty() возвращает true, в случае, если в группе нет активных фраз. :visible() возвращает число видимых фраз (0 – если группа пуста). В случае, если параметр не указан, анализируется текущая группа.
В случае перехода по psub/pstart/pjump, первая фраза, на который выполняется переход, может служить заголовком группы фраз.
Например:
{'Расскажи что-нибудь о погоде?', code =[[ psub 'погода' ]]},{ always=true,[[Пока!]], code =[[ back() ]]},{},{ tag ='погода',"Хорошо, что тебя интересует?"},{'Какая температура?','25 градусов!'},{'Какая влажность?','80%'},
Фраза с тегом 'погода' не содержит в себе реакцию, и выполняет роль заголовка ветки. При переходе на ветку 'погода' с помощью psub будет выведен текст «Хорошо, что тебя интересует?».
Как вы знаете, вопрос может быть функцией, тем самым позволяя выполнять код при переходе между ветками:
{'Расскажи что-нибудь о погоде?', code =[[ psub 'погода' ]]},{ always=true,[[Пока!]], code =[[ back() ]]},{},{ tag ='погода',function()
p "Хорошо, что тебя интересует?";
weather_asked =true;end},{'Какая температура?','25 градусов!'},{'Какая влажность?','80%'},
Кроме того, заголовок группы может содержать метод empty, который вызывается в ситуации, когда все вопросы данной группы исчерпаны:
{'Расскажи что-нибудь о погоде?', code =[[ psub 'погода' ]]},{ always=true,[[Пока!]], code =[[ back() ]]},{},{ tag ='погода',"Хорошо, что тебя интересует?",
empty = code [[ p "Хватит о погоде..."; pret() ]]},{'Какая температура?','25 градусов!'},{'Какая влажность?','80%'},
empty вызывается в ситуации, когда в ветке не остается фраз. Если empty не определена, то действие по-умолчанию это возврат по pret(). Если вы переопределяете empty, вам придется вызвать pret() явно, если это требуется.
На самом деле, написание диалога не такая сложная штука, как может показаться. В большинстве случаев используется небольшое подмножество возможностей движка.
Данный фрагмент документации содержит описание устаревшего синтаксиса, тем не менее, если вам не понятна идея диалогов из предыдущей части, возможно, этот фрагмент вам поможет, так как некоторые вещи являются общими для обоих вариантов диалогов.
Простейший диалог в старом синтаксисие может выглядеть следующим образом:
povardlg = dlg {
nam ='На кухне';
dsc =[[Передо мной полное лицо женщины -
повара в белом колпаке и усталым взглядом...]];
obj ={[1]= phr('Мне вот-этих зелененьких... Ага -- и бобов!','На здоровье!'),[2]= phr('Картошку с салом, пожалуйста!','Приятного аппетита!'),[3]= phr('Две порции чесночного супа!!!','Прекрасный выбор!'),[4]= phr('Мне что-нибудь легонькое, у меня язва...','Овсянка!'),};};
phr – создание фразы. Фраза содержит вопрос, ответ и реакцию (реакция в данном примере отсутствует). Когда игрок выбирает одну из фраз, фраза отключается. Когда все фразы отключатся диалог заканчивается. Реакция – это строка кода на lua, который выполнится после отключения фразы. Например:
food = obj {
nam ='еда',
inv =function(s)
iremove('food', inv());
p 'Я ем.';end};
gotfood =function(w)
take 'food';
food._num = w;
back();end
povardlg = dlg {
nam ='На кухне';
dsc =[[Передо мной полное лицо женщины -
повара в белом колпаке и усталым взглядом...]];
obj ={[1]= phr('Мне вот-этих зелененьких... Ага -- и бобов!','На здоровье!',[[pon(); gotfood(1);]]),[2]= phr('Картошку с салом, пожалуйста!','Приятного аппетита!',[[pon(); gotfood(2);]]),[3]= phr('Две порции чесночного супа!!!','Прекрасный выбор!',[[pon();gotfood(3);]]),[4]= phr('Мне что-нибудь легонькое, у меня язва...','Овсянка!',[[pon(); gotfood(4);]]),};};
В данном примере, игрок выбирает еду. Получает ее (запомнив выбор в переменной food._num) и возвращается обратно (в ту сцену откуда попал в диалог).
В реакции может быть любой lua код, но, как и в случае с расширенными диалогами, обычно в нем содержится логика по управлению фразами. pon/poff/prem/pseen/punseen работают только с номерами (так как тегов здесь нет).
Вы можете переходить из одного диалога в другой диалог, организовывая иерархические диалоги.
Также, вы можете прятать некоторые фразы при инициализации диалога и показывать их при некоторых условиях.
facectrl = dlg {
nam ='Фэйсконтроль';
dsc ='Я вижу перед собой неприятное лицо полного охранника.';
obj ={[1]= phr('Я пришел послушать лекцию Белина...',[[-- Я не знаю кто вы -- ухмыляется охранник --
но мне велели пускать сюда только приличных людей.]],[[pon(2);]]),[2]= _phr('У меня есть приглашение!',[[-- А мне плевать! Посмотри на себя в зеркало!!!
Ты пришел слушать самого Белина -- правую руку самого...
-- охранник почтительно помолчал -- Так что пошел вон..]],[[pon(3,4)]]),[3]= _phr('Сейчас я дам тебе по роже!','-- Ну все... Мощные руки выталкивают меня в коридор...',[[poff(4)]]),[4]= _phr('Ты, кабан! Я же тебе сказал -- у меня есть приглашение!',[[-- Чтоооооо? Глаза охранника наливаются кровью...
Мощный пинок отправляет меня в коридор...]],[[poff(3)]]),};exit=function(s,w)
s:pon(1);end;};
_phr – создает выключенную фразу, которую можно включить. Данный пример показывает также возможность использования методов pon, poff, prem для диалога (см. exit).
15. Облегченные объекты
Иногда сцену нужно наполнить декорациями, которые обладают ограниченной функциональностью, но делают игру разнообразней. Или вы хотите сделать что-то вроде кнопки «дальше».
Для таких вещей можно использовать облегченный объект. Например:
sside = room {
nam ='Южная сторона';
dsc =[[Я нахожусь у южной стены здания института. ]];
act =function(s, w)if w =="подъезд"then
ways():add('stolcorridor');
p [[Я подошел к подъезду. На двери подъезда надпись --
'Столовая'. Хм -- зайти внутрь?]];elseif w =="люди"then
p 'Те, кто выходят, выглядят более довольными...';endend;
obj ={ vobj("подъезд","У восточного угла находится небольшой {подъезд}."),
vobj("люди",[[Время от времени дверь подъезда хлопает
впуская и выпуская {людей}.]])},};
Как видим, vobj позволяет сделать легкую версию статического объекта, с которым, тем не менее, можно взаимодействовать (за счет определения обработчика act в сцене и анализа имени объекта). vobj также вызывает метод used, при этом в качестве третьего параметра передается объект, воздействующий на виртуальный объект. Если вы используете предмет на vobj, то как и с обычными объектами, у предмета инвентаря вызовется use. Но объекты vobj обычно не имеют дескриптора, поэтому, для определения страдательного объекта можно воспользоваться stead.nameof.
use =function(s, w)if stead.nameof(w)=="люди"then
p "Не стоит беспокоить людей."returnendend;
Синтаксис vobj прост: vobj(имя, описатель); vobj можно добавлять в сцену динамически, например:
put(vobj("дальше","{Дальше}"));
Хотя я не рекомендую этот стиль, он больше характерен для старых версий движка. Нагляднее использовать disable/enable;
Существует модификация объекта vobj – vway. vway реализует ссылку-переход.
Синтаксис vway: vway(имя, описатель, сцена назначения); например:
obj ={ vway("дальше","Нажмите {здесь}.",'nextroom')};-- при нажатии - перейдем в nextroom
На самом деле, если вы пишите что-то вроде книги-игры, где игровой процесс представляет из себя переход по ссылкам, то (если не считать, что это неудачная идея для вашей первой игры) вам следует воспользоваться модулем «xact», в котором реализован более простой механизм создания ссылок.
Вы можете динамически заполнять сцену объектами vway (аналогично vobj). Например:
put(vway("next","{Дальше}.",'next_room'));-- другой способ, явно вызывая метод списка
objs():add(vway("next","{Дальше}.",'next_room'))
Следует понимать, что и vobj и vway это обычные объекты, с заранее определенными обработчиками и функциями сохранения (что позволяет создавать эти объекты на лету, как показано в примерах выше). Когда вы узнаете архитектуру движка INSTEAD, вы сможете писать свои варианты объектов с требуемыми свойствами.
Говоря об облегченных объектах, хотелось бы обратить внимание еще на один способ описания декораций. Если объект в сцене является статическим, то его можно определить непосредственно в obj, без присваивания дескриптора. Например:
hall = room {
nam ='Гостинная';
dsc =[[Я в просторной гостинной.]];
obj ={
obj {
nam ='стол';
dsc =[[Посреди гостинной находится {стол}.]];
act =[[Из красного дерева.]];};};}
В обработчике use вы можете идентифицировать такие объекты также, как и vobj:
use =function(s, w)if stead.nameof(w)=='стол'then
p [[Не хочется портить красивую вещь.]]returnendend
Использовать или нет такую форму решать вам, многие считают, что присвоение дескриптора всем объектам делает код более понятным. Я в своих играх использую оба подхода.
Наконец, еще один способ для создания декораций, это использование одного и того-же объекта в разных сценах. Например, можно создать объект «гильзы дробовика», и выбрасывать его на сцену всегда, когда герой стреляет. Понятно, что в таком случае, гильзы могут служить только в качестве декораций, их нельзя будет взять и вообще произвести какое-то действие, меняющее состояние.
16. Динамические события
Вы можете определять обработчики, которые выполняются каждый раз, когда время игры увеличивается на 1. Обычно, это имеет смысл для живых персонажей, или каких-то фоновых процессов игры. Алгоритм шага игры выглядит примерно так:
Игрок нажимает на ссылку;
Реакция act, use, inv, осмотр сцены (клик по названию сцены) или переход в другую сцену;
Динамические события;
Вывод состояния сцены (если нужно статическая часть, и всегда – динамическая).
Например, сделаем Барсика живым:
mycat = obj {
nam ='Барсик';
lf ={[1]='Барсик шевелится у меня за пазухой.',[2]='Барсик выглядывает из-за пазухи.',[3]='Барсик мурлычит у меня за пазухой.',[4]='Барсик дрожит у меня за пазухой.',[5]='Я чувствую тепло Барсика у себя за пазухой.',[6]='Барсик высовывает голову из-за пазухи и осматривает местность.',};
life =function(s)local r = rnd(5);if r >2then-- делать это не всегдаreturn;end
r = rnd(#s.lf);-- символ # -- число элементов в массиве
p(s.lf[r]);-- выводим одно из 6 состояний Барсикаend;....-- и вот момент в игре, когда Барсик попадает к нам за пазуху!
take 'mycat'-- добавить в инвентарь
lifeon 'mycat'-- оживить Барсика!....
Любой объект (в том числе и сцена) могут иметь свой обработчик life, который вызывается каждый такт игры, если объект был добавлен в список живых объектов с помощью lifeon. Не забывайте удалять живые объекты из списка с помощью lifeoff, когда они больше не нужны. Это можно сделать, например, в обработчике left, или любым другим способом.
Если в вашей игре много «живых» объектов, вы можете задавать им приоритеты. Для этого, воспользуйтесь вторым числовым параметром (целое неотрицательное число) lifeon, чем меньше число, тем выше приоритет. 1 – самый высокий.
Если вам нужен фоновый процесс в какой-то комнате, запускайте его в entered и удаляйте в left, например:
podval = room {
nam ='В подвале';
dsc =[[Тут темно!]];
entered =function(s)
lifeon(s);end;
left =function(s)
lifeoff(s);end;
life =function(s)if rnd(10)>8then
p [[Я слышу какие-то шорохи!]];-- изредка пугать игрока шорохамиendend;
way ={'upstair'};}
Если вам нужно определить, был ли переход игрока из одной сцены в другую, воспользуйтесь player_moved.
flash = obj {
nam ='фонарик';
var { on =false};
life =function(s)if player_moved()then-- гасить фонарик при переходах
s.on =false
p "Я выключил фонарик."returnendend;...}
Для отслеживания протекающих во времени событий, используйте time() или вспомогательную переменную-счетчик. Для определения местоположения игрока – here(). Для определения факта, что объект «живой» – live().
dynamite = obj {
nam ='динамит';
var {
timer =0;};
used =function(s, w)if w == fire thenif live(s)thenreturn"Уже горит!"end
p "Я поджег динамит."
lifeon(s)endend;
life =function(s)
s.timer = s.timer +1if s.timer ==5then
lifeoff(s)if here()== where(s)then
p [[Динамит взорвался рядом со мной!]]else
p [[Я услышал, как взорвался динамит.]];endendend;...}
Если life обработчик возвращает текст события, он печатается после описания сцены.
Вы можете вернуть из обработчика life второй код возврата, важность. (true или false). Например:
p 'В комнату вошел охранник.'returntrue
Или:
return'В комнату вошел охранник.',true
При этом текст события будет выведен до описания объектов.
Если вы хотите блокировать life обработчики в какой-то из комнат, воспользуйтесь модулем nolife. Например:
Отдельно стоит рассмотреть вопрос перехода игрока из life обработчика. Если вы собираетесь использовать функции walk… внутри life, то вам следует учитывать следующее поведение.
Если life переносит игрока в новую локацию, то подавляется весь вывод, который произведен life обработчиками объектов с прошлой сцены (то есть, все предыдущие life выводы). Остается только вывод life обработчиков, сработавших после перехода. Это сделано специально, так как вывод прошлых life относился к прошлой сцене, например:
life сцены скала вывел текст о том, что герою страшно, когда он висит на тросе;
life объекта трос вывел текст о том, что трос оборвался и герой упал вниз и сделал walk в новую локацию море;
Здесь life1 выполнялся в ином контексте и его вывод подавляется.
Кроме всего прочего, обработчик life может влиять на текст реакций действий игрока, которые произошли в этом игровом такте. Например, рассмотрим такую ситуацию:
Игрок осмотрел окно («Я выглянул в окно. Унылый пейзаж.»);
life обработчик гоблин сообщил, что внезапно дверь открылась и в комнату влетел гоблин;
Автору игры может показаться, что информация о пейзаже, когда перед игроком стоит свирепый гоблин, неуместна. Тогда он пишет в life обработчике:
p [[Свирепый гоблин влетел в комнату!]];
ACTION_TEXT =nil-- текст реакции пуст (раньше он был равен -- "Я выглянул в окно. Унылый пейзаж.")
Таким образом, ACTION_TEXT это текстовая переменная, доступная в life обработчике для модификации. Обычно, имеет смысл или не трогать ее, или обнулять, как в примере выше.
При выполнении перехода из life метода следует также иметь в виду, что player_moved (выполненный внутри life метода) не учитывает такие переходы. Они будут учтены только после завершения всех life методов этого такта. Начиная с версии 2.2.0 вы можете воспользоваться life_moved для определения факта перехода выполненного из предыдущих life методов.
17. Графика
Графический интерпретатор INSTEAD анализирует атрибут сцены pic, и воспринимает его как путь к картинке, например:
home = room {
pic ='gfx/home.png';
nam ='Дома';
dsc ='Я у себя дома';};
Важно!
Используйте в путях только прямые '/'. Также, настоятельно рекомендуется использовать в именах каталогов и файлов только латинские строчные символы. Этим самым вы обезопасите свою игру от проблем с совместимостью и она будет работать на всех архитектурных платформах, куда портирован INSTEAD.
Конечно, pic может быть функцией, расширяя возможности разработчика.
Если в текущей сцене не определен атрибут pic, то берется атрибут game.pic. Если не определен и он, то картинка не отображается.
Поддерживаются все наиболее распространенные форматы изображений, но я рекомендую вам использовать png и (когда важен размер) jpg.
Вы можете использовать в качестве картинок анимированные gif файлы.
Вы можете встраивать графические изображения прямо в текст, в том числе в инвентарь, переходы, заглавия комнат и dsc с помощью функции img. Например:
apple = obj {-- склеить текстовую строку с изображением
nam ='яблоко '..img ('img/apple.png');}
Хотя, в данном случае предпочтительнее воспользоваться disp:
apple = obj {
nam ='яблоко';
disp ='яблоко '..img('img/apple.png');}
Теперь мы разделили имя объекта и его отображение.
Тем-не менее, картинку сцены всегда следует оформлять в виде pic атрибута, а не вставки img в dsc комнаты.
Дело в том, что картинка сцены масштабируется по другому алгоритму. Картинки img масштабируются в соответствии с настройками INSTEAD (масштаб темы), а pic – учитывает также размер картинки.
Кроме того, картинки pic обладают и другими свойствами, например, возможностью отслеживания координат кликов мышью.
Если вы поместите img внутрь { и }, то получите графическую ссылку.
apple = obj {
nam ='яблоко';
disp ='яблоко '..img('img/apple.png');
dsc =function(s)
p ("На полу лежит {яблоко",
img 'img/apple.png',"}");-- другие варианты:-- return "На полу лежит {яблоко"..img('img/apple.png').."}";-- p "На полу лежит {яблоко"..img('img/apple.png').."}";-- или dsc = "На полу лежит {яблоко"..img('img/apple.png').."}";end;}
INSTEAD поддерживает обтекание картинок текстом. Если картинка вставляется с помощью функции imgl/imgr, она будет расположена у левого/правого края.
Важно!
Картинки, вставленные в текст с помощью imgl/imgr не могут быть ссылками!!! Используйте их только в декоративных целях.
Для задания отступов вокруг изображения используйте pad, например:
imgl 'pad:16,picture.png'-- отступы по 16 от каждого края
imgl 'pad:0 16 16 4,picture.png'-- отступы: вверху 0, справа 16, внизу 16, слева 4
imgl 'pad:0 16,picture.png'-- отступы: вверху 0, справа 16, внизу 0, слева 16
Вы можете использовать псевдо-файлы для изображений прямоугольников и пустых областей:
Таким образом, составная картинка представляет собой набор путей к изображениям, разделенных символом ;. Вторая и последующие компоненты могут содержать постфикс в виде @x_координата,y_координата, где координате 0,0 соответствует левый верхний угол всего изображения. Общий размер картинки считается равным общему размеру первой компоненте составной картинки, то есть, первый компонент (в нашем примере – gfx/mycat.png) играет роль холста, а последующие компоненты накладываются на этот холст.
Наложение происходит для левого верхнего угла накладываемой картинки. Если вам нужно, чтобы наложение происходило относительно центра накладываемой картинки, используйте перед координатами префикс c, например:
pic ='gfx/galaxy.png;gfx/star.png@c128,132';
Оформив в виде функции формирование пути составной картинки, вы можете генерировать изображение на основе игрового состояния.
Если вы в своей игре привязываетесь к каким-то координатам изображений, или к их размерам, делайте это относительно оригинальных размеров изображений. При масштабировании темы под заданное игроком разрешение, INSTEAD сам будет осуществлять пересчёт координат (при этом координаты для игры выглядят так, как будто игра запущена без масштабирования). Однако, возможны небольшие погрешности вычислений.
Если вам не хватает функций, описанных в этой главе, изучите модуль «sprites», который предоставляет более широкие возможности по графическому оформлению. Но я крайне не рекомендую делать это в своей первой игре.
18. Музыка
Интерпретатор проигрывает в цикле текущую музыку, которая задается с помощью функции:
set_music(путь к музыкальному файлу).
Важно!
Используйте в путях только прямые '/'. Также, настоятельно рекомендуется использовать в именах каталогов и файлов только латинские строчные символы. Этим самым вы обезопасите свою игру от проблем с совместимостью и она будет работать на всех архитектурных платформах, куда портирован INSTEAD.
Поддерживается большинство музыкальных форматов, но настоятельно рекомендуется использовать формат ogg, так как именно он поддерживается наилучшим образом во всех версиях INSTEAD (для различных платформ).
Важно!
Следует проявлять осторожность при использовании трекерной музыки, так как в некоторых дистрибутивах Linux могут быть проблемы при проигрывании определенных файлов (ошибки в связке библиотек SDL_mixer и libmikmod).
Также, если вы используете mid файлы, будьте готовы к тому, что игрок услышит их только в Windows версии INSTEAD (так как в большинстве случаев, Unix версии SDL_mixer собраны без поддержки timidity).
Например:
street = room {
pic ='gfx/street.png';
enter =function()
set_music('mus/rain.ogg')end;
nam ='на улице';
dsc ='На улице идет дождь.';};
get_music() возвращает текущее имя трека.
В функцию set_music() можно передавать второй параметр – количество проигрываний (циклов). Получить текущий счетчик можно с помощью get_music_loop. 0 – означает вечный цикл. 1..n – количество проигрываний. -1 – проигрывание текущего трека закончено.
Часто бывает необходимым сменить музыку на время, а затем восстановить предыдущий трек. Для этого можно воспользоваться функциями save_music()/restore_music(). Эта пара функций запоминает/восстанавливает трек в/из переменных объекта текущего контекста. Например, для обработчиков enter/exit/entered/left это будет текущая комната.
Если вы хотите явно задать объект, в котором будет сохранено состояние (из которого будет восстановлено состояние) трека, укажите его в качестве необязательного параметра. Данные функции не работают с ссылками, поэтому вы не можете передавать в них текстовые строки-указатели на объекты.
Например:
street = room {
pic ='gfx/street.png';
entered =function()
save_music();
set_music('mus/rain.ogg')end;
left = restore_music;
nam ='на улице';
dsc ='На улице идет дождь.';};
Обратите внимание, что в примере left обработчику присваивается значение restore_music. Отсутствие () после restore_music означает, что это присваивание самого кода функции, а не ее вызов. Таким образом, при вызове обработчика left, будет вызвана restore_music с параметром равным street, что нам и требуется. Впрочем, можно было написать и такой код:
left =function()
restore_music()end;
Или:
restore_music(street)
Для того, чтобы отменить проигрывание музыки, вы можете использовать stop_music()
Функция is_music() позволяет узнать, проигрывается ли музыка в данный момент.
Вы можете задавать время нарастания и затухания музыки, с помощью вызова:
stead.set_music_fading(o,[i])
Здесь o - время в мс. для затухания и i - время в мс. для нарастания музыки. Если задан только один параметр – оба времени считаются одинаковыми. После вызова, установленные параметры будут влиять на проигрывание всех музыкальных файлов.
Для проигрывания звуков используйте set_sound(). Настоятельно рекомендуется использовать формат ogg, хотя большинство распространенных звуковых форматов также будет работать.
Различие между музыкой и звуковым файлом заключается в том, что движок следит за процессом проигрывания музыки и сохраняет/восстанавливает текущий проигрываемый трек. Выйдя из игры и загрузив ее снова, игрок услышит то же музыкальное оформление, что слышал при выходе. Звуки обычно означают кратковременные эффекты, и движок не сохраняет и не восстанавливает звуковые события. Так, если игрок не успел дослушать звук выстрела и вышел из игры, после загрузки файла сохранения он не
услышит звук (или его окончание) снова.
Тем не менее, если учесть то, что set_sound позволяет запускать зацикленные звуки, то различие между музыкой и звуками становится уже не таким однозначным.
Итак, определение функции: set_sound(файл, [канал], [цикл]), где:
файл – путь и\или имя звукового файла;
канал – номер канала [0..7]; Если не указан, то выберется первый свободный.
цикл – количество проигрываний 1..n, 0 – зацикливание.
Для остановки проигрывания звука можно использовать stop_sound() Для остановки звука в определенном канале stop_sound(канал).
На самом деле, set_sound имеет одну особенность. Если вы вызовете эту функцию подряд несколько раз, то эффект принесет только последний вызов. Если вам нужно запускать несколько одновременных звуков за один такт игры, используйте add_sound(). Параметры к функции имеют тот же смысл, что и у set_sound().
Важно!
Если вы используете зацикленные звуки, вам придется самим восстанавливать их состояние (запускать снова с помощью set_sound()/add_sound()) в функции start().
Если вам не достаточно описанных здесь функций по работе со звуком, используйте модуль «sound».
19. Форматирование и оформление вывода
Обычно INSTEAD сам занимается форматированием и оформлением вывода. Например, отделяет статическую сцену от динамической. Выделяет курсивом действия игрока. Переводит фокус на изменение в тексте и т.д. Модули вроде «quotes», «para» и подобные улучшают качество вывода игры без дополнительных усилий со стороны автора.
Например, я рекомендую всегда включать в вашу игру следующие модули:
И ваша игра будет выглядеть гораздо лучше. Если вам нужна какая-то автоматическая обработка выводимого текста, вы можете включить модуль «format» и определить функцию format.filter. Например:
instead_version "1.8.2"require"format"format.filter =function(s)return s..'^Эта строка будет добавлена к выводу';end
Многие хорошие игры на INSTEAD никак не занимаются своим оформлением, кроме разбиения текста dsc на параграфы с помощью символов '^^', поэтому подумайте, а так ли вам хочется заниматься оформлением своей игры вручную?
Тем не менее, иногда это все-таки необходимо.
Форматирование
Вы можете делать простое форматирование текста с помощью функций:
txtc(строка) - разместить по центру;
txtr(строка) - разместить справа;
txtl(строка) - разместить слева;
txttop(строка) - сверху строки;
txtbottom(строка) - снизу строки;
txtmiddle(строка) - середина строки (по умолчанию);
Например:
main = room {
nam ='Intro';
dsc = txtc 'Добро пожаловать!';-- если у функции только 1 параметр, -- скобки можно опускать;}
Вышеописанные функции влияют не только на текст, но и на изображения, вставленные с помощью img().
Следует отметить, что если вы используете несколько функций форматирования, то предполагается, что они относятся к разным строкам (параграфам). В противном случае, результат не определен. Разбивайте текст на абзацы символами ^ или pn().
INSTEAD при выводе удаляет лишние пробелы. Это значит, что неважно сколько пробелов вы вставляете между словами, все равно при выводе они не будут учитываться для расчета расстояния между словами. Иногда это может стать проблемой.
Вы можете создавать неразрывные строки с помощью: txtnb(строка). Например, модуль «para» использует неразрывные строки для создания отступов в начале параграфов. Также, txtnb может оказаться удобной для вывода служебных символов. Можно сказать, что вся строка-параметр txtnb воспринимается движком как одно большое слово.
Еще один пример. Если вы используете подчеркивание текста, то промежутки между словами не будут подчеркнуты. При использовании txtnb промежутки также будут подчеркнуты.
INSTEAD не поддерживает отображение таблиц, однако для вывода простых табличных данных можно воспользоваться txttab(). Эта функция используется для абсолютного позиционирования в строке (табулятор).
txttab(позиция, [центр])
Позиция, это текстовый или числовой параметр. Если задан числовой параметр, он воспринимается как позиция в пикселях. Если он задан в виде строкового параметра число%, то он воспринимается как позиция, выраженная в процентах от ширины окна вывода сцены.
Необязательный строковой параметр центр задает позицию в следующем за txttab слове, которая будет размещена по указанному смещению в строке. Позиции могут быть следующими:
left;
right;
center;
По-умолчанию считается что задан параметр «left».
Так, например:
main = room {
nam ='Начало';-- размещение 'Начало!' по центру строки
dsc = txttab('50%','center')..'Начало!';}
Конечно, не очень удачный пример, так как то же самое можно было сделать с помощью txtc(). Более удачный пример.
main = room {
nam ='Начало';
dsc =function(s)
p(txttab '0%')
p "Слева";
p(txttab '100%','right')
p "Справа";end}
На самом деле, единственная ситуация, когда применение txttab оправдано – это вывод табличных данных.
Следует отметить, что в ситуации, когда мы пишем что-то вроде:
-- размещение 'Раз' по центру строки
dsc = txttab('50%','center')..'Раз два три!';
Только слово 'Раз' будет помещено в центр строки, остальные слова будут дописаны справа от этого слова. Если вы хотите центрировать 'Раз два три!' как одно целое, воспользуйтесь txtnb().
-- размещение 'Раз два три!' по центру строки
dsc = txttab('50%','center')..txtnb 'Раз два три!';
В INSTEAD также существует выполнять простое вертикальное форматирование. Для этого используйте вертикальный табулятор:
txty(позиция, [центр])
Как и в случае с txttab позиция, это текстовый или числовой параметр. Здесь он воспринимается как позиция строки, выраженная в пикселях или процентах от высоты области сцены. Например, 100% – соответствует нижней границе области сцены. 200% – соответствует нижней границе второй страницы вывода (две высоты области вывода сцены).
Необязательный строковой параметр центр задает позицию внутри строки, относительно которой выполняется позиционирование:
top; (по верхнему краю)
middle; (по центру)
bottom; (по нижнему краю – значение по умолчанию)
Следует отметить, что txty работает целиком для строки. Если в строке встретится несколько txty, действовать будет последний из табуляторов.
-- размещение 'ГЛАВА I' - в центре сцены
dsc = txty('100%').."ГЛАВА I";
Если позиция, указанная табулятором, уже занята другой строкой, табулятор игнорируется.
По умолчанию, статическая часть сцены отделяется от динамической двойным переводом строки. Если вам это не подходит, вы можете переопределить stead.scene_delim, например:
instead_version "1.8.2"
stead.scene_delim ='^'-- одинарный перевод строки
Вы не можете менять эту переменную в обработчиках, так как она не сохраняется, но вы можете задать ее для игры целиком, или восстанавливать ее вручную в функции start().
Если вас категорически не устраивает то, как INSTEAD формирует вывод (последовательность абзацов текста), вы можете переопределить функцию iface.fmt, которая по умолчанию выглядит следующим образом:
iface.fmt =function(self, cmd, st, moved, r, av, objs, pv)-- st -- changed state (main win), move -- loc changedlocal l, vv
if st then
av = txtem(av);-- вывод "важных" life
pv = txtem(pv);-- вывод обычных life
r = txtem(r)-- реакция на действиеif isForcedsc(stead.here())or NEED_SCENE then
l = stead.here():scene();-- статическая часть сценыendendif moved then-- компонуем вывод для случая, -- когда игрок перешел в новую комнату
vv = stead.fmt(stead.cat(
stead.par(stead.scene_delim, r, l, av, objs, pv),'^'));else-- компонуем вывод, когда игрок не делал перехода
vv = stead.fmt(stead.cat(
stead.par(stead.scene_delim, l, r, av, objs, pv),'^'));endreturn vv
end
Тот факт, что я привел здесь этот код, не означает, что я рекомендую переопределять эту функцию. Напротив, я категорически против такой сильной привязки к форматированию текста. Тем не менее, иногда возникает ситуация, когда полный контроль за последовательностью вывода необходим. Если вы пишите свою первую игру, просто пропустите этот текст.
Оформление
Вы можете менять начертание текста с помощью комбинаций функций:
txtb(строка) - жирный текст;
txtem(строка) - курсив;
txtu(строка) - подчеркнутый текст;
txtst(строка) - перечеркнутый текст;
Например:
main = room {
nam ='Intro';
dsc =function()
p ('Вы находитесь в комнате ')
p (txtb 'main','.');end;}
Используя функции txtu и txtst на строках, содержащих пробелы, вы получите разрывы линий в этих местах: текстcпропусками. Что избежать этого, можно превратить текст в неразрывную строку:
txtu( txtnb "теперь текст без пропусков")
Имейте ввиду, что символ перевода на новую строку ^ или конструкции xact внутри txtxnb трактуются именно как обычный тест
Строго говоря, INSTEAD не поддерживает одновременный вывод разными шрифтами в окно сцены (если не считать разное начертание), поэтому если вам все-таки требуется более гибкий контроль вывода, вы можете сделать следующее:
Использовать графические вставки img();
Использовать модуль fonts, в котором реализована отрисовка разными шрифтами за счет модуля sprite;
Использовать другой движок, так как скорее всего вы используете INSTEAD не по назначению.
20. Конструкторы и наследование
Внимание!
Если вы пишите свою первую игру, было бы лучше, если бы она была простая. Для простой игры информация из этой главы не понадобится. Более того, 90% игр на INSTEAD не использует вещей, описанных в этой главе!
Если вы пишите игру, в которой много однотипных объектов, возможно, вам захочется упростить их создание.
Конструктор, это функция, которая создает объект. На самом деле конструкции obj, room, dlg – все это конструкторы. Когда вы пишите что-то вроде:
apple = obj {
nam ='яблоко';}
Вызывается функция obj в качестве параметра к которой передается таблица { nam = 'яблоко' }; Зная это, вы можете писать свои конструкторы. Например, рассмотрим такую задачу. Нужно создавать окна, любое окно можно разбить. Мы можем написать конструктор window.
window =function(v)
v.window =trueif v.nam ==nilthen
v.nam ='окно'endif v.dsc ==nilthen
v.dsc ='Здесь есть {окно}'end
v.act =function(s)if s._broken then
p [[Окно разбито.]]else
p [[За окном темно.]]endendif v.used ==nilthen
v.used =function(s, w)if w == hammer thenif s._broken then
p [[Окно уже разбито.]]else
p [[Я разбил окно.]]
s._broken =true;endendendendreturn obj(v)end
Мы видим, что функция window заполняет некоторые атрибуты и обработчики (позволяя игроку переопределить некоторые из них), а потом вызывает функцию создания объекта и возвращает новенький объект.
Теперь, можно создавать объекты окна:
win1 = window {
dsc ="В восточной стене есть {окно}.";}
Или, так как окно это обычно статический объект, можно создавать его прямо в obj.
obj ={ window {
dsc ='В восточной стене есть {окно}.';}};
Если вам нравится более классический синтаксис оформления конструктора в виде функции, принимающей несколько параметров вместо одной таблицы (примеры: vroom, vobj, vway и подобные), то можно было бы определить конструктор так:
window =function(nam, dsc)local v ={}-- создаем пустую таблицу-- заполняем ее
v.window =true
v.nam ='окно'if dsc ==nilthen
v.dsc ='Здесь есть {окно}'end
v.act =function(s)if s._broken then
p [[Окно разбито.]]else
p [[За окном темно.]]endend
v.used =function(s, w)if w == hammer thenif s._broken then
p [[Окно уже разбито.]]else
p [[Я разбил окно.]]
s._broken =true;endendendreturn obj(v)-- создаем объектend
Тогда вызов конструктора будет выглядеть по-другому:
obj ={ window ('окно','В восточной стене есть {окно}.')}
На самом деле, оба подхода применимы, но в разных ситуациях. Если для создания объекта достаточно указать два-три атрибута, то проще и наглядней оформлять конструктор как функцию с несколькими параметрами (как vroom).
Если же предполагается, что объекту могут быть присвоены различные (в том числе и необязательные) атрибуты, то проще делать конструктор в виде функции, принимающей таблицу (как obj/room и др.)
Отдельно стоит рассмотреть вопрос добавления новых переменных. В нашем примере мы использовали переменную с префиксом _, так как такие переменные можно создавать на лету. Другим способом является использование stead.add_var(), например:
window =function(v)
stead.add_var(v,{ broken =false})-- добавить переменные к ''v'';
v.window =true-- ... пропущеноif v.used ==nilthen
v.used =function(s, w)if w == hammer thenif s.broken then
p [[Окно уже разбито.]]else
p [[Я разбил окно.]]
s.broken =true;endendendendreturn obj(v)end
Если вы хотите добавить глобальную переменную (на лету), используйте синтаксис:
Теперь, если вы поняли что-такое конструктор, вы можете перейти к такому понятию, как наследование.
На самом деле, в примерах выше уже используется наследование. Действительно, ведь конструктор window вызывает другой конструктор obj, тем самым получая все свойства обычного объекта. Также, window определяет переменную признак window, чтобы в игре мы могли понять, что мы имеем дело с окном. Например:
use =function(s, w)if w.window then
p [[Я посветил фонариком в окно.]]returnendend
Для иллюстрации механизма наследования создадим класс объектов treasure, те. сокровищ.
global { score =0}
treasure =function()local v ={}
v.nam ='сокровище'
v.treasure =true
v._points =100
v.dsc =function(s)
p ('Здесь есть {', stead.dispof(s),'}.')end;
v.inv =function(s)
p ('Это же ', stead.dispof(s),'.');end;
v.tak =function(s)
score = score + s.points;-- увеличим счет
p [[Дрожащими руками я забрал сокровища.]];endreturn obj(v)end
А теперь, на его основе создадим золото, алмаз и сундук.
gold =function(dsc)local v = treasure();
v.nam ='золото';
v.gold =true;
v.points =50;
v.dsc = dsc;return v
end
diamond =function(dsc)local v = treasure();
v.nam ='алмаз';
v.diamond =true;
v.points =200;
v.dsc = dsc;return v
end
chest =function(dsc)local v = treasure();
v.nam ='сундук';
v.chest =true
v.points =1000;
v.dsc = dsc;return v
end
Теперь, в игре можно создавать сокровища через конструкторы:
diamond1 = diamond("В грязи я заметил {алмаз}.")
diamond2 = diamond();-- тут будет стандартное описание алмаза
gold1 = gold("В углу я заметил блеск {золота}.");
cave = room {
nam ='пещера';
obj ={'diamond1','gold1',
chest("А еще я вижу {сундук}!")};}
На самом деле, как именно писать функции-конструкторы и реализовывать принцип наследования, зависит только от вас. Выберете наиболее простой и понятный способ.
При написании конструкторов иногда бывает полезным сделать вызов обработчика так, как это делает INSTEAD. Для этого используется stead.call(объект, метод, параметры), при этом эта функция вернет реакцию атрибута в виде строки. Например, рассмотрим модификацию window, которая заключается в том, что можно определять свою реакцию на осмотр окна, которая будет выполнена после стандартного сообщения о том, что это разбитое окно (если оно разбито).
window =function(nam, dsc, what)local v ={}-- создаем пустую таблицу-- заполняем ее
v.window =true
v.nam ='окно'
v.what = what
if dsc ==nilthen
v.dsc ='Здесь есть {окно}'end
v.act =function(s)if s._broken then
p [[Окно разбито.]]endlocal r = stead.call(s,'what')if r then
p(r)else
p [[За окном темно.]]endend...
Таким образом, мы можем при создании окна задать третий параметр, в котором определить функцию или строку, которая будет реакцией во время осмотра окна. При этом сообщение о том, что окно разбито (если оно действительно разбито), будет выведено перед этой реакцией.
В качестве завершающего примера, рассмотрим свою версию реализации vway, назовем ее xway (Впрочем, в lua можно переопределять функции. Вы можете, например, написать свою версию vway и она заменит ту, что определена в INSTEAD).
function xway(name, dsc, w)return obj {
nam = name;
dsc = dsc;
act =function(s)
walk(s.where)end;
where = w;};end
Как видим, xway реализован как объект, который при клике выполняет xwalk в заданную при создании xway комнату.
На самом деле, у xway есть один недостаток, по сравнению с vway. Это невозможность создания объектов на лету, например, если вы напишите:
put (xway('в пещеру','{В пещеру}','cave'));
То INSTEAD не сможет сохранить этот объект. Это произойдет потому, что вообще говоря, INSTEAD умеет сохранять только те объекты, которые имеют дескрипторы. Объекты, имеющие дескрипторы, это объекты созданные одним из следующих способов:
Явным созданием внутри списков obj и way (дескриптором станет элемент массива);
Через механизм new (описано ниже).
В случае же с put мы создаем абсолютно безымянный объект. Тем не менее существует способ сделать объекты сохраняемыми в любом случае. То, что я опишу далее, вряд ли стоит использовать при написании игр. Обычно такие вещи имеют смысл при разработке модулей или расширений движка, так что вы можете спокойно пропустить эту часть.
Итак, для сохранения можно определить свою функцию сохранения.
function xway(name, dsc, w)return obj {
nam = name;
dsc = dsc;
act =function(s)
walk(s.where)end;
where = w;
save =function(self, name, h, need)-- self - текущий объект-- name -- полное имя переменной-- h - файловый дескриптор файла сохранения-- need - признак того, что это создание объекта, -- файл сохранения должен создать объект -- при загрузкеlocal dsc = self.dsc;local w = stead.deref(self.where);if need then-- в случае создания, запишем строку -- с вызовом конструктора
h:write(stead.string.format("%s = xway(%s, %s, %s);\n",
name,-- формирование строк
stead.tostring(self.nam),
stead.tostring(dsc),
stead.tostring(w)));end
stead.savemembers(h, self, name,false);-- сохраним все -- остальные переменные объекта-- например, состояние включен/выключен-- итд-- false в последней позиции означает что будет -- передано в save-методы вложенных объектов в -- качестве параметра needend};end
На самом деле, я надеюсь, что вам никогда не понадобится разбираться с движком на таком глубоком уровне.
21. Полезные советы
Разбиение на файлы
Когда ваша игра становится большой, размещение ее кода целиком в main.lua – плохая идея.
Для разбиения текста игры на файлы вы можете использовать dofile. Вы должны использовать dofile в глобальном контексте таким образом, чтобы во время загрузки main.lua загрузились и все остальные фрагменты игры, например.
-- main.lua
instead_version "1.8.2"dofile"episode1.lua"dofile"npc.lua"dofile"start.lua"
main = room {....
Как именно разбивать исходный текст на файлы зависит только от вас. Я использую файлы в соответствии с эпизодами игры (которые обычно слабо связаны между собой), но можно создавать файлы, хранящие отдельно комнаты, объекты, диалоги и т.д. Это вопрос личного удобства.
Также есть возможность динамически подгружать части игры (с возможностью переопределения существующих объектов). Для этого вы можете воспользоваться функцией gamefile:
Внимание! Если в вашей игре определена функция init(), то в подгружаемых частях она также должна присутствовать! В противном случае, после подгрузки файла, будет вызвана текущая функция init(), что обычно не является желательным.
gamefile также позволяет загрузить новый файл и забыть стек предыдущих загрузок, запустив этот новый файл как самостоятельную игру. Для этого, задайте второй параметр функции как true. gamefile можно использовать только в обработчиках.
Во втором варианте gamefile можно использовать для оформления мультиязычных игр или игр-сборников, где фактически из оболочки выполняется запуск самостоятельной игры.
Модули
Дополнительная функциональность часто реализована в INSTEAD в виде модулей. Для использования модуля необходимо после «instead_version» написать:
require"имя модуля"
Часть модулей входит в поставку INSTEAD, но есть и такие, которые вы можете скачать отдельно и положить в каталог с игрой. Вы можете заменить любой стандартный модуль своим, если положите его в каталог с игрой под тем-же именем файла, что и стандартный.
Модуль, это фактически lua файл с именем: имя_модуля.lua.
Стандартное поведение предмета инвентаря состоит в том, что игрок должен сделать два щелчка мышью. Это необходимо потому, что каждый предмет инвентаря может быть использован на другой предмет сцены или инвентаря. После второго щелчка происходит игровой такт игры. Иногда такое поведение может быть нежелательным. Возможно, вы захотите сделать игру в которой игровая механика отличается от классических INSTEAD игр. Тогда вам может понадобится меню.
Меню – это элемент инвентаря, который срабатывает на первый клик. При этом меню может сообщить движку, что действие не является игровым тактом. Таким образом, используя меню вы можете создать в зоне инвентаря управление игрой любой сложности. Например, существует модуль «proxymenu», который реализует управление игрой в стиле квестов на ZX-«Спектрум». В игре «Особняк» свое управление, которое вводит несколько модификаторов действий, и т.д.
Итак, вы можете делать меню в области инвентаря, определяя объекты с типом menu. При этом, обработчик меню (menu) будет вызван после одного клика мыши. Если обработчик не возвращает текст, то состояние игры не изменяется. Например, реализация кармана:
pocket = menu {
var {
state =false};
nam ='карман';
disp =function(s)if s.state thenreturn txtu('карман');-- подчеркиваем активный карманendreturn'карман';end;
gen =function(s)if s.state then
s:enable_all();-- показать все предметы в карманеelse
s:disable_all();-- спрятать все предметы в карманеendend;
menu =function(s)
s.state =not s.state -- изменить состояние
s:gen();-- открыть или закрыть карманend;};
knife = obj {
nam ='нож';
inv ='Это нож';};function init()
take(pocket)
put(knife, pocket)-- нож будет в кармане
pocket:gen()-- проинициализируем карманend
main = room {
nam ='test',};
Статус игрока
Иногда возникает желание выводит какой-нибудь статус. Например, количество игровых очков, состояние героя или, наконец, время суток. INSTEAD не предоставляет каких-то других областей вывода, кроме сцены и инвентаря, поэтому, самым простым способом вывода статуса является вывод его в зону инвентаря.
Ниже представлена реализация статуса игрока в виде текста, который появляется в инвентаре, но не может быть выбран, то есть, выглядит просто как текст.
global {
life =10;
power =10;}
status = stat {-- stat -- объект "статус"
nam ='статус';
disp =function(s)
pn ('Жизнь: ', life)
pn ('Сила: ', power)end};
take(status)
walk из обработчиков exit и enter
Вы можете делать walk из обработчиков enter и exit. Например, vroom реализован как комната с обработчиком enter, который переносит игрока в другую комнату. Если бы не было vroom, можно было бы написать переход без него следующим образом:
home = room {
nam ='Дома';...
way ={
room {
nam ='На улицу';
enter = code [[ walk 'street' ]];};}
Динамически создаваемые ссылки.
Строго говоря, ссылок в INSTEAD нет. Есть объекты и переходы.
Динамически создаваемые ссылки могут быть реализованы разным способом.
Через включение/выключение объектов/переходов;
Через динамическое добавление/удаление объектов/переходов;
Следует заметить, что объекты типа vway, vobj и переходы vroom, wroom сделаны таким образом, что могут создаваться на лету, это делает возможным записи вида:
put(vway('Дорога','Я заметил {дорогу}, ведущую в лес...','forest'), field);-- добавили ''vway'' в сцену field
Однако, я рекомендую использование enable/disable().
Кодирование исходного кода игры
Если вы не хотите показывать исходный код своих игр, вы можете закодировать исходный код с помощью параметра командной строки -encode:
sdl-instead -encode <путь к файлу> [выходной путь]
И использовать закодированный файл с помощью lua функции doencfile. При этом главный файл main.lua необходимо оставлять открытым. Таким образом, схема выглядит следующим образом (game – закодированный game.lua):
-- $Name: Моя закрытая игра!$
instead_version "1.8.2"
doencfile("game");-- никто не узнает, как ее пройти!
Важно!
Не используйте компиляцию игр с помощью luac, так как luac создает платформозависимый код!
Однако, компиляция игр может быть использована для поиска ошибок в коде.
Запаковка ресурсов
Вы можете упаковать ресурсы игры (графику, музыку, темы) в файл ресурсов .idf, для этого поместите все ресурсы в каталог data и запустите INSTEAD:
sdl-instead -idf <путь к data>
При этом, в текущем каталоге должен будет создастся файл data.idf. Поместите его в каталог с игрой. Теперь ресурсы игры в виде отдельных файлов можно удалить (конечно, оставив себе оригинальные файлы).
Вы можете запаковать в формат .idf всю игру:
sdl-instead -idf <путь к игре>
Игры в формате idf можно запускать как обычные игры instead (как если бы это были каталоги) а также из командной строки:
sdl-instead game.idf
Переключение между игроками
Вы можете создать игру с несколькими персонажами и время от времени переключаться между ними (см. change_pl). Но вы можете также использовать этот трюк для того, чтобы иметь возможность переключаться между разными типами инвентаря.
Использование параметров обработчика
Пример кода.
stone = obj {
nam ='камень';
dsc ='На краю лежит {камень}.';
act =function()remove('stone');
p 'Я толкнул камень, он сорвался и улетел вниз...';end
Обработчик act мог бы выглядеть проще:
act =function(s)remove(s);
p 'Я толкнул камень, он сорвался и улетел вниз...';end
Таймер
Для асинхронных событий, привязанных к реальному времени, в INSTEAD есть возможность использовать таймер. На самом деле, вам следует хорошо подумать, стоит ли в приключенческой игре использовать таймер. Обычно, игроком это воспринимается не слишком благосклонно. С другой стороны, таймер вполне можно использовать для управления музыкой или в оформительских целях.
Для использования таймера, вам следует подключить модуль «timer».
require"timer"
Таймер программируется с помощью объекта timer.
timer:set(мс) – задать интервал таймера в миллисекундах;
timer:stop() – остановить таймер;
При срабатывании таймера, вызывается обработчик game.timer. Если game.timer возвращает пустое значение, сцена не перерисовывается. В противном случае, возвращаемое значение выводится как реакция.
Вы можете делать локальные для комнаты обработчики timer. Если в комнате объявлен обработчик timer, он вызывается вместо game.timer.
Например:
game.timer =function(s)
set_sound('gfx/beep.ogg');
p ("Timer:",time())endfunction init()
timer:set(1000)-- раз в секундуend
myroom = room {
entered =function(s)
timer:set(1000);end;
timer =function(s)
timer:stop();
walk 'myroom2';end;
nam ='Проверка таймера';
dsc =[[Ждите.]];}
Состояние таймера попадает в файл сохранения, таким образом, вам не нужно заботиться о его восстановлении.
Кроме того, в INSTEAD существует возможность отслеживать интервалы времени в миллисекундах. Для этого используйте функцию stead.ticks(). Функция возвращает число миллисекунд, прошедшее с момента старта игры.
Музыкальный плеер
Вы можете написать для игры свой проигрыватель музыки, создав его на основе живого объекта, например:
-- играет треки в случайном порядке, начиная со 2-го
mplayer = obj {
tracks ={"mus/astro2.mod","mus/aws_chas.xm","mus/dmageofd.xm","mus/doomsday.s3m"};
nam ='плеер';
life =function(s)ifnot is_music()thenlocal n = tracks[rnd(#s.tracks)]
set_music(n,1);endend;};
lifeon('mplayer');
Ниже приводится пример более сложного плеера. Меняем трек только если он закончился или прошло более 2 минут и игрок перешел из комнаты в комнату. В каждом треке можно указать число проигрываний (0 - зацикленный трек):
require"timer"
global { track_time =0};
music_player = obj {
nam ='player';
var { pos =0;};
playlist ={'01 Frozen sun.ogg',0,'02 Thinking.ogg',0,'03 Melancholy.ogg',0,'04 Everyday happiness.ogg',0,'10 Good morning again.ogg',1,'15 [Bonus track] The end (demo cover).ogg',1};
life =function(s)if is_music()and( track_time <120ornot player_moved())thenreturnend
track_time =0if s.pos ==0then
s.pos =1else
s.pos = s.pos +2endif s.pos >#s.playlist then
s.pos =1end
set_music('mus/'..s.playlist[s.pos], s.playlist[s.pos +1]);end;}
game.timer =function(s)
track_time = track_time +1
music_player:life();endfunction init()
timer:set(1000)end
Живые объекты
Если вашему герою нужен друг, одним из способов может стать метод life этого персонажа, который всегда переносит объект в локацию игрока:
horse = obj {
nam ='лошадь';
dsc ='Рядом со мной стоит {лошадь}.';
act =[[Моя лошадка.]];
life =function(s)if player_moved()then
move(s, here(), where(s));endend;};function init()
put (horse, main);-- только в этом случае where() будет работать
lifeon(horse);-- сразу оживим лошадьend
Клавиатура
Вы можете перехватывать события клавиатуры с помощью модуля «kbd».
Обычно, перехват клавиш имеет смысл использовать для организации текстового ввода.
За описанием, обращайтесь к документации модуля «kbd».
Мышь
Вы можете отслеживать в своей игре клики по картинке сцены, а также по фону. Для этого, воспользуйтесь модулем «click».
Также, вы можете отслеживать состояние мыши с помощью функции:
stead.mouse_pos([x, y])
Которая возвращает координаты курсора. Если задать параметры (x, y), то можно переместить курсор в указанную позицию (все координаты рассчитываются относительно левого верхнего угла окна INSTEAD).
Вызов меню
Вы можете вызвать из игры меню INSTEAD с помощью функции stead.menu_toggle(). Если в качестве параметра задать: 'save', 'load' или 'quit', то будет вызван соответствующий подраздел меню. (Поддерживается начиная с версии INSTEAD 1.8.3).
Динамическое создание объектов
Как вы уже знаете, объекты нельзя создавать на лету, если только у них не определена функция save, которая сможет сохранить состояние такого объекта.
Тем не менее существует способ создание любого объекта на лету. Для этого вам понадобится написать конструктор вашего объекта и воспользоваться функцией new.
Итак, вы можете использовать функции new и delete для создания и удаления динамических объектов.
Примеры:
local o = new ([[obj { nam = 'test', act = 'test' }]]);
take(o);...
put(new [[obj {nam = 'test' } ]]);
put(new('myconstructor()');
n = new('myconstructor()');...
delete(n)
new воспринимает строку-аргумент, как конструктор объекта. Результатом выполнения конструктора должен быть объект. Таким образом в аргументе обычно задан вызов функции-конструктора. Например:
function myconstructor()local v ={}
v.nam ='тестовый объект'
v.act ='Тестовая реакция'return obj(v)end
Созданный объект будет попадать в файл сохранения. new() возвращает сам объект.
Запрет на сохранение игры
Иногда может понадобиться запретить игроку делать сохранения в игре. Например, если речь идет о сценах, где важный элемент составляет случай, или для коротких игр, в которых проигрыш должен быть фатальным и требовать перезапуска игры.
Для управлением функции сохранения используется атрибут game.enable_save.
Например:
game.enable_save =false-- запретить сохранения
Если вы хотите запрещать сохранения не везде, а в некоторых сценах, оформите game.enable_save в виде функции:
-- запретить сохранения в комнатах, которые содержат атрибут nosave.
game.enable_save =function()if here().nosave thenreturnfalseendreturntrueend
Следует отметить, что запрет на сохранения не означает запрета на автосохранение. Для управления автосохранением воспользуйтесь аналогичным атрибутом game.enable_autosave.
Вы можете явно сохранять игру с помощью вызова: autosave([номер слота]); Если номер слота не задан, то игра будет сохранена под слотом 'автосохранение'. Имейте в виду, что сохраняется состояние после завершение текущего такта игры.
Определение типа объекта
В INSTEAD существует множество функций для определения типа объекта. Эти функции используются внутри движка, не переопределяйте их!!!
isDialog(объект) – возвращает true, если объект – это диалог;
isRoom(объект) – возвращает true, если объект – это комната (также вернет true и для диалога);
isObject(объект) – возвращает true, если параметр – это объект (также вернет true для диалога, комнаты, меню, xact, статуса, vobj и всех instead-объектов);
isMenu(объект) – возвращает true, если параметр - объект-меню;
isXaction(объект) – возвращет true, если параметр - объект-xact;
isStatus(объект) – возвращает true, если параметр - объект-статус;
isCode(объект) – возвращает true, если параметр - конструкция code;
isVobject(объект) – возвращает true, если параметр - объект vobj;
isVroom(объект) – возвращает true, если параметр - объект vroom (или wroom);
isPhrase(объект) – возвращает true, если параметр - фраза диалога;
Большинство из них вряд ли понадобятся вам при написании игры, но некоторые, например isDialog(), могут быть очень полезными в некоторых ситуациях (например, для предотвращения перемещения живых объектов по диалогам).
22. Темы для sdl-instead
Графический интерпретатор поддерживает механизм тем. Тема представляет из себя каталог, с файлом theme.ini внутри.
Тема, которая является минимально необходимой – это тема default. Эта тема всегда загружается первой. Все остальные темы наследуются от нее и могут частично или полностью заменять ее параметры. Выбор темы осуществляется пользователем через меню настроек, однако конкретная игра может содержать собственную тему и таким образом влиять на свой внешний вид. В этом случае в каталоге с игрой должен находиться свой файл theme.ini. Тем не менее, пользователь свободен отключить данный механизм, при этом интерпретатор будет предупреждать о нарушении творческого замысла автора игры.
Синтаксис theme.ini очень прост.
<параметр> = <значение>
или
; комментарий
Значения могут быть следующих типов: строка, цвет, число.
Цвет задается в форме #rgb, где r g и b компоненты цвета в шестнадцатеричном виде. Кроме того некоторые основные цвета распознаются по своим именам. Например: yellowgreen, или violet. Полный их перечень можно найти в "Справочном пособии", на странице 31.
Параметры могут принимать значения:
scr.w = ширина игрового пространства в пикселях (число)
scr.h = высота игрового пространства в пикселях (число)
scr.col.bg = цвет фона
scr.gfx.scalable = [0|1|2] (0 - не масштабируемая тема, 1 - масштабируемая, 2 - кратно-масштабируемая), начиная с версии 2.2.0 доступны дополнительно [4|5|6]: 4 - полностью не масштабируемая (с не масштабируемыми шрифтами), 5 - масштабируемая, с не масштабируемыми шрифтами, 6 - кратно-масштабируемая, с не масштабируемыми шрифтами
scr.gfx.bg = путь к картинке фонового изображения (строка)
scr.gfx.cursor.x = x координата центра курсора (число)
scr.gfx.cursor.y = y координата центра курсора (число)
scr.gfx.cursor.normal = путь к картинке-курсору (строка)
scr.gfx.cursor.use = путь к картинке-курсору режима использования (строка)
scr.gfx.use = путь к картинке-индикатору режима использования (строка)
scr.gfx.pad = размер отступов к скролл-барам и краям меню (число)
scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h = координаты, ширина и высота окна изображений. Области в которой располагается картинка сцены. Интерпретация зависит от режима расположения (числа)
win.gfx.h - синоним scr.gfx.h (для совместимости)
scr.gfx.icon = пусть к файлу-иконке игры (ОС зависимая опция, может работать некорректно в некоторых случаях)
scr.gfx.mode = режим расположения (строка fixed, embedded или float). Задает режим изображения. embedded – картинка является частью содержимого главного окна, параметры scr.gfx.x, scr.gfx.y, scr.gfx.w игнорируются. float – картинка расположена по указанным координатам (scr.gfx.x, scr.gfx.y) и масштабируется к размеру scr.gfx.w x scr.gfx.h если превышает его. fixed – картинка является частью сцены как в режиме embedded, но не скроллируется вместе с текстом а расположена непосредственно над ним. Доступны модификации режима float с модификаторами 'left/right/center/middle/bottom/top', указывающими как именно размещать картинку в области scr.gfx. Например: float-top-left;
win.scroll.mode = [0|1|2|3] режим прокрутки области сцены. 0 - нет автоматической прокрутки, 1 - прокрутка на изменение в тексте, 2 прокрутка на изменение, только если изменение не видно, 3 - всегда в конец;
win.x, win.y, win.w, win.h = координаты, ширина и высота главного окна. Области в которой располагается описание сцены (числа)
win.fnt.name = путь к файлу-шрифту (строка). Здесь и далее, шрифт может содержать описание всех начертаний, например: {sans,sans-b,sans-i,sans-bi}.ttf (заданы начертания для regular, bold, italic и bold-italic). Вы можете опускать какие-то начертания, и движок сам сгенерирует их на основе обычного начертания, например: {sans,,sans-i}.ttf (заданы только regular и italic);
win.align = center/left/right/justify (выравнивание текста в окне сцены);
win.fnt.size = размер шрифта главного окна (размер)
win.fnt.height = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
win.gfx.up, win.gfx.down = пути к файлам-изображениям скорллеров вверх/вниз для главного окна (строка)
win.up.x, win.up.y, win.down.x, win.down.y = координаты скроллеров (координата или -1)
win.col.fg = цвет текста главного окна (цвет)
win.col.link = цвет ссылок главного окна (цвет)
win.col.alink = цвет активных ссылок главного окна (цвет)
win.ways.mode = top/bottom (задать расположение списка переходов, по умолчанию top – сверху сцены)
inv.x, inv.y, inv.w, inv.h = координаты, высота и ширина области инвентаря. (числа)
inv.mode = строка режима инвентаря (horizontal или vertical). В горизонтальном режиме инвентаря в одной строке могут быть несколько предметов. В вертикальном режиме, в каждой строке инвентаря содержится только один предмет. (число) Существует модификации (-left/right/center). Вы можете задать режим disabled если в вашей игре не нужен инвентарь;
inv.col.fg = цвет текста инвентаря (цвет)
inv.col.link = цвет ссылок инвентаря (цвет)
inv.col.alink = цвет активных ссылок инвентаря (цвет)
inv.fnt.name = путь к файлу-шрифту инвентаря (строка)
inv.fnt.size = размер шрифта инвентаря (размер)
inv.fnt.height = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
inv.gfx.up, inv.gfx.down = пути к файлам-изображениям скорллеров вверх/вниз для инвентаря (строка)
inv.up.x, inv.up.y, inv.down.x, inv.down.y = координаты скроллеров (координата или -1)
menu.col.bg = фон меню (цвет)
menu.col.fg = цвет текста меню (цвет)
menu.col.link = цвет ссылок меню (цвет)
menu.col.alink = цвет активных ссылок меню (цвет)
menu.col.alpha = прозрачность меню 0-255 (число)
menu.col.border = цвет бордюра меню (цвет)
menu.bw = толщина бордюра меню (число)
menu.fnt.name = путь к файлу-шрифту меню (строка)
menu.fnt.size = размер шрифта меню (размер)
menu.fnt.height = междустрочный интервал как число с плавающей запятой (1.0 по умолчанию)
menu.gfx.button = путь к файлу изображению значка меню (строка)
snd.click = путь к звуковому файлу щелчка (строка)
include = имя темы (последний компонент в пути каталога) (строка)
Кроме того, заголовок темы может включать в себя комментарии с тегами. На данный момент существует только один тег: $Name:, содержащий UTF-8 строку с именем темы. Например:
; $Name:Новая тема$; модификация темы bookinclude= book -- использовать тему ''Книга''
scr.gfx.h = 500 -- заменить в ней один параметр
Интерпретатор выполняет поиск тем в каталоге themes. Unix версия кроме этого каталога, просматривает также каталог ~/.instead/themes/
Windows версия – Documents and Settings/USER/Local Settings/Application Data/instead/themes
Кроме этого, новые версии INSTEAD поддерживают механизм множественных тем в одной игре. Давая возможность игроку через стандартное меню INSTEAD выбрать подходящее оформление, из предусмотренных автором игры. Для этого, все темы должны располагаться в игре в подкаталоге themes. В свою очередь, каждая тема – это подкаталог в каталоге themes. В каждом таком подкаталоге должен находится свой файл theme.ini и ресурсы темы (картинки, шрифты, звуки). При этом обязательно наличие темы-каталога themes/default - эта тема будет загружена по умолчанию. Формат файлов theme.ini мы только что рассмотрели. Однако, пути к файлам с ресурсами в theme.ini пишутся не относительно корневого каталога игры, а относительно текущего каталога темы. Это означает, что обычно они содержат только имя самого файла, без пути к каталогу. Например:
При этом, все игровые темы наследуются от темы themes/default. Поддерживается механизм include. При этом, INSTEAD сначала пытается найти одноименную тему игры, и если такой темы не находится, будет загружена тема из стандартных тем INSTEAD (если она существует). Далее, в theme.ini можно изменять только те параметры, которые требуют изменения.
The engine for INSTEAD is written in Lua 5.1 and 5.2, therefore knowing this language is useful, although completely unnecessary. In fact, it was the express intention of the creator that new designers should be able to take on interesting projects without any need for engine-level programming.
Throughout its evolution, the engine's capabilities have widened and allowed implementation of games in various genres including arcade games and highly complex text-parsing adventures. It's even possible to launch games written for other engines. However, the original intent was custom text-based adventures, and this remains the primary focus. This is what the present documentation describes, and strengthening these foundation skills is important even if you plan to write a genre-bending game in the future, you clever child. So let's start learning INSTEAD by writing a simple text adventure!
Adventures In Text
Most people associate a text adventure with some text printed on the screen and a few pre-defined courses of action, for example:
You see a table in front of you. There is an apple on it. What do you do?
1) Take the apple
2) Step away from the table
Some classic games have the player type her actions literally, like this:
You are in the kitchen. There is a table near a window.
PLAYER TYPES> look at the table
There is an apple on the table.
Both of these implementations have advantages as well as drawbacks. The first approach resembles what's known as the gamebook genre. It's perfectly serviceable yet doesn't suit exploration, where the player should be free to move and interact with in-world objects. The second approach gives more of this freedom but it requires more effort from the game developer and her players, accommodating the difficulties inherent in human grammar.
The INSTEAD game engine is intended to combine both approaches, attempting to avoid both sets of drawbacks. A game world in INSTEAD is modeled in a similar fashion to the second approach in that there are places (rooms) which the protagonist can visit, and objects (including NPC characters) she can interact with. The player explores the world and can manipulate objects freely. Acting upon objects might remind her of classic graphic quests from the 1990s, yet with a greater depth of features.
Before reading this manual it's recommended you play some INSTEAD games to get an idea how each concept will be implemented. However, it must be noted that many old games are written with less than optimal code using deprecated constructions, so studying their code is a treacherous endeavor for fledgling game developers. Modern structure and syntax enables you to write more laconically, so go have fun with some games then return to pick up best practices.
Basic Anatomy
The game window displays text and images for the room the player is standing in.
The static elements of a room are shown only once, upon entering. Clicking on the room's title will recall them.
The dynamic elements of a room are defined by its objects, including character dialogues. These are always visible to the player, yet can change based on the player's actions.
The player's inventory is represented in a separate panel that contains interactive objects the player can access throughout the game.
Player actions in the game world include:
looking around in a room to discover new objects
unfolding the story as it appears
interacting with a room's objects
using inventory objects
following passages to other rooms
Where To Begin
A game should have its own primary directory within your system directory, with one main.lua script inside the top level. Other game resources should be placed in this directory as well, and can be organized in sub-directories. This includes Lua scripts, image files, sound files, and savegame files. All references in the code must be made relative to this top level directory.
To save yourself having to search for save files elsewhere, be sure to create a sub-directory named “saves”. Your saved games will automatically be stored here.
At the beginning of the main.lua file a header can be defined with information tags. Each tag should start with – & and end with a closing $ symbol.
The $Name tag contains the game title, encoded in UTF-8 without BOM!, which will be displayed by the interpreter when the player opens the game:
-- $Name: The most interesting game!$
It's also recommended to specify the development version of the game:
-- $Version: 0.5$
As well as the author(s):
-- $Author: Non Anonymous$
If you develop your game in Windows make sure your editor supports UTF-8 encoding without BOM! and use it to write the code.
Since version 1.2.0 you must define the STEAD API version after the header. It's currently version 1.9.1 as of this writing.
instead_version "1.9.1"
Without this line STEAD API will operate in compatibility/legacy mode. The game engine has competent backwards compatibility, but by explicitly specifying a modern API version number it's possible to use features such as snapshots, autosaved global variables, on-the-fly functions, auto-formatting, feature modules, and so on. There's no point writing new games in an old API, although such practice is common. For this reason, it's best NOT to attack game code until you've studied this manual. It will result in a better game.
Modules are included beneath the API declaration:
require"para"--different indentsrequire"dash"--replaces double hyphen sign with a large hyphenrequire"quotes"--replaces quotation marks with chevrons << >>
It's usually a good idea to define default handlers here as well:
-- an impossible attempted action generates -- this response by default
game.act ='Does not work.';
game.use ='It will not help.';
game.inv ='Do I really need this?';
Game initialization should be defined in the init function which will be called by the engine at the very beginning. It's a good place to set the player initial state or similar early requirements for the player. However you may not need an init function.
function init()-- set a savegame stored variable
me()._know_truth =false;-- add knife to the player's inventory
take(knife);-- add paper to the player's inventory
take(paper);end;
The INSTEAD installation directory is where the graphic interpreter looks for available games. The Unix version also checks ~/.instead/games. The Windows version since 0.8.7 checks Documents_and_Settings/USER/Local_Settings/Application_Data/instead/games but from STEAD 1.2.0 on both Windows and Unix standalone builds looks at ./appdata/games, if it exists.
In some INSTEAD builds (on Windows or on Linux if the project is built with gtk, etc.) it's possible to open a game from any location using the menu option “Select Game” or by hitting F4. If there's only one game in the directory it will be launched by the interpreter automatically, which is handy if you distribute your game with the engine included. Your players can simply launch INSTEAD to start their adventure.
While writing code it's strongly recommended to use indents for nested levels like in the code samples in this manual. It really helps to clarify and decreases mistakes.
Here's a basic template for your first game:
-- $Name: My first game$-- $Version: 0.1$-- $Author: Homo codius$
instead_version "1.9.1"require"para"--for decorationrequire"dash"require"quotes"require"dbg"--for debugging
game.act ='Um...';
game.use ='It does not works!';
game.inv ='Why?';function init()--some initialization code if neededend
Debugging Basics
In the event bugs occur (they will), it's handy to run INSTEAD with the command line parameter -debug which will generate more informative error messages. The -debug parameter can also be specified in the desktop icon/shortcut properties. Right click your INSTEAD desktop icon, select Properties from the dialog menu, then type -debug at the end of the Target: field.
In Windows, debug mode opens a separate console window where errors are output. Unix uses the console where INSTEAD was launched from.
Also you can engage an internal hotkey debugger module (F7) by adding the following line after instead_version in the header of the main.lua file.
require"dbg"
Debug mode displays the call stack, as well as allows you to quickly restart the game with Alt+R. Combining this hotkey with quicksave/quickload (F8/F9) it's possible to assess changes very efficiently with each edit you write to your code.
In your code you can force output your own custom debug messages using the print() method inside a function placed within an object's act handler:
act =function(s)print("Act is here! "..stead.deref(s));...end;
Don't fear the previous example! After reading this manual, and with your own game on the anvil, you'll look at this code with more inspiration.
HINT: It's often useful to examine savegame files during debugging because they contain special stored game state variables.
If you're already an experienced debugger you'd probably like the ability to check your Lua syntax by parsing it with the luac compiler. In Unix just use the -p option at the command line.
On Windows you'll have to install the Lua binaries from here , then run luac52.exe to parse your code.
You can also run a syntax check with your initial program call, as long as you're running version 1.9.2 or higher. Just add -luac to your call.
sdl-instead -debug-luac <path/to/lua/script.lua>
Rooms
A room is a basic game unit in which the player can exist and interact with objects. A room can be inside a house or it can be a massive redwood forest.
Every game must contain one reference variable main of object type room. This is where the game begins and where the player appears first.
HINT: It could also be a custom start menu.
main = room {
nam ="The Main Room";
dsc =[[You are in the first room of this game.]];}
The assignment statement above (signified by the = sign) creates an object in the game world, a room simply being a special kind of object. Most things in INSTEAD are objects.
For every object there are two required attributes: nam and dsc, which are closed by a semicolon or a comma.
There are many more attributes and handlers. They can be assigned text strings, numbers, booleans, and even methods.
The example's nam attribute contains a text string which displays at the top of the game window. The nam attribute can be used to target an object from other code, rather than always using its variable identifier (main in this example).
In fact, nam doesn't have to be output to the screen at all. The disp attribute, if present, will override it in the graphical interpreter and be displayed instead.
main = room {
nam ='Start';-- displayed in the window
disp ='My old room, covered in dust...';
dsc =[[I'd almost forgotten the color of the walls.]];}
This provides the developer with multiple identifiers by which to target an object.
The dsc attribute contains a static description of the object, which gets displayed to the player on her first entrance. To display dsc again she must look at the room explicitly, or if it's your intention for dsc to be shown repeatedly, you can include the forcedsc attribute.
Put it with your other game definitions at the top of the main.lua file:
game.forcedsc =true;
Or put it inside the room's attribute list:
main = room {
forcedsc =true;
nam ='The main room';
dsc =[[Guess what.]];};
However, it's not recommended to use forcedsc because the engine is currently optimized for classic games which don't recognize it.
You can also use stead.need_scene() to force display static dsc in current room wherever you need. Read more in the Auxiliary Methods/Functions section.
A string can be enclosed with single or double quotation marks.
main = room {
nam ='The very same room';
dsc ="Got it in one!";};
For long text strings it's better to use double-bracket notation.
dsc =[[ Very loooong description... ]];
Line breaks are ignored, so you must use the ^ symbol if you want to display a line break on the player's screen.
dsc =[[ First paragraph. ^^
Second one.^^
The third.^
New line here:)]];
Passing Between Rooms
Typical passages look like links at the top of the main story panel in the player's game window. These connections are defined in the way attribute as a list. Rooms are listed by nam reference (or using a variable identifier, as long the room has been defined earlier in the code). This is written the same as an obj list.
main = room {
nam ='main room',
dsc ='You are in a large room.',
obj ={'tabl'},
way ={'room2'},};
room2 = room {
nam ='hall',
dsc ='You are in a huge hall.',
way ={'main'},};
Two rooms are defined here. It's useful to remember that nam and disp can be written as functions to utilize dynamic room captions, which are generated on the fly, for example in a room whose nam shouldn't be known to the player until he enters. However, there are more appropriate tools for this purpose such as the wroom module discussed later.
When passing between rooms the engine calls the exit handler of the current room and the enter handler of the destination:
hall = room {
enter ='You enter the hall.',
nam ='Hall',
dsc ='This is one gigantically stupendous hall.',
way ={'main'},exit='You leave the hall.',};
Like any handler, exit and enter may be defined as functions. The first parameter is the current room. The second parameter is the previous room f for entering, or the destination room t for exiting:
hall = room {
enter =function(s, f)if f == main then
p 'You came from the room.';endend,
nam ='hall',
dsc ='You are in a huge hall.',
way ={'main'},exit=function(s, t)if t == main then
p 'I don\'t wanna go back!'returnfalseendend,};
As we see, the handlers can return two values, a string for graphical display and/or a boolean for changing a status. In our example the exit handler's function returns false if the player tries to go into the main room from the hall. It means she'll have to find another way, like the Barlog :). The same logic can be applied to the enter and tak handlers.
You can also return both values in a more concise way:
-- action text followed by comma separating the status valuereturn"I don't wanna go back.",false
You need to be aware that the room pointer here() may not sync with an enter call! You can use the left and entered handlers instead which get triggered after the player is passed through. These handlers are recommended when you don't need to forbid passage.
Sometimes you'll want to give a passage a clever title that's distinct from the destination's nam. There are several ways to implement this, but the easiest is by using the vroom module:
hall = room {
nam ='hall';
dsc ='You are in the hall';-- the syntax for vroom is ('passage name', 'destination room')
way ={
vroom('To the first room','main')};}
main = room {
nam ='main room';
dsc ='You are in the small room.';
obj ={'tabl'};
way ={ vroom('To the hall', hall)};};
Technically the vroom module returns an auxiliary room object with a special enter handler set to bounce the player into the destination room. Yippee!
Sometimes you may need to get a bit more complex by disabling and enabling passages. The engine's concept of passages assumes they are always visible even if blocked, such as a room within a house with a locked door. Therefore it's unnecessary to hide passages.
Instead you can check for a key in the player's inventory, which will be implemented in the enter handler of the destination room. If no key is present, a notification can alert the player and forbid her passing through. You can also create a door as a room object, place it inside another room, and implement unlocking it with a key, to allow the player to pass using the customary way list.
There are situations when you won't want a passage to be obvious to the player, or to make it appear as the result of a special event. For example a clock might be hiding a secret tunnel behind it:
hall = room {
nam ='hall';
dsc ='You are in a huge hall.';
obj ={'clock'};
way ={ vroom('Behind the clock','behindclock'):disable()};};clock= obj {
nam ='clock';
dsc =[[You see an old {clock}.]];
act =function(s)--the path() method employs the enable method
path('Behind the clock'):enable()
p [[You discover a secret tunnel behind the clock!]];end;};
So as you can see, an initially hidden passage is created with the vroom() and :disable() methods. You can easily hide objects in lists this fashion, so behindclock can disappear from the room description, but it will still be available to be called later. A useful technique for hidden objects is to initialize them in a disabled state like this. The object isn't processed inside the engine until the :enable() method has returned it.
Semantically, you can also write these enable/disable methods reversed like this:
way ={ disable(vroom('In the hours, 'inclock')) };
Then inside the clock object, its act handler invokes the path() method, which references the 'Behind the clock' object vroom aliased just prior, and calls enable() on it. You can also write it this way:
act =function(s)
enable(path('Behind the clock'))--or, since you don't have to parenthesize solo -- parameters in Lua, you could also write it like this: -- enable(path 'Behind the clock')
p [[You discover a secret tunnel behind the clock!]];end;
If the passage we want to toggle on or off is in another room, we can pass it as the second parameter of path().
path('Behind the clock','room312'):enable();
If you'd rather create simple passage variables, you can define them with vroom inside like this:
path_clock = vroom('Behind the clock','behindclock');clock= obj {
nam ='clock';
dsc =[[You see an old {clock}.]];
act =function(s)
path_clock:enable()
p [[You discover a secret tunnel behind the clock!]];end;}
hall = room {
nam ='hall';
dsc ='You are in a huge hall';
obj ={'clock'};
way ={ path_clock:disable()};};
You can also toggle a room object itself without employing vroom.
inclock = room {
nam ='In the tunnel';
dsc =[[It's dark here.]];}:disable();--simply tacks on a disable -- method to the end of the room object-- the following three methods are also equivalent:-- }:disable()-- }; inclock:disable()-- }; disable(inclock)clock= obj {
nam ='clock';
dsc =[[You see an old {clock}.]];
act =function(s)-- toggles the "inclock" room to true
inclock:enable()
p [[You discover a secret tunnel behind the clock!]];end;};
hall = room {
nam ='hall';
dsc ='You are in a huge hall';
obj ={'clock'};
way ={'inclock'};};
Objects
Objects are what the player interacts with. Here are two examples:
tabl = obj {--you can't name it table with an 'e' because -- it's a Lua keyword. Be careful with keywords and variables.
nam ='table',
disp ='Table',
dsc ='There\'s a {table} in the room.',-- you can escape -- apostrophes using \ so they -- won't interfere with the string
act ='Hm... just a table...',-- displays when -- object's hotlink is clicked in the dsc};
salt = obj {-- this variable can be used -- inside the obj list of other objects, -- making this object display with them
nam ='salt shaker';-- name that can be-- used as a reference in some functions-- displayed in the inventory panel
disp ='Salt Shaker';-- displays a highlighted hotlink for anything within -- curly braces, i.e., {salt shaker}
dsc ='There\'s a {salt shaker}.';-- displays when player clicks the hotlink above
tak ='I took hold of the salt shaker of the table';-- displays when the player uses the item on another item in her inventory
inv ='It's the corner of the table.';
};
Objects are represented in the inventory panel by the nam or the disp attribute, disp taking precedence. Also, disp can be set to false which makes the object invisible in the inventory.
Text enclosed with curly braces { } inside the dsc attribute will be displayed as a hotlink in the graphic interpreter.
The act event handler must be a string, a boolean value, or a function returning one of these two.
WARNING: Some objects (table, io, string, etc…) are reserved in the Lua namespace. Be careful not to use any reserved words when choosing variable identifiers for your objects. In modern INSTEAD versions this problem is almost solved. However, you still will not be able to use variable identifiers that match INSTEAD constructors (obj, game, player, list, room, dlg, etc…)
Adding Objects to Rooms
To include an object to a room or another object add the intended object's variable to the target room/object's obj table. For example salt can be bound to the tabl object this way:
Add references to the obj table to place corresponding objects at the room:
tabl = room {
nam ='Table',
dsc ='It\'s a large kitchen table with a bowl of {salt}.',
obj ={'salt'},};
You can also use an unquoted variable identifier instead of a quoted reference, but you must define the object before defining the room in order for this to work. References have no such limitation, therefore using references is highly recommended.
Separate multiple objects within a list with commas or semicolons (standard Lua delimiters):
obj ={'tabl','apple'};
Feel free to use line breaks to make your code more readable.
obj ={'tabl','apple','knife',};
You can also use functions to place objects in rooms, which will come later.
Objects Referencing Objects
Any object may have an obj list. The parent object displays its dsc then carries out the same operation for each of its children inside obj. For example let's place a bowl on the desk.
main = room {
nam ='A Kitchen',
dsc ='You walk inside.',-- These references can be defined after because -- they're housed within quotes
obj ={'window','desk'},};
window = obj {
nam ='Window',
dsc =[[Diffused daylight pours through a
round porthole in the middle of the ceiling.]],};
desk = obj {
nam ='Desk',
dsc =[[There's a desk in the center of the room.]],
obj ={'bowl'},};
bowl = obj {
nam ='Bowl',
dsc =[[On the surface there's a metal
bowl covered in strange engravings.]]};
First the player will see the room dsc with a large line break, then the window's dsc, the desk's dsc, and finally the bowl's dsc (it's the desk's child object).
Also if you move an object to another room, nested objects will be moved with it. This is part of the referencing mechanism: an obj table stays bound to its parent object. So be diligent about keeping track of nested objects.
Using Objects On Objects
Players often try to use objects in their inventory on other objects. To satisfy this basic urge, the use handler can be invoked for the held object, and used for the targeted object. For example:
knife = obj {
nam ='knife',
dsc =[[There's a {knife} on the chair]],
inv =[[It's sharp!]],
tak ='I took the knife!',
use ='You try to use the knife.',};
chair = obj {
nam ='chair',
dsc ='There is a {chair} in the room.',
act ='Hm... Just a chair...',
obj ={'apple','knife'},--you monster!
used ='You try to hurt the chair...',};
If the player takes the knife and uses it on the chair, he gets the text of knife's use handler along with the chair's used handler. These two can also contain functions. The first parameter s refers to the object itself, and the second parameter w is the target object if you're defining the use handler, the affected object if you're defining the used handler.
knife = obj {
nam ='knife',
dsc ='There is a knife on the {chair}',
inv ='Sharp!',
tak ='I took the knife!',
use =function(s, w)if w ~= tabl then
p [[I don't want to cut this.]]returnfalseelse
p 'You incise your initials on the chair.';endend;};
In the example above you can only use the knife on the chair. If use returns false then used won't be invoked, and its return value is ignored. Also, if these two handlers aren't defined or they return nothing the default handler game.use is called.
It's your choice which one to implement in any given situation, but it's a good idea to place the handler near the object it affects. For example, a trash bin that allows the player to throw anything inside makes sense to employ the trash bin's used handler.
trash = obj {
nam ='trash';
dsc =[[I see a {trash bin}.]];
act ='It is not for me.';
used =function(s, w)remove(w, me())
p [[I do not need it anymore.]];end;}
Bugs often occur when use and used are implemented by two unintended objects the player decides to play with. For example, if the player has a knife you intend for her to use on an apple, but she decides to cut the trash bin, the knife will vanish. We'll need to rework the knife's handlers to correct this behavior. The nouse handler should be implemented:
..........................require"nouse"..........................
knife = obj {
nam ='knife',
use =function(s, w)if w ~= apple then-- if the apple is not an action subjectreturnendif w.knife thenreturn"It's already skinned."end
w.knife =true
p 'I skinned the apple.'end;
nouse =[[I don't want slash it.]];};
The nouse handler will be called by the engine if neither use or used returns something adequate. If nouse isn't called, then the noused handler within the affected object will searched for and called. If none of these result in an adequate return value, then the default handler game.nouse will be deployed.
All of these usage handlers can be functions, each with three arguments (the handler owner object, the actor object, the target object).
NOTE: The nouse handler actually redefines game.use so you must switch to its alternatives such as game.nouse.
It's also possible to use a scene object (placed in scene, not in inventory) on other objects. If you are going to implement this mechanism, you need to set an object or global game.scene_use boolean attribute scene_use, or build a function that returns a boolean value.
stone = obj {
nam ='stone1';
var { pushed =false};
scene_use =true;
dsc =[[I see the heavy {stone} on the floor.]];
use =function(s, w)if w == door then
s.pushed =true
p [[I pushed stone to the door.]]else
p [[I do not want to do a hard work!]];endend;}-- here, this object in scene can be used on other-- objects in scene, like the inventory item...
The Player Object
The player is represented by the object variable pl of type player. In the engine it's represented this way:
pl = player {
nam ="Incognito",
where ='main',
obj ={}};
You can target the current player object with the me() pointer. In most instances me() == pl.
The obj table inside pl represents the player's inventory. Also, if you want the player to have special qualities you can implement them in the var table (such as power, speed, etc.):
pl = player {
nam ="James";
where ='main';
var { power =100};
obj ={'apple'};-- let's give him an apple};
You can even create multiple players (pl2, pl3, pl4, etc.) and alternate between them using the change_pl() function. It only has one parameter, which targets the player object being switched to. Using this function will change the current game location to whereever the target player is located.
Inventory
A player's inventory exists inside the player object. The easiest way to put something there is to define its tak handler.
apple = obj {
nam ='apple',
dsc ='There is an {apple} on the table.',
inv =function(s)
inv():del(s);--//inv()// is -- a pointer that you can use in the shorthand -- Lua syntax describes before to target the -- inventory of (s) which refers to applereturn'I ate the apple.';end,
tak ='You took the apple.',-- same as -- the act handler, only it puts the -- target object in the inventory panel};
Examine the inv handler above. After the object has been take, the player can click it in her inventory panel. The apple is then removed from the inventory and the returned action text is displayed:
'I ate the apple.'
It's also possible to implement taking inside the act handler:
apple = obj {
nam ='apple';
dsc ='There is an {apple} on the table.';
inv =function(s)remove(s, me());-- remove(parent target, consumer) -- me() is the player pointer. The effect of -- consuming an inventory item can be defined
p 'I ate the apple.'end;
act =function(s)
take(s)--take() is another method
p 'You took that apple.';end;};
If an object doesn't have an inv handler then game.inv will be called.
You should also be aware that the term “inventory” is loosely defined in the game engine, so the inventory panel could even house abstract objects such as interactive command buttons and status meters. Go wild.
Multiple Inventory Presets
In fact, you may use multiple characters for switching between different types of inventory.
Like this:
instead_version "2.1.0"
pl1 = player {
nam ='1';
where ='main';}
pl2 = player {
nam ='2';
where ='main';}
inv_sw = menu {
nam =function(s)return('Inv:'.. stead.nameof(me()))end;
menu =function(s)if me()== pl1 then
pl2.where = here()
change_pl(pl2)else
pl1.where = here()
change_pl(pl1)endend}
apple = obj {
nam ='apple';}
knife = obj {
nam ='knife';}function init()
change_pl 'pl1'
place(inv_sw, pl1)
place(inv_sw, pl2)
place(apple, pl1)
place(knife, pl2)end
main = room {
nam ='room 1';
dsc ='room 1';
way ={'r2'}}
r2 = room {
nam ='room 2';
dsc ='room 2';
way ={'main'}}
The Game Object
The game itself is represented as the object game of type game. The engine defines it as follows:
game = game {
codepage ="UTF-8",
nam ="INSTEAD, version "..stead.version.." 'by Peter Kosyh",
dsc =[[
Commands:
look (or just enter),
act <on what> (or just what),
use <what> [on what],
go <where>,
back,
inv,
way,
obj,
quit,
save <fname>,
load <fname>.
]],
pl ='p1',
showlast =true,
_scripts ={},};
As we can see, the game object holds the current player pl reference and some settings. At the beginning of your game you can set the text encoding to anything you want, but keep in mind it's better to just switch your editor to UTF-8 without BOM! encoding and work with the game engine. Changing the encoding is useful when running games written for other platforms (like URQL).
game.codepage ="cp1251";
The game object also contains your default handlers for act, inv, use (It's best to always define them!).
NOTE: Don't forget that the nouse handler reserves the game.use default handler for its own peculiar needs, so if you implement nouse then you'll need to to include a game.nouse handler as a fallback for use to prevent bugs.
The Timer Object
Since version 1.1.0 of the SDL version, the game engine has incorporated a timer object.
timer:set(ms)--sets the timer interval in milliseconds
timer:stop()--stops the timer
timer.callback(s)--creates a callback for the timer, calling it in a fixed time interval
You can create a function that will return a stead interface command invoked after the callback's execution. For example:
timer.callback =function(s)
main._time = main._time +1;return"look";end;
timer:set(100);
main = room {
_time =1,
forcedsc =true,
nam ='Timer',
dsc =function(s)return'Example: '..tostring(s._time);end;};
Lightweight Objects
Sometimes we need to fill a room with non-functional decorations to make the game world seem more diverse. Lightweight objects may be used for this purpose. For example:
sside = room {
nam ='southern side',
dsc =[[I walk to the southern wall of the building. ]],
act =function(s, w)if w =="porch"then-- this references-- the lightweight objects created -- below in the obj list
ways():add('stolcorridor');
p [[There's an entrance with a porch.
The sign on the door reads 'Canteen'.
I'm not hungry... Should I go in?]];elseif w =="people"then
p "The people leaving look quite content.";endend;
obj ={-- this is a lightweight object
vobj("porch","There is a small {porch} by the eastern corner."),--another lightweight
vobj("people",[[From time to time the porch
door slams letting {people} in and out..]])},};
The method vobj() allows you to create a lightweight version of a static object. It can be interacted with by means of its parent's act handler which assesses the child's name. The vobj() method will call the used handler. The third parameter can call a separate object to interact with it. The vobj() syntax is simple:
vobj(name, description, interactive_object)
NOTE: Lightweight objects often don't have handles to help you with interactions via inventory items, etc., so if you want the player to be able to access them you can define those behaviors within their use handler, via the stead.nameof() method, like so:
use =function(s, w)if stead.nameof(w)=="humans"then
p "You should not disturb humans."returnendend;
One useful trick is adding vobj() to the room dynamically with a place() method:
place(vobj("cont_button","{Continue}"));
But this style is old-fashioned. Instead try using a disable() or enable() method within your static object descriptions, like so:
obj ={ vobj("cont_button","{Continue}"):disable()};.......................................................
exist('cont_button'):enable();-- returns the object -- if it's present regardless of "able" state, then enables it
A useful permutation of vobj() is vway() which creates a reference leading to a specified room. Here's the syntax:
vway(name, description, destination_room)
And an example implementation in an obj list:
obj ={ vway("next","Press {here} to pass to the next room.",'nextroom')}
If you're creating a game in the storybook genre where the gameplay consists of wandering from one link to another (which is probably a poor decision for your first game!) you should utilize the xact() module. It provides a simpler mechanism for creating references and also has the xwalk feature.
instead_version "1.9.0"require"xact"
main = room {
nam ='Begin';
forcedsc =true;
dsc =[[ Begin {xwalk(startgame)|adventure}? ]];}
startgame = room {
dsc =[[ A long time ago ...]];}
Using simple methods like add() and del() you can dynamically fill a room with vway() objects:
objs(home):add(vway("next","{Next}.",'next_room'));-- some code here
home.obj:del("next");-- home.obj is legacy style, use objs(home) instead
This behavior is due to vobj() and vway() being generic objects with predefined handlers and save methods, which allows on-the-fly creation. Knowing the engine architecture will help you implement object variants like these.
In addition to using lightweight objects for scene decoration, you can also define a static object directly in the obj list without a handle:
hall = room {
nam ='living room';
dsc =[[I'm in a spacious living room.]];
obj ={
obj {
nam ='table';
dsc =[[There's a {table} in the middle.]];
act =[[It looks like mahogany.]];};};}
You can also target such objects via stead.nameof() within the use handler, just like you did with vobj() lightweight objects:
use =function(s, w)if stead.nameof(w)=='table'then
p [[I don't want to spoil such thing of beauty.]]returnendend;
(NEEDS AN EXAMPLE)
You can also use a single object in multiple rooms, such as shotgun shell which gets duplicated as it's dropped into the player's current room each time she fires her shotgun. In this case the shells might merely serve as decoration without being interactive.
Dynamically Spawning Objects
NOTE: It's recommended you study constructors before taking on this supplementary concept.
You can use the new() and delete() methods to create and remove objects dynamically. The new() method can accept a string argument that's written in the form of a constructor, which will of course return an object. Yay!
-- adds a constructor definition to the code
new ("obj { nam = 'test', act = 'test' }");
Better yet:
-- places a new constructor in-world
place(new [[
function myconstructor()
local v = {}
v.nam = 'test object',
v.act = 'test feedback',
return obj(v);
end;]]);
The constructed object will be saved every time the game is saved.
Something else to have fun with is the fact that new() adds a real object to the world, so you can find its variable identifier using the deref() method then do whatever you want with it, like delete it.
NOTE: This is an advanced topic so there's no need for beginners to study this section in detail.
Dynamically created references can be implemented in various ways. The example below uses vway objects. Notice, it's most convenient to create dynamic references in the enter handler, but you're free to leave them wherever they fall in your code.
To add a reference you can use the add() method.
objs(home):add(vway('Road','I noticed a {road} going into the forest...','forest'));
To delete a reference you can use the del() method.
objs(home):del('Road');
The srch() method will check if the reference is present in the target room.
ifnot objs(home):srch('Road')then
objs(home):add(vway('Road','I noticed a {road} going into the forest...','forest'));end
If a reference is created in the current room, the previous example can be simplified.
ifnot seen('Road')then
objs():add(vway('Road','I noticed a {road} going into the forest...','forest'));end
Or you can manage references with enable() and disable().
Creating initially disabled vobj and vway references works like this:
obj ={vway('Road','I noticed a {road} going into the forest...','forest'):disable()},
Enabling them by their index in the obj table or by finding them with srch is similar.
objs()[1]:enable();-- but better variant is
exist 'Road':enable();
Attributes and Handlers
Most attributes and handlers can be defined as functions. For example the nam attribute could just be defined as a string like so:
nam ='apple';
Or you could be fancy and define it as a function that returns that string, like so:
nam =function()return'apple';end;
Keep in mind the nam attribute must be defined as a string, even if you put some extra mojo inside its definition.
Strings passed to any of the p() methods are accumulated in an internal buffer for that specific attribute/handler. The accumulated content is passed back to the engine when the attribute/handler reaches its return. With this you can build a long text out of sequential calls to p()/pn()/pr().
NOTE: The engine performs general formatting with spaces and line breaks to separate corresponding text parts. So, usually you do not need to do manual formatting. Use pxxx-functions to format a single attribute value.
The principal difference between handlers and attributes is that handlers can change the game world's state, while attributes cannot. So, if you define an attribute as a function remember an attribute's only purpose is to return a value. This is because the calling of attributes within the engine as a player roams around freely is often unpredictable and isn't bound to a specific game process, so their application within mechanisms is strongly discouraged.
Something to note about the always mechanism-friendly handler is that it cannot pause the engine to wait for an event to happen. That means you can't employ delay loops or other game delay mechanisms inside the handler itself. They're intended to change the game state and immediately return control to the game engine. The interpreter must deliver the handler's changes quickly so it can continue listening for player input. If you need to delay something use the timer or cutscene modules.
Handler functions almost always contain conditional statements when dealing with variables.
apple = obj {
nam ='red apple',
dsc =function(s)ifnot s._seen then
p 'There is {something} on the table.';--first entrance descriptionelse
p 'There is an {apple} on the table.';--alternate descriptionendend;
act =function(s)if s._seen then
p 'The same apple, huh...';else
s._seen =true;
p 'Wow! It\'s an apple!';endend;};
The first parameter in a function (s) always refers to the parent object it self. When the player enters the room from this example her display will read “There's something on the table.” When the player clicks something the _seen variable inside the apple object will be set to true, which plays in nicely to the dsc function above it.
The stored variable _seen has an underscore at the beginning to signify it should be saved in the player's savegame file automatically. Now every time she returns to the room the game knows she's already seen the apple and will display the second description.
When an if statement is built without an operator ==, ⇐, ~=,…, the variable is judged as a boolean value. Therefore, being defined as anything other than false means it's true. So for example:
if s._seen then--asks "Does s._seen exist and is it not false?"
ACTION --if the first statement is true this is performedelse--deduces that s._seen doesn't exist or was defined as false
ACTION --so this is performed insteadend
When a numeric variable hasn't been defined and it's used in a conditional expression like the one above, the engine assumes the variable equals emptiness nil. You can check if a numeric variable exists with a similar code like this:
if z ==nilthen
p "Global variable z does not exist."end
The nil variable is also treated as false in conditional expressions:
ifnot z then
p "Variable z is not defined or false."end
But:
if z ==falsethen
p "Variable z is false."-- z == nil will not pass this condition!!!end
NOTE: The z variable above could be undefined and so, condition z == false will not work, that might produce a bug in your game logic, so double check!
You can also cascade conditional statements using then as a delimiter, like so:
ifnot z thenif z ~=nilthen
p "Variable z equals false."else
p "z is undefined! Sell your property and go hunt bugs!"endend
Take these behaviors into account when writing and debugging your game. If you have a misprint in a variable name, the expression will be evaluated as “undefined” with no error report. Your game logic will be broken and it will be difficult to find the error later.
On-The-Fly Attribute Generation
Considering the dynamic room dsc in the above example is generated with a function, why not alter the dsc on the fly? Actually this won't work unless you define the dsc in the room's var table like this:
button = obj {
nam ="button";
dsc ="There is a big red {button} on the room wall.";
act =function(s)
here().dsc =[[Everything changed in the room!!!]];
pn [[The room transformed after I pressed the button.^
The book-case disappeared along with the table and the chest,^
and a strange looking device took its place.]];end};
r12 = room {
nam ='room';
var {
dsc =[[I am in the room.]];};
obj ={'button'}};
It's highly recommended to NOT do this. Instead, write your attributes themselves as functions, and refrain from changing their values externally. Such programming style is costly, since you're making future modifications difficult by not keeping attribute values inside the objects they describe, and your savegame files will consume much more disk space.
Custom Attribute Lists
Standard attribute lists such as way or obj store objects in a table and manage themselves with a pre-defined set of methods, but lucky you can create lists for your own needs, and they don't have to be defined through var or global. For example:
treasures = list {'gold','silver'};
Your available list methods are:
add(object, [pos]) –add the object to this list, at the optional position
cat(b, [pos]) –insert the content of “b” (another list object) into this list at position
zap() –clear this entire list
del(object) –remove the object from this list, but ONLY if it's not disabled! (see next method)
purge(object) –remove any object, EVEN disabled ones
srch(object) –search for the object in this list. If found, return the found item and its index
replace(old, new) –replace the old object with the new one
enable(object) –enable the object, if found
disable(object) –disable the object, if found
enable_all –enable all objects in this list
disable_all –disable all objects in this list
The methods add, del, purge, replace, srch and others can receive objects by nam as well as by a referenced link.
A simple example involves the system method inv() that references the player's inventory list, which can be quickly manipulated with a list method like so:
inv():del('apple'),
You could even cleverly recreate the take functionality inside the act handler using list methods:
knife = obj {
nam ='knife',
dsc ='There is a {knife} on the table.',
inv ='It\'s sharp!',
act =function(s)
objs():del(s);
inv():add(s);end,};
The system method objs() returns the list containing all current room objects, or if given a parameter it can target any room. The corresponding method for getting the list of passages for a given room is ways().
Starting in version 0.8 the game object can be used as a parameter for the add() method. Also there's an optional second parameter that targets a specific position in the receiving list. You can now also set a list's values by index using the set() method. For example:
objs():set('knife',1);
Starting in version 0.9.1 the enable_all() and disable_all() methods work with embedded objects (objects within object), and the methods zap() and cat() became available.
Starting in version 1.8.0 the methods disable() and enable() can be used, however…
NOTE: It's highly recommended when manipulating objects and inventory to use higher level methods such as:
place/get/take/drop/remove/seen/have
The Handler's Text Buffer
Sometimes we need to format output from a handler based upon a prescribed set of conditions. In such a situation the p() and pn() methods are quite useful. These two methods add formatted text to the internal buffer of the handler, which gets returned by the handler in one big chunk.
dsc =function(s)
p "There's a {barrel}."--this gets buffered, with whitespaceif s._opened then
p "Its lid is leaning against the wall."--this gets tacked onto the first oneendend;
One way to update the player with the status of some action is via pget() in the return.
knife = obj {...
use =function(s, w)-- (w) is another object the player -- is using the knife to interact withif w == apple then-- so if it's an apple-- she says she peeled it
p 'I peeled the apple';-- and the apple stays peeled
apple._peeled =truereturnend
p [[You shouldn't use a knife like that.]]return pget(),false;-- false is to stop call 'used' methodend;
No-Output Handlers
Sometimes we need a handler doing something without producing any output.
button = obj {
nam ="button";
var {
on =false;};
dsc ="There is a big red {button} on the wall.";
act =function(s)
s.on =truereturntrueend;};
r12 = room {
nam ='room';
forcedsc =true;
dsc =function(s)ifnot button.on then
p [[This button hasn't been pressed.]];else
p [[The button has been pressed.]];endend,
obj ={'button'}};
Here the act handler alternates the room description, so there's no need for it to print anything itself. It's a contrived example, and you'll likely never need handlers with no output. As for the above example, you could instead print “Pressed the button.” with the act handler's function. Moreover, the example requires forcedsc mode, which breaks backward compatibility. However, such a handler could at some point be useful, so it's worthwhile to be aware of it.
You could also return only true from an act handler, which would mean an action completed successfully but didn't require any output. If you need some kind of report for the player, just don't return anything, which will display your default game.act text. Problem solved.
Handler Calls Using Lua
Sometimes you need to call a handler manually. You can use the shortform Lua syntax for method calls object:method(parameters) if the handler act, tak, inv, etc., was written as a function within the target object:
-- the function in the act handler of apple is called
apple:act()
For more clarity here is the longform of the Lua syntax:
-- so the shortform above simply erases the (self) parameter -- since it's redundant, and replaces the period with a colon
apple.act(apple)
If the object's handler is not a function you can utilize the stead.call() routine to call the handler in the same manner as the interpreter does.
The syntax is: stead.call(object, 'name of handler/attribute', arguments…)
So, for example, to get nam value of the object apple you can do this:
act =function(s)-- somewherelocal r = stead.call(apple,'nam')
p(r)end
In fact, there is the stead.nameof() for this case, but this is just an example.
Dynamic Events And Life Simulation
Handlers can be programmed to execute as certain player conditions are met. This technique is often used to run background processes such as life simulation. For this purpose a special event handler exists, called the life handler.
Any object, including rooms, can employ a life handler, which is called every time the game clock advances as long as the object has been added to the worldwide list of living objects (more on this in a moment). Remember, the game clock is the total number of player actions so far in the game.
Dynamic events follow this rough algorithm for each tick of the game clock:
The player does something in the game, which increments the game clock
The life handlers for all living objects are triggered
Relevant object's commence their life-like behaviors
To illustrate a simple living object, first let's give Barsik the cat some animation.
mycat = obj {
nam ='Barsik',
behaviors ={[1]='Barsik peers out of my coat.',[2]='Barsik kneads my chest. OWWW!',[3]='Barsik purrs against me.',[4]='Barsik yawns.',[5]='Barsik\'s warmth is welcome.',[6]='Barsik escapes from my coat.',},
life =function(s)local r = rnd(5);if r >2thenreturn;--provides a 60% chance of no behaviorend
r = rnd(#s.behaviors);--the # symbol gets the last index number
p(s.behaviors[r]);--displays a random behaviorend;};
Now that Barsik looks like a living creature, let's make it final.
lifeon('mycat');
Okay, like you were told before, this is the part about the worldwide list of living objects. The lifeon() method as seen in the code above, adds Barsik the cat to this list. Don't forget to remove deceased objects from the list with lifeoff(), usually via the exit handler.
If there are a dizzying number of living objects in your game, you may want to assign priority levels. The second parameter of lifeon() takes a positive integer, with 1 being highest priority.
lifeon(object_name, positive_integer);
To initialize a life simulation background process for a particular room, simply include lifeon() in an object's entered handler and lifeoff() in the left handler, then define the process in the life handler.
cellar= room {
nam ='in the basement';
dsc =[[It's dark down here.]];
entered =function(s)
lifeon(s);end;
left =function(s)
lifeoff(s);end;
life =function(s)if rnd(10)>8then
p [[I heard something rustling!]];--scare tactics!endend;
way ={'upstair'};};
You can discern whether the player has crossed a room's border by employing the player_moved() method inside the life handler.
flash = obj {
nam ='flashlight';
var { on =false};
life =function(s)if player_moved()then-- extinguishes the flashlight on room changes
s.on =false-- let's the player know what happened
p "My light went out..."returnendend;...};
To track continuous events you can use the time() method, or utilize a custom counter variable. To track player location use here() function. Use these in conjunction with the live() method, which checks if an object is still living.
dynamite = obj {
nam ='dynamite';
dsc ='BOOM!';
var { timer =0};
used =function(s, w)-- checks if the player uses their zippo lighterif w == zippo thenif live(s)then--checks redundantreturn"It's already lit."else-- the player lights it
p "I ignited the fuse."-- the dynamite is added to list of living objects
lifeon(s)endendend;
life =function(s)-- if it's living/lit the timer starts
s.timer = s.timer +1if s.timer ==5then-- when it reaches 5 the dynamite dies
lifeoff(s)if here()== where(s)then-- checks if player is in the same room
p [[...yeah I'm dead.]]else
p [[I heard an explosion somewhere.]];endendend;};
If a life handler returns a string, it displays below the room's static description by default. To make the text appear above, simply return true as it's second value.
life =function(s)return"The guardian entered the room.",true;end;
If you want to prevent all life processing in a particular room, add the nolife module to your game.
instead_version "1.8.2"--needs to be this version or higherrequire"hideinv"require"nolife"...
guarddlg = dlg {
nam ='Guardian';
hideinv =true;
nolife =true;...};
WARNING: When putting the walk() method inside a room's life handler you should take into account whether the life handler will affect player location. If so, any results from the handler will will be discarded at the point of player movement, then her new location's life handler will activate. This can get confusing, so it's best to implement walk() carefully.
The life handler is capable of removing all act handler text from the display, in order to let more pressing events dominate the player's focus. Imagine the player is examining a regular window in a boring room…
window = obj {
rainrain =false;...
act =function(s)
s.rainrain =true;--the variable that triggers the goblinreturn"It's probably about to rain. I wonder where my umbrella is..."end;};
Suddenly an evil goblin enters the room and runs right for her. In such a panicky situation, the window's act handler telling her about rain and umbrellas is useless information. The following code removes it and allows the goblin's life handler to take center stage.
goblin = obj {...
life =function(s)if rainrain ==truethen
p("A goblin's running toward me!");-- removes all act handlers' text from the display
ACTION_TEXT =nil;end;end};
The ACTION_TEXT variable is unique to life handlers because of their clock-based routine, and allows them to change the output of all act handlers in the room at once. It's usually best to simply clear it with nil. This way the player can focus on the goblin. :O
Methods/Functions
The game engine provides several standardized methods for returning frequently used objects (Methods are also called functions, but this manual tries to reserve the term “function” for your own code definitions, and tries to use the term “method” for anything pre-defined within the engine, simply to make it easier for young developers to discern what's being addressed in this manual).
The following terminology is used throughout this section, so refer to it as you study the methods themselves.
Common Terminology
brackets [ ] – encloses optional parameters
what – the target object (including rooms) passed as a link, reference, or variable handle
where – the target object (including rooms) passed as a link or reference
room – a room object passed as a link or reference
object – an obj object, such as a table or an NPC
passage – a value held in the way list as a link, reference, or variable handle
Methods Returning Lists
inv() –returns the inventory objects list
objs([where]) –returns the objects list of the current room or of an object passed as the optional parameter
ways([room]) –returns the passages list for the current room or for one passed as the optional parameter
Methods Returning Objects
me() –returns the current player object
here() –returns the current room
where(object) –returns a room or an object that was placed using a put/move/drop/replace method, or others
from([room]) –returns the previous current room of the player or the room visited prior to the room passed as the optional parameter
seen(what[, where]) –returns the object in the current or optionally targeted room, if the object is present there and is not diabled (see exist())
exist(what[, where]) –similar to seen() but will find disabled objects
have(what) –returns the object (what) if it's present in the inventory and is not disabled
live(what) –returns the object (what) if it belongs to an alive object (read further in wiki)
path(passage[, room]) –returns the passage from the way list of the current room or one passed as the optional parameter, even if it is disabled
The methods used in the following example are usually used in conditional statements or to find objects for subsequent modification.
exit=function(s)-- remember that Lua allows solo parameters to go without parenthesesif seen 'monster'then
p 'The monster blocks your way!'--another solo parameterreturnfalseendend;...
use =function(s, w)if w == window and path 'Through the window':disabled()then-- checks whether actions on the window and -- its corresponding passage are currently -- disabled in the game--enables them
path 'Through the window':enable();-- dramatizes with descriptive fiction. Yay!
p 'I broke the window!'endend;...
act =function(s)if have('knife')then
p 'But I have a knife!';returnendend;
Two useful system methods are:
stead.ref(reference)
stead.deref(object)
The first returns a link to the object passed by reference, the second one returns a reference to the object passed directly. This might seem confusing now, but you'll soon understand the concept of references versus objects. Here's an illustration:
-- if apple is defined, the reference is equal to the variable itself
stead.ref 'apple'== apple
Here's a simple example of stead.deref() in action:
act =function(s)-- this takes the object, s, that was passed to the -- function directly and returns a reference so that -- the print method can concatenate it with its string
p('You clicked the object '.. stead.deref(s));end;
Code Statements
A code statement is useful for defining a very short function. To save them you can assign them to a stored variable in the global or var tables. Here's an example of a short code function called in an act event handler:
act = code [[ walk(sea) ]];
You can also redefine code functions on the fly, although this is usually an example of bad programming.
Invoked code statements can create objects automatically. They are also “self” variables that target the object that contains them. An args[] table holds all the code statement's arguments.
dsc = code [[
if not self._seen then
p '{Something} lays on the tabe.';
else
p 'There is an {apple} on the table.';
end
]];
NOTE: The code statement above is already surrounded with double brackets, so if you need to write a multi-line string literal inside a code statement you must use nested brackets such as [=[ ]=], [==[ ]==], [===[ ]===], etc. The same is needed for delineating line breaks with the ^ symbol. Study the Lua syntax rules for more detail.
Temporary Values
Sometimes you may need to create an auxiliary variable in the var table to store a temporary value, for example:
kitten = obj {
nam ='kitten';
var { state =1};-- state variable--(s) refers to the entire kitten object, and -- this act handler function is triggered by each click
act =function(s)-- increments the "state" temporary value -- by 1 with each click
s.state = s.state +1-- iterates until it reaches 3if s.state >3then-- resets it to 1, creating a clickable looping effect
s.state =1end
p [[Purrr!]]end;-- this dsc attribute function's table is related -- to the handler function above through the index
dsc =function(s)local effects ={"The {kitten} is purring.","The {kitten} is playing.","The {kitten} is washing up.",};-- Now the index corresponding to the -- click loop in "act" can be printed to the player's screen-- s.state is a numerical index for the -- effects table which has 1,2,3 (a Lua index begins at 1)
p(effects[s.state])end;}
In the dsc function above, a local table is created. The keyword local constrains the table's visibility to within the function itself, meaning nothing outside can see or use it. And local variables are not written in save files. You should define all auxiliary/temporary variables as local.
The example could also be written like this:
dsc =function(s)if s.state ==1then
p "The {kitten} is purring."elseif s.state ==2then
p "The {kitten} is playing."else
p "The {kitten} is washing up.",endend;
Or like this with an external function:
function mprint(n,...)-- temporary table for the function argumentslocal a ={...};-- prints the nth element of the temp table
p(a[n])end;
Which can be called inside the dsc attribute:
dsc =function(s)
mprint(s.state,{"The {kitten} is purring.","The {kitten} is playing.","The {kitten} is washing up.",});end;
To simplify it all you could write it like this:
dsc =function(s)
p(({"The {kitten} is purring.","The {kitten} is playing.","The {kitten} is washing up.",})[s.state]);end;
Saving Game Variables
Savegame files store the delta (change) between the initial and current game state. They can store these types of variables:
string
boolean
numerical value
link to an object
code statement
There are three ways to create a stored variable.
Write the variable name with an underscore character at the beginning (which we saw in the previous section with _seen)
Define the variable in a “var” table inside some object
Define the variable in a global table
Using var and global is more intuitive so it's recommended.
Defining the global table is simple:
global {-- a number stored in the savegame file
gl_var =1;-- another number
gl_num =1.2;-- a string
gl_str ='hello world';-- a boolean
knows_truth =false;}
Defining a var table inside an object is also simple:
main = room {
var {-- this will be stored in the savegame file
i ="a";
z ="b";};
nam ='My first room.';
var {-- also stored
new_var =3;};
dsc =function(s)--(s) refers to main, so s.i refers to -- the variable "i" that's housed in -- main's var table
p ("i == ", s.i);-- global elements are mapped to the -- global namespace. It's as if "global"-- and "var" tables are excluded from the -- object tree
p ("gl_var == ", gl_var);end;
Every element inside the var and global tables must have an initial value. A system dummy object called null will serve as a default placeholder if you don't define a value. It can be replaced by your game code later.
Auxiliary Methods/Functions
The game engine has a number of high-level methods that may be useful to you. You've already encountered some of them.
-- moves the object from the current -- or given room to a given destination
move(what, where_to[, where_from])
If you want to move an object from an arbitrary room you must to know its location. For implementing objects moving in a complicated fashion you could write an advanced function to track the object's location as it moves between distant rooms, or you could locate the object with the where() method each time, like so:
move(mycat, here(), where(mycat))--mycat travels with me
NOTE: You must handle the moving object with a place() method in order for the where() parameter to work.
The movef() method is similar to move() but the target object is sent to the beginning of the destination obj list.
The twin methods drop()/dropf() and take()/takef() act in the same way except they focus on the player's inventory.
drop(what[, where]) –removes the object from the player's inventory and places it at the end of the current or optionally given room's obj list
dropf(what[, where]) –drops it at the beginning of the target room's obj list
take(what[, where_from]) –removes the object from the current or optionally given room and places it at the end end of the player's inventory
takef(what[, where_from]) –adds the object to the beginning of the player's inventory
The four methods above can materialize objects in the target obj lists even if the object wasn't present in the source location. This trick is often used for filling the player inventory in the init function.
function init()
-- this knife doesn't exist anywhere,
-- it simply materializes in the player's
-- inventory list. Yay!
take('knife');
end;
The following methods all work in a similar fashion to those above.
place(what[, where]) –places the object at the ending of the current (or optionally given) room
placef(what[, where]) –places the object at the beginning
put(what[, where]) –OBSOLETE!
putf(what[, where]) –OBSOLETE!
replace(what, what_with[, where]) –replaces the first object with the second in the current or optionally given room
remove(what[, where]) –removes an enabled object from its current location or an optionally given room
purge(what[, where_from]) –same as remove() but disabled objects may be removed as well
The methods above can be applied directly to rooms and objects as well.
-- uses the me() pointer to access the -- 'pl' object's inventory listremove(apple, me())
Some methods above have variations suffixed with to which receive an additional parameter, the specific position for the object to be inserted into the target list.
dropto(what[, where], pos) –since version 2.2.0
taketo(what[, where], pos)
placeto(what[, where], pos)
More methods include:
lifeon(object[, priority]) –adds the object to the dynamic “alive” objects list (explained more later). The optional parameter must be a numeric value, with 1 representing the highest priority
lifeoff(object) –removes the object from the alive list
taken(object) –returns true if the object has been taken (with tak handler or a take() method) and defaults to false
rnd(m) –returns a random integer in the range from 1 to m
You will often move the player to different rooms. It's important to mention that calling walk() triggers a corresponding exit/enter/left/entered handler, which may occasionally cancel the player's movement. Continue reading to learn how to manage this situation when it arises.
Here's the basic format for walk() followed by an example:
walk(where_to) –moves the player to the specified room
.......................................
act = code [[
pn "I'm going to the next room..."
walk(nextroom);
]]
Here's an example of passing a function reference to the walk() method:
mycar = obj {
nam ='my car',
dsc ='In front of the cabin there is my old Toyota {pickup}.',
act =function(s)
walk('inmycar');end;};
NOTE: walk() doesn't interrupt the parent handler's execution, so you should typically place your function's return operator immediately after walk() like this:
act = code [[
pn "I'm going to the next room..."
walk(nextroom);
return
]]
To change players:
change_pl(player)-- switches the game to the specified player, -- who has an independent inventory and location
When changing players the exit/enter/left/entered handlers are not triggered.
If you'd like to simulate offscreen movement for an inactive player without having to change players, you can simply change her location using the where attribute:
jane.where ='Living Room'
Here are even more auxiliary functions:
walkback([where_to]) –moves the current player to the previous room, or to a specified room, and the player's from property will not be changed
back([where_to]) –similar to walkback() except when using back() to move from a dialogue to a room, the room's dsc/enter/entered handlers will not be called, but the dialogue's exit/left handlers will be called
walkin(where_to) –moves the player to the specified room, while the current room's exit/left handlers are not called
walkout() –moves the player to the previous room, and its enter/entered handlers are not called
time() –returns current game time, calculated by the number of player actions performed so far
cat(arg1,arg2,arg3,etc…) –returns a concatenation of its arguments, but returns nil if the first argument is nil
par(arg1,arg2,arg3,etc…) –returns a concatenation of its arguments, each delimited by the content of the first argument
visited([room]) –returns the number of times the current or specified room has been visited, or nil if never
visits([room]) –similar to visited() but returns zero instead of nil
player_moved([player]) –returns true if the player has moved this game tick, usually used for life handlers
In cases where forcedsc is incompatible with your intentions you can use the following method to maintain compatibility:
stead.need_scene([room]) –makes the engine show the room's static dsc during the next game tick
stead.nameof(object) –returns the object's nam attribute
stead.dispof(object) –returns the object's disp attribute, or nam if disp is empty
disabled(object) –returns true if the object is disabled
stead.call(link, attribute_handler_string_name, parameters) –calls the handler, or returns the attribute value (this method is explained further later)
instead_gamepath() –returns the full path of the game directory
instead_savepath() –returns the full path of the saved game files directory
Text Formatting
You can accomplish simple text formatting with the following methods:
txtmiddle() –aligns text at the middle of the screen on both axes (this is the default setting)
txtc() –longitudinally aligns text in a central column on the screen
txtr() –longitudinally aligns text to the right side of the screen
txtl() –longitudinally aligns text to the left side of the screen
txttop() –latitudinally pushes text to the top of the screen
txtbottom() –latitudinally pushes text to the bottom of the screen
For example:
main = room {
nam ='Intro',
dsc = txtc('Welcome!'),};
You can define a special text style with these methods:
txtb() –boldface
txtem() –embossed
txtu() –underlined
txtst() –strikethrough
For example:
main = room {
nam ='Intro',-- only boldfaces the word "main"
dsc ='You are in the room: '..txtb('main')..'.',};
Since version 1.1.0 you can create unwrapped strings using the txtnb() method.
main = room {
nam ='Intro',
dsc ='You are in the room '..txtnb('main')..'.',};
Constructors and Inheritance
WARNING: If you're writing your first game you should make it simple, that is to say the information in this section of the manual is not important. Moreover, 90% of the games written for INSTEAD don't use anything in this section! That's not to say it's not a good section.
The idea of constructors should come to mind once your (second/third/tenth) game has a lot of similar objects. You might say to yourself, “I really would love to simplify this code…” Constructors are a method for automating common pieces of code. In fact the obj, room, and dlg handlers are constructors themselves.
When you write something as an obj a constructor method calls obj as a parameter which passes the table {nam = 'name'}.
apple = obj {
nam ='apple';
dsc ="It's a nice apple.";};
Say you need a window in your game. Realizing that real windows can be broken, you can write a constructor that will manifest all windows with this realistic behavior, saving yourself from ever having to code it again.
-- first create a variable as a function
window =function(v)-- pass itself in as true
v.window =true-- assess whether nam is emptyif v.nam ==nilthen-- if so, fill it with this generic title
v.nam ='window'end-- assess whether dsc is emptyif v.dsc ==nilthen-- if so, fill it with this phrase
v.dsc ='There is a {window}'end-- function to define the default act handler -- regardless of whether it's empty
v.act =function(s)-- assess whether the act handler's _broken variable is set to trueif s._broken then-- if so display this phrase when the player clicks the window
p [[broken window.]]else
p [[dark outside.]]--otherwise display thisendend;-- assess whether the used handler is emptyif v.used ==nilthen-- function to autofill the used handler
v.used =function(s, w)-- assess whether the second parameter (parent self?) if w == hammer thenif s._broken then
p [[Window already broken.]]else
p [[I broke the window.]]
s._broken =true;endendend;endreturn obj (v)end;
This constructor fills in some of its attributes and handlers itself, but still allows the player's game and your own extraneous code to override some of them. At the end it calls for the manifestation of the object with finalized parameters and returns the custom window object.
win1 = window {-- overrides only the dsc attribute, while all others auto-fill
dsc ="In the eastern wall is {window frame}.";};
Since windows are usually static objects, you can could put it directly into the obj handler for your room instead.
obj ={
window {
dsc ='In the east wall is a {window frame}.';}};
If you want to use a more conventional constructor syntax, as a function that takes several parameters instead of a table it's perfectly workable. In fact, vroom(), vobj(), and vway() follow this paradigm.
window =function(nam, dsc)-- creates an empty table inside functionlocal v ={}
v.window =true-- begins to fill the table
v.nam ='window'-- checks if the given window being passed in already has a dscif dsc ==nilthen
v.dsc ='There is a {window}.'end
v.act =function(s)if s._broken then
p [[broken window.]]else
p [[dark outside.]]endend
v.used =function(s, w)if w == hammer thenif s._broken then
p [[Window already broken.]]else
p [[I broke the window.]]
s._broken =true;endendend-- sends the local window settings out -- into the world as a constructed (pre-filled) objectreturn obj(v)end;
A call to this constructor will look different than the table-based calls.
obj ={window ('Window','In the east wall is {box}.')}
In fact, both approaches are useful in different scenarios. If the constructed object might be assigned new or optional attributes later, it's easier to define the constructor as a function of the host table, the same as obj, room, and dlg are. Otherwise, if the object is sure to be hands-off later, you can use define it as a function itself.
You might need to add new variables to your constructor. In the example above we created the stored variable _broken prefixed with an underscore because stored variables can be created on-the-fly. But if you need unstored variables you can use stead.add_var() instead. The syntax is shown below.
stead.add_var(constructor,{variable = value})
Here's the previous example with an unstored variable.
window =function(v)-- adds the "broken" with a value to your constructor
stead.add_var (v,{broken =false})
v.window =true...end;
If you need to add a global variable it can be done on-the-fly just like stored variables.
-- no need for parentheses since it's only one parameter
stead.add_var {global_var =1}
Now that we understand how to build constructors, let's think about the concept of inheritance. It's employed in the examples above, at least it will be since there's no other reason to have constructors. They call another constructor window obj, thereby obtaining all the properties of a regular object. Also, window defines a variable attribute window, to the game, we can understand that we are dealing with a window. For example:
use =function(s, w)if w.window then
p [[I point my flashlight at the window.]]returnendend;
To illustrate the mechanism, create a class of objects called treasure.
global {score =0}
treasure =function()local v ={}
v.nam ='treasure'
v.treasure =true
v._points =100
v.dsc =function(s)
p ('There are {', stead.dispof (s),'}.')end;
v.inv =function(s)
p ('same', stead.dispof (s),'.');end;
v.tak =function(s)-- Increase the account
score = score + s.points;
p [[With trembling hands I took the treasure.]];endreturn obj (v)end;
Now, based on our treasure constructor we can define any specific kind of treasure for our world. How about gold, diamond and a treasure chest?
gold =function(dsc)local v = treasure ();
v.nam ='gold';
v.gold =true;
v.points =50;
v.dsc = dsc;return v
end;
diamond =function(dsc)local v = treasure ();
v.nam ='diamond';
v.diamond =true;
v.points =200;
v.dsc = dsc;return v
end;
chest =function(dsc)local v = treasure ();
v.nam ='chest';
v.chest =true
v.points =1000;
v.dsc = dsc;return v
end;
To put them in the game, just define a variable with them, and as you should be able to tell the inherited aspects from treasure() fall into diamond(), gold(), and chest(). Cool huh?
diamond1 = diamond("I see a glittering {sparkle in the mud}...");
diamond2 = diamond();--fills in with the default dsc
gold1 = gold("In the corner, a bit of yellow {shimmers}.");
cave = room {
nam ='cave';
obj ={'diamond1','gold1',
chest ("A heavy steel {chest}!")};}
When writing constructors sometimes you'll want to run a handler or a method from within. Simply use this system method to access it:
stead.call(object, method/handler, parameters)
You could modify the window constructor you created earlier (example to show syntax, rather than a purposeful modification).
window =function(nam, dsc, customHandler)local v ={}--creates an empty table
v.window =true--fills the table
v.nam ='window'
v.newHandler =function(s)-- custom event handler that returns a string
p("It looks to be antique glass.")end;if dsc ==nilthen
v.dsc =[[There's a blurry plate glass {window}]]end
v.act =function(s)if s._broken then
p [[It's broken.]]end-- calls the custom 'newHandler' and places its result in r local r = stead.call(s,'newHandler')if r then
p(r)else
p [[It's so dark outside.]]endend;...end;
WARNING: The following section is merely to prevent frustrating misunderstandings with the nature of constructors. This is the fact that it's impossible to save objects created on-the-fly without giving them a variable identifier. To do so requires much deeper engine manipulation than should be necessary for the scope of this engine.
For example, you could try to create a custom version of vway(), calling it xway(). When clicked, an xway() object executes xwalk(), like so:
function xway(name, dsc, w)-- you can't pass the variable handle itselfreturn obj {
nam = name;
dsc = dsc;
act =function(s)
walk (s.where)end;
where = w;};end
When you attempt to implement this in real code it will be impossible to save any object it constructs because there's no variable identifier.
-- creates an handle-less object
place(xway('the cave','{In the cave}','cave'));
The engine then won't be able to save the object. This is because generally speaking, INSTEAD is only able to save objects that have identifiers. Objects that only have descriptors are created by explicitly assigning them in a global context such as (apple = obj {…}) or by creating them within lists such as obj and way where they're indexed and stored. (Also you can use the inadvisable new() system method described below).
Nevertheless, there is a way to store these objects, but it isn't useful for writing games. It's intended for the development of modules and extensions of the engine. If you're an average storytelling game designer, you can safely skip the following explanation. In fact, it's the wish of the creator of INSTEAD that designers never need to deal with the engine on this level.
So, to save a variable in a very unfriendly and non-game-code kind of way you can do this:
function xway (name, dsc, w)return obj {
nam = name;
dsc = dsc;
act =function(s)
walk (s.where)end;
where = w;
save =function(self, name, h, need)-- self == the current object-- name == the intended handle of the variable-- h == file descriptor for savegame-- need == a sign that it is creating an object, -- the save file is to create an object at startuplocal dsc = self.dsc;--startuplocal w = stead.deref(self.where);if need then-- in the case of creation, -- we write the string to call the constructor
h:write(stead.string.format("% s = xway (%s,%s,%s); \ n",
name,-- Formation of lines
stead.tostring(self.nam),
stead.tostring(dsc),
stead.tostring(w)));end
stead.savemembers (h, self, name,false);-- keep all other variables such as object, -- the state of the on / off, etc. false -- in last position.-- this will mean they'll be transferred to -- the save-nested objects as the 'need' parameterend};end;
Dialogues
Character dialogues are actually room objects of type dlg which contain a phr object list. There are two types of dialogues: extended and simple. Both share the same behavior, but simple dialogues are deprecated and therefore not recommended for use.
When the player enters a dialogue she will see a list of phrases. When she clicks a phrase it triggers a reaction. By default, clicked phrases become hidden. When all of them have disappeared the dialogue returns the player to the previous room. Often you may want to include permanent phrases like 'End this dialogue.' or 'Ask one more time.' to prevent the dialogue from escaping to the previous room.
Dialogues are entered in the same way as rooms:
cook = obj {
nam ='cook';
dsc ='I see a {cook}.';
act =function()return walkin 'cookdlg'end;};
It's recommended to use walkin() instead of walk() because the current room's exit/left handlers won't be activated. Also, the person we want to talk to is usually in the same room with the player, so we don't want to officially leave.
You can enter a dialogue from another one, implementing hierarchical branches. You can return to the previous branch with a back() call. Extended dialogues implement hierarchical branches much better than simple dialogues.
The engine prevents entering a dialogue with no phrases because it will be exited automatically. Take this into account while debugging your games.
NOTE: It's highly recommended to use the hideinv handler when implementing dialogue into your game. Dialogues will look better if inventory items aren't affecting phrases, and you will prevent unexpected conversation bugs. Add it by putting this at the top of the main.lua file:
require"hideinv"
Then declare it as true inside your dialogues like this:
guarddlg = dlg {
nam ='guard';
hideinv =true;
phr ={{...};{...};};}
The most common mistake is incorrectly calling a dialogue from the inv handler without hiding the inventory itself from the dialogue. For example, when building a mobile phone inside your game, you would expect the player to click the phone inside her inventory which would trigger a dialogue. As you know from above, escaping dialogues is usually done through a back() call, but if the inventory isn't hidden and the player clicks the mobile phone in the inventory one more time, we run into the same dialogue again! The back() call will put the player in the previous instance of the dlg “room” instead of the actual room she's supposed to be standing in. Of course you can avoid such situations by doing the following:
phone = obj {
nam ='mobile';-- when the player clicks on -- her mobile phone in her inventory
inv =function(s)if here()~= phone_dlg then-- this line checks if the current room -- is the actual room she's in, not the dlg "room"-- if she's not in the dlg, it makes her walk into the dlg
walkin(phone_dlg)returnend-- this relates the situation to the player :)
p "I am already holding the mobile."end;};
Extended Syntax
Since version 1.7.0 a more powerful dialogue syntax has been supported, known as extended dialogues. Unlike the earlier format, now phrases are defined in a phrase table:
cookdlg = dlg {
nam ='in the kitchen';
hideinv =true;
entered =[[I notice the round face of the
cook under her white hat. Her eyelids are sagging...]];
phr ={{ always =true,"Some of those greens, please. And beans too.","Mmmmkay."};{ always =true,"Fried potatoes please.","Of course."};{ always =true,"Two bowls of garlic soup.","My favorite..."};{ always =true,"Something light, please. I've got an ulcer.","No soup for you!"};{ always =true,"Thank you, I don't want anything.","Get out of the line.",[[back()]]};};};
If the dialogue's dsc attribute isn't defined, it will automatically call the last reply from the player's interactions. The player will see it after clicking the room's caption. Because of this pleasing behavior it's better to put any necessary static description of the dlg into its entered handler, as was done in the above example.
Phrases are written with the following syntax:
phr ={[index or tag=TAG],[falseif disabled],[always =true],"Question","Reply",[[reaction code]]};
Each phrase is presented to the player as its “Question” value. The “Reply” value is displayed after it's been clicked. The phrase then becomes disabled (changing its second value to false), and the reaction code is invoked (if present). The reaction code may contain any Lua code.
The game engine provides the following methods to use on phrases:
pon(indexes or tags) – unhides phrases
poff(indexes or tags) – hides phrases
prem(indexes or tags) – removes/blocks phrases, and they CANNOT be re-enabled with pon()
pseen(indexes or tags) – returns true if all targeted phrases are visible
punseen(indexes or tags) – returns true if all targeted phrases are hidden
If called with no arguments, these methods are applied to the current phrase, wherever the code is called from.
To manipulate phrases in a different dialogue, you can use the following syntax:
dialogue:method()
For example, if the guard_dlg exists somewhere outside the current room, you can target it like this:
guard_dlg:pon('show_card')
You will probably find it quite useful at times to disable a phrase initially, then enable it later, like so:
cookdlg = dlg {
nam ='in the kitchen';
hideinv =true;
entered =[[I see a fat face of a lady-cook wearing a white hat. She looks tired...]];
phr ={-- this phrase is initially disabled{1,false, always =true,[[I love those!]],[[Everyone does.]]};-- once the player finds out there are -- french rolls on the shelf the first phrase is enabled{[[What's on the shelf?]],[[French rolls.]],[[pon(1)]]};{ always =true,"Some greens, please.","Good."};{ always =true,"Fried potatoes, please!","Eat now!"};{ always =true,"Thanks, but I'm not hungry.","As you wish.",[[back()]]};};};
If you'll never need to manipulate a certain phrase, you can drop its first field entirely:
{"Question?","Reply!"};
To manipulate phrases you can target them by index:
{2,"Question?","Reply!"};
However for complex dialogues, tags are more suitable:
{ tag='exit',"I'm leaving!",[[back()]]};
A tag is simply a string label. As explained earlier, you can pass phrases by index or tag to the pon/poff/pseen/punseen methods. You can also assign a tag to an indexed phrase.
NOTE: If multiple phrases have the same tag, the method's action will be applied to all of them.
Also be aware that:
pseen() will return true if at least one of its targeted phrases that has the plural tag is visible
punseen() will return true if at least one of its targeted phrases that has the plural tag is hidden
If a phrase contains always=true it won't be automatically hidden after being clicked by the player.
If you want to implement reaction code without returning any text after the player clicks the phrase, you can use one of the following examples:
-- put "nil" in the Reply field{ tag='exit', always=true,"Ok, I'm leaving!",nil,[[back()]]}-- which will return nil simply omit the Reply field, { tag='exit', always=true,"Ok, I'm leaving!", code =[[back()]]}
You can also define questions and replies as functions or code:
-- this phrase unhides the next{ tag='exit', code[[p"I'm leaving."]], code[[p"Please stay."; pon'really']]},-- this phrase is hidden by default{ tag='really',false, always=true,"No, I'm really leaving!",function() back()end}
For complex branching dialogues you can group phrases, which essentially removes the need for massively convoluted pon/poff crossovers between numerous dlg objects. Phrase groups are displayed to the player as a new dialogue screen.
Each phrase group must be separated by a phrase with no reaction code. The simplest implementation is using curly braces { } but you can use anything to separate your groups, even a tag which allows you to target switching between branches, like so:
--First Phrase Group{'Tell me about the weather.','Does I look like I keep up with the weather?',[[psub 'weather']]},{ always=true,[[Goodbye.]], code =[[back()]]},-- this is the delimiter, essentially serving as -- a targetable nametag leading into the following branch{ tag ='weather'},-- Second Phrase Group{'Do you know the current temperature?','Maybe... 25 Celsius.'},{'What the humidity like?','Definitely eighty percent. You can bet on it.'},
Only one group can be visible to the player at a time. Upon entering the sample dialogue above, the player sees two phrases to choose from. The first phrase's code pushes her down to the phrase with the tag weather, which is empty, so it basically falls out of the way and the second phrase group takes over. After both questions in the second group are asked, the player is pushed back to the initial branch, where the only visible phrase left is 'Goodbye.'
Branch switching is implemented with the following methods:
psub('branch') –pushes the player into the target branch, or the following sub-group by default, and adds a return behavior, such that after all branch phrases are disabled or pret() is invoked (this method is explained later), the player will automagically return to the caller branch. Nifty!
pjump('branch') –makes an unconditional jump to the target branch, or the following sub-group by default, and adds a return behavior like psub() does
pstart('branch') –makes an unconditional jump, and erases the psub() call stack, preventing the return behavior
To get the current branch's index use this method:
dialogue_nam:current()
To get its tag use this:
dialogue_nam:curtag()
You can apply the two methods above to external dialogues as well, like so:
shop_dlg:pstart(1)
A branch's active/inactive status as well as the number of visible phrases remaining. Simply use the following methods, both of which accept the index/tag of the leading phrase in the target branch as their argument. If no argument is supplied, the current group is processed:
-- returns true if the group contains no active phrases
dialogue_nam:empty([t])-- returns the number of visible phrases, 0 if empty
dialogue_nam:visible([t])
You can get fancy with how branches are displayed to the player by including a Question field in the target, which becomes a static caption for the entire group:
{'Tell me about the weather.', code =[[psub'weather']]},{ always=true,[[Goodbye.]], code =[[back()]]},{},-- plays the role of branch caption{ tag ='weather','Okay, what are you interested in?'},{'What is the temperature?','25 degrees Celsius.'},{'What about the humidity?','80%.'},
This caption question can also be a function, allowing you to invoke code as branches switch.
{'Tell me about the weather.', code =[[psub'weather']]},{ always=true,[[Goodbye.]], code =[[back()]]},{},{ tag ='weather',function()
p 'Ok, what exactly?';
weather_asked =true;end},{'What\'s the temperature?','25 degrees Celsius...'},{'Do you know the humidity?','80%!'},
The caption phrase can additionally contain an empty method, which will be called after all phrases in the branch are hidden:
{'Tell me about the weather.', code =[[psub'weather']]},{ always=true,[[Bye!]], code =[[back()]]},{},{ tag ='weather','What do you?',
empty = code [[p'Enough talking about the weather!';
pret()]]},{'Temperature?','25 C!'},{'Humidity?','80%'},
Despite being included in the above empty method, the default action for empty is pret(). However, if you choose to redefine empty with some other method you must call pret() explicitly when needed from then on.
If you want to see a full branching dialogue.. in Russian, check out the following link to the GitHub repository: Example Dialogue
Deprecated Syntax
WARNING: The following examples utilize deprecated syntax, and are included in this documentation for the sake of potentially helping you sort your way through obsolete code in old games. Some concepts are shared with Extended dialogues, but it's still important to follow the Extended rules for modern development.
Here's a dialogue written in the deprecated syntax:
food = obj {
nam ='meal',
inv =function(s)remove('food', inv());
p "I'm eating.";end;};
gotfood =function(w)
take 'food';-- this is a savegame stored variable
food._num = w;
back();end;
povardlg = dlg {
nam ='In the kitchen';
dsc =[[I notice the round face of the cook under
a big white hat. She looks really, really tired.]];
obj ={[1]= phr("Some greens, please.","Enjoy...",[[pon(); gotfood(1);]]),[2]= phr("Fried potatoes now!","Bon appetit, ya filthy animal...",[[pon(); gotfood(2);]]),[3]= phr("Two bowls of garlic soup","That's my favorite too!",[[pon(); gotfood(3);]]),[4]= phr("Something light, please. I've got an ulcer...","Porridge for you then.",[[pon(); gotfood(4);]]),};};
The player chooses her meal, she receives it and the nam is stored in food._num, then returns back to the room where she began the dialogue. Reactions can contain any Lua code, but usually it's phrase management logic.
NOTE: The pon/poff/prem/pseen/punseen methods only work with indexes here. Deprecated dialogues CANNOT employ tags.
To pass from one deprecated dialogue to another, you would hide phrases while initializing the dialogue then unhide them later through the conditional _phr variable and pon/poff/prem methods, like so:
facectrl = dlg {
nam ='face control';
dsc ='I see the unpleasant and unfriendly face of the fat security guy.';
obj ={[1]= phr("I've come to listen to the lecture of Belin...",[["I don't know you, -- the guard grins
-- and I was told to only let decent people in."]],[[pon(2);]]),[2]= _phr("I have the invitation right here!",[["I see. Now look at that mirror there.
I suppose you may not have one in your home.
You've come to listen to Belin. Be-lin!
The right hand of... - the guard pauses for a second -
get out of here!"]],[[pon(3,4)]]),[3]= _phr("I'm gonna kick you in your fat face!",[["Well, let's see it." - powerful hands push me
into the corridor, but I feel lucky to remain in one piece...]],[[poff(4)]]),[4]= _phr("You boar! I told you I have an invitation!",[["Do you?" - the guard's eyes turn red -
a hard knock sends me out - it could be worse...]],[[poff(3)]]),};exit=function(s,w)
s:pon(1);end;};
NOTE: Again, do not write dialogues like this! Extended dialogues are much easier and better.
For style/aesthetic purposes you can define the prefix placed in front of every phrase, which is its numerical index by default:
Each of these causes all phrases to be prefixed by the given string instead of their index number.
This constant (stead.phrase_prefix) is not variable and so it must be defined early in game. Or you can set it in init() function.
NOTE: The string value isn't stored in the player's savegame file, so the game has to define it explicitly for each playthrough. Be aware of this.
Extra Features
Graphics
The game's graphic interpreter (the part that displays things onscreen) will treat a room's pic attribute as a path to an image that should be displayed in the viewer.
home = room {
pic ='gfx/home.png',
nam ='at home',
dsc ="I'm at home",};
Of course, pic can also be a function. If the current room has no pic attribute defined, the game.pic attribute is assessed instead. If it's also undefined, no image will be displayed.
From version 0.9.2 you can use animated gif files, and graphics can be embedded everywhere, including within the text itself and inside the inventory panel useing the img() method.
knife = obj {-- the nam will be a concatenation of the two
nam ='Knife'..img('img/knife.png'),}
In the current version you're free to use the disp attribute instead of the nam attribute.
knife = obj {
nam ='Knife';...
disp ='Knife'..img('img/knife.png'),}
Starting with version 1.3.0 text flow around images is supported. Use the imgl() and imgr() methods, which insert the image at the left or right border respectively. To add open space around the image use the pad: prefix.
NOTE: The text flow images (imgl and imgr) cannot be a link.
Notice the abbreviated versions below and how they affect more than one side.
-- the image is padded on top 0px, right 16px, bottom 16px, left 4px
imgl('pad:0 16 16 4, picture.png')-- the image is padded on top 0px, right 16px, bottom 0px, left 16px
imgr('pad:0 16, picture.png')-- the image is padded on all sides with 16px
imgl('pad:16, picture.png')
A clever use of the img() method is to create blank areas and border boxes.
dsc = img 'blank:32x32'..[[Line with blank image.]];
dsc = img 'box:32x32,red,128'..[[Line with red semi-transparent square.]];
Starting with version 1.0.0 the interpreter can compose images from a base image and an overlay.
The interpreter cycles music that's been called with the set_music() method, which is often implemented in the enter handler.
street = room {
pic ='gfx/street.png',
enter =function()
set_music('mus/rain.ogg');end,
nam ='on the street',
dsc ='It is raining outside.',};
The get_music() method returns the name of the current track.
Starting with version 0.7.7 the set_music() function can receive an additional parameter for the number of loops you wish it to play.
You can ask for the current loop position with the get_music_loop() method. If -1 is returned it means the current track is finished looping.
From version 0.9.2 the set_sound() method will play a sound file.
The get_sound() method returns the filename of the sound that's queued to be played.
Starting with version 1.0.0 you can simply call stop_music() to end the track being played.
From version 1.0.0 you can use the is_music() method to check if music is playing or not.
Themes
The SDL version's graphic interpreter has a fancy theme subsystem. It accesses your game's theme directory which must house a theme.ini file. It must also include a default theme. This is always the first to load. All other themes inherit from it and can partially or completely override its parameters.
Themes can be selected by the player in the settings menu, but your game can initially inject its own theme which will override all others by as much as you choose. To do this, place a duplicate of the theme.ini file within the game directory, where main.lua resides. The player can always override your theme if she wants. If she does, the interpreter will issue a warning that she's overruling the godlike developer's creative intentions. ;)
You can include comments with tags inside the theme header. Currently there's only one tag available. Here's a sample theme to show the basic format to follow:
; $Name:New theme$
; modified "book" theme
include = book
scr.gfx.h =500
The interpreter searches for the player's own themes in her installation's themes directory. The Unix version also checks ~/.instead/themes/ and since version 0.8.7 the Windows version checks Documents and Settings/USER/Local Settings/Application Data/instead/themes
The theme.ini file has a very simple syntax:
<parameter>=<value>; comment
A value can be a string, a number, or a color. Certain colors can be called with a string, such as yellow, green, and violet, but other colors must be written in #RGB format, where R, G, and B are replaced with hexadecimal values.
Parameters
scr.w –game window width in pixels
scr.h –game window height in pixels
scr.col.bg –background color of the entire game window
scr.gfx.bg –directory path to the static background picture, gifs are not enabled
scr.gfx.mode –sets the layout mode for room pictures as float, embedded, or fixed
float – the picture will be centered within scr.gfx.x, scr.gfx.y and downscaled to fit inside scr.gfx.w, scr.gfx.h
embedded – the picture is made part of the main story panel, and scr.gfx.x, scr.gfx.y, and scr.gfx.w are ignored so that it can scroll with the text
fixed – same as embedded, except it doesn't scroll with the text
scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h –X/Y coordinates, width, and height of room pictures, adjusted by layout mode
scr.gfx.scalable = [0|1|2|4|5|6]
0 - Unscalable
1 - Scalable
2 - Fold-Scalable
Added in Version 2.2.0
4 - Fully Scalable (with Unscalable Fonts)
5 - Scalable, with Unscalable Fonts
6 - Fold-Scalable, with Unscalable Fonts
win.gfx.h –synonymous with scr.gfx.h (for compatibility)
win.x, win.y, win.w, win.h –X/Y coordinates, width, and height of the main text area only, excluding the scrollbar width, and the text will be pushed by a fixed or embedded room picture
win.fnt.name –path to a font file
win.fnt.size –font size for the main text area
win.fnt.height –line height for the main text area, as a float number, 1.0 by default
win.align = left/center/right/justify – alignment of text in the story panel's text area
win.gfx.up, win.gfx.down –paths to the pictures of up/down scroll handles for the main story panel
scr.gfx.pad –sets padding for all scrollbars and menu edges
win.up.x, win.up.y, win.down.x, win.down.y –coordinates for scroll handles, literal position or -1 for invisible
win.col.fg –font color for the main story panel
win.col.link –color of clickable words in the main story panel
win.col.alink –hover-over color for clickable words in the main story panel
win.scroll.mode = [0|1|2|3] – the scrolling area on the scene
0 - No auto-scroll
1 - Scroll to change the text only
2 - Only scroll if area not visible
3 - Always in the end
inv.x, inv.y, inv.w, inv.h –X/Y coordinates, width, and height of the inventory panel
inv.mode –horizontal or vertical inventory stacking mode
–in vertical mode only 1 object fits per row, in horizontal multiple objects can fit
–you must reposition and resize the panel to make good use of horizontal mode
inv.col.fg –text color for the inventory panel
inv.col.link –color for clickable words inventory panel
inv.col.alink –hover-over color for clickable words in the inventory panel
inv.fnt.name –path to a font file for the inventory panel
inv.fnt.size –font size for the inventory panel
inv.fnt.height –line height for the inventory panel, as a float number, 1.0 by default
inv.gfx.up, inv.gfx.down –paths to the pictures for the inventory panel's up/down scroll handles
inv.up.x, inv.up.y, inv.down.x, inv.down.y –coordinates of scroll handles, literal position or -1 for invisible
menu.col.bg –background color for menus
menu.col.fg –text color for menus
menu.col.link –color for clickable menu items
menu.col.alink –hover-over color for clickable menu items
menu.col.alpha –transparency for menus as an alpha value, from 0-255
menu.col.border –border color for menus
menu.bw –border width for menus
menu.fnt.name –paths to font files for menus
menu.fnt.size –font size for menus
menu.fnt.height –line height for menus, as a float number, 1.0 by default
menu.gfx.button –path to the menu icon picture file
menu.button.x, menu.button.y –coordinates for the button location on the main screen
snd.click –path to a click sound file
include –path of a theme file to serve as filler for missing parameters, no extension or = sign needed
For example, this will build your custom parameters over the Book theme that comes included with Instead:
include book
The following parameters have been available since version 0.8.9 of the engine:
scr.gfx.cursor.x –X coordinate of the center of the cursor
scr.gfx.cursor.y –Y coordinate of the center of the cursor
scr.gfx.cursor.normal –directory path to the cursor's picture file
scr.gfx.cursor.use –path to a picture file for the cursor's use indicator
scr.gfx.use –path to a picture file for the room's use indicator
Extending The Engine With Modules
Starting with version 1.2.0 you can employ feature extension modules via the require method. Modules must be manually added to your main.lua file, usually near the top.
If the engine is higher than version 1.2.0 the 'vars', object, and walk modules are automatically activated.
To illustrate the use of modules in code, let's take a look at the prefs module. A special object variable called counter is part of this engine extension. It can store game variables such as player progress or number of attempts.
require"prefs"...
prefs.counter =0--number of times the player has attempted a room...exit=function(s)-- the number of exits is incremented
prefs.counter = prefs.counter +1-- saves the counter inside the prefs object
prefs:store()end;...
enter =function(s)-- retrieves the counter and displays itreturn'You passed the game '..prefs.counter..' times';end;...
act =function(s)-- clears the prefs object
prefs:purge()return"Preferences has been cleared"end;
Real-Time Keyboard Input
WARNING: The following examples utilize deprecated syntax, and are included in this documentation for the sake of potentially helping you sort your way through obsolete code in old games.
Please, use module kbd to handle keyboard. Also, there is a module keyboard to make possible the player text input.
Since version 1.1.0 the SDL version has supported real-time keyboard input. This can be activated via the input object.
input.key(s, pressed, key)
The first argument references the keyboard handler's self. The second argument accesses the engine's press and release events. The third argument receives the name of the specific key being activated.
The input object's key handler can also return an engine interface command, in which case the interpreter won't handle it, which is useful for behind-the-scenes effects. For example:
input.key =function(s, pr, key)ifnot pr or key =="escape"then--checks if the Escape key was activatedreturn--exits the current typing area??elseif key =='space'then--checks if the Spacebar was activated
key =' '--outputs an empty space through the key handlerelseif key =='return'then--checks if the Enter key was activated
key ='^';--outputs a line break through the key handlerendif key:len()>1then--checks if the returnend
main._txt = main._txt:gsub('_$','');
main._txt = main._txt..key..'_';return"look";end;
main = room {
_txt ='_',
forcedsc =true,
nam ='Keyboard',
dsc =function(s)return'Example: '..tostring(s._txt);end;};
Real-Time Mouse Input
You may use click module to handle mouse click events.
Module will call here().click or game.click method with coordinates (x, y) of click in parameters.
0,0 is the left top corner. You can get even background clicks, just define:
click.bg =true;
So, the method will called in this case with those parameters:
game.click(s, x, y, px, py)
Where x, y is the coordinates of whole window and px,py - coordinates in image (if image was clicked).
If you need to get press and release events, you may define:
click.press =true;
In such case the parameters are:
game.click(s, press, x, y, px, py)
Where press is boolean.
If you need to get button number, please, define:
click.button =true
And get the button number as the 3d parameter.
Here is the example of the module usage:
-- $Name: Tets module -- click$-- $Version: 0.1$-- $Author: instead$
instead_version "1.8.0"-- use modulerequire"click"-- define game.click
game.click =function(s, x, y)-- this is global method,-- will called if not here().click defined
p ("You are clicked at: ", x,", ", y);end
main = room { nam ='Forest',
forcedsc =true,
pic ='house.png',
dsc =[[ You are seeing a house. Door is open. ]],-- define own click method in this scene
click =function(s, x, y)-- is this the door area?if x >80and x <200and y >225and y <325then
walk('house');elsereturn'This is not the door.';end;end,};
house = room { nam ='Дом',
forcedsc =true,
pic ='door.png',
dsc =[[ You are inside the house.]],};
Neat Ideas/Examples
In-Game Walkman
To implement a personal music device for the player to control you must create it as an “alive” object. First create a tracklist in the tracks handler.
--might be an attribute... not sure
tracks ={"mus/astro2.mod","mus/aws_chas.xm","mus/dmageofd.xm","mus/doomsday.s3m"}
Then create a living, breathing Walkman object to entertain your players. ;)
mplayer = obj {
nam ='media player',
life =function(s)local n = get_music();local v = get_music_loop();ifnot n ornot v then
set_music(tracks[2],1);elseif v ==-1thenlocal n = get_music();while get_music()== n do
n = tracks[rnd(4)]end
set_music(n,1);endend;};
lifeon('mplayer');
You can even use get_music_loop() and get_music() to remember the last track and let the player restore it.
function save_music(s)
s._oldMusic = get_music();
s._oldMusicLoop = get_music_loop();end;function restore_music(s)
set_music(s._oldMusic, s._oldMusicLoop);end;...
enter =function(s)
save_music(s);end;exit=function(s)
restore_music(s);end;...
Since version 0.8.5 the save_music() and restore_music() methods have been permanently included in the game library.
Teleportation Hack
If your player lost their horse or they need something else to appear next to them, one way to materialize that object is to implement the lifeon() method as a teleporter, controlled by the object's life handler. This will materialize the object at the player's current location, technically just bringing it to life again. But you get the idea.
horse = obj {
nam ='horse',
dsc ='A {horse} is next to me.',
life =function(s)ifnot seen('horse')then
move('horse', here(), s.__where);
s.__where = pl.where;endend,};function init()
lifeon('horse');end;
Custom Menus Within Items
Menus are a great feature to add to your world. You can add them to an object by incorporating the following menu constructor into your code. It's activated by the player with a single mouse click. If it has no return string, the state of game will not change. For example, here's a simple implementation of a pocket housed inside the inventory.
pocket = menu {
var { state =false};
nam =function(s)if s.state thenreturn txtu('pocket');endreturn'pocket';end;
gen =function(s)-- this is our custom function -- pocket generatorif s.state then
s:enable_all();else
s:disable_all();endend;
menu =function(s)
s.state =not s.state
s:gen();-- generate pocketend;};
knife = obj {
nam ='knife',
inv ='This is knife',};function init()
inv():add(pocket);
place(knife, pocket);
pocket:gen();end;
main = room {
nam ='test',};
Another cool implementation of menus is to place an unclickable text readout of the player's status within the inventory panel.
global {
life =10;
power =10;}
status = stat {-- create status object
nam ='my status';
disp =function(s)
p ('Life: ', life,'Power: ', power)end;};function init()
take 'status';end;
Preparing Your Game For Release
Splitting Your Game Into Files
Split your game into multiple files to make it easier to manage. You can use the dofile() method to insert your separated source code files into the game. You must use dofile() in a global context, so load all of them while parsing main.lua at the opening of the game.
Dynamically including files gives you the ability to redefine objects and rooms as the game progresses. Nifty! Just use the gamefile() method.
act = code [[ gamefile("episode2.lua"); ]]
You can also clear the game stack of old files, leaving you with a brand new game without the player needing to load anything herself. This is quite useful for multi-part stories where locations need to be revisited in a brand new light. Just put true as a second parameter for the gamefile() method.
act = code [[ gamefile("episode3.lua", true); ]]
Encoding Your Game's Source
Since version 0.9.3, if you want to hide your source code you can encode it from the command line as follows:
sdl-instead -encode <lua file>[encoded file]
Then to load your encoded file from Lua use:
doencfile()
It's necessary to keep main.lua as a plain text file for this, so the recommended scheme is:
game is a encoded game.lua ):
main.lua
-- $Name: My closed source game!$
doencfile("game");
WARNING: Do NOT use the luac compiler for encoding, as it produces platform-dependent code! For regular game compilation it's quite useful for hunting down bugs, but not for encoding.
Packaging Your Game
Since version 1.4.0 you're able to package all your game's resources including graphics, sounds, themes, etc., into one fancy .idf file. Just put your resource directories all into a single directory called data and from the command line run:
instead -idf <path_to_your_parent_data_directory>
A file called data.idf will be created. Put this into your game's top level directory along with the code files and remove the original resource files.
You can also pack your entire game into one .idf file like so:
instead -idf <path_to_game_directory>
A game stored in .idf format can be run like any other game within INSTEAD, or straight from the command line with:
English Localization and Technical Editing by Ryan Joseph
instead-3.1.2/doc/instead.txt 000644 001751 001751 00000007756 13146577500 016432 0 ustar 00peter peter 000000 000000 INSTEAD(6) GAMES INSTEAD(6)
NAME
INSTEAD - Simple Text Adventure Engine, The Interpreter
DESCRIPTION
The interpreter of STEAD (Simple Text Adventures) allows one to play
games that combine visual novels, text adventures and classic quests of
1990-s. The STEAD games features are:
* very simple source code of the stories. (based on LUA);
* graphical game interface;
* supports music and images;
* theme support - a game may change the interface look;
* portability (originally written for Linux, depends on SDL and lua).
SYNOPSIS
sdl-instead [options] [game.zip or game.idf or path to game]
OPTIONS
-debug Debug mode (for game developers).
-nosound
Run the game without sound.
-vsync Vsync output. (SDL2 only)
-gamespath
Add path with games.
-themespath
Add path with themes.
-nostdgames
Do not use default games.
-nostdthemes
Do not use default themes.
-game
Select game in gamespath.
-theme
Select theme in themespath.
-owntheme
Force game to use own theme.
-fullscreen
Run the game in fullscreen mode.
-hires / -nohires
Set/unset the high resolution when run in fullscreen mode.
-window
Run the game in windowed mode.
-resizable
Resizable window (SDL2 only)
-scale
Scale window (SDL2 only)
-noautosave
Disable autosave load.
-encode [encoded file]
Encode lua file. Encoded file can be executed with
doencfile("encoded file").
-idf
Build idf file [INSTEAD data file] from directory.
-mode [WxH]
Use WxH resolution.
-modes [,[,]] ...
Define available resolutions.
-software
Force software rendering
-fontscale
Font scaling.
-nopause
Do not pause the game on window minimize.
-hinting 0|1|2|3
Set the font hinting mode (helpful with infinality).
-install [game in zip]
Install game from zip archive
-quit Quit :)
-appdata [fullpath]
Store saves and settings in appdata path. Path must exist!
-chunksize [size in bytes]
Size for audio buffer. Try this if sound lags.
-version
Show version and exit.
-lua [arguments]
Exec lua script (for game developers).
-luac
Check lua script (for game developers).
-standalone
Run game in standalone mode (no sandbox, no games selection
...).
-noconfig
Do not load saved config (run with defaults).
-profile
Using configuration profile. Profile file is the ini-styled file
with parameter = value pairs. Where is parameters are arguments
without -.
Configuration file name is 'insteadrc' and it's located in ~/.instead/.
Games can be downloaded from http://instead.sourceforge.net. Unzip them
in ~/.instead/games.
CONTROLS
Please, see tutorial.
LICENCE
This software is covered by the terms of the MIT license.
WARRANTY
None.
COPYRIGHT AND CREDITS
Peter Kosyh
INSTEAD author.
Ilya Ryndin
Initial port to Windows.
Mahno Aleksey, Anton Kolosov
Port to Android.
.dm Port to Mac OS X.
Andrey Afletdinov
Port to Windows CE.
Vadim Balashoff
Advices.
Vladimir Zhirov
Advices.
Alexander Yakovlev
manual.tex support.
Serj Kalichev
Advices and default theme correction.
Vladimir Podobaev, Oleg Gvozdev
Testers.
Alexander Soborov
Wiki support.
Many others for comments, bug reports, games and ideas.
SEE ALSO
http://instead.sourceforge.net
Instead Version 3.1.2 INSTEAD(6)
instead-3.1.2/doc/stead3-manual.tex 000644 001751 001751 00001013233 13146577501 017407 0 ustar 00peter peter 000000 000000 \chapter{Общие сведения}
\label{Общиесведения}
Код игр под INSTEAD пишется на языке Lua (5.1), поэтому, знание этого
языка полезно, хотя и не необходимо. Ядро движка также написано на
lua, поэтому знание Lua может быть полезным для углубленного понимания
принципов его работы, конечно, при условии, если вам интересно этим
заниматься.
За время своего развития, INSTEAD получил множество новых
функций. Теперь с его помощью можно делать игры разных жанров (от
аркад, до игр с текстовым вводом). А также, в INSTEAD можно запускать
игры, написанные на некоторых других движках, но основой INSTEAD
остается первоначальное ядро, которое ориентировано на создание
текстографических приключенческих игр. В данной документации описана
именно эта базовая часть, изучение которой необходимо даже в том
случае, если вы хотите написать что-то другое{\ldots} Начните свое изучение
INSTEAD с написания простой игры!
В феврале 2017 года, после 8 лет разработки, INSTEAD (версия 3.0)
вышел с поддержкой нового ядра STEAD3. Старое ядро получило название
STEAD2. INSTEAD поддерживает работу игр написанных как на STEAD2, так
и на STEAD3. Это руководство описывает STEAD3.
Если у вас возникают вопросы, вы можете зарегистрироваться на форуме
INSTEAD и задавать их там:
http:\slash \slash instead.syscall.ru\slash talk
Также, вы можете посетить Jabber конференцию:
instead@conference.jabber.ru
\section{История создания}
\label{Историясоздания}
Когда мы говорим ``текстовое приключение'' у большинства людей возникает
один из двух привычных образов. Это либо текст с кнопками действий,
например:
\begin{verbatim}
Вы видите перед собой стол. На столе лежит яблоко. Что делать?
1) Взять яблоко
2) Отойти от стола
\end{verbatim}
Или, гораздо реже, это классические игры с текстовым вводом, где для
управления игрой необходимо было вводить действия с клавиатуры.
\begin{verbatim}
Вы на кухне. Тут есть стол.
> осмотреть стол.
На столе есть яблоко.
\end{verbatim}
У обоих подходов есть свои преимущества и недостатки.
Если говорить про первый подход, то он близок к жанру книг-игр и
удобен больше для литературных текстов, которые описывают \emph{события},
происходящие с главным героем, и не очень удобен для создания
классических квестов, где главный герой исследует \emph{смоделированный} в
игре \emph{мир}, свободно перемещаясь по нему и взаимодействуя с объектами
этого мира.
Второй подход моделирует мир, но требует значительных усилий от автора
игры, и, что более важно, более подготовленного игрока. Особенно,
когда мы имеем дело с русским языком.
Проект INSTEAD был создан для написания другого типа игр, которые
совмещают преимущества обоих подходов, одновременно пытаясь избежать
их недостатков.
Мир игры на INSTEAD моделируется как при втором подходе, то есть в
игре есть места (сцены или комнаты) которые может посещать главный
герой и объекты, с которыми он взаимодействует (включая живых
персонажей). Игрок свободно изучает мир и манипулирует
объектами. Причем, действия с объектами не прописаны в виде явных
пунктов меню, а скорее напоминают классические графические квесты в
стиле 90-х.
На самом деле, в INSTEAD есть множество незаметных на первый взгляд
вещей, которые направлены на развитие выбранного подхода, и который
делает процесс игры максимально динамичным и непохожим на привычные
``текстовые квесты''. Это подтверждается в том числе и тем, что на
движке было выпущено множество замечательных игр, интерес к которым
проявляют не только любители текстовых игр как таковых, но и люди не
знакомые с данным жанром.
Перед изучением данного руководства, я рекомендую поиграть в
классические игры INSTEAD, чтобы понять о чем идет речь. С другой
стороны, раз вы здесь, то наверное вы уже сделали это.
Правда, не стоит пока изучать код этих игр, так как старые игры очень
часто написаны неоптимально, с использованием устаревших
конструкций. Текущая версия INSTEAD позволяет реализовывать код
лаконичнее, проще и понятнее. Об этом и рассказывается в данном
документе.
Если вас интересует история создания движка, то вы можете прочитать
статью о том, как все начиналось: http:\slash \slash instead.syscall.ru\slash 2010\slash 05\slash history
\section{Как выглядит классическая INSTEAD игра}
\label{Каквыглядитклассическаяinsteadигра}
Итак, как выглядит классическая INSTEAD игра?
\emph{Главное окно} игры содержит описательную часть, информацию о
статической и динамической части сцены, активные события и картинку
сцены (в графическом интерпретаторе) с возможными переходами в другие
сцены.
\emph{Описательная часть сцены} отображается только один раз, при показе
сцены, или при явном осмотре сцены (в графическом интерпретаторе --
\emph{Статическая часть сцены} содержит информацию о статических объектах
сцены (обычно, это декорации) и отображается всегда. Эта часть
написана автором игры.
\emph{Динамическая часть сцены} составлена из описаний объектов сцены,
которые присутствуют в данный момент на сцене. Эта часть генерируется
автоматически и отображается всегда. Обычно, в ней представлены
объекты, которые могут менять свое местоположение.
Игроку доступны объекты, доступные на любой сцене --
\emph{инвентарь}. Игрок может взаимодействовать с объектами инвентаря и
действовать объектами инвентаря на другие объекты сцены или инвентаря.
\begin{quote}
Следует отметить, что понятие инвентаря является условным. Например,
в ``инвентаре'' могут находиться такие объекты как ``открыть'',
``осмотреть'', ``использовать'' и т.д.
\end{quote}
\emph{Действиями} игрока могут быть:
\begin{itemize}
\item осмотр сцены;
\item действие на объект сцены;
\item действие на объект инвентаря;
\item действие объектом инвентаря на объект инвентаря;
\item действие объектом инвентаря на объект сцены;
\item переход в другую сцену.
\end{itemize}
\section{Как создавать игру}
\label{Каксоздаватьигру}
Игра представляет из себя каталог, в котором должен находиться скрипт
(текстовый файл) main3.lua. (Обратите внимание, наличие файла
main3.lua означает, что вы пишите игру на STEAD3!) Другие ресурсы игры
(скрипты на lua, графика и музыка) должны находиться в рамках этого
каталога. Все ссылки на ресурсы делаются относительно текущего
каталога -- каталога игры.
В начале файла main3.lua может быть определен заголовок, состоящий из
тегов (строк специального вида). Теги должны начинаться с символов:
два минуса.
\begin{verbatim}
--
\end{verbatim}
Два минуса это комментарий с точки зрения Lua. На данный момент
существуют следующие теги.
Тег \$Name: содержит название игры в кодировке UTF--8. Пример
использования тега:
\begin{verbatim}
-- $Name: Самая интересная игра!$
\end{verbatim}
Затем следует (желательно) задать версию игры:
\begin{verbatim}
-- $Version: 0.5$
\end{verbatim}
И указать авторство:
\begin{verbatim}
-- $Author: Анонимный любитель текстовых приключений$
\end{verbatim}
Дополнительную информацию об игре, можно задать тегом Info:
\begin{verbatim}
-- $Info: Это ремейк старой игры\nС ZX specturm$
\end{verbatim}
Обратите внимание на \textbackslash{}n в Info, это станет переводом строки, когда вы
выберете пункт ``Информация'' в INSTEAD.
Если вы разрабатываете игру в Windows, то убедитесь, что ваш редактор
поддерживает кодировку UTF--8 \emph{без BOM}. Именно эту кодировку следует
использовать при написании игры!
Далее, обычно следует указать модули, которые требуются игре. О
модулях будет рассказано отдельно.
\begin{verbatim}
require "fmt" -- некоторые функции форматирования
fmt.para = true -- включить режим параграфов (отступы)
\end{verbatim}
Кроме того, обычно стоит определить обработчики по-умолчанию:
game.act, game.use, game.inv, о которых также будет рассказано ниже.
\begin{verbatim}
game.act = 'Не работает.';
game.use = 'Это не поможет.';
game.inv = 'Зачем мне это?';
\end{verbatim}
\emph{Инициализацию} игры следует описывать в функции init, которая
вызывается движком в самом начале. В этой функции удобно
инициализировать состояние игрока на начало игры, или какие-то другие
действия, нужные для первоначальной настройки мира игры. Впрочем,
функция ``init'' может быть и не нужна.
\begin{verbatim}
function init() -- добавим в инвентарь нож и бумагу
take 'нож'
take 'бумага'
end
\end{verbatim}
После того как игра проинициализирована, выполняется запуск игры. Вы
можете определить функцию start(), которая запускается непосредственно
перед запуском игры. Это значит, например, что в случае загрузки
сохраненной игры, start() вызовется после того, как сохранение будет
прочитано,
\begin{verbatim}
function start(load) -- восстановить состояние?
if load then
dprint "Это загрузка состояния."
else
dprint "Это старт игры."
end
-- нам сейчас не нужно ничего делать
end
\end{verbatim}
Графический интерпретатор ищет доступные игры в каталоге
games. Unix-версия интерпретатора кроме этого каталога просматривает
также игры в каталоге \ensuremath{\sim}\slash .instead\slash games. Windows-версия: Documents and
Settings\slash USER\slash Local Settings\slash Application Data\slash instead\slash games. В
Windows- и standalone-Unix-версии игры ищутся в каталоге
.\slash appdata\slash games, если он существует.
В некоторых сборках INSTEAD (в Windows, в Linux если проект собран с
gtk и др.) можно открывать игру по любому пути из меню ``Выбор игры''.
Либо, нажать f4. Если в каталоге с играми присутствует только одна
игра, INSTEAD запустит ее автоматически, это удобно, если вы хотите
распространять свою игру вместе с движком.
Таким образом, вы кладете игру в свой каталог и запускаете INSTEAD.
\textbf{Важно!}
При написании игры, настоятельно рекомендуется использовать отступы
для оформления кода игры, как это сделано в примере из данного
руководства, этим самым вы сократите количество ошибок и сделаете свой
код наглядней!
Ниже приводится минимальный шаблон для вашей первой игры:
\begin{verbatim}
-- $Name: Моя первая игра$
-- $Version: 0.1$
-- $Author: Анонимный автор$
require "fmt"
fmt.para = true
game.act = 'Гм...';
game.use = 'Не сработает.';
game.inv = 'Зачем это мне?';
function init()
-- инициализация, если она нужна
end
\end{verbatim}
\section{Основы отладки}
\label{Основыотладки}
Во время отладки (проверки работоспособности вашей игры) удобно, чтобы
INSTEAD был запущен с параметром -debug, тогда в случае ошибок будет
показана более подробная информация о проблеме в виде стека
вызовов. Параметр -debug можно задать в ярлыке (если вы работаете в
Windows), а для других систем, я думаю вы и так знаете как передавать
параметры командной строки.
Кроме того, в режиме -debug автоматически подключается отладчик. Вы
можете активировать его с помощью клавиш ctrl-d или f7. Вы можете
подключить отладчик и явно указав:
\begin{verbatim}
require "dbg"
\end{verbatim}
В коде вашей игры.
При отладке игры обычно нужно часто сохранять игру и загружать
состояние игры. Вы можете использовать стандартный механизм сохранений
через меню (или по клавишам f2\slash f3), или воспользоваться быстрым
сохранением\slash загрузкой (клавиши f8\slash f9).
В режиме `-debug' вы можете перезапускать игру клавишами
`alt-r'. В комбинации с f8\slash f9 это дает возможность быстро посмотреть
изменения в игре после ее правки.
В режиме `-debug' Windows-версия INSTEAD создает консольное окно (в
Unix версии, если вы запускаете INSTEAD из консоли, вывод будет
направлен в нее) в которое будет осуществляться вывод ошибок. Кроме
того, используя функцию `print()' вы сможете порождать свои
сообщения с отладочным выводом. Например:
\begin{verbatim}
act = function(s)
print ("Act is here! ");
...
end;
\end{verbatim}
Не пугайтесь, когда вы прочитаете все руководство и начнете писать
свою игру, вы, скорее всего, взглянете на этот пример с большим
воодушевлением.
Вы также можете использовать функцию dprint(), которая посылает вывод
в окно отладчика, и вы сможете посмотреть его при входе в режим
отладки.
\begin{verbatim}
act = function(s)
dprint ("Act is here! ");
...
end;
\end{verbatim}
Во время отладки бывает удобно изучать файлы сохранений, которые
содержат состояние переменных игры. Чтобы не искать каждый раз файлы
сохранений, создайте каталог saves в директории с вашей игрой (в том
каталоге, где содержится main3.lua) и игра будет сохраняться в
saves. Этот механизм также будет удобен для переноса игры на другие
компьютеры.
Возможно (особенно, если вы пользуетесь Unix системами) вам понравится
идея проверки синтаксиса ваших скриптов через запуск компилятора
``luac''. В Windows это тоже возможно, нужно только установить
выполняемые файлы lua для Windows
(http:\slash \slash luabinaries.sourceforge.net)\slash и воспользоваться luac52.exe.
Вы можете проверить синтаксис и с помощью INSTEAD, для этого
воспользуйтесь параметром -luac:
\begin{verbatim}
sdl-instead -debug -luac <пусть к скрипту.lua>
\end{verbatim}
\chapter{Сцена}
\label{Сцена}
\emph{Сцена} (или комната) -- это единица игры, в рамках которой игрок
может изучать все объекты сцены и взаимодействовать с ними. Например,
сценой может быть комната, в которой находится герой. Или участок
леса, доступный для наблюдения.
В любой игре должна быть сцена с именем ``main''. Именно с нее
начнется и ваша игра!
\begin{verbatim}
room {
nam = 'main';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
\end{verbatim}
Запись означает создание объекта (так как почти все сущности в INSTEAD
это объекты) main типа room (комната). Атрибут объекта nam хранит имя
комнаты `main', по которому можно обращаться к комнате из кода. Каждый
объект имеет свое уникальное имя. Если вы попробуете создать два
объекта с одинаковыми именами, вы получите сообщение об ошибке.
Для обращения к объекту по имени, вы можете использовать следующую
запись:
\begin{verbatim}
dprint("Объект: ", _'main')
\end{verbatim}
У каждого объекта игры есть \emph{атрибуты} и \emph{обработчики событий}. В
данном примере есть два атрибута: nam и dsc. Атрибуты разделяются
разделителем (в данном примере -- символом точка с запятой `;').
Обычно, атрибуты могут быть текстовыми строками,
функциями-обработчиками и булевыми значениями. Однако, атрибут nam
всегда должен быть текстовой строкой, если он задан.
На самом деле, вы можете не указывать имя при создании объекта:
\begin{verbatim}
room {
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
\end{verbatim}
В таком случае, движок сам даст имя объекту, и это имя будет неким
числом. Так как вы не знаете это число, вы не можете обратиться к
объекту явно. Иногда удобно создавать безымянные объекты, например,
для декораций. При создании объекта, даже если он ``безымянный'', вы
можете создать переменную - ссылку на объект, например:
\begin{verbatim}
myroom = room {
disp = "Чулан";
dsc = [[Вы в чулане.]];
}
\end{verbatim}
Переменная myroom в таком случае становится синонимом объекта (ссылкой
на сам объект).
\begin{verbatim}
dprint("Объект: ", myroom)
\end{verbatim}
Вы можете придерживаться какого-то одного способа, или применять
оба. Например, вы можете задать и имя и переменную-ссылку:
\begin{verbatim}
main_room = room {
nam = 'main';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
\end{verbatim}
Важно понять, что движок в любом случае работает с именами объектов, а
переменные-ссылки -- это просто способ упростить доступ к часто
используемым объектам. Поэтому, для нашей первой игры мы обязаны
указать атрибут nam = `main', чтобы создать комнату main с которой и
начнется наше приключение!
В нашем примере, при показе сцены, в качестве заголовка сцены будет
использован атрибут `disp'. На самом деле, если бы мы его не задали,
то в заголовке мы бы увидели `nam'. Но nam не всегда удобно делать
заголовком сцены, особенно если это строка вроде `main', или если это
числовой идентификатор, который движок присвоил объекту автоматически.
Есть еще более понятный атрибут `title'. Если он задан, то при
отображении комнаты в качестве заголовка будет указан именно он.
title используется тогда, когда игрок находится \emph{внутри} комнаты. Во
всех остальных случаях (при показе переходов в эту комнату)
используется `disp' или `nam'.
\begin{verbatim}
mroom = room {
nam = 'main';
title = 'Начало приключения';
disp = "Главная комната";
dsc = [[Вы в большой комнате.]];
}
\end{verbatim}
Атрибут `dsc' -- это описание сцены, которое выводится один раз при
входе в сцену или при явном осмотре сцены. В нем нет описаний
объектов, присутствующих в сцене.
Вы можете использовать символ `,' вместо `;' для разделения
атрибутов. Например:
\begin{verbatim}
room {
nam = 'main',
disp = 'Главная комната',
dsc = 'Вы в большой комнате.',
}
\end{verbatim}
В данном примере все атрибуты -- строковые. Строка может быть записана
в одинарных или двойных кавычках:
\begin{verbatim}
room {
nam = 'main';
disp = 'Главная комната';
dsc = "Вы в большой комнате.";
}
\end{verbatim}
Для длинных описаний удобно использовать запись вида:
\begin{verbatim}
dsc = [[ Очень длинное описание... ]];
\end{verbatim}
При этом переводы строк игнорируются. Если вы хотите, чтобы в выводе
описания сцены присутствовали абзацы -- используйте символ `\^{}'.
\begin{verbatim}
dsc = [[ Первый абзац. ^^
Второй Абзац.^^
Третий абзац.^
На новой строке.]];
\end{verbatim}
Я рекомендую всегда использовать [[ и ]] для `dsc'.
Напомню еще раз, что имя `nam' объекта и его отображение (в данном
случае то, как сцена будет выглядеть для игрока в виде надписи сверху)
можно (и, часто, нужно!) разделять. Для этого существуют атрибуты
`disp' и `title'. `title' бывает только у комнат и работает как
описатель, когда игрок находится внутри данной комнаты. В остальных
случаях используется `disp' (если он есть).
Если `disp' и `title' не заданы, то считается, что отображение
равняется имени.
`disp' и `title' могут принимать значение false, в таком случае,
отображения не будет.
\chapter{Объекты}
\label{Объекты}
\emph{Объекты} -- это единицы сцены, с которыми взаимодействует игрок.
\begin{verbatim}
obj {
nam = 'стол';
dsc = 'В комнате стоит {стол}.';
act = 'Гм... Просто стол...';
};
\end{verbatim}
Имя объекта ``nam'' используется при попадании его в инвентарь. Хотя,
в нашем случае, стол вряд ли туда попадет. Если у объекта определен
`disp', то при попадании в инвентарь для его отображения будет
использоваться именно этот атрибут. Например:
\begin{verbatim}
obj {
nam = 'стол';
disp = 'угол стола';
dsc = 'В комнате стоит {стол}.';
tak = 'Я взялся за угол стола';
inv = 'Я держусь за угол стола.';
};
\end{verbatim}
Все-таки стол попал к нам в инвентарь.
Вы можете скрывать отображение предмета в инвентаре, если `disp'
атрибут будет равен `false'.
`dsc' -- описание объекта. Оно будет выведено в динамической части
сцены, при наличии объекта в сцене. Фигурными скобками отображается
фрагмент текста, который будет являться ссылкой в окне INSTEAD. Если
объектов в сцене много, то все описания выводятся одно за другим,
через пробел,
`act' -- это обработчик события, который вызывается при действии
пользователя (действие на объект сцены, обычно -- клик мышкой по
ссылке). Его основная задача -- вывод (возвращение) строки текста,
которая станет частью событий сцены, и изменение состояния игрового
мира.
\chapter{Добавляем объекты в сцену}
\label{Добавляемобъектывсцену}
Для того, чтобы поместить в сцену объекты, существует несколько путей.
Во-первых, при создании комнаты можно определить список `obj',
состоящий из имен объектов:
\begin{verbatim}
obj { -- объект с именем, но без переменной
nam = 'ящик';
dsc = [[На полу я вижу {ящик}.]];
act = [[Тяжелый!]];
}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
obj = { 'ящик' };
};
\end{verbatim}
Теперь, при отображении сцены мы увидим объект ``ящик'' в динамической
части.
Вместо имени объекта, вы можете использовать переменную-ссылку, если
только она была определена заранее:
\begin{verbatim}
apple = obj { -- объект с переменной, но без имени
dsc = [[Тут есть {яблоко}.]];
act = [[Красное!!]];
}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
obj = { apple };
};
\end{verbatim}
Альтернативной формой записи является конструкция with:
\begin{verbatim}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
}:with {
'ящик',
}
\end{verbatim}
Конструкция with позволяет избавиться от лишнего уровня вложенности в
коде игры.
Во-вторых, вы можете объявлять объекты прямо внутри obj или with,
описывая их определение:
\begin{verbatim}
room {
nam = 'main';
disp = 'Большая комната';
dsc = [[Вы в большой комнате.]];
}:with {
obj {
nam = 'ящик';
dsc = [[На полу я вижу {ящик}.]];
act = [[Тяжелый!]];
}
};
\end{verbatim}
Это удобно делать для объектов - декораций. Но в таком случае, вы не
сможете создавать объекты с переменной-ссылкой. К счастью, для
декораций это и не нужно.
Если в комнату помещаются несколько объектов, разделяйте их ссылки
запятыми, например:
\begin{verbatim}
obj = { 'ящик', apple };
\end{verbatim}
Вы можете вставлять переводы строк для наглядности, когда объектов
много, например, так:
\begin{verbatim}
obj = {
'tabl',
'apple',
'knife',
};
\end{verbatim}
Еще один способ размещения предметов заключается в вызове функций,
которые поместят объекты в требуемые комнаты. Он будет рассмотрен в
дальнейшем.
\chapter{Декорации}
\label{Декорации}
Объекты, которые могут быть перенесены из одной сцены в другую (или
попадать в инвентарь), обычно имеют имя и\slash или переменную-ссылку. Так
как таким образом вы всегда можете найти объект где угодно и работать
с ним.
Но немалую часть игрового мира составляют объекты, которые занимают
конкретную локацию и служат в качестве декораций.
Таких объектов может быть очень много, и более того, обычно это
однотипные объекты вроде деревьев и тому подобных объектов.
Для создания декораций можно использовать различные подходы.
\section{Один и тот же объект в нескольких комнатах}
\label{Одинитотжеобъектвнесколькихкомнатах}
Вы можете создать один объект, например, `дерево' и помещать их в
разные комнаты.
\begin{verbatim}
obj {
nam = 'дерево';
dsc = [[Тут стоит {дерево}.]];
act = [[Все деревья выглядят похожими.]];
}
room {
nam = 'Лес';
obj = { 'дерево' };
}
room {
nam = 'Улица';
obj = { 'дерево' };
}
\end{verbatim}
\section{Использование тегов вместо имен}
\label{Использованиетеговвместоимен}
Если вам не нравится придумывать уникальные имена для однотипных
декоративных объектов, вы можете использовать для таких объектов
теги. Теги задаются атрибутом tag и всегда начинаются с символа `\#':
\begin{verbatim}
obj {
tag = '#цветы';
dsc = [[Тут есть {цветы}.]]
}
\end{verbatim}
В данном примере, имя у объекта будет сформировано автоматически, но
обращаться к объекту вы сможете по тегу. При этом объект будет
искаться в текущей комнате. Например:
\begin{verbatim}
dprint(_'#цветы') -- ищем в текущей комнате первый объект с тегом '#цветы'
\end{verbatim}
Теги, это в каком то смысле, синоним локальных имен, поэтому
существует альтернативная запись создания предмета с тегом:
\begin{verbatim}
obj {
nam = '#цветы';
dsc = [[Тут есть {цветы}.]]
}
\end{verbatim}
Если имя у объекта начинается с символа `\#', то такой объект получает
тег и автоматически сгенерированное числовое имя.
\section{Использование атрибута сцены decor}
\label{Использованиеатрибутасценыdecor}
Так как декорации не меняют свое место-положение, есть смысл сделать
их частью описания сцены, а не динамической области. Это делается с
помощью атрибута сцены `decor'. decor показывается всегда и его
основная функция -- описание декораций сцены.
\begin{verbatim}
room {
nam = 'Дом';
dsc = [[Я у себя дома.]];
decor = [[Тут я вижу много интересных вещей. Например, на {#стена|стене}
висит {#картина|картина}.]];
}: with {
obj {
nam = '#стена';
act = [[Стена как стена!]];
};
obj {
nam = '#картина';
act = [[Ван-Гог?]];
}
}
\end{verbatim}
Здесь мы видим сразу несколько приемов:
\begin{enumerate}
\item В decor в виде связанного текста описаны декорации;
\item В качестве ссылок используются конструкции с явным заданием
объектов, к которым они относятся \{имя объекта\textbar{}текст\};
\item В качестве имен объектов используются теги, чтобы не думать над их
уникальностью;
\item У объектов-декораций в сцене отсутствуют атрибуты dsc, так как их
роль играет decor.
\end{enumerate}
Конечно, вы можете комбинировать все описанные приемы между собой в
любых пропорциях.
\chapter{Объекты, связанные с другими объектами}
\label{Объектысвязанныесдругимиобъектами}
Объекты тоже могут содержать в себе атрибут `obj' (или конструкцию
`with'). При этом, при выводе объектов, INSTEAD будет разворачивать
списки последовательно. Такая техника может использоваться для
создания объектов-контейнеров или просто для связывания нескольких
описаний вместе. Например, поместим на стол яблоко.
\begin{verbatim}
obj {
nam = 'яблоко';
dsc = [[На столе лежит {яблоко}.]];
act = 'Взять что-ли?';
};
obj {
nam = 'стол';
dsc = [[В комнате стоит {стол}.]];
act = 'Гм... Просто стол...';
obj = { 'яблоко' };
};
room {
nam = 'Дом';
obj = { 'стол' };
}
\end{verbatim}
При этом, в описании сцены мы увидим описание объектов `стол' и
`яблоко', так как `яблоко' -- связанный со столом объект и движок при
выводе объекта `стол' вслед за его `dsc' выведет последовательно
``dsc'' всех вложенных в него объектов.
Также, следует отметить, что оперируя объектом `стол' (например,
перемещая его из комнаты в комнату) мы автоматически будем перемещать
и вложенный в него объект `яблоко'.
Конечно, данный пример мог бы быть написан и по другому, например,
так:
\begin{verbatim}
room {
nam = 'Дом';
obj = { 'стол' };
}:with {
obj {
nam = 'стол';
dsc = [[В комнате стоит {стол}.]];
act = 'Гм... Просто стол...';
}: with {
obj {
nam = 'яблоко';
dsc = [[На столе лежит {яблоко}.]];
act = 'Взять что-ли?';
};
}
}
\end{verbatim}
Выбирайте тот способ, который для вас понятней.
\chapter{Атрибуты и обработчики как функции}
\label{Атрибутыиобработчикикакфункции}
Большинство атрибутов и обработчиков могут быть \emph{функциями}. Так,
например:
\begin{verbatim}
disp = function()
p 'яблоко';
end
\end{verbatim}
Пример не очень удачен, так как проще было бы написать disp =
`яблоко', но показывает синтаксис записи функции.
Основная задача такой функции -- это возврат строки или булевого
значения. Сейчас мы рассматриваем возврат строки. Для возврата строки
вы можете использовать явную запись в виде:
\begin{verbatim}
return "яблоко";
\end{verbatim}
При этом ход выполнения кода функции прекращается и она возвращает
движку строку. В данном случае ``яблоко''.
Более привычным способом вывода являются функции:
\begin{itemize}
\item p (``текст'') -- вывод текста и пробела;
\item pn (``текст'') -- вывод текста с переводом строки;
\item pr (``текст'') -- вывод текста ``как есть''.
\end{itemize}
\begin{quote}
Если ``p''\slash ``pn''\slash ``pr'' вызывается с одним текстовым параметром, то скобки можно опускать.
\end{quote}
\begin{verbatim}
pn "Нет скобкам!"
\end{verbatim}
Все эти функции дописывают текст в буфер и при возврате из функции
возвращают его движку. Таким образом, вы можете постепенно
формировать вывод за счет последовательного выполнения p\slash pn\slash pr. Имейте
в виду, что автору крайне редко необходимо явно форматировать текст,
особенно, если это описание объектов, движок сам расставляет
необходимые переводы строк и пробелы для разделения информации разного
рода и делает это унифицированным способом.
Вы можете использовать `..' или `,' для склейки строк. Тогда `(' и `)'
обязательны. Например:
\begin{verbatim}
pn ("Строка 1".." Строка 2");
pn ("Строка 1", "Строка 2");
\end{verbatim}
\begin{quote}
Основное отличие атрибутов от обработчиков событий состоит в том,
что обработчики событий могут менять состояние игрового мира, а
атрибуты нет. Поэтому, если вы оформляете атрибут (например, `dsc')
в виде функции, помните, что задача атрибута это возврат значения, а
не изменение состояния игры! Дело в том, что движок обращается к
атрибутам в те моменты времени, которые обычно четко не определены,
и не связаны явно с какими-то игровыми процессами!
\end{quote}
\textbf{Важно!}
\begin{quote}
Еще одной особенностью обработчиков является тот факт, что вы не
должны ждать каких то событий внутри обработчика. То есть, не должно
быть каких-то циклов ожидания, или организации задержек (пауз). Дело
в том, что задача обработчика -- изменить игровое состояние и отдать
управление INSTEAD, который визуализирует эти изменения и снова
перейдет в ожидание действий пользователя. Если вам требуется
организовать задержки вывода, вам придется воспользоваться модулем
``timer''.
\end{quote}
Функции практически всегда содержат условия и работу с
переменными. Например:
\begin{verbatim}
obj {
nam = 'яблоко';
seen = false;
dsc = function(s)
if not s.seen then
p 'На столе {что-то} лежит.';
else
p 'На столе лежит {яблоко}.';
end
end;
act = function(s)
if s.seen then
p 'Это яблоко!';
else
s.seen = true;
p 'Гм... Это же яблоко!';
end
end;
};
\end{verbatim}
Если атрибут или обработчик оформлен как функция, то всегда \emph{первый
аргумент} функции (s) -- сам объект. То-есть, в данном примере, `s'
это синоним \_`яблоко'. Когда вы работаете с самим объектом в функции,
удобнее использовать параметр, а не явное обращение к объекту по
имени, так как при переименовании объекта вам не придется переписывать
вашу игру. Да и запись будет короче.
В данном примере при показе сцены в динамической части сцены будет
выведен текст: `На столе что-то лежит'. При взаимодействии с `что-то',
переменная `seen' объекта `яблоко' будет установлена в true -- истина,
и мы увидим, что это было яблоко.
Как видим, синтаксис оператора `if' довольно очевиден. Для
наглядности, несколько примеров.
\begin{verbatim}
if <выражение> then <действия> end
if have 'яблоко' then
p 'У меня есть яблоко!'
end
if <выражение> then <действия> else <действия иначе> end
if have 'яблоко' then
p 'У меня есть яблоко!'
else
p 'У меня нет яблока!'
end
if <выражение> then <действия> elseif <выражение 2> then <действия 2> else <иначе> end -- и т.д.
if have 'яблоко' then
p 'У меня есть яблоко!'
elseif have 'вилка' then
p 'У меня нет яблока, но есть вилка!'
else
p 'У меня нет ни яблока, ни вилки!'
end
\end{verbatim}
Выражение в операторе if может содержать логическое ``и'' (and), ``или''
(or), ``отрицание'' (not) и скобки ( ) для задания приоритетов. Запись
вида if $<$переменная$>$ then означает, что переменная не равна
false. Равенство описывается как `==', неравенство `\ensuremath{\sim}='.
\begin{verbatim}
if not have 'яблоко' and not have 'вилка' then
p 'У меня нет ни яблока, ни вилки!'
end
...
if w ~= apple then
p 'Это не яблоко.';
end
...
if time() == 10 then
p '10 й ход настал!'
end
\end{verbatim}
\textbf{Важно!}
В ситуации когда переменная не была определена, но используется в
условии, INSTEAD даст ошибку. Вам придется заранее определять
переменные, которые вы используете.
\section{Переменные объекта}
\label{Переменныеобъекта}
Запись `s.seen' означает, что переменная `seen' размещена в объекте
`s' (то есть `яблоко'). Помните, мы назвали первый параметр функции
`s' (от self), а первый параметр -- это сам текущий объект.
Переменные объекта должны быть определены заранее, если вы собираетесь
их модифицировать. Примерно так, как мы поступили с seen. Но
переменных может быть много.
\begin{verbatim}
obj {
nam = 'яблоко';
seen = false;
eaten = false;
color = 'красный';
weight = 10;
...
};
\end{verbatim}
Все переменные объекта, при их изменении, попадают в файл сохранения
игры.
Если вы не хотите, чтобы переменная попала в файл сохранения, вы
можете объявить такие переменные в специальном блоке:
\begin{verbatim}
obj {
nam = 'яблоко';
{
t = 1; -- эта переменная не попадет в сохранения
x = false; -- и эта тоже
}
};
\end{verbatim}
Обычно, вам не стоит так делать. Однако есть ситуация, при которой
этот прием будет полезным. Дело в том, что массивы и таблицы объекта
всегда сохраняются. Если вы используете массивы для хранения
неизменяемых значений, вы можете написать так:
\begin{verbatim}
obj {
nam = 'яблоко';
{
text = { "раз", "два", "три" }; -- никогда не попадет в файл сохранения
}
...
};
\end{verbatim}
Вы можете обращаться к переменным объекта через s -- если это сам
объект. или по переменной - ссылке, например:
\begin{verbatim}
apple = obj {
color = 'красный';
}
...
-- где-то в другом месте
apple.color = 'зеленый'
\end{verbatim}
Или по имени:
\begin{verbatim}
obj {
nam = 'яблоко';
color = 'красный';
}
...
-- где-то в другом месте
_'яблоко'.color = 'зеленый'
\end{verbatim}
На самом деле, вы можете создавать переменные-объекта на лету (без
предварительного их определения), хотя обычно в этом нет смысла.
Например:
\begin{verbatim}
apple 'xxx' (10) -- создали переменную xxx у объекта apple по ссылке
(_'яблоко') 'xxx' (10) -- то же самое, но по имени объекта
\end{verbatim}
\section{Локальные переменные}
\label{Локальныепеременные}
Кроме переменных объекта вы можете использовать локальные и глобальные
переменные.
Локальные переменные создаются с помощью служебного слова local:
\begin{verbatim}
act = function(s)
local w = _'лампочка'
w.light = true
p [[Я нажал на кнопку и лампочка загорелась.]]
end
\end{verbatim}
В данном примере, переменная w существует только в теле
функции-обработчика act. Мы создали временную ссылку-переменную w,
которая ссылается на объект `лампочка', чтобы изменить
свойство-переменную light у этого объекта.
Конечно, мы могли написать и:
\begin{verbatim}
_'лампочка'.light = true
\end{verbatim}
Но представьте себе, если нам нужно произвести несколько действий с
объектом, в таких случаях проще воспользоваться временной переменной.
Локальные переменные никогда не попадают в файл-сохранение и играют
роль временных вспомогательных переменных.
Локальные переменные можно создавать и вне функций, тогда данная
переменная видима только в пределах данного lua файла и не попадает в
файл сохранения.
Еще один пример использования локальных переменных:
\begin{verbatim}
obj {
nam = 'котенок';
state = 1;
act = function(s)
s.state = s.state + 1
if s.state > 3 then
s.state = 1
end
p [[Муррр!]]
end;
dsc = function(s)
local dsc = {
"{Котенок} мурлычет.",
"{Котенок} играет.",
"{Котенок} облизывается.",
};
p(dsc[s.state])
end;
end
\end{verbatim}
Как видим, в функции dsc мы определили массив dsc. `local' указывает
на то, что он действует в пределах функции dsc. Конечно, данный
пример можно было написать и так:
\begin{verbatim}
dsc = function(s)
if s.state == 1 then
p "{Котенок} мурлычет."
elseif s.state == 2 then
p "{Котенок} играет."
else
p "{Котенок} облизывается.",
end
end
\end{verbatim}
\section{Глобальные переменные}
\label{Глобальныепеременные}
Вы также можете создать глобальную переменную:
\begin{verbatim}
global { -- определение глобальных переменных
global_var = 1; -- число
some_number = 1.2; -- число
some_string = 'строка';
know_truth = false; -- булево значение
array = {1, 2, 3, 4}; -- массив
}
\end{verbatim}
Еще одна форма записи, удобная для одиночных определений:
\begin{verbatim}
global 'global_var' (1)
\end{verbatim}
Глобальные переменные всегда попадают в файл-сохранение.
Кроме глобальных переменных вы можете задавать константы. Синтаксис
аналогичен глобальным переменным:
\begin{verbatim}
const {
A = 1;
B = 2;
}
const 'Aflag' (false)
\end{verbatim}
Движок будет контролировать неизменность констант. Константы не
попадают в файл-сохранение.
Иногда вам нужно работать с переменной, которая не определена как
local (и видима во всех ваших lua файлах игры), но не должна попадать
в файл сохранения. Для таких переменных вы можете использовать
декларации:
\begin{verbatim}
declare {
A = 1;
B = 2;
}
declare 'Z' (false)
\end{verbatim}
Декларации не попадают в файл сохранения. Одно из важных свойств
деклараций состоит в том, что вы можете декларировать функции,
например:
\begin{verbatim}
declare 'test' (function()
p "Hello world!"
end)
global 'f' (test)
\end{verbatim}
В таком случае, вы можете присваивать значение функции `test' другим
переменным и состояние этих переменных может быть сохранено в файле
сохранения. То-есть, декларированную функцию можно использовать как
значение переменной!
Вы можете декларировать ранее определенные функции, например:
\begin{verbatim}
declare 'dprint' (dprint)
\end{verbatim}
Тем самым делая такие недекларированные функции -- декларированными.
Декларация функции, по сути, это присвоение функции имени, благодаря
чему мы можем сохранить эту функцию как ссылку.
\section{Вспомогательные функции}
\label{Вспомогательныефункции}
Вы можете писать свои вспомогательные функции и использовать их из
своей игры, например:
\begin{verbatim}
function mprint(n, ...)
local a = {...}; -- временный массив с аргументами к функции
p(a[n]) -- выведем n-й элемент массива
end
...
dsc = function(s)
mprint(s.state, {
"{Котенок} мурлычет.",
"{Котенок} играет.",
"{Котенок} облизывается." });
end;
\end{verbatim}
Пока не обращайте внимания на данный пример, если он кажется вам сложным.
\section{Возвращаемые значения обработчиков}
\label{Возвращаемыезначенияобработчиков}
Если необходимо показать, что действие не выполнено (обработчик не
сделал ничего полезного), возвращайте значение false. Например:
\begin{verbatim}
act = function(s)
if broken_leg then
return false
end
p [[Я ударил ногой по мячу.]]
end
\end{verbatim}
При этом будет отображено описание по умолчанию, заданное с помощью
обработчика `game.act'. Обычно описание по умолчанию содержит
описание невыполнимых действий. Что-то вроде:
\begin{verbatim}
game.act = 'Гм... Не получается...';
\end{verbatim}
Итак, если вы не задали обработчик act или вернули из него false --
считается, что реакции нет и движок выполнит аналогичный обработчик у
объекта `game'.
Обычно, нет никакого смысла возвращать false из act, но существуют
другие обработчики, о которых будет рассказано дальше, для которых
описанное поведение точно такое же.
На самом деле, кроме `game.act' и `act' -- атрибута объекта существует
обработчик `onact' у объекта game, который может прервать выполнение
обработчика `act'.
Перед тем как вызвать обработчик `act' у объекта, вызывается onact у
game. Если обработчик вернет false, выполнение `act' обрывается.
`onact' удобно использовать, для контроля
событий в комнате или игре, например:
\begin{verbatim}
-- вызываем onact комнат, если они есть
-- для действий на любой объект
game.onact = function(s, ...)
local r, v = std.call(here(), 'onact', ...)
if v == false then -- если false, обрубаем цепочку
return r, v
end
return
end
room {
nam = 'shop';
disp = 'Магазин';
onact = function(s, w)
p [[В магазине нельзя воровать!]]
p ([[Даже, если это всего-лишь ]], w, '.')
return false
end;
obj = { 'мороженное', 'хлеб' };
}
\end{verbatim}
В данном примере, при попытке ``потрогать'' любой предмет, будет
выведено сообщение о запрете данного действия.
Все, что описано выше на примере `act' действует и для других
обработчиков: tak, inv, use, а также при переходах, о чем будет
рассказано далее.
\begin{quote}
Иногда возникает необходимость вызвать функцию - обработчик вручную.
Для этого используется синтаксис вызова метода
объекта. `Объект:метод(параметры)'. Например:
\end{quote}
\begin{verbatim}
apple:act() -- вызовем обработчик 'act' у объекта 'apple' (если он
определен как функция!). _'яблоко':act() -- то же самое, но по
имени, а не по переменной-ссылке
\end{verbatim}
Такой метод работает только в том случае, если вызываемый метод
оформлен как функция. Вы можете воспользоваться `std.call()' для
вызова обработчика тем способом, каким это делает сам INSTEAD. (Будет
описано в дальнейшем).
\chapter{Инвентарь}
\label{Инвентарь}
Простейший вариант сделать объект, который можно брать -- определить
обработчик `tak'.
Например:
\begin{verbatim}
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
inv = function(s)
p 'Я съел яблоко.'
remove(s); -- удалить яблоко из инвентаря
tak = 'Вы взяли яблоко.';
};
\end{verbatim}
При этом, при действии игрока на объект ``яблоко'' (щелчок мыши на
ссылку в сцене) -- яблоко будет убрано из сцены и добавлено в
инвентарь. При действии игрока на инвентарь (двойной щелчок мыши на
названии объекта) -- вызывается обработчик `inv'.
В нашем примере, при действии игроком на яблоко в инвентаре -- яблоко
будет съедено.
Конечно, мы могли бы реализовать код взятия объекта в ``act'',
например, так:
\begin{verbatim}
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
inv = function(s)
p 'Я съел яблоко.'
remove(s); -- удалить яблоко из инвентаря
end;
act = function(s)
take(s)
p 'Вы взяли яблоко.';
end
};
\end{verbatim}
Если у объекта в инвентаре не объявлен обработчик `inv', будет вызван `game.inv'.
Если обработчик `tak' вернет false, то предмет не будет взят, например:
\begin{verbatim}
obj {
nam = 'яблоко';
dsc = 'На столе лежит {яблоко}.';
tak = function(s)
p "Оно же червивое!"
return false
end;
};
\end{verbatim}
\chapter{Переходы между сценами}
\label{Переходымеждусценами}
Традиционные переходы в INSTEAD выглядят как ссылки над описанием
сцены. Для определения таких переходов между сценами используется
атрибут сцены -- список `way'. В списке определяются комнаты, в виде
имен комнат или переменных-ссылок, аналогично списку `obj'. Например:
\begin{verbatim}
room {
nam = 'room2';
disp = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
};
room {
nam = 'main';
disp = 'Главная комната';
dsc = 'Вы в большой комнате.';
way = { 'room2' };
};
\end{verbatim}
При этом, вы сможете переходить между сценами `main' и `room2'. Как вы
помните, `disp' может быть функцией, и вы можете генерировать имена
переходов на лету. Или использовать title, для разделения имени сцены
как заголовка и как имени перехода:
\begin{verbatim}
room {
nam = 'room2';
disp = 'В зал';
title = 'В зале';
dsc = 'Вы в огромном зале.';
way = { 'main' };
};
room {
nam = 'main';
title = 'В главной комнате';
disp = 'В главную комнату';
dsc = 'Вы в большой комнате.';
way = { 'room2' };
};
\end{verbatim}
При переходе между сценами движок вызывает обработчик `onexit' из
текущей сцены и `onenter' в той сцене, куда идет игрок. Например:
\begin{verbatim}
room {
onenter = 'Вы заходите в зал.';
nam = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
onexit = 'Вы выходите из зала.';
};
\end{verbatim}
Конечно, как и все обработчики, `onexit' и `onenter' могут быть
функциями. Тогда первый параметр это (как всегда) сам объект -
комната, а второй -- это комната куда игрок собирается идти (для
`onexit') или из которой собирается уйти (для `onenter'). Например:
\begin{verbatim}
room {
onenter = function(s, f)
if f^'main' then
p 'Вы идете из комнаты main.';
end
end;
nam = 'Зал';
dsc = 'Вы в огромном зале.';
way = { 'main' };
onexit = function(s, t)
if t^'main' then
p 'Я не хочу назад!'
return false
end
end;
};
\end{verbatim}
Запись вида:
\begin{verbatim}
if f^'main' then
\end{verbatim}
Это сопоставление объекта с именем. Это альтернатива записям:
\begin{verbatim}
if f == _'main' then
\end{verbatim}
Или:
\begin{verbatim}
if f.nam == 'main' then
\end{verbatim}
Или:
\begin{verbatim}
if std.nameof(f) == 'main' then
\end{verbatim}
Как видим на примере onexit, эти обработчики, кроме строки могут
возвращать булевое значение статуса. Аналогично обработчику onact, мы
может отменить переход, вернув false из onexit\slash onenter.
Вы можете сделать возврат статуса и другим способом, если это кажется
вам удобным:
\begin{verbatim}
return "Я не хочу назад", false
\end{verbatim}
Если же вы используете функции `p'\slash `pn'\slash `pr', то просто возвращайте
статус операции с помощью завершающего `return', как показано в
примере выше.
\textbf{Важно!}
\begin{quote}
Следует отметить, что при вызове обработчика `onenter' указатель на
текущую сцену (here()) \textbf{еще не изменен}!!! В INSTEAD есть
обработчики `exit' (уход из комнаты) и `enter' (заход в комнату),
которые вызываются уже \emph{после} того, как переход произошел. Эти
обработчики рекомендованы к использованию всегда, когда нет
необходимости запрещать переход.
\end{quote}
Иногда есть необходимость, чтобы название перехода отличалось от
названия комнаты, в которую ведет этот переход. Существует несколько
способов сделать это. Например, с помощью `path'.
\begin{verbatim}
room {
nam = 'room2';
title = 'Зал';
dsc = 'Вы в огромном зале.';
way = { path { 'В главную комнату', 'main'} };
};
room {
nam = 'main';
title = 'Главная комната';
dsc = 'Вы в большой комнате.';
way = { path {'В зал', 'room2'} };
};
\end{verbatim}
На самом деле, `path' создает комнату с атрибутом `disp', который
равен первому параметру, и специальной функцией `onenter', которая
перенаправляет игрока в комнату заданную вторым параметром `path'.
Если вы укажете три параметра:
\begin{verbatim}
way = { path {'#взал', 'В зал', 'room2'} };
\end{verbatim}
То первый параметр станет именем (или тегом, как в приведенном
примере) такой комнаты.
Альтернативная форма записи с явным заданием атрибута nam:
\begin{verbatim}
way = { path { nam = '#взал', 'В зал', 'room2'} };
\end{verbatim}
Вы можете менять название перехода, после того, как переход происходил
хотя бы раз, и вы узнали, что же это за комната:
\begin{verbatim}
way = { path {'#вдверь', 'В дверь', after = 'В гостиную', 'room2'} };
\end{verbatim}
Все параметры, кроме имени перехода, могут быть функциями.
Таким образом, `path' позволяет именовать переходы удобным способом.
Иногда вам может потребоваться включать и выключать переходы. На самом
деле это требуется не часто. Идея переходов состоит в том, что переход
виден даже тогда, когда он невозможен. Например, представим себе сцену
перед домом у входной двери. Войти в дом нельзя, так как дверь
закрыта.
Нет особого смысла прятать переход ``дверь''. Просто в функции `onenter'
сцены внутри дома мы проверяем, а есть ли у героя ключ? И если ключа
нет, говорим о том, что дверь закрыта и запрещаем переход. Это
повышает интерактивность и упрощает код. Если же вы хотите сделать
дверь объектом сцены, поместите ее в комнату, но в `act' обработчике
сделайте осмотр двери, или дайте возможность игроку открыть ее ключом
(как это сделать - мы рассмотрим позже), но сам переход дайте сделать
игроку привычным способом через строку переходов.
Тем не менее, бывают ситуации, когда переход не очевиден и он
появляется в результате каких-то событий. Например, мы осмотрели часы
и увидели там секретный лаз.
\begin{verbatim}
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
enable '#часы'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { '#часы', 'В часы', 'inclock' }:disable() };
};
\end{verbatim}
В данном примере, мы создали \emph{отключенный} переход, за счет вызова
метода `disable' у комнаты созданной с помощью `path'. Метод `disable'
есть у всех объектов (не только комнат), он переводит объект в
отключенное состояние, которое означает, что объект перестает быть
доступным игроку. Замечательным свойством отключенного объекта
является то, что его можно \emph{включить} с помощью `enable()';
Далее, когда игрок нажимает на ссылку, описывающую часы, вызывается
обработчик `act', который с помощью функции `enable()' делает переход
видимым.
Альтернативный вариант заключается не в выключении, а `закрытии'
объекта:
\begin{verbatim}
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
open '#часы'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { '#часы', 'В часы', 'inclock' }:close() };
};
\end{verbatim}
В чем разница? Выключение объекта означает то, что объект перестает
быть доступным для игрока. Если в объекте вложены другие объекты, то
и эти объекты становятся недоступными.
Закрытие объекта делает недоступным содержимое данного объекта, но не
сам объект.
Однако, в случае комнат, и закрытие комнаты и отключенные комнаты
приводят к одному результату -- переход на них становится недоступным.
Еще один вариант:
\begin{verbatim}
room {
nam = 'inclock';
dsc = [[Я в часах.]];
}:close()
obj {
nam = 'часы';
dsc = [[Тут есть старинные {часы}.]];
act = function(s)
open 'inclock'
p [[Вы видите, что в часах есть потайной ход!]];
end;
}
room {
nam = 'Зал';
dsc = 'Вы в огромном зале.';
obj = { 'часы' };
way = { path { 'В часы', 'inclock' } };
};
\end{verbatim}
Здесь мы закрываем и открываем не переход, а комнату, в которую ведет
переход. path не показывает себя, если комната в которую он ведет
отключена или закрыта.
\chapter{Действие объектов друг на друга}
\label{Действиеобъектовдругнадруга}
Игрок может действовать объектом инвентаря на другие объекты. Для
этого он щелкает мышью на предмет инвентаря, а затем, на предмет
сцены. При этом вызывается обработчик `used' у объекта, на который
действуют, и обработчик `use' объекта, которым действуют.
Например:
```
obj \{
nam = `нож';
dsc = `На столе лежит \{нож\}';
inv = `Острый!';
tak = `Я взял нож!';
use = `Вы пытаетесь использовать нож.';
\};
obj \{
nam = `стол';
dsc = `В комнате стоит \{стол\}.';
act = `Гм{\ldots} Просто стол{\ldots}';
obj = \{ `нож' \};
used = function(s)
p `Вы пытаетесь сделать что-то со столом{\ldots}';
return false
end;
\};
```
В данном примере, обработчик used возвращает false. Зачем? Если вы
помните, возврат false означает, что обработчик сообщает движку о том,
что событие он не обработал. Если бы мы не вернули бы false, очередь
до обработчика `use' объекта `нож' просто бы не дошла. На самом деле,
в реальности обычно вы будете пользоваться или use или used, вряд ли
имеет смысл выполнять оба обработчика во время действия предмета
на предмет.
Еще один пример, когда удобно вернуть false:
\begin{verbatim}
use = function(s, w)
if w^'яблоко' then
p [[Я почистил яблоко.]]
w.cut = true
return
end
return false;
end
\end{verbatim}
В данном случае use у яблока обрабатывает только одну ситуацию --
действие на яблоко. В остальных случаях, обработчик возвращает false и
движок вызовет метод по-умолчанию: game.use.
Но лучше, если вы пропишете действие по-умолчанию для ножа:
\begin{verbatim}
use = function(s, w)
if w^'яблоко' then
p [[Я почистил яблоко.]]
w.cut = true
return
end
p [[Не стоит размахивать ножом!]]
end
\end{verbatim}
Этот пример также демонстрирует тот факт, что вторым параметром у use
является предмет на который мы действуем. У метода `used',
соответственно, второй параметр -- это объект, который действует на
нас:
\begin{verbatim}
obj {
nam = 'мусорка';
dsc = [[В углу стоит {мусорка}.]];
used = function(s, w)
if w^'яблоко' then
p [[Я выбросил яблоко в мусорку.]]
remove(w)
return
end
return false;
end
}
\end{verbatim}
Как вы помните, перед вызовом use вызывается обработчик onuse у
объекта game, потом у объекта `игрок', а потом у текущей комнаты. Вы
можете блокировать `use', вернув из любого из перечисленных методов
`onuse' -- false.
Использовать `use' или `used' (или оба) это вопрос личных
предпочтений, однако, метод used вызывается раньше и, следовательно,
имеет больший приоритет.
\chapter{Объект ``Игрок''}
\label{ОбъектИгрок}
Игрок в мире INSTEAD представлен объектом типа `player'. Вы можете
создавать несколько игроков, но один игрок присутствует по-умолчанию.
Имя этого объекта -- `player'. Существует переменная-ссылка pl,
которая указывает на этот объект.
Обычно, вам не нужно работать с этим объектом напрямую. Но иногда это
может быть необходимым.
По умолчанию, атрибут `obj' у игрока представляет собой инвентарь.
Обычно, нет смысла переопределять объект типа player, однако, вы
можете это сделать:
\begin{verbatim}
game.player = player {
nam = "Василий";
room = 'кухня'; -- стартовая комната игрока
power = 100;
obj = { 'яблоко' }; -- заодно добавим яблоко в инвентарь
};
\end{verbatim}
В INSTEAD есть возможность создавать нескольких игроков и
переключаться между ними. Для этого служит функция `change\_pl()'. В
качестве параметра передайте функции требуемый объект типа `player'
(или его имя). Функция переключит текущего игрока, и при
необходимости, осуществит переход в комнату, где находится новый
игрок.
Функция `me()' всегда возвращает текущего игрока. Следовательно, в
большинстве игр me() == pl.
\chapter{Объект ``Мир''}
\label{ОбъектМир}
Игровой мир представлена объектом типа world. Имя такого объекта
`game'. Существует ссылка-переменная, которая также называется game.
Обычно вы не работаете с этим объектом напрямую, однако иногда вы
можете вызывать его методы, или менять значения переменных этого
объекта.
Например, переменная game.codepage содержит кодировку исходного кода
игры, и по-умолчанию равна ``UTF--8''. Я не рекомендую использовать
другие кодировки, но иногда, выбор кодировки может стать
необходимостью.
Переменная game.player -- содержит текущего игрока.
Кроме того, как вы уже знаете, объект `game' может содержать
обработчики по умолчанию: `act', `inv', `use', `tak', которые будут
вызваны, если в результате действий пользователя не будут найдены
никакие другие обработчики (или все они вернули false). Например, вы
можете написать в начале игры:
\begin{verbatim}
game.act = 'Не получается.';
game.inv = 'Гм.. Странная штука..';
game.use = 'Не сработает...';
game.tak = 'Не нужно мне это...';
\end{verbatim}
Конечно, все они могут быть функциями.
Также, объект game может содержать обработчики: onact, ontak, onuse,
oninv, onwalk -- которые могут прерывать действия, в случае возврата
false.
Еще, у объекта game можно задать обработчики: afteract, afterinv,
afteruse, afterwalk -- которые вызываются в случае успешного
выполнения соответствующего действия.
\chapter{Атрибуты-списки}
\label{Атрибуты-списки}
Атрибуты-списки (такие как `way' или `obj') позволяют работать со
своим содержимым с помощью набора методов. Атрибуты-списки призваны
сохранять в себе списки объектов. На самом деле, вы можете создавать
списки для собственных нужд, и размещать их в объектах, например:
\begin{verbatim}
room {
nam = 'холодильник';
frost = std.list { 'мороженное' };
}
\end{verbatim}
Хотя, обычно, это не требуется.
Ниже перечислены методы объектов типа `список'. Вы можете вызывать их
для любых списков, хотя обычно это будут way и obj, например:
\begin{verbatim}
ways():disable() -- отключить все переходы
\end{verbatim}
\begin{itemize}
\item disable() - отключает все объекты списка;
\item enable() - включает все объекты списка;
\item close() - закрыть все объекты списка;
\item open() - открыть все объекты списка;
\item add(объект\textbar{}имя, [позиция]) - добавить объект;
\item for\_each(функция, аргументы) - вызвать для каждого объекта функцию с
аргументами;
\item lookup(имя\slash тег или объект) - поиск объекта в списке. Возвращает
объект и индекс;
\item srch(имя\slash тег или объект) - поиск видимого объекта в списке;
\item empty() - вернет true, если список пуст;
\item zap() - очистить список;
\item replace(что, на что) - заменить объект в списке;
\item cat(список, [позиция]) - добавить содержимое списка в текущий список
по позиции;
\item del(имя\slash объект) - удалить объект из списка.
\end{itemize}
Существуют функции, возвращающие объекты-списки:
\begin{itemize}
\item inv([игрок]) - вернуть инвентарь игрока;
\item objs([комната]) - вернуть объекты комнаты;
\item ways([комната]) - вернуть переходы комнаты.
\end{itemize}
Конечно, вы можете обращаться к спискам и напрямую:
\begin{verbatim}
pl.obj:add 'нож'
\end{verbatim}
Объекты в списках хранятся в том порядке, в котором вы их
добавите. Однако, если у объекта присутствует числовой атрибут pri, то
он играет роль \emph{приоритета} в списке. Если pri не задан, значением
приоритета считается 0. Таким образом, если вы хотите, чтобы какой-то
объект был первым в списке, давайте ему приоритет pri $<$ 0. Если в
конце списка -- $>$ 0.
\begin{verbatim}
obj {
pri = -100;
nam = 'штука';
disp = 'Очень важный предмет инвентаря';
inv = [[Осторожней с этим предметом.]];
}
\end{verbatim}
\chapter{Функции, которые возвращают объекты}
\label{Функциикоторыевозвращаютобъекты}
В INSTEAD определены некоторые функции, которые возвращают различные объекты.
При описании функции используются следующие соглашения о параметрах.
\begin{itemize}
\item в символах [ ] описаны необязательные параметры;
\item `что' или `где' - означает объект (в том числе комнату), заданный тегом,
именем или переменной-ссылкой;
\end{itemize}
Итак, основные функции:
\begin{itemize}
\item `\_(что)' - получить объект;
\item `me()' возвращает текущего объекта-игрока;
\item `here()' возвращает текущую сцену;
\item `where(что)' возвращает комнату или объект в котором находится
заданный объект, если объект находится в нескольких местах, то можно
передать второй параметр -- таблицу Lua, в которую будут добавлены
эти объекты;
\item `inroom(что)' аналогично where(), но вернет комнату, в которой
расположен объект (это важно для объектов в объектах);
\item `from([где])' возвращает прошлую комнату, из которой игрок перешел в
заданную комнату. Необязательный параметр -- получить прошлую комнату
не для текущей комнаты, а для заданной;
\item `seen(что, [где])' возвращает объект или переход, если он
присутствует и видим, есть второй необязательный параметр -- выбрать
сцену или объект\slash список в котором искать;
\item `lookup(что, [где])' возвращает объект или переход, если он
существует в сцене или объекте\slash списке;
\item `inspect(что)' возвращает объект, если он виден\slash доступен на
сцене. Поиск производится по переходам и объектам, в том числе, в
объектах игрока;
\item `have(что)' возвращает объект, если он есть в инвентаре и не
отключен;
\item `live(что)' возвращает объект, если он присутствует среди живых
объектов (описано далее);
\end{itemize}
Эти функции в основном используются в условиях, либо для поиска
объекта с последующей модификацией. Например, вы можете использовать
`seen' для написания условия:
\begin{verbatim}
onexit = function(s)
if seen 'монстр' then -- если у функции 1 параметр,
--- скобки писать не обязательно
p 'Монстр загораживает проход!'
return false
end
end
\end{verbatim}
А также, для нахождения объекта в сцене:
\texttt{
use = function(s, w)
if w\^{}'окно' then
local ww = lookup 'собака'
if not ww then
p [[А где моя собака?]]
return
end
place(ww, 'улица')
p 'Я разбил окно! Моя собака выпрыгнула на улицу.'
return
end
return false
end
}
Пример с функцией `have':
\begin{verbatim}
...
act = function(s)
if have 'нож' then
p 'Но у меня же есть нож!';
return
end
take 'нож'
end
...
\end{verbatim}
\begin{quote}
Может возникнуть вопрос, в чем разница между функциями lookup и \_ ()?
Дело в том, что lookup() ищет объект, и в случае, если объект не найден
-- просто ничего не вернет. А запись \_ () предполагает, что вы точно
знаете, что за предмет вы получаете. Другими словами, \_ () это
безусловное получение объекта по имени. Эта функция в общем случае не
занимается \emph{поиском}. Только если в качестве параметра задан тег,
будет осуществлен поиск среди доступных объектов. Если вы используете
\_ () на несуществующий объект или недоступный тег -- вы получите ошибку!
\end{quote}
\chapter{Другие функции стандартной библиотеки}
\label{Другиефункциистандартнойбиблиотеки}
В INSTEAD в модуле stdlib, который всегда подключается автоматически,
определены функции, которые предлагаются автору как основной рабочий
инструмент по работе с миром игры. Рассмотрим их в этой главе.
При описании функции в большинстве функций под параметром `w'
понимается объект или комната, заданная именем, тегом или по
переменной-ссылке. [ wh ] - означает необязательный параметр.
\begin{itemize}
\item include(файл) - включить файл в игру;
\begin{verbatim}
include "lib" -- включит файл lib.lua из текущего каталога с игрой;
\end{verbatim}
\item loadmod(модуль) - подключить модуль игры;
\begin{verbatim}
loadmod "module" -- включит модуль module.lua из текущего каталога;
\end{verbatim}
\item rnd(m) - случайное целочисленное значение от `1' до `m';
\item rnd(a, b) - случайное целочисленное значение от `a' до `b', где `a'
и `b' целые $>$= 0;
\item rnd\_seed(что) - задать зерно генератора случайных чисел;
\item p({\ldots}) - вывод строки в буфер обработчика\slash атрибута (с пробелом в конце);
\item pr({\ldots}) - вывод строки в буфер обработчика\slash атрибута ``как есть'';
\item pn({\ldots}) - вывод строки в буфер обработчика\slash атрибута (с переводом строки в конце);
\item pf(fmt, {\ldots}) - вывод форматной строки в буфер обработчика\slash атрибута;
\begin{verbatim}
local text = 'hello';
pf("Строка: %q Число: %d\n", text, 10);
\end{verbatim}
\item pfn({\ldots})({\ldots}){\ldots} ``строка'' - формирование простого обработчика;
Данная функция упрощает создание простых обработчиков:
\begin{verbatim}
act = pfn(walk, 'ванная') "Я решил зайти в ванную.";
act = pfn(enable, '#переход') "Я заметил отверстие в стене!";
\end{verbatim}
\item obj \{\} - создание объекта;
\item stat \{\} - создание статуса;
\item room \{\} - создание комнаты;
\item menu \{\} - создание меню;
\item dlg \{\} - создание диалога;
\item me() - возвращает текущего игрока;
\item here() - возвращает текущую сцену;
\item from([w]) - возвращает комнату из которой осуществлен переход в
текущую сцену;
\item new(конструктор, аргументы) - создание нового \emph{динамического}
объекта (будет описано далее);
\item delete(w) - удаление динамического объекта;
\item gamefile(файл, [сбросить состояние?]) - подгрузить динамически файл
с игрой;
\begin{verbatim}
gamefile("part2.lua", true) -- сбросить состояние игры (удалить
объекты и переменные), подгрузить part2.lua и начать с main комнаты.
\end{verbatim}
\item player \{\} - создать игрока;
\item dprint({\ldots}) - отладочный вывод;
\item visits([w]) - число визитов в данную комнату (или 0, если визитов не было);
\item visited([w]) - число визитов в комнату или false, если визитов не
было;
\begin{verbatim}
if not visited() then
p [[Я тут первый раз.]]
end
\end{verbatim}
\item walk(w, [булевое exit], [булевое enter]) - переход в сцену;
\begin{verbatim}
walk('конец', false, false) -- безусловный переход (игнорировать
onexit/onenter/exit/enter);
\end{verbatim}
\item walkin(w) - переход в под-сцену (без вызова exit\slash onexit текущей комнаты);
\item walkout([w]) - возврат из под-сцены (без вызова enter\slash onenter);
\item \_(w) - получение объекта;
\item for\_all(fn, {\ldots}.) - выполнить функцию для всех аргументов;
\begin{verbatim}
for_all(enable, 'окно', 'дверь');
\end{verbatim}
\item seen(w, [где]) - поиск видимого объекта;
\item lookup(w, [где]) - поиск объекта;
\item ways([где]) - получить список переходов;
\item objs([где]) - получить список объектов;
\item search(w) - поиск доступного игроку объекта;
\item have(w) - поиск предмета в инвентаре;
\item inroom(w) - возврат комнаты\slash комнат, в которой находится объект;
\item where(w, [таблица]) - возврат объекта\slash объектов, в котором находится объект;
\begin{verbatim}
local list = {}
local w = where('яблоко', list)
-- если яблоко находится в более, чем одном месте, то
-- list будет содержать массив этих мест.
-- Если вам достаточно одного местоположения, то:
where 'яблоко' -- будет достаточно
\end{verbatim}
\item closed(w) - true если объект закрыт;
\item disabled(w) - true если объект выключен;
\item enable(w) - включить объект;
\item disable(w) - выключить объект;
\item open(w) - открыть объект;
\item close(w) - закрыть объект;
\item actions(w, строка, [значение]) - возвращает (или устанавливает)
число действий типа t для объекта w.
\begin{verbatim}
if actions(w, 'tak') > 0 then -- предмет w был взят хотя бы 1 раз;
if actions(w) == 1 then -- act у предмета w был вызван 1 раз;
\end{verbatim}
\item pop(тег) - возврат в прошлую ветвь диалога;
\item push(тег) - переход в следующую ветвь диалога
\item empty([w]) - пуста ли ветвь диалога? (или объект)
\item lifeon(w) - добавить объект в список живых;
\item lifeoff(w) - убрать объект из списка живых;
\item live(w) - объект жив?;
\item change\_pl(w) - смена игрока;
\item player\_moved([pl]) - текущий игрок перемещался в этом такте?;
\item inv([pl]) - получить список-инвентарь;
\item remove(w, [wh]) - удалить объект из объекта или комнаты; Удаляет
объект из списков obj и way (оставляя во всех остальных, например,
game.lifes);
\item purge(w) - уничтожить объект (из всех списков); Удаляет объект из
\emph{всех} списков, в которых он присутствует;
\item replace(w, ww, [wh]) - заменить один объект на другой;
\item place(w, [wh]) - поместить объект в объект\slash комнату (удалив его из
старого объекта\slash комнаты);
\item put(w, [wh]) - поместить объект без удаления из старого местоположения;
\item take(w) - забрать объект;
\item drop(w, [wh]) - выбросить объект;
\item path \{\} - создать переход;
\item time() - число ходов от начала игры.
\end{itemize}
\textbf{Важно!}
На самом деле, многие из этих функций также умеют работать не только с
комнатами и объектами, но и со списками. То есть `remove(apple,
inv())' сработает также как и `remove(apple, me())''; Впрочем,
remove(apple) тоже сработает и удалит объект из тех мест, где он
присутствует.
Рассмотрим несколько примеров.
\begin{verbatim}
act = function()
pn "Я иду в следующую комнату..."
walk (nextroom);
end
obj {
nam = 'моя машина';
dsc = 'Перед хижиной стоит мой старенький {пикап} Toyota.';
act = function(s)
walk 'inmycar';
end
};
\end{verbatim}
\textbf{Важно!}
\begin{quote}
После вызова `walk' выполнение обработчика продолжится до его
завершения. Поэтому обычно, после `walk' всегда следует
`return', если только это не последняя строка функции, хотя и в
этом случае безопасно поставить `return'.
\end{quote}
\begin{verbatim}
act = function()
pn "Я иду в следующую комнату..."
walk (nextroom);
return
end
\end{verbatim}
Не забывайте также, что при вызове `walk' вызовутся обработчики
`onexit\slash onenter\slash exit\slash enter'' и если они запрещают переход, то он не
произойдет.
\chapter{Диалоги}
\label{Диалоги}
Диалоги -- это сцены специального типа `dlg', содержащие объекты --
фразы. При входе в диалог игрок видит перечень фраз, которые может
выбирать, получая какую-то реакцию игры. По умолчанию, уже выбранные
фразы скрываются. При исчерпании всех вариантов, диалог завершается
выходом в предыдущую комнату (конечно, если в диалоге нет постоянно
видимых фраз, среди которых обычно встречается что-то типа `Завершить
разговор' или `Спросить еще раз'). При повторном входе в диалог, все
скрытые фразы снова становятся видимыми и диалог сбрасывается в
начальное состояние (если, конечно, автор игры специально не
прикладывал усилия по изменению вида диалога).
Переход в диалог в игре осуществляется как переход на сцену:
\begin{verbatim}
obj {
nam = 'повар';
dsc = 'Я вижу {повара}.';
act = function()
walk 'povardlg'
end,
};
\end{verbatim}
Хотя я рекомендую использовать `walkin', так как в случае `walkin' не
вызываются `onexit\slash exit' текущей комнаты, а персонаж, с которым мы
можем поговорить, обычно находиться в этой же комнате, где и главный
герой. То есть:
\begin{verbatim}
obj {
nam = 'повар';
dsc = 'Я вижу {повара}.';
act = function()
walkin 'povardlg'
end,
};
\end{verbatim}
Если вам не нравится префикс у фраз в виде дефиса, вы можете определить строковую переменную:
\begin{verbatim}
std.phrase_prefix = '+';
\end{verbatim}
И получить префикс в виде `+' перед каждой фразой. Вы также можете
сделать префикс функцией. На вход функции в таком случае будет
поступать в виде параметра номер фразы. Задача функции -- вернуть
строковый префикс.
Обратите внимание, что `std.phrase\_prefix' не сохраняется, если вам
нужно переопределять ее на лету, вам придется восстанавливать ее
состояние в `start()' функции вручную!
\textbf{Важно!}
\begin{quote}
Я рекомендую использовать модуль `noinv' и задавать свойство `noinv'
в диалогах. Диалоги будут выглядеть красивей и вы обезопасите свою
игру от ошибок и непредсказуемых реакций при использовании инвентаря
внутри диалога (так как обычно автор не подразумевает такие
вещи). Например:
\end{quote}
\begin{verbatim}
require "noinv"
...
dlg {
nam = 'Охранник';
-- в диалогах обычно не нужен инвентарь
noinv = true;
...
}
\end{verbatim}
\section{Фразы}
\label{Фразы}
Центральным понятием в диалогах является \emph{фраза}. Фразы это не просто
вопрос-ответ, как можно подумать. Фраза является деревом, и в этом
смысле, весь диалог может быть реализован единственной
фразой. Например:
\begin{verbatim}
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = {
{ 'У вас есть бобы?', '-- Нет.'},
{ 'У вас есть шоколад?', '-- Нет.'},
{ 'У вас есть квас?', '-- Да',
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
\end{verbatim}
Как видно из примера, фраза задается атрибутом phr и может содержать
разветвленный диалог. Фраза содержит в себе выборы, каждый из которых
тоже может содержать в себе выборы и так далее{\ldots}
Фраза имеет формат пары: описатель -- реакция. В простейшем случае,
это строки. Но это могут быть функциями. Обычно, функцией бывает
реакция, которая может содержать код по изменению игрового мира.
Пара может быть простой:
\begin{verbatim}
{'Вопрос', 'Ответ }
\end{verbatim}
А может содержать в себе массив пар:
\begin{verbatim}
{'Вопрос', 'Ответ',
{'Под-вопрос1', 'Под-ответ1' },
{'Под-вопрос2', 'Под-ответ2' },
}
\end{verbatim}
На самом деле, если вы посмотрите внимательно на атрибут phr, то вы
заметите, что массив выборов тоже является вложенным в главную фразу
phr, но только первоначальная пара отсутствует:
\begin{verbatim}
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = {
-- тут мог бы быть вопрос ответ 1-го уровня!
-- 'Главный вопрос', 'Главный ответ',
{ 'У вас есть бобы?', '-- Нет.'},
{ 'У вас есть шоколад?', '-- Нет.'},
{ 'У вас есть квас?', '-- Да',
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
\end{verbatim}
На самом деле, так и есть. И вы можете добавить `Главный вопрос' и
`Главный ответ', но только вы не увидите этот главный вопрос. Дело в
том, что при входе в диалог фраза phr автоматически раскрывается, так
как обычно нет никакого смысла в диалогах из одной единственной фразы.
И гораздо проще понять диалог как набор выборов, чем как единственную
древовидную фразу. Так что у phr никогда нет первоначальной пары
вопрос-ответ, но мы сразу попадаем в массив вариантов, что более
понятно.
Когда мы говорим о том, что диалог на самом деле реализован одной
фразой, мы не совсем правы. Дело в том, что мы имеем дело с фразой,
внутри которой находятся другие фразы{\ldots} Это напоминает нам ситуацию с
объектами. Действительно, фразы -- это объекты! Которые могут
находиться внутри друг-друга. Итак, взглянем на диалог свежим взглядом:
\begin{verbatim}
dlg {
nam = 'разговор';
title = [[Разговор с продавцом]];
enter = [[Я обратился к продавцу.]];
phr = { -- это объект типа фраза, без dsc и act
-- это 1-я фраза, внутри фразы с dsc и act
{ 'У вас есть бобы?', '-- Нет.'},
-- это 2-я фраза, внутри фразы с dsc и act
{ 'У вас есть шоколад?', '-- Нет.'},
-- это 3-я фраза, внутри фразы с dsc и act
{ 'У вас есть квас?', '-- Да',
-- это 1-я фраза внутри 3й фразы с dsc и act
{ 'А сколько он стоит?', '-- 50 рублей.' },
{ 'А он холодный?', '-- Холодильник сломался.',
{ 'Беру два!', 'Остался один.',
-- здесь act в виде функции
{ 'Дайте один!', function() p [[Ок!]]; take 'квас'; end };
}
}
}
}
}
\end{verbatim}
Как видим, диалог -- это комната, а фразы -- специальные объекты!
Теперь вам станет понятным дальнейшее изложение.
\begin{quote}
Внимание! По умолчанию, когда игрок нажимает на один из вопросов в
списке, движок повторяет его в выводе и только потом выводит
ответ. Это сделано для того, чтобы диалог выглядел связанным. Если
вы хотите отключить такое поведение, используйте настройку
std.phrase\_show:
\end{quote}
\begin{verbatim}
std.phrase_show = false -- не выводить фразу-вопрос при выборе
\end{verbatim}
Эта настройка действует на все диалоги, устанавливайте ее в init() или
start() функции.
\section{Атрибуты фраз}
\label{Атрибутыфраз}
Рассмотрим вариант фразы:
\texttt{
phr = \{
\{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
\{'Красную', 'Держите!' \},
\{'Синюю', 'Вот!' \},
\}
\}
}
Если запустить этот диалог, то в после выбора, скажем, красной
таблетки, у нас останется еще один выбор синей таблетки. Но наш
замысел, явно не в этом! Существует несколько способов сделать диалог
правильным.
Во первых, вы можете воспользоваться pop() -- возвратом на предыдущий
уровень диалога:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'Красную', function() p 'Держите!'; pop() end; },
{'Синюю', function() p 'Вот!'; pop() end; },
}
}
\end{verbatim}
Или, в другой записи:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'Красную', pfn(pop) 'Держите!' },
{'Синюю', pfn(pop) 'Вот!' },
}
}
\end{verbatim}
Но это не слишком удобно, кроме того, что если эти фразы содержат в
себе новые фразы? В случаях, когда вариант предлагает выбор, и этот
выбор должен быть единственным, вы можете задать у фразы атрибут only:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
}
}
\end{verbatim}
В таком случае, после выбора фразы, все фразы текущего контекста будут
закрыты.
Еще одна частая ситуация, вы хотите, чтобы фраза не пряталась после ее
активации. Это делается заданием флага true:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
}
}
\end{verbatim}
Альтернативная запись, с явным заданием атрибута always:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ always = true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
}
}
\end{verbatim}
Еще один пример. Что-если мы хотим, чтобы фраза была показана(или
спрятана) по какому-либо условию? Для этого есть функция-обработчик
cond.
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
{ true, 'А какая лучше?', 'Тебе выбирать.' }, -- фраза
-- которая никогда не будет скрыта
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
\end{verbatim}
В данном примере, только при наличии у игрока яблока, покажется ветка
диалога `А хотите яблоко?'.
Иногда бывает удобно выполнить действие в тот момент, когда варианты
текущего уровня(контекста) диалога исчерпаны. Для этого служит
функция-обработчик onempty.
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
onempty = function()
p [[Ты сделал свой выбор.]]
pop()
end;
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
\end{verbatim}
Обратите внимание, что когда есть метод onempty, автоматический
возврат в предыдущую ветку не производится, предполагается, что метод
onempty сделает все, что нужно.
Все описанные атрибуты могут быть установлены у любой фразы. В том
числе и на 1-м уровне:
\begin{verbatim}
phr = {
onempty = function()
p [[Вот и поговорили.]]
walkout()
end;
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!' },
{'Синюю', 'Вот!' },
onempty = function()
p [[Ты сделал свой выбор.]]
pop()
end;
},
{ cond = function() return have 'яблоко' end,
'А хотите яблоко?', 'Спасибо, нет.' };
}
\end{verbatim}
\section{Теги}
\label{Теги}
Только что мы рассмотрели механизмы диалогов, которые уже позволяют
создавать довольно сложные диалоги. Однако, и этих средств может не
хватить. Иногда нам нужно уметь обращаться к фразам из других мест
диалога. Например, выборочно включать их, или анализировать их
состояние. А также делать переходы из одних ветвей диалога в другие.
Все это возможно для фраз, у которых есть тег. Создать фразу с тегом
очень просто:
\begin{verbatim}
phr = {
{ '#что?', 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'#красная', 'Красную', 'Держите!' },
{'#синяя', 'Синюю', 'Вот!' },
},
}
\end{verbatim}
Как видим, наличие в начале фразы строки, которая начинается на символ
`\#' - означает наличие тега.
Для таких фраз работают стандартные методы, такие как seen или
enable\slash disable. Например, мы могли бы обойтись без атрибута only
следующим образом:
\begin{verbatim}
phr = {
{ '#что?', 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
{'#красная', 'Красную', 'Держите!'
cond = function(s)
return opened '#синяя'
end
},
{'#синяя', 'Синюю', 'Вот!',
cond = function(s)
return opened '#красная'
end
},
},
}
\end{verbatim}
Теги, кроме того, что позволяют узнавать и менять состояние конкретных
фраз, делают возможным переходы между фразами. Для этого используются
функции push и pop.
push(куда) -- делает переход на фразу с запоминанием позиции в стеке.
pop([куда]) -- вызванная без параметра, поднимается на 1 позицию в
стеке истории. Можно указать конкретный тег фразы, которая должна быть
в истории, в таком случае возврат будет осуществлен на нее.
Нужно отметить, что при переходе по push, мы переходим не на одну
фразу, а на список фраз этой фразы. То-есть раскрываем ее, также как
это сделано для главной фразы phr. Например:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
},
{ false, '#отаблетке',
{'Я сделал верный выбор?', 'Время покажет.'}
},
}
\end{verbatim}
Тут мы видим сразу несколько приемов:
\begin{itemize}
\item атрибут next, вместо явного описания реакции в виде функции с
push. next -- это простой способ записать push.
\item false в начале фразы, делает фразу выключенной. Она находится в
состоянии выключена, пока не сделать явный enable. Однако внутрь фразы
мы можем перейти, и показать содержимое выборов. Альтернативная запись
возможна с использованием атрибута hidden:
\end{itemize}
\begin{verbatim}
{ hidden = true, '#отаблетке',
{'Я сделал верный выбор?', 'Время покажет.'}
},
\end{verbatim}
Таким образом можно записывать диалоги не древовидно, а линейно. Еще
одна особенность переходов состоит в том, что если у фразы не описана
реакция, то при переходе будет вызван заголовок фразы:
\begin{verbatim}
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
},
{ false, '#отаблетке', [[Я взял таблетку и мастер хитро улыбнулся.]],
{'Я сделал верный выбор?', 'Время покажет.'},
{'Что делать дальше?', 'Ты свободен.'},
},
}
\end{verbatim}
При выборе таблетки, будет вызван заголовочный метод фразы
`\#отаблетке', а уже потом будет представлен выбор.
Если вам нравится линейная запись, вы можете предпочесть следующий
вариант:
\begin{verbatim}
dlg {
nam = 'диалог';
phr = {
{ 'Что это у вас?', 'Таблетки. Красная и синяя. Вам какую?',
only = true,
{'Красную', 'Держите!', next = '#отаблетке' },
{ 'Синюю', 'Вот!', next = '#отаблетке' },
}
}
}:with {
{ '#отаблетке', [[Я взял таблетку и мастер хитро улыбнулся.]],
{'Я сделал верный выбор?', 'Время покажет.'},
{'Что делать дальше?', 'Ты свободен.'},
},
}
\end{verbatim}
Дело в том, что атрибут phr диалога задает первый объект комнаты. Но
вы можете заполнить объекты комнаты обычным образом: задав obj или
with. Так как при входе в диалог раскрывается 1-я фраза, то остальные
фразы вы не увидите (обратите внимания, у фразы `\#отаблетке' не стоит
false), но вы сможете делать переходы на эти фразы.
\section{Методы}
\label{Методы}
Как вы уже знаете, объекты в INSTEAD могут находиться в состоянии
открыт\slash закрыт и выключен\slash включен. Как это соответствует фразам
диалога?
Для обычных фраз, после активации выбора фраза \emph{закрывается}. При
повторном входе в диалог все фразы \emph{открываются}.
Для фраз с always = true (или true в начале определения) -- такого
закрытия не происходит.
Для фраз с hidden = true (или false в начале определения) -- фраза
будет создана как выключенная. Она не будет видима до тех пор, пока не
будет явно включена.
Для фраз с cond(), каждый раз при просмотре фраз вызывается этот
метод, и в зависимости от возвращаемого значения (true\slash не true) фраза
включается или выключается.
Зная это поведение, вы можете прятать\slash показывать и анализировать фразы
обычными функциями вида: disable \slash enable \slash empty \slash open \slash close \slash
closed \slash disabled и так далее{\ldots}
Однако, делать вы это можете только в самом диалоге, так как все фразы
идентифицируются по тегам. Если вы хотите модифицировать
состояние\slash анализировать фразы из других комнат вы можете:
\begin{itemize}
\item дать фразе имя \{ nam = `имя' \}{\ldots}
\item искать фразу по тегу в другой комнате: local ph = lookup(`\#тег',
`диалог') и потом работать с ней;
\end{itemize}
Что касается функций push\slash pop, то вы можете вызывать их явно как
методы диалога, например:
\begin{verbatim}
_'диалог':push '#новая'
\end{verbatim}
Но лучше это делать в самом диалоге, например, в enter.
Кроме того есть метод :reset, который сбрасывает стек переходов и
устанавливает стартовую фразу, например:
\begin{verbatim}
enter = function(s)
s:reset '#начало'
end
\end{verbatim}
\begin{quote}
Следует отметить, что когда вы делаете enable\slash disable\slash open\slash close
фразы, то вы выполняете действие именно над этой фразой, а не над
фразами включенными внутрь. Но так как при показе фраз движок
остановится на выключенном\slash закрытом объекте-фразе и не войдет
внутрь, этого достаточно.
\end{quote}
\chapter{Специальные объекты}
\label{Специальныеобъекты}
В STEAD3 существуют специальные объекты, которые выполняют
специфические функции. Все такие объекты можно разделить на два
класса:
\begin{enumerate}
\item Системные объекты @;
\item Подстановки \$.
\end{enumerate}
Системные объекты, это объекты, чье имя начинается с символа
`@' или `\$'. Такие объекты обычно создаются в \emph{модулях}. Они не уничтожаются
при смерти игрового мира (например, при подгрузке gamefile, при
загрузке игры из сохранения, и так далее). Примеры объектов: @timer,
@prefs, @snd.
Такие объекты, кроме своих специальных функций, могут быть
использованы по ссылке, без явного помещения объекта в сцену или
инвентарь, но механизм действия таких объектов -- особенный.
\section{Объект `@'}
\label{Объект}
Обычно, вам не нужно работать с такими объектами, но в качестве
примера рассмотрим реализацию `ссылок'.
Пусть мы хотим сделать ссылку, при нажатии на которую мы перейдем в
другую комнату. Конечно, мы могли бы добавить объект в сцену, но стоит
ли это делать в таком простом случае?
Как нам может помочь системный объект?
\begin{verbatim}
obj {
nam = '@walk';
act = function(s, w)
walk(w, false, false)
end;
}
room {
nam = 'main';
title = 'Начало';
decor = [[Начать {@walk старт|приключение}]];
}
\end{verbatim}
При нажатии на ссылку ``приключение'' будет вызван метод act объекта
`@walk' с параметром ``старт''.
На самом деле, в стандартной библиотеке stdlib уже есть объект, с
именем `@', который позволяет делать свои обработчики ссылок следующим
образом:
\begin{verbatim}
xact.walk = walk
room {
nam = 'main';
title = 'Начало';
decor = [[Начать {@ walk старт|приключение}]];
}
\end{verbatim}
Обратите внимание, на пробел после @. Данная запись делает следующее:
\begin{itemize}
\item берет объект `@' (такой объект создан библиотекой stdlib);
\item берет его act;
\item вызывает act с параметрами walk и старт;
\item act объекта `@' смотрит в массив xact;
\item walk определяет метод, который будет вызван из массива xact;
\item старт -- параметр этого метода.
\end{itemize}
Другой пример:
\begin{verbatim}
xact.myprint = function(w)
p (w)
end
room {
nam = 'main';
title = 'Начало';
decor = [[Нажми {@ myprint "hello world"|на кнопку}]];
}
\end{verbatim}
\section{Подстановки}
\label{Подстановки}
Объекты, чье имя начинается на символ `\$' тоже считаются системными
объектами, но работают они по-другому.
Если в выводе текста встречается ``ссылка'' вида:
\begin{verbatim}
{$my a b c|текст}
\end{verbatim}
То происходит следующее:
\begin{enumerate}
\item Берется объект \$my;
\item Берется act объекта \$my;
\item Вызывается act: \_`\$my':(a, b, c, текст);
\item Возвращаемая строка заменяет собой всю конструкцию \{{\ldots}\}.
\end{enumerate}
Таким образом, объекты играют роль подстановки.
Зачем это нужно? Представьте себе, что вы разработали модуль, который
превращает записи формул из текстового вида в графические. Вы пишете
объект \$math который в своем act методе превращает текст в графическое
изображение (спрайт) и возвращает его в текстовый поток. Тогда
пользоваться таким модулем крайне просто, например:
\begin{verbatim}
{$math|(2+3*x)/y^2}
\end{verbatim}
\chapter{Динамические события}
\label{Динамическиесобытия}
Вы можете определять обработчики, которые выполняются каждый раз,
когда время игры увеличивается на 1. Обычно, это имеет смысл для живых
персонажей, или каких-то фоновых процессов игры. Алгоритм шага игры
выглядит примерно так:
- Игрок нажимает на ссылку;
- Реакция `act', `use'', `inv', `tak', осмотр сцены (клик по названию
сцены) или переход в другую сцену;
- Динамические события;
- Вывод нового состояния сцены.
Например, сделаем Барсика живым:
\begin{verbatim}
obj {
nam = 'Барсик';
{ -- не сохранять массив lf
lf = {
[1] = 'Барсик шевелится у меня за пазухой.',
[2] = 'Барсик выглядывает из-за пазухи.',
[3] = 'Барсик мурлычит у меня за пазухой.',
[4] = 'Барсик дрожит у меня за пазухой.',
[5] = 'Я чувствую тепло Барсика у себя за пазухой.',
[6] = 'Барсик высовывает голову из-за пазухи и осматривает местность.',
};
};
life = function(s)
local r = rnd(5);
if r > 2 then -- делать это не всегда
return;
end
r = rnd(#s.lf); -- символ # -- число элементов в массиве
p(s.lf[r]); -- выводим одно из 6 состояний Барсика
end;
....
\end{verbatim}
И вот момент в игре, когда Барсик попадает к нам за пазуху!
\begin{verbatim}
take 'Барсик' -- добавить в инвентарь
lifeon 'Барсик' -- оживить Барсика!
\end{verbatim}
Любой объект (в том числе и сцена) могут иметь свой обработчик `life',
который вызывается каждый такт игры, если объект был добавлен в список
живых объектов с помощью `lifeon'. Не забывайте удалять живые объекты
из списка с помощью `lifeoff', когда они больше не нужны. Это можно
сделать, например, в обработчике `exit', или любым другим способом.
\begin{quote}
Если в вашей игре много ``живых'' объектов, вы можете задавать им
явную позицию в списке, при добавлении. Для этого, воспользуйтесь
вторым числовым параметром (целое неотрицательное число) `lifeon',
чем меньше число, тем выше приоритет. 1 -- самый высокий. Или вы
можете использовать атрибут pri у объекта. Правда, этот атрибут
будет влиять на приоритет объекта в любом списке.
\end{quote}
Если вам нужен фоновый процесс в какой-то комнате, запускайте его в
`enter' и удаляйте в `exit', например:
\begin{verbatim}
room {
nam = 'В подвале';
dsc = [[Тут темно!]];
enter = function(s)
lifeon(s);
end;
exit = function(s)
lifeoff(s);
end;
life = function(s)
if rnd(10) > 8 then
p [[Я слышу какие-то шорохи!]];
-- изредка пугать игрока шорохами
end
end;
way = { 'Дом' };
}
\end{verbatim}
Если вам нужно определить, был ли переход игрока из одной сцены в
другую, воспользуйтесь `player\_moved()'.
\begin{verbatim}
obj {
nam = 'фонарик';
on = false;
life = function(s)
if player_moved() then -- гасить фонарик при переходах
s.on = false
p "Я выключил фонарик."
return
end
end;
...
}
\end{verbatim}
Для отслеживания протекающих во времени событий, используйте `time()'
или вспомогательную переменную-счетчик. Для определения местоположения
игрока -- `here()'. Для определения факта, что объект ``живой'' --
`live()'.
\begin{verbatim}
obj {
nam = 'динамит';
timer = 0;
used = function(s, w)
if w^'спичка' then -- спичка?
if live(s) then
return "Уже горит!"
end
p "Я поджег динамит."
lifeon(s)
return
end
return false -- если не спичка
end;
life = function(s)
s.timer = s.timer + 1
if s.timer == 5 then
lifeoff(s)
if here() == where(s) then
p [[Динамит взорвался рядом со мной!]]
else
p [[Я услышал, как взорвался динамит.]];
end
end
end;
...
}
\end{verbatim}
Если `life' обработчик возвращает текст события, он печатается после
описания сцены.
Вы можете вернуть из обработчика `life' второй код возврата, (`true'
или `false'). Если вы вернете true -- то это будет признаком важного
события, которое выведется до описания объектов сцены, например:
\begin{verbatim}
p 'В комнату вошел охранник.'
return true
\end{verbatim}
Или:
\texttt{
return 'В комнату вошел охранник.', true
}
Если вы вернете false, то цепочка life методов прервется на вас. Это
удобно делать при выполнении walk из метода life, например:
\begin{verbatim}
life = function()
walk 'theend'
return false -- это последний life
end
\end{verbatim}
Если вы хотите блокировать `life' обработчики в какой-то из комнат,
воспользуйтесь модулем `nolife'. Например:
\begin{verbatim}
require "noinv"
require "nolife"
dlg {
nam = 'Охранник';
noinv = true;
nolife = true;
...
}
\end{verbatim}
Отдельно стоит рассмотреть вопрос перехода игрока из `life'
обработчика. Если вы собираетесь использовать функции `walk' внутри
`life', то вам следует учитывать следующее поведение.
Если `life' переносит игрока в новую локацию, то обычно предполагается
что вы:
\begin{enumerate}
\item Очищаете вывод реакций: game:reaction(false);
\item Очищаете вывод живых методов на данный момент: game:events(false,
false)
\item Делаете walk.
\item Останавливаете цепочку life вызовов с помощью return false;
\end{enumerate}
Некоторые моменты требуют пояснений.
game:reaction() -- позволяет взять\slash изменить вывод реакции
пользователя, если задать его в false это означает сбросить реакцию.
game:events() -- позволяет взять\slash изменить вывод life методов. В
качестве параметров принимаются приоритетные и не приоритетные
сообщения, задав false, false мы отменили весь вывод предыдущих life
методов.
В стандартной библиотеке уже есть функция life\_walk(), которая делает
описанные действия. Вам остается только вернуть false.
\chapter{Графика}
\label{Графика}
Графический интерпретатор INSTEAD анализирует атрибут сцены `pic', и
воспринимает его как путь к картинке, например:
\begin{verbatim}
room {
pic = 'gfx/home.png';
nam = 'Дома';
dsc = 'Я у себя дома';
};
\end{verbatim}
\textbf{Важно!}
Используйте в путях только прямые `\slash '. Также, настоятельно
рекомендуется использовать в именах каталогов и файлов только
латинские строчные символы. Этим самым вы обезопасите свою игру от
проблем с совместимостью и она будет работать на всех архитектурных
платформах, куда портирован INSTEAD.
Конечно, `pic' может быть функцией, расширяя возможности разработчика.
Если в текущей сцене не определен атрибут `pic', то берется атрибут
`game.pic'. Если не определен и он, то картинка не отображается.
Поддерживаются все наиболее распространенные форматы изображений, но я
рекомендую вам использовать `png' и (когда важен размер) `jpg'.
Вы можете использовать в качестве картинок анимированные gif файлы.
Вы можете встраивать графические изображения прямо в текст, в том
числе в инвентарь, переходы, заглавия комнат и `dsc' с помощью
функции `fmt.img' (Для этого включите модуль fmt).
Например:
\begin{verbatim}
require "fmt"
obj {
nam = 'яблоко'
disp = 'Яблоко'..fmt.img('img/apple.png');
}
\end{verbatim}
Тем-не менее, картинку сцены всегда следует оформлять в виде `pic'
атрибута, а не вставки `fmt.img' в `dsc' комнаты.
Дело в том, что картинка сцены масштабируется по другому алгоритму.
Картинки `fmt.img' масштабируются в соответствии с настройками INSTEAD
(масштаб темы), а `pic' -- учитывает также размер картинки.
Кроме того, картинки `pic' обладают и другими свойствами, например,
возможностью отслеживания координат кликов мышью.
Если вы поместите `fmt.img' внутрь \{ и \}, то получите графическую ссылку.
\begin{verbatim}
obj {
nam = 'яблоко';
disp = 'яблоко ' ..img('img/apple.png');
dsc = function(s)
p ("На полу лежит {яблоко",fmt.img 'img/apple.png', "}");
-- другие варианты:
-- return "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
-- p "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
-- или dsc = "На полу лежит {яблоко"..fmt.img('img/apple.png').."}";
end;
}
\end{verbatim}
INSTEAD поддерживает обтекание картинок текстом. Если картинка
вставляется с помощью функции `fmt.imgl'\slash `fmt.imgr', она будет
расположена у левого\slash правого края.
\textbf{Важно!}
\begin{quote}
Картинки, вставленные в текст с помощью `fmt.imgl\slash fmt.imgr' не могут быть
ссылками!!! Используйте их только в декоративных целях.
\end{quote}
Для задания отступов вокруг изображения используйте `pad', например:
\begin{verbatim}
fmt.imgl 'pad:16,picture.png' -- отступы по 16 от каждого края
fmt.imgl 'pad:0 16 16 4,picture.png' -- отступы: вверху 0, справа 16, внизу 16, слева 4
fmt.imgl 'pad:0 16,picture.png' -- отступы: вверху 0, справа 16, внизу 0, слева 16
\end{verbatim}
Вы можете использовать псевдо-файлы для изображений прямоугольников и
пустых областей:
\begin{verbatim}
dsc = fmt.img 'blank:32x32'..[[Строка с пустым изображением.]];
dsc = fmt.img 'box:32x32,red,128'..[[Строка красным полупрозрачным квадратом.]];
\end{verbatim}
INSTEAD может обрабатывать составные картинки, например:
\begin{verbatim}
pic = 'gfx/mycat.png;gfx/milk.png@120,25;gfx/fish.png@32,32';
\end{verbatim}
Таким образом, составная картинка представляет собой набор путей к
изображениям, разделенных символом `;'. Вторая и последующие
компоненты могут содержать постфикс в виде
@x\_координата,y\_координата\%, где координате 0,0 соответствует левый
верхний угол всего изображения. Общий размер картинки считается равным
общему размеру первой компоненте составной картинки, то есть, первый
компонент (в нашем примере -- gfx\slash mycat.png) играет роль холста, а
последующие компоненты накладываются на этот холст.
Наложение происходит для левого верхнего угла накладываемой
картинки. Если вам нужно, чтобы наложение происходило относительно
центра накладываемой картинки, используйте перед координатами префикс
``c'', например:
\begin{verbatim}
pic = 'gfx/galaxy.png;gfx/star.png@c128,132';
\end{verbatim}
Оформив в виде функции формирование пути составной картинки, вы можете
генерировать изображение на основе игрового состояния.
Если вы в своей игре привязываетесь к каким-то координатам
изображений, или к их размерам, делайте это относительно оригинальных
размеров изображений. При масштабировании темы под заданное игроком
разрешение, INSTEAD сам будет осуществлять пересчёт координат (при
этом координаты для игры выглядят так, как будто игра запущена без
масштабирования). Однако, возможны небольшие погрешности вычислений.
Если вам не хватает функций, описанных в этой главе, изучите модуль
``sprite'', который предоставляет более широкие возможности по
графическому оформлению. Но я крайне не рекомендую делать это в своей
первой игре.
\chapter{Музыка}
\label{Музыка}
Для работы с музыкой и звуками вам понадобится модуль snd.
\begin{verbatim}
require "snd"
\end{verbatim}
Интерпретатор проигрывает в цикле текущую музыку, которая задается
с помощью функции: `snd.music(путь к музыкальному файлу)'.
\textbf{Важно!}
Используйте в путях только прямые `\slash '. Также, настоятельно
рекомендуется использовать в именах каталогов и файлов только
латинские строчные символы. Этим самым вы обезопасите свою игру от
проблем с совместимостью и она будет работать на всех архитектурных
платформах, куда портирован INSTEAD.
Поддерживается большинство музыкальных форматов, но настоятельно
рекомендуется использовать формат `ogg', так как именно он
поддерживается наилучшим образом во всех версиях INSTEAD (для
различных платформ).
\textbf{Важно!}
Следует проявлять осторожность при использовании трекерной музыки, так
как в некоторых дистрибутивах Linux могут быть проблемы при
проигрывании определенных файлов (ошибки в связке библиотек SDL\_mixer
и libmikmod).
Также, если вы используете `mid' файлы, будьте готовы к тому, что
игрок услышит их только в Windows версии INSTEAD (так как в
большинстве случаев, Unix версии SDL\_mixer собраны без поддержки
``timidity'').
В качестве частоты музыкальных файлов используйте частоты кратные
11025.
\begin{verbatim}
room {
pic = 'gfx/street.png';
enter = function()
snd.music 'mus/rain.ogg'
end;
nam = 'на улице';
dsc = 'На улице идет дождь.';
};
\end{verbatim}
`snd.music()' без параметра возвращает текущее имя трека.
В функцию `snd.music()' можно передавать второй параметр -- количество
проигрываний (циклов). Получить текущий счетчик можно с помощью
`snd.music()' без параметров -- второе возвращаемое значение. 0 --
означает вечный цикл. 1..n -- количество проигрываний. --1 --
проигрывание текущего трека закончено.
Для того, чтобы отменить проигрывание музыки, вы можете использовать
`snd.stop\_music()'
Для того, чтобы узнать, играет ли музыка:
\begin{verbatim}
snd.music_playing()
\end{verbatim}
Вы можете задавать время нарастания и затухания музыки, с помощью вызова:
\begin{verbatim}
snd.music_fading(o, [i])
\end{verbatim}
Здесь o - время в мс. для затухания и i - время в мс. для нарастания
музыки. Если задан только один параметр -- оба времени считаются
одинаковыми. После вызова, установленные параметры будут влиять на
проигрывание всех музыкальных файлов.
Для проигрывания звуков используйте `snd.play()'. Настоятельно
рекомендуется использовать формат `ogg', хотя большинство
распространенных звуковых форматов также будет работать.
Различие между музыкой и звуковым файлом заключается в том, что движок
следит за процессом проигрывания музыки и сохраняет\slash восстанавливает
текущий проигрываемый трек. Выйдя из игры и загрузив ее снова, игрок
услышит то же музыкальное оформление, что слышал при выходе. Звуки
обычно означают кратковременные эффекты, и движок не сохраняет и не
восстанавливает звуковые события. Так, если игрок не успел дослушать
звук выстрела и вышел из игры, после загрузки файла сохранения он не
услышит звук (или его окончание) снова.
Тем не менее, если учесть то, что `snd.play()' позволяет запускать
зацикленные звуки, то различие между музыкой и звуками становится уже
не таким однозначным.
Итак, определение функции: `snd.play(файл, [канал], [цикл])', где:
\begin{itemize}
\item файл -- путь и\textbackslash{}или имя звукового файла;
\item канал -- номер канала [0..7]; Если не указан, то выберется первый свободный.
\item цикл -- количество проигрываний 1..n, 0 -- зацикливание.
\end{itemize}
Для остановки проигрывания звука можно использовать `snd.stop()'. Для
остановки звука в определенном канале `snd.stop(канал)'.
\textbf{Важно!}
\begin{quote}
Если вы используете зацикленные звуки, вам придется самим
восстанавливать их состояние (запускать снова с помощью
`snd.sound()') в функции `start()'
\end{quote}
Например:
\begin{verbatim}
global 'wind_blow' (false)
...
function start()
if wind_blow then
snd.play('snd/wind.ogg', 0)
end
end
\end{verbatim}
Если вам не достаточно описанных здесь функций по работе со звуком,
используйте полное описание модуля ``snd''.
\chapter{Форматирование и оформление вывода}
\label{Форматированиеиоформлениевывода}
Обычно INSTEAD сам занимается форматированием и оформлением
вывода. Например, отделяет статическую сцену от динамической. Выделяет
курсивом действия игрока. Переводит фокус на изменение в тексте и
т.д. Модули вроде ``fmt'' улучшают качество вывода игры без
дополнительных усилий со стороны автора.
Например:
\begin{verbatim}
require 'fmt'
fmt.para = true -- включить отступы параграфов
\end{verbatim}
И ваша игра будет выглядеть гораздо лучше. Если вам нужна какая-то
автоматическая обработка выводимого текста, вы можете включить модуль
``fmt'' и определить функцию `fmt.filter'. Например:
\begin{verbatim}
require "fmt"
fmt.filter = function(s, state)
-- s -- вывод
-- state -- true, если это вывод сцены
return s..'\nЭта строка будет добавлена к выводу';
end
end
\end{verbatim}
Многие хорошие игры на INSTEAD никак не занимаются своим оформлением,
кроме разбиения текста `dsc' на параграфы с помощью символов `\^{}\^{}',
поэтому подумайте, а так ли вам хочется заниматься оформлением своей
игры вручную?
Тем не менее, иногда это все-таки необходимо.
\begin{quote}
Внимание! По умолчанию, все конечные и начальные переводы строк,
пробелы и символы табуляции вырезаются из вывода обработчиков. Так
как обычно они не имеют смысла и даже вредны. В редких случаях,
автору может понадобиться более полный контроль над выводом, тогда он
может задать std.strip\_call как false в init() или start(),
например:
\end{quote}
\begin{verbatim}
std.strip_call = false
obj {
dsc = [[Тут лежит {яблоко}.^^^^]] -- теперь переводы строк
-- не будут вырезаны, хотя это странное желание
}
\end{verbatim}
\begin{quote}
Но обычно такое ручное форматирование свидетельствует о плохом
стиле. Для оформления сцены лучше использовать decor и\slash или
подстановки \$.
\end{quote}
\section{Форматирование}
\label{Форматирование}
Вы можете делать простое форматирование текста с помощью функций:
\begin{itemize}
\item fmt.c(строка) - разместить по центру;
\item fmt.r(строка) - разместить справа;
\item fmt.l(строка) - разместить слева;
\item fmt.top(строка) - сверху строки;
\item fmt.bottom(строка) - снизу строки;
\item fmt.middle(строка) - середина строки (по умолчанию).
\end{itemize}
Например:
\texttt{
room \{
nam = 'main';
title = 'Добро пожаловать';
dsc = fmt.c 'Добро пожаловать!'; -{}- если у функции только 1 параметр,
-{}- скобки можно опускать;
\}
}
Вышеописанные функции влияют не только на текст, но и на изображения,
вставленные с помощью '`fmt.img()'.
Следует отметить, что если вы используете несколько функций
форматирования, то предполагается, что они относятся к разным строкам
(параграфам). В противном случае, результат не определен. Разбивайте
текст на абзацы символами `\^{}' или `pn()'.
INSTEAD при выводе удаляет лишние пробелы. Это значит, что неважно
сколько пробелов вы вставляете между словами, все равно при выводе они
не будут учитываться для расчета расстояния между словами. Иногда это
может стать проблемой.
Вы можете создавать \emph{неразрывные строки} с помощью:
fmt.nb(строка). Например, модуль ``fmt'' использует неразрывные строки
для создания отступов в начале параграфов. Также, `fmt.nb' может
оказаться удобной для вывода служебных символов. Можно сказать, что
вся строка-параметр `fmt.nb' воспринимается движком как одно большое
слово.
Еще один пример. Если вы используете подчеркивание текста, то
промежутки между словами не будут подчеркнуты. При использовании
`fmt.nb' промежутки также будут подчеркнуты.
INSTEAD не поддерживает отображение таблиц, однако для вывода простых
табличных данных можно воспользоваться `fmt.tab()'. Эта функция
используется для абсолютного позиционирования в строке (табулятор).
\begin{verbatim}
fmt.tab(позиция, [центр])
\end{verbatim}
\emph{Позиция}, это текстовый или числовой параметр. Если задан числовой
параметр, он воспринимается как позиция в пикселях. Если он задан в
виде строкового параметра `число\%', то он воспринимается как
позиция, выраженная в процентах от ширины окна вывода сцены.
Необязательный строковой параметр \emph{центр} задает позицию в следующем
за `fmt.tab' слове, которая будет размещена по указанному смещению в
строке. Позиции могут быть следующими:
\begin{itemize}
\item left;
\item right;
\item center.
\end{itemize}
По-умолчанию считается что задан параметр ``left''.
Так, например:
\begin{verbatim}
room {
nam = 'main';
disp = 'Начало';
-- размещение 'Начало!' по центру строки
dsc = fmt.tab('50%', 'center')..'Начало!';
}
\end{verbatim}
Конечно, не очень удачный пример, так как то же самое можно было
сделать с помощью `fmt.c()'. Более удачный пример.
\begin{verbatim}
dsc = function(s)
p(fmt.tab '0%')
p "Слева";
p(fmt.tab '100%', 'right')
p "Справа";
end
\end{verbatim}
На самом деле, единственная ситуация, когда применение `fmt.tab()'
оправдано -- это вывод табличных данных.
Следует отметить, что в ситуации, когда мы пишем что-то вроде:
\begin{verbatim}
-- размещение 'Раз' по центру строки
dsc = fmt.tab('50%', 'center')..'Раз два три!';
\end{verbatim}
Только слово `Раз' будет помещено в центр строки, остальные слова
будут дописаны справа от этого слова. Если вы хотите центрировать `Раз
два три!' как одно целое, воспользуйтесь `fmt.nb()'.
\begin{verbatim}
-- размещение 'Раз два три!' по центру строки
dsc = fmt.tab('50%', 'center')..fmt.nb ('Раз два три!');
\end{verbatim}
В INSTEAD также существует выполнять простое вертикальное
форматирование. Для этого используйте вертикальный табулятор:
\begin{verbatim}
fmt.y(позиция, [центр])
\end{verbatim}
Как и в случае с fmt.tab \emph{позиция}, это текстовый или числовой
параметр. Здесь он воспринимается как позиция строки, выраженная в
пикселях или процентах от высоты области сцены. Например, 100\% --
соответствует нижней границе области сцены. 200\% -- соответствует
нижней границе второй страницы вывода (две высоты области вывода
сцены).
Необязательный строковой параметр \emph{центр} задает позицию внутри
строки, относительно которой выполняется позиционирование:
\begin{itemize}
\item top (по верхнему краю);
\item middle (по центру);
\item bottom (по нижнему краю -- значение по умолчанию).
\end{itemize}
Следует отметить, что `fmt.y' работает целиком для строки. Если в
строке встретится несколько fmt.y, действовать будет последний из
табуляторов.
\begin{verbatim}
-- размещение 'ГЛАВА I' - в центре сцены
dsc = fmt.y('100%').."ГЛАВА I";
\end{verbatim}
\emph{Если позиция, указанная табулятором, уже занята другой строкой, табулятор игнорируется.}
По умолчанию, статическая часть сцены отделяется от динамической двойным переводом строки. Если вам это не подходит, вы можете переопределить `std.scene\_delim', например:
\begin{verbatim}
std.scene_delim = '^' -- одинарный перевод строки
\end{verbatim}
Вы не можете менять эту переменную в обработчиках, так как она не
сохраняется, но вы можете задать ее для игры целиком, или
восстанавливать ее вручную в функции `start()'.
Если вас категорически не устраивает то, как INSTEAD формирует вывод
(последовательность абзацов текста), вы можете переопределить функцию
`game.display()', которая по умолчанию выглядит следующим образом:
\begin{verbatim}
game.display = function(s, state)
local r, l, av, pv
local reaction = s:reaction() or nil -- реакция
r = std.here()
if state then -- такт игры?
reaction = iface:em(reaction) -- курсив
av, pv = s:events()
av = iface:em(av) -- вывод "важных" life
pv = iface:em(pv) -- вывод фоновых life
l = s.player:look() -- objects [and scene] -- объекты и сцена
end
l = std.par(std.scene_delim,
reaction or false, av or false, l or false,
pv or false) or ''
return l
end;
\end{verbatim}
Тот факт, что я привел здесь этот код, не означает, что я рекомендую
переопределять эту функцию. Напротив, я категорически против такой
сильной привязки к форматированию текста. Тем не менее, иногда
возникает ситуация, когда полный контроль за последовательностью
вывода необходим. Если вы пишите свою первую игру, просто пропустите
этот текст.
\section{Оформление}
\label{Оформление}
Вы можете менять начертание текста с помощью комбинаций функций:
\begin{itemize}
\item fmt.b(строка) - жирный текст;
\item fmt.em(строка) - курсив;
\item fmt.u(строка) - подчеркнутый текст;
\item fmt.st(строка) - перечеркнутый текст.
\end{itemize}
Например:
\texttt{
room \{
nam = 'Intro';
title = false;
dsc = function(s)
p ('Вы находитесь в комнате: ')
p (fmt.b(s))
end;
\}
}
\begin{quote}
Используя функции `fmt.u' и `fmt.st' на строках, содержащих пробелы,
вы получите разрывы линий в этих местах. Что избежать этого, можно
превратить текст в \emph{неразрывную строку}:
\end{quote}
\begin{verbatim}
fmt.u(fmt.nb "теперь текст без пропусков" )
\end{verbatim}
Строго говоря, INSTEAD не поддерживает одновременный вывод разными шрифтами в окно сцены (если не считать разное начертание), поэтому если вам все-таки требуется более гибкий контроль вывода, вы можете сделать следующее:
\begin{itemize}
\item Использовать графические вставки `fmt.img()';
\item Использовать модуль `fonts', в котором реализована отрисовка
разными шрифтами за счет модуля `sprite';
\item Использовать другой движок, так как скорее всего вы используете INSTEAD не по назначению.
\end{itemize}
\chapter{Конструкторы и наследование}
\label{Конструкторыинаследование}
\textbf{Внимание!}
Если вы пишите свою первую игру, было бы лучше, если бы она была
простая. Для простой игры информация из этой главы не
понадобится. Более того, 90\% игр на INSTEAD не использует вещей,
описанных в этой главе!
Если вы пишите игру, в которой много однотипных объектов, возможно,
вам захочется упростить их создание. Это можно сделать одним из
следующих способов:
\begin{itemize}
\item Создать свой конструктор;
\item Создать новый класс объектов.
\end{itemize}
\section{Конструкторы}
\label{Конструкторы}
Конструктор -- это функция, которая создает за вас объект и заполняет
его атрибуты так, как вам это нужно. Рассмотрим пример. Допустим, в
вашей игре будет много окон. Нужно создавать окна, любое окно можно
разбить. Мы можем написать конструктор `window'.
\begin{verbatim}
window = function(v)
v.window = true
v.broken = false
if v.dsc == nil then
v.dsc = 'Здесь есть {окно}.'
end
v.act = function(s)
if s.broken then
p [[Окно разбито.]]
else
p [[За окном темно.]]
end
end
if v.used == nil then
v.used = function(s, w)
if w^'молоток' then
if s.broken then
p [[Окно уже разбито.]]
else
p [[Я разбил окно.]]
s.broken = true;
end
return
end
return false
end
end
return obj(v)
end
\end{verbatim}
Как видим, идея конструкторов проста. Вы просто создаете функцию,
которая получает на вход таблицу с атрибутами \{\}, которую конструктор
может дозаполнить нужными атрибутами. Затем эта таблица передается
конструктору obj\slash room\slash dlg и возвращается полученный объект.
Теперь, создавать окна стало легко:
\begin{verbatim}
window {
dsc = [[Тут есть {окно}.]];
}
\end{verbatim}
Или, так как окно это обычно статический объект, можно создавать его
прямо в `obj'.
\begin{verbatim}
obj = { window {
dsc = 'В восточной стене есть {окно}.';
}
};
\end{verbatim}
У нашего окна будет готовый used метод и act метод. Вы можете
проверить тот факт, что объект окно -- просто проверив признак window:
\begin{verbatim}
use = function(s, w)
if w.window then
p [[Действие на окно.]]
return
end
return false
end
\end{verbatim}
Состояние ``разбитости'' окна, это атрибут broken.
Как реализовать наследование в конструкторах?
На самом деле, в примере выше уже используется
наследование. Действительно, ведь конструктор `window'' вызывает
другой конструктор `obj', тем самым получая все свойства обычного
объекта. Также, `window' определяет переменную признак `window',
чтобы в игре мы могли понять, что мы имеем дело с окном. Например:
Для иллюстрации механизма наследования создадим класс объектов
`treasure', те. сокровищ.
\begin{verbatim}
global { score = 0 }
treasure = function()
local v = {}
v.disp = 'сокровище'
v.treasure = true
v.points = 100
v.dsc = function(s)
p ('Здесь есть {', std.dispof(s), '}.')
end;
v.inv = function(s)
p ('Это же ', std.dispof(s), '.');
end;
v.tak = function(s)
score = score + s.points; -- увеличим счет
p [[Дрожащими руками я забрал сокровища.]];
end
return obj(v)
end
\end{verbatim}
А теперь, на его основе создадим золото, алмаз и сундук.
\begin{verbatim}
gold = function(dsc)
local v = treasure();
v.disp = 'золото';
v.gold = true;
v.points = 50;
v.dsc = dsc;
return v
end
diamond = function(dsc)
local v = treasure();
v.disp = 'алмаз';
v.diamond = true;
v.points = 200;
v.dsc = dsc;
return v
end
chest = function(dsc)
local v = treasure();
v.disp = 'сундук';
v.chest = true
v.points = 1000;
v.dsc = dsc;
return v
end
\end{verbatim}
Теперь, в игре можно создавать сокровища через конструкторы:
\begin{verbatim}
diamond1 = diamond("В грязи я заметил {алмаз}.")
diamond2 = diamond(); -- тут будет стандартное описание алмаза
gold1 = gold("В углу я заметил блеск {золота}.");
room {
nam = 'пещера';
obj = {
diamond1,
gold1,
chest("А еще я вижу {сундук}!")
};
}
\end{verbatim}
На самом деле, как именно писать функции-конструкторы и реализовывать
принцип наследования, зависит только от вас. Выберете наиболее простой
и понятный способ.
При написании конструкторов иногда бывает полезным сделать вызов
обработчика так, как это делает INSTEAD. Для этого используется
`std.call(объект, метод, параметры)', при этом эта функция вернет
реакцию атрибута в виде строки. Например, рассмотрим модификацию
`window', которая заключается в том, что можно определять свою
реакцию на осмотр окна, которая будет выполнена после стандартного
сообщения о том, что это разбитое окно (если оно разбито).
\begin{verbatim}
window = function(nam, dsc, what)
local v = {} -- создаем пустую таблицу
-- заполняем ее
v.window = true
v.what = what
v.broken = false
if dsc == nil then
v.dsc = 'Здесь есть {окно}'
end
v.act = function(s)
if s.broken then
p [[Окно разбито.]]
end
local r, v = stead.call(s, 'what')
if v then -- обработчик выполнился?
p(r)
else
p [[За окном темно.]]
end
end
return obj(v)
end
\end{verbatim}
Таким образом, мы можем при создании окна задать третий параметр, в
котором определить функцию или строку, которая будет реакцией во время
осмотра окна. При этом сообщение о том, что окно разбито (если оно
действительно разбито), будет выведено перед этой реакцией.
\section{Класс объектов}
\label{Классобъектов}
Конструкторы объектов широко использовались в STEAD2. В STEAD3
obj\slash dlg\slash room реализованы как классы объектов. Класс объектов удобно
создавать для тех случаев, когда поведение создаваемого объекта не
укладывается в стандартные объекты obj\slash room\slash dlg и вы хотите поменять
методы класса. Изменив метод класса, например, вы можете вообще
изменить то, как выглядит предмет в сцене. В качестве примера,
рассмотрим создание класса ``контейнер''. Контейнер может хранить в себе
другие объекты, быть закрытым и открытым.
\begin{verbatim}
-- create own class container
cont = std.class({ -- создаем класс cont
display = function(s) -- переопределяем метод показа предмета
local d = std.obj.display(s)
if s:closed() or #s.obj == 0 then
return d
end
local c = s.cont or 'Внутри: ' -- описатель содержимого
local empty = true
for i = 1, #s.obj do
local o = s.obj[i]
if o:visible() then
empty = false
if i > 1 then c = c .. ', ' end
c = c..'{'..std.nameof(o)..'|'..std.dispof(o)..'}'
end
end
if empty then
return d
end
c = c .. '.'
return std.par(std.space_delim, d, c)
end;
}, std.obj) -- мы наследуемся от стандартного объекта
\end{verbatim}
После этого, вы можете создавать контейнеры так:
\begin{verbatim}
cont {
nam = 'ящик';
dsc = [[Тут есть {ящик}.]];
cont = 'В ящике: ';
}: with {
'яблоко', 'груша';
}
\end{verbatim}
Когда контейнер будет открыт, вы увидите описание ящика, а также
содержимое ящика в виде строки ссылок: В ящике: яблоко, груша.
dsc объектов яблоко и груша будут скрыты.
К сожалению, подробное описание классов выходит за рамки данного
руководства, эти вещи будут описаны в другом руководстве для
разработчиков модулей. А пока, для вашей первой игры, вам не стоит
писать свои классы объектов.
\chapter{Полезные советы}
\label{Полезныесоветы}
\section{Разбиение на файлы}
\label{Разбиениенафайлы}
Когда ваша игра становится большой, размещение ее кода целиком в `main3.lua' -- плохая идея.
Для разбиения текста игры на файлы вы можете использовать
`include'. Вы должны использовать `include' в глобальном контексте
таким образом, чтобы во время загрузки `main3.lua' загрузились и все
остальные фрагменты игры, например.
\begin{verbatim}
-- main3.lua
include "episode1" -- .lua можно опускать
include "npc"
include "start"
room {
nam = 'main';
....
\end{verbatim}
Как именно разбивать исходный текст на файлы зависит только от вас. Я
использую файлы в соответствии с эпизодами игры (которые обычно слабо
связаны между собой), но можно создавать файлы, хранящие отдельно
комнаты, объекты, диалоги и т.д. Это вопрос личного удобства.
Также есть возможность динамически подгружать части игры (с
возможностью доопределять объекты). Для этого вы
можете воспользоваться функцией `gamefile':
\begin{verbatim}
...
act = function() gamefile ("episode2") end -- .lua можно опускать
...
\end{verbatim}
\begin{quote}
Внимание! Если в вашей игре определена функция init(), то в
подгружаемых частях она также должна присутствовать! В противном
случае, после подгрузки файла, будет вызвана текущая функция init(),
что обычно не является желательным.
\end{quote}
`gamefile()' также позволяет загрузить новый файл и забыть стек
предыдущих загрузок, запустив этот новый файл как самостоятельную
игру. Для этого, задайте второй параметр функции как `true'. Имейте в
виду, что существующие модули остаются и переживают операцию gamefile
в обоих случаях. `gamefile()' можно использовать только в
обработчиках.
\begin{verbatim}
act = function() gamefile ("episode3.lua", true); end;
\end{verbatim}
Во втором варианте `gamefile()' можно использовать для оформления
мультиязычных игр или игр-сборников, где фактически из оболочки
выполняется запуск самостоятельной игры.
\section{Модули}
\label{Модули}
Дополнительная функциональность часто реализована в INSTEAD в виде
модулей. Для использования модуля необходимо
написать:
\begin{verbatim}
require "имя модуля"
\end{verbatim}
Или:
\begin{verbatim}
loadmod "имя модуля"
\end{verbatim}
Если модуль поставляется вместе с игрой.
Часть модулей входит в поставку INSTEAD, но есть и такие, которые вы
можете скачать отдельно и положить в каталог с игрой. Вы можете
заменить любой стандартный модуль своим, если положите его в каталог с
игрой под тем-же именем файла, что и стандартный.
Модуль, это фактически `lua' файл с именем: `имя\_модуля.lua'.
Ниже перечислены основные стандартные модули, с указанием
функциональности, которые они предоставляют.
\begin{itemize}
\item `dbg' — модуль отладки;
\item `click' — модуль перехвата кликов мыши по картинке сцены;
\item `prefs' — модуль настроек (хранилище данных настроек);
\item `snapshots' — модуль поддержки снапшотов (для откатов игровых ситуаций);
\item `fmt' — модуль оформления вывода;
\item `theme' — управление темой на лету;
\item `noinv' - модуль работы с инвентарем;
\item `key'' - модуль обработки событий срабатывания клавиш;
\item `timer' - таймер;
\item `sprite' — модуль для работы со спрайтами;
\item `snd' — модуль работы со звуком;
\item `nolife' – модуль блокировки методов life;
\end{itemize}
Пример загрузки модулей:
\begin{verbatim}
--$Name: Моя игра!$
require "fmt"
require "click"
\end{verbatim}
\section{Меню}
\label{Меню}
Стандартное поведение предмета инвентаря состоит в том, что игрок
должен сделать два щелчка мышью. Это необходимо потому, что каждый
предмет инвентаря может быть использован на другой предмет сцены или
инвентаря. После второго щелчка происходит игровой такт игры. Иногда
такое поведение может быть нежелательным. Возможно, вы захотите
сделать игру в которой игровая механика отличается от классических
INSTEAD игр. Тогда вам может понадобится меню.
Меню -- это элемент инвентаря, который срабатывает на первый клик. При
этом меню может сообщить движку, что действие не является игровым
тактом. Таким образом, используя меню вы можете создать в зоне
инвентаря управление игрой любой сложности. Например, существует
модуль ``proxymenu'', который реализует управление игрой в стиле квестов
на ZX-``Спектрум''. В игре ``Особняк'' свое управление, которое вводит
несколько модификаторов действий, и т.д.
Итак, вы можете делать меню в области инвентаря, определяя объекты с
типом `menu'. При этом, обработчик меню (`act') будет вызван
после одного клика мыши. Если обработчик возвращает false, то
состояние игры не изменяется. Например, реализация кармана:
\begin{verbatim}
menu {
state = false;
nam = 'карман';
disp = function(s)
if s.state then
return fmt.u('карман'); -- подчеркиваем активный карман
end
return 'карман';
end;
gen = function(s)
if s.state then
s:open(); -- показать все предметы в кармане
else
s:close(); -- спрятать все предметы в кармане
end
return s
end;
act = function(s)
s.state = not s.state -- изменить состояние
s:gen(); -- открыть или закрыть карман
end;
}: with {
obj {
nam = 'нож';
inv = 'Это нож';
};
}
function init()
take 'карман':gen()
end
\end{verbatim}
\section{Статус игрока}
\label{Статусигрока}
Иногда возникает желание выводит какой-нибудь статус. Например,
количество игровых очков, состояние героя или, наконец, время
суток. INSTEAD не предоставляет каких-то других областей вывода, кроме
сцены и инвентаря, поэтому, самым простым способом вывода статуса
является вывод его в зону инвентаря.
Ниже представлена реализация статуса игрока в виде текста, который
появляется в инвентаре, но не может быть выбран, то есть, выглядит
просто как текст.
\begin{verbatim}
global {
life = 10;
power = 10;
}
stat { -- stat -- объект "статус"
nam = 'статус';
disp = function(s)
pn ('Жизнь: ', life)
pn ('Сила: ', power)
end
};
function init()
take 'статус'
end
\end{verbatim}
\section{walk из обработчиков onexit и onenter}
\label{walkизобработчиковonexitиonenter}
Вы можете делать `walk' из обработчиков `onenter' и
`onexit'. Например, `path' реализован как комната с обработчиком
`onenter', который переносит игрока в другую комнату.
Рекомендуется возвращать из onexit\slash onenter false в случае, если вы
делаете walk из этих обработчиков.
\section{Кодирование исходного кода игры}
\label{Кодированиеисходногокодаигры}
Если вы не хотите показывать исходный код своих игр, вы можете
закодировать исходный код с помощью параметра командной строки
`-encode':
\begin{verbatim}
sdl-instead -encode <путь к файлу> [выходной путь]
\end{verbatim}
И использовать закодированный файл с помощью обычных include\slash gamefile.
Однако, для этого вы должны написать в начале main3.lua:
\begin{verbatim}
std.dofile = std.doencfile
\end{verbatim}
При этом главный файл `main3.lua' необходимо оставлять открытым. Таким
образом, схема выглядит следующим образом (`game.lua' -- закодированный
файл):
\begin{verbatim}
-- $Name: Моя закрытая игра!$
std.dofile = std.doencfile
include "game"; -- никто не узнает, как ее пройти!
\end{verbatim}
\textbf{Важно!}
\begin{quote}
Не используйте компиляцию игр с помощью `luac', так как `luac' создает
платформозависимый код! Однако, компиляция игр может быть
использована для поиска ошибок в коде.
\end{quote}
\section{Запаковка ресурсов}
\label{Запаковкаресурсов}
Вы можете упаковать ресурсы игры (графику, музыку, темы) в файл
ресурсов `.idf', для этого поместите все ресурсы в каталог `data'
и запустите INSTEAD:
\begin{verbatim}
sdl-instead -idf <путь к data>
\end{verbatim}
При этом, в текущем каталоге должен будет создастся файл
`data.idf'. Поместите его в каталог с игрой. Теперь ресурсы игры в
виде отдельных файлов можно удалить (конечно, оставив себе
оригинальные файлы).
Вы можете запаковать в формат `.idf' всю игру:
\begin{verbatim}
sdl-instead -idf <путь к игре>
\end{verbatim}
Игры в формате `idf' можно запускать как обычные игры `instead' (как
если бы это были каталоги) а также из командной строки:
\begin{verbatim}
sdl-instead game.idf
\end{verbatim}
\section{Переключение между игроками}
\label{Переключениемеждуигроками}
Вы можете создать игру с несколькими персонажами и время от времени
переключаться между ними (см. `change\_pl()'). Но вы можете также
использовать этот трюк для того, чтобы иметь возможность переключаться
между разными типами инвентаря.
\section{Использование параметров обработчика}
\label{Использованиепараметровобработчика}
Пример кода.
\begin{verbatim}
obj {
nam = 'камень';
dsc = 'На краю лежит {камень}.';
act = function()
remove 'камень';
p 'Я толкнул камень, он сорвался и улетел вниз...';
end
\end{verbatim}
Обработчик act мог бы выглядеть проще:
\begin{verbatim}
act = function(s)
remove(s);
p 'Я толкнул камень, он сорвался и улетел вниз...';
end
\end{verbatim}
\section{Таймер}
\label{Таймер}
Для асинхронных событий, привязанных к реальному времени, в INSTEAD
есть возможность использовать таймер. На самом деле, вам следует
хорошо подумать, стоит ли в приключенческой игре использовать
таймер. Обычно, игроком это воспринимается не слишком благосклонно. С
другой стороны, таймер вполне можно использовать для управления
музыкой или в оформительских целях.
Для использования таймера, вам следует подключить модуль ``timer''.
\begin{verbatim}
require "timer"
\end{verbatim}
Таймер программируется с помощью объекта `timer'.
\begin{itemize}
\item timer:set(мс) -- задать интервал таймера в миллисекундах;
\item timer:stop() -- остановить таймер.
\end{itemize}
При срабатывании таймера, вызывается обработчик game.timer. Если
game.timer возвращает false, сцена не перерисовывается. В
противном случае, возвращаемое значение выводится как реакция.
Вы можете делать локальные для комнаты обработчики `timer'. Если в
комнате объявлен обработчик `timer', он вызывается вместо
`game.timer'. Если он возвращает false -- вызывается game.timer.
Например:
\begin{verbatim}
game.timer = function(s)
if time() > 10 then
return false
end
snd.play 'gfx/beep.ogg';
p ("Timer:", time())
end
function init()
timer:set(1000) -- раз в секунду
end
\end{verbatim}
\begin{verbatim}
room {
enter = function(s)
timer:set(1000);
end;
timer = function(s)
timer:stop();
walk 'комната2';
end;
nam = 'Проверка таймера';
dsc = [[Ждите.]];
}
\end{verbatim}
Состояние таймера попадает в файл сохранения, таким образом, вам не
нужно заботиться о его восстановлении.
Вы можете вернуть из таймера специальный статус:
\begin{verbatim}
return true, false
\end{verbatim}
В этом режиме перерисуется только область инвентаря. Это можно
использовать для статусов вроде часов.
Кроме того, в INSTEAD существует возможность отслеживать интервалы
времени в миллисекундах. Для этого используйте функцию
instead.ticks(). Функция возвращает число миллисекунд, прошедшее с
момента старта игры.
\section{Музыкальный плеер}
\label{Музыкальныйплеер}
Вы можете написать для игры свой проигрыватель музыки, создав его на основе живого объекта, например:
\begin{verbatim}
-- играет треки в случайном порядке
require "snd"
obj {
{
tracks = {"mus/astro2.mod",
"mus/aws_chas.xm",
"mus/dmageofd.xm",
"mus/doomsday.s3m"};
};
nam = 'плеер';
life = function(s)
if not snd.music_playing() then
local n = s.tracks[rnd(#s.tracks)]
snd.music(n, 1);
end
end;
}:lifeon();
\end{verbatim}
Ниже приводится пример более сложного плеера. Меняем трек только если
он закончился или прошло более 2 минут и игрок перешел из комнаты в
комнату. В каждом треке можно указать число проигрываний (0 -
зацикленный трек):
\begin{verbatim}
require "timer"
global { track_time = 0 };
obj {
nam = 'player';
pos = 0;
{
playlist = { '01 Frozen sun.ogg', 0,
'02 Thinking.ogg', 0,
'03 Melancholy.ogg', 0,
'04 Everyday happiness.ogg', 0,
'10 Good morning again.ogg', 1,
'15 [Bonus track] The end (demo cover).ogg', 1
};
};
tick = function(s)
if snd.music_playing() and ( track_time < 120 or not player_moved() ) then
return
end
track_time = 0
if s.pos == 0 then
s.pos = 1
else
s.pos = s.pos + 2
end
if s.pos > #s.playlist then
s.pos = 1
end
snd.music('mus/'..s.playlist[s.pos], s.playlist[s.pos + 1]);
end;
}
game.timer = function(s)
track_time = track_time + 1
music_player:tick();
end
function init()
timer:set(1000)
end
\end{verbatim}
\section{Живые объекты}
\label{Живыеобъекты}
Если вашему герою нужен друг, одним из способов может стать метод
`life' этого персонажа, который всегда переносит объект в локацию
игрока:
\begin{verbatim}
obj {
nam = 'лошадь';
dsc = 'Рядом со мной стоит {лошадь}.';
act = [[Моя лошадка.]];
life = function(s)
if player_moved() then
place(s);
end
end;
}
function init()
lifeon 'лошадь'; -- сразу оживим лошадь
end
\end{verbatim}
\section{Клавиатура}
\label{Клавиатура}
Вы можете перехватывать события клавиатуры с помощью модуля ``keys''.
Обычно, перехват клавиш имеет смысл использовать для организации текстового ввода.
За описанием, обращайтесь к документации модуля ``keys''.
Ниже приводится простой пример:
\begin{verbatim}
require "keys"
function keys:filter(press, key)
return press -- ловим все нажатия
end
game.onkey = function(s, press, key)
dprint("pressed: ", key)
return false
end
\end{verbatim}
Еще пример:
\begin{verbatim}
require "keys"
game.timer = function(s) -- показываем состояние клавиши курсор вправо
dprint("state of 'right' key: ", keys:state 'right')
end
timer:set(30)
\end{verbatim}
\section{Мышь}
\label{Мышь}
Вы можете отслеживать в своей игре клики по картинке сцены, а также по
фону. Для этого, воспользуйтесь модулем ``click''. Также, вы можете
отслеживать состояние мыши с помощью функции:
\begin{verbatim}
instead.mouse_pos([x, y])
\end{verbatim}
Которая возвращает координаты курсора. Если задать параметры (x, y),
то можно переместить курсор в указанную позицию (все координаты
рассчитываются относительно левого верхнего угла окна INSTEAD).
\begin{verbatim}
require "click"
function click:filter(press, btn, x, y, px, py)
dprint(press, btn, x, y, px, py)
return press and px -- ловим только нажатия на картинку
end
room {
nam = 'main';
pic = "box:320x200,red";
onclick = function(s, press, btn, x, y, px, py)
pn("Вы нажали на картинку: ", px, ", ", py)
pn("Абсолютные координаты: ", x, ", ", y)
p("Кнопка: ", btn)
end;
}
\end{verbatim}
\section{Вызов меню}
\label{Вызовменю}
Вы можете вызвать из игры меню INSTEAD с помощью функции
`istead.menu()'. Если в качестве параметра задать: `save', `load' или
`quit', то будет вызван соответствующий подраздел меню.
\section{Динамическое создание объектов}
\label{Динамическоесозданиеобъектов}
Обычные объекты и комнаты нельзя создавать ``на лету''. Обычно вы
создаете их в глобальном пространстве lua файла. Однако, существуют
игры в которых количество объектов неизвестно заранее, или количество
объектов велико и они добавляются по ходу игры.
В INSTEAD существует способ создания любого объекта на лету. Для этого
вам понадобится написать \emph{конструктор} вашего объекта и
воспользоваться функцией `new'.
Конструктор должен быть декларирован.
Итак, вы можете использовать функции `new' и `delete' для создания
и удаления динамических объектов. Примеры:
\begin{verbatim}
declare 'box' (function()
return obj {
dsc = [[Тут лежит {коробка}.]];
tak = [[Я взял коробку.]];
}
end)
local o = new (box);
take(o);
\end{verbatim}
\begin{verbatim}
declare 'box' (function(dsc)
return obj {
dsc = dsc;
tak = [[Я взял коробку.]];
}
end)
take(new(box, 'В углу стоит {коробка}'))
\end{verbatim}
`new' воспринимает первый аргумент -- как задекларированную
функцию-конструктор, а все остальные параметры -- как аргументы
конструктору. Результатом выполнения конструктора должен быть
объект.
\begin{verbatim}
function myconstructor()
local v = {}
v.disp = 'тестовый объект'
v.act = 'Тестовая реакция'
return obj(v)
end
\end{verbatim}
Если вы хотите уничтожить объект по его имени или ссылке-переменной,
воспользуйтесь:
\begin{verbatim}
purge(o) -- удалить из всех списков
delete(o) -- освободить объект
\end{verbatim}
При этом, delete это именно удаление объекта из INSTEAD, а не аналог
remove() или purge(). Обычно, нет особого смысла делать delete. Только
если предмет больше никогда не понадобится в игре, или вы собираетесь
пересоздать объект с тем же именем, имеет смысл освободить его с
помощью delete().
Более практически-полезный пример:
\begin{verbatim}
-- декларируем существующую
-- функцию
declare 'path' (path)
-- динамический переход
-- создали новый объект
-- и положили его в ways()
put( new (path, { 'переход', 'комната2'}, ways())
\end{verbatim}
\section{Запрет на сохранение игры}
\label{Запретнасохранениеигры}
Иногда может понадобиться запретить игроку делать сохранения в
игре. Например, если речь идет о сценах, где важный элемент составляет
случай, или для коротких игр, в которых проигрыш должен быть фатальным
и требовать перезапуска игры.
Для управлением функции сохранения используется атрибут `instead.nosave'.
Например:
instead.nosave = true -- запретить сохранения
Если вы хотите запрещать сохранения не везде, а в некоторых сценах,
оформите `instead.nosave' в виде функции, или же меняйте состояние
атрибута на лету -- он попадает в файл сохранений.
\begin{verbatim}
-- запретить
-- сохранения в комнатах, которые содержат атрибут nosave.
instead.nosave = function()
return here().nosave
end
\end{verbatim}
Следует отметить, что запрет на сохранения не означает запрета на
автосохранение. Для управления автосохранением воспользуйтесь
аналогичным атрибутом `instead.noautosave'.
\begin{quote}
Вы можете явно сохранять игру с помощью вызова:
`instead.autosave([номер слота])'; Если номер слота не задан, то
игра будет сохранена под слотом `автосохранение'. Имейте в виду, что
сохраняется состояние \textbf{после} завершение текущего такта игры.
\end{quote}
\section{Определение типа объекта}
\label{Определениетипаобъекта}
В INSTEAD существует два способа определить тип объекта.
Первый - с помощью функции std.is\_obj(переменная, [тип]).
Например:
```
a = room \{
nam = `объект';
\};
dprint(std.is\_obj(a)) -- выведет true
dprint(std.is\_obj(`объект')) -- выведет false
dprint(std.is\_obj(a, `room')) -- выведет true
dprint(std.is\_obj(a.obj, `list')) -- выведет true
```
std.is\_obj() удобная для определения типа пременной или аргумента
функции.
Второй способ связан с использованием метода type:
\begin{verbatim}
a = room {
nam = 'объект';
};
dprint(a:type 'room') -- выведет true
\end{verbatim}
\chapter{Темы для sdl-instead}
\label{Темыдляsdl-instead}
Графический интерпретатор поддерживает механизм тем. \emph{Тема}
представляет из себя каталог, с файлом `theme.ini' внутри.
Тема, которая является минимально необходимой -- это тема
`default'. Эта тема всегда загружается первой. Все остальные темы
наследуются от нее и могут частично или полностью заменять ее
параметры. Выбор темы осуществляется пользователем через меню
настроек, однако конкретная игра может содержать собственную тему и
таким образом влиять на свой внешний вид. В этом случае в каталоге с
игрой должен находиться свой файл `theme.ini'. Тем не менее,
пользователь свободен отключить данный механизм, при этом
интерпретатор будет предупреждать о нарушении творческого замысла
автора игры.
Синтаксис `theme.ini' очень прост.
\begin{verbatim}
<параметр> = <значение>
\end{verbatim}
или
\begin{verbatim}
; комментарий
\end{verbatim}
Значения могут быть следующих типов: строка, цвет, число.
Цвет задается в форме \#rgb, где r g и b компоненты цвета в
шестнадцатеричном виде. Кроме того некоторые основные цвета
распознаются по своим именам. Например: yellowgreen, или
violet.
Параметры могут принимать значения:
\begin{itemize}
\item scr.w = ширина игрового пространства в пикселях (число)
\item scr.h = высота игрового пространства в пикселях (число)
\item scr.col.bg = цвет фона
\item scr.gfx.scalable = [0\textbar{}1\textbar{}2] (0 - не масштабируемая тема, 1 -
масштабируемая, 2 - кратно-масштабируемая), начиная с версии 2.2.0
доступны дополнительно [4\textbar{}5\textbar{}6]: 4 - полностью не масштабируемая (с
не масштабируемыми шрифтами), 5 - масштабируемая, с не
масштабируемыми шрифтами, 6 - кратно-масштабируемая, с не
масштабируемыми шрифтами
\item scr.gfx.bg = путь к картинке фонового изображения (строка)
\item scr.gfx.cursor.x = x координата центра курсора (число)
\item scr.gfx.cursor.y = y координата центра курсора (число)
\item scr.gfx.cursor.normal = путь к картинке-курсору (строка)
\item scr.gfx.cursor.use = путь к картинке-курсору режима использования (строка)
\item scr.gfx.use = путь к картинке-индикатору режима использования (строка)
\item scr.gfx.pad = размер отступов к скролл-барам и краям меню (число)
\item scr.gfx.x, scr.gfx.y, scr.gfx.w, scr.gfx.h = координаты, ширина и
высота окна изображений. Области в которой располагается картинка
сцены. Интерпретация зависит от режима расположения (числа)
\item win.gfx.h - синоним scr.gfx.h (для совместимости)
\item scr.gfx.icon = пусть к файлу-иконке игры (ОС зависимая опция, может
работать некорректно в некоторых случаях)
\item scr.gfx.mode = режим расположения (строка fixed, embedded или
float). Задает режим изображения. embedded -- картинка является
частью содержимого главного окна, параметры scr.gfx.x, scr.gfx.y,
scr.gfx.w игнорируются. float -- картинка расположена по указанным
координатам (scr.gfx.x, scr.gfx.y) и масштабируется к размеру
scr.gfx.w x scr.gfx.h если превышает его. fixed -- картинка является
частью сцены как в режиме embedded, но не скроллируется вместе с
текстом а расположена непосредственно над ним. Доступны модификации
режима float с модификаторами `left\slash right\slash center\slash middle\slash bottom\slash top',
указывающими как именно размещать картинку в области
scr.gfx. Например: float-top-left;
\item win.scroll.mode = [0\textbar{}1\textbar{}2\textbar{}3] режим прокрутки области сцены. 0 - нет
автоматической прокрутки, 1 - прокрутка на изменение в тексте, 2
прокрутка на изменение, только если изменение не видно, 3 - всегда
в конец;
\item win.x, win.y, win.w, win.h = координаты, ширина и высота главного
окна. Области в которой располагается описание сцены (числа)
\item win.fnt.name = путь к файлу-шрифту (строка). Здесь и далее, шрифт
может содержать описание всех начертаний, например:
\{sans,sans-b,sans-i,sans-bi\}.ttf (заданы начертания для regular,
bold, italic и bold-italic). Вы можете опускать какие-то начертания,
и движок сам сгенерирует их на основе обычного начертания, например:
\{sans,,sans-i\}.ttf (заданы только regular и italic);
\item win.align = center\slash left\slash right\slash justify (выравнивание текста в окне
сцены);
\item win.fnt.size = размер шрифта главного окна (размер)
\item win.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
\item win.gfx.up, win.gfx.down = пути к файлам-изображениям скорллеров
вверх\slash вниз для главного окна (строка)
\item win.up.x, win.up.y, win.down.x, win.down.y = координаты скроллеров
(координата или --1)
\item win.col.fg = цвет текста главного окна (цвет)
\item win.col.link = цвет ссылок главного окна (цвет)
\item win.col.alink = цвет активных ссылок главного окна (цвет)
\item win.ways.mode = top\slash bottom (задать расположение списка переходов, по
умолчанию top -- сверху сцены)
\item inv.x, inv.y, inv.w, inv.h = координаты, высота и ширина области
инвентаря. (числа)
\item inv.mode = строка режима инвентаря (horizontal или vertical). В
горизонтальном режиме инвентаря в одной строке могут быть несколько
предметов. В вертикальном режиме, в каждой строке инвентаря содержится
только один предмет. (число) Существует модификации
(-left\slash right\slash center). Вы можете задать режим disabled если в вашей
игре не нужен инвентарь;
\item inv.col.fg = цвет текста инвентаря (цвет)
\item inv.col.link = цвет ссылок инвентаря (цвет)
\item inv.col.alink = цвет активных ссылок инвентаря (цвет)
\item inv.fnt.name = путь к файлу-шрифту инвентаря (строка)
\item inv.fnt.size = размер шрифта инвентаря (размер)
\item inv.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
\item inv.gfx.up, inv.gfx.down = пути к файлам-изображениям скорллеров
вверх\slash вниз для инвентаря (строка)
\item inv.up.x, inv.up.y, inv.down.x, inv.down.y = координаты скроллеров
(координата или --1)
\item menu.col.bg = фон меню (цвет)
\item menu.col.fg = цвет текста меню (цвет)
\item menu.col.link = цвет ссылок меню (цвет)
\item menu.col.alink = цвет активных ссылок меню (цвет)
\item menu.col.alpha = прозрачность меню 0--255 (число)
\item menu.col.border = цвет бордюра меню (цвет)
\item menu.bw = толщина бордюра меню (число)
\item menu.fnt.name = путь к файлу-шрифту меню (строка)
\item menu.fnt.size = размер шрифта меню (размер)
\item menu.fnt.height = междустрочный интервал как число с плавающей
запятой (1.0 по умолчанию)
\item menu.gfx.button = путь к файлу изображению значка меню (строка)
\item menu.button.x, menu.button.y = координаты кнопки меню (числа)
\item snd.click = путь к звуковому файлу щелчка (строка)
\item include = имя темы (последний компонент в пути каталога) (строка)
\end{itemize}
Кроме того, заголовок темы может включать в себя комментарии с
тегами. На данный момент существует только один тег: \$Name:,
содержащий UTF--8 строку с именем темы. Например:
\begin{verbatim}
; $Name:Новая тема$
; модификация темы book
include = book -- использовать тему Книга
scr.gfx.h = 500 -- заменить в ней один параметр
\end{verbatim}
\begin{quote}
Интерпретатор выполняет поиск тем в каталоге themes. Unix версия
кроме этого каталога, просматривает также каталог \ensuremath{\sim}\slash .instead\slash themes\slash
Windows версия -- Documents and Settings\slash USER\slash Local
Settings\slash Application Data\slash instead\slash themes
\end{quote}
Кроме этого, новые версии INSTEAD поддерживают механизм множественных
тем в одной игре. Давая возможность игроку через стандартное меню
INSTEAD выбрать подходящее оформление, из предусмотренных автором
игры. Для этого, все темы должны располагаться в игре в подкаталоге
themes. В свою очередь, каждая тема -- это подкаталог в каталоге
themes. В каждом таком подкаталоге должен находится свой файл
theme.ini и ресурсы темы (картинки, шрифты, звуки). При этом
обязательно наличие темы-каталога themes\slash default - эта тема будет
загружена по умолчанию. Формат файлов theme.ini мы только что
рассмотрели. Однако, пути к файлам с ресурсами в theme.ini пишутся не
относительно корневого каталога игры, а относительно текущего каталога
темы. Это означает, что обычно они содержат только имя самого файла,
без пути к каталогу. Например:
\begin{verbatim}
mygame/
themes/
default/
theme.ini
bg.png
widescreen/
theme.ini
main3.lua
\end{verbatim}
theme.ini
\begin{verbatim}
scr.gfx.bg = bg.png
; ...
\end{verbatim}
При этом, все игровые темы наследуются от темы
themes\slash default. Поддерживается механизм include. При этом, INSTEAD
сначала пытается найти одноименную тему игры, и если такой темы не
находится, будет загружена тема из стандартных тем INSTEAD (если она
существует). Далее, в theme.ini можно изменять только те параметры,
которые требуют изменения.
\chapter{Модуль theme}
\label{Модульtheme}
Модуль theme позволяет изменять параметры темы на лету.
\begin{quote}
Имейте в виду, что изменение параметров темы на лету для игр,
которые не содержат собственную тему -- источник потенциальных
проблем! Дело в том, что ваша игра в таком случае должна быть готова
работать с любыми разрешениями и параметрами тем, что крайне сложно
добиться. Поэтому, если вы собираетесь менять параметры темы из кода
-- создайте свою тему и включите ее в игру!
\end{quote}
При этом, сохранение изменений темы в файле сохранения не
поддерживается. Автор игры должен сам восстановить параметры темы в
функции start(), как это делается при работе с модулем спрайтов.
Для изменения параметров действующей темы, используются следующие
функции:
\begin{verbatim}
-- настройка окна вывода
theme.win.geom(x, y, w, h)
theme.win.color(fg, link, alink)
theme.win.font(name, size, height)
theme.win.gfx.up(pic, x, y)
theme.win.gfx.down(pic, x, y)
-- настройка инвентаря
theme.inv.geom(x, y, w, h)
theme.inv.color(fg, link, alink)
theme.inv.font(name, size, height)
theme.inv.gfx.up(pic, x, y)
theme.inv.gfx.down(pic, x, y)
theme.inv.mode(mode)
-- настройка меню
theme.menu.bw(w)
theme.menu.color(fg, link, alink)
theme.menu.font(name, size, height)
theme.menu.gfx.button(pic, x, y)
-- настройка графики
theme.gfx.cursor(norm, use, x, y)
theme.gfx.mode(mode)
theme.gfx.pad(pad)
theme.gfx.bg(bg)
-- настройка звука
theme.snd.click(name);
\end{verbatim}
Существует возможность чтения текущих параметров тем:
\begin{verbatim}
theme.get 'имя переменной темы';
\end{verbatim}
Возвращаемое значение всегда в текстовой форме.
\begin{verbatim}
theme.set ('имя переменной темы', значение);
\end{verbatim}
Вы можете сбросить значение параметра темы на то, которое было
установлено во встроенной теме игры:
\begin{verbatim}
theme.reset 'имя переменной';
theme.win.reset();
\end{verbatim}
Существует функция, для того, чтобы узнать текущую выбранную тему.
\begin{verbatim}
theme.name()
\end{verbatim}
Функция возвращает строку -- имя каталога темы. Если игра использует
собственный файл `theme.ini', функция вернет точку. Это удобно, для
определения того, включен ли механизм собственных тем игр:
\begin{verbatim}
if theme.name() ~= '.' then
error "Please, enable own theme mode in menu!"
end
\end{verbatim}
Если в игре используется механизм множественных тем, то имя темы
начинается с точки, например:
\begin{verbatim}
if theme.name() ~= '.default' then
-- наша встроенная тема default
elseif theme.name() ~= 'default' then
-- стандартная тема default в INSTEAD
end
\end{verbatim}
Пример использования:
\begin{verbatim}
theme.gfx.bg "dramatic_bg.png";
theme.win.geom (0,0, theme.get 'scr.w', theme.get 'scr.h');
theme.inv.mode 'disabled'
\end{verbatim}
\chapter{Модуль sprite}
\label{Модульsprite}
Модуль sprite позволяет работать с графическими изображениями.
Для включения модуля напишите:
\begin{verbatim}
require "sprite"
\end{verbatim}
Спрайты не могут попасть в файл сохранения, поэтому восстановление
состояния спрайтов -- задача автора игры. Обычно, для этого
используются функции init() и\slash или start(). start() вызывается после
загрузки игры, поэтому в этой функции вы можете использовать
переменные игры.
На самом деле в модуле спрайт реализованы два модуля: спрайты и
пиксели. Но так как они используются совместно, они размещены в одном
модуле. Начнем со спрайтов:
\section{Спрайты}
\label{Спрайты}
Для создания спрайта используйте метод sprite.new, например:
\begin{verbatim}
declare 'my_spr' (sprite.new 'gfx/bird.png')
local heart = sprite.new 'heart.png'
local blank = sprite.new (320, 200) -- пустой спрайт 320x200
\end{verbatim}
У созданного спрайтового объекта существуют следующие методы:
\begin{itemize}
\item :alpha(alpha) - создает новый спрайт с заданной прозрачностью alpha
(255 - не прозрачно);
\item :dup(spr) - создает копию спрайта;
\item :scale(spr, xs, ys, [smooth]) -- масштабирует спрайт, для отражений
используйте масштаб --1.0 (медленно! не для реального времени). Создает
новый спрайт.
\item :rotate(angle, [smooth]) -- поворот спрайта на заданный угол в
градусах (медленно! не для реального времени). Создает новый спрайт.
\item :size() -- Возвращает ширину и высоту спрайта в пикселях.
\item :draw(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Рисование
области src спрайта в область dst спрайта (задание alpha сильно
замедляет выполнение функции).
\item :draw(dst\_spr, x, y, [alpha]) -- Рисование спрайта, укороченный
вариант; (задание alpha сильно замедляет выполнение функции).
\item :copy(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Копирование
прямоугольника fw-на-fh из спрайта в спрайт
dst\_spr по координатам [x,y] (рисование - замещение). Существует
укороченный вариант (как :draw).
\item :compose(fx, fy, fw, fh, dst\_spr, x, y, [alpha]) -- Рисование - с
учетом прозрачности обоих спрайтов). Существует укороченный вариант
(как :draw).
\item :fill(x, y, w, h, [col]) -- Заполнение спрайта цветом.
\item :fill([col]) -- Заполнение спрайта цветом.
\item :pixel(x, y, col, [alpha]) -- Заполнение пикселя спрайта.
\item :pixel(spr, x, y) -- Взятие пикселя спрайта (возвращает четыре
компонента цвета).
\item :colorkey(spr, color) -- Задает в спрайте цвет, который выступает в
роли прозрачного фона. При этом, при последующем выполнении операции
:copy, из рассматриваемого спрайта будут скопированы только те
пиксели, цвет которых не совпадает с цветом прозрачного фона.
\end{itemize}
В качестве ``цвета'' методы получают строки вида: `green', `red',
`yellow' или `\#333333', `\#2d80ff'{\ldots}
Пример:
\begin{verbatim}
local spr = sprite.new(320, 200)
spr:fill 'blue'
local spr2 = sprite.new 'fish.png'
spr2:draw(spr, 0, 0)
\end{verbatim}
Кроме того, существует возможность работы с шрифтами. Шрифт создается
с помощью sprite.fnt(), например:
\begin{verbatim}
local font = sprite.fnt('sans.ttf', 32)
\end{verbatim}
У созданного объекта определены следующие методы:
\begin{itemize}
\item :height() -- высота шрифта в пикселях;
\item :text(text, col, [style]) -- создание спрайта из текста, col - здесь
и далее - цвет в текстовом формате (в формате `\#rrggbb' или
`текстовое название цвета').
\item :size(text) -- вычисляет размер, который будет занимать текстовый
спрайт, без создания спрайта.
\end{itemize}
Вам также может пригодиться функция:
\begin{verbatim}
sprite.font_scaled_size(size)
\end{verbatim}
Она возвращает размер шрифта с учетом масштабирование, которое
выставил игрок в настройках INSTEAD. Если вы в своей игре хотите
учитывать такую настройку, используйте эту функцию для определения
размера шрифта.
Пример:
\texttt{
local f = sprite.fnt('sans.ttf', 32)
local spr = sprite.new('box:320x200,black')
f:text("HELLO!", 'white'):draw(spr, 0, 0)
}
Теперь, рассмотрим варианты применения модуля sprite.
\section{Функция pic}
\label{Функцияpic}
Функция pic может вернуть спрайт. Вы можете формировать каждый раз
новый спрайт (что будет не очень эффективно), или можете возвращать
заранее выделенный спрайт. Если в такой спрайт вносятся изменения, то
эти изменения будут отражены в следующем кадре игры. Так, меняя спрайт
по таймеру, можно делать анимацию:
\begin{verbatim}
require "sprite"
require "timer"
local spr = sprite.new(320, 200)
function game:timer()
local col = { 'red', 'green', 'blue'}
col = col[rnd(3)]
spr:fill(col)
return false
end
game.pic = spr
function start()
timer:set(30)
end
room {
nam = 'main';
decor = [[ГИПНОЗ!]];
}
\end{verbatim}
\section{Подстановки}
\label{Подстановки}
Вы можете создать свой системный объект - подстановку, и формировать
графику в выводе игры с помощью img, например:
\begin{verbatim}
require "sprite"
require "timer"
require "fmt"
obj {
nam = '$spr';
{
["квадрат"] = sprite.new 'box:32x32,red';
};
act = function(s, w)
return fmt.img(s[w])
end
}
room {
nam = 'main';
decor = [[Сейчас мы вставим спрайт: {$spr|квадрат}.]];
}
\end{verbatim}
\section{direct режим}
\label{directрежим}
В INSTEAD существует режим прямого доступа к графике. В теме он
задается с помощью параметра:
\begin{verbatim}
scr.gfx.mode = direct
\end{verbatim}
Этот параметр можно заранее выставить в theme.ini, или воспользоваться
модулем theme. Или, специальной функцией:
\begin{verbatim}
sprite.direct(true)
\end{verbatim}
Если режим удалось включить -- функция вернет true. sprite.direct()
без параметра -- возвращает текущий режим (true -- если direct
включен.)
В этом режиме игра имеет прямой доступ ко всему окну и может выполнять
отрисовку в процедуре таймера. Экран представлен специальным спрайтом:
\begin{verbatim}
sprite.scr()
\end{verbatim}
Например:
\begin{verbatim}
require "sprite"
require "timer"
require "theme"
sprite.direct(true)
local stars = {}
local w, h
local colors = {
"red",
"green",
"blue",
"white",
"yellow",
"cyan",
"gray",
"#002233",
}
function game:timer()
local scr = sprite.scr()
scr:fill 'black'
for i = 1, #stars do
local s = stars[i]
scr:pixel(s.x, s.y, colors[s.dy])
s.y = s.y + s.dy
if s.y >= h then
s.y = 0
s.x = rnd(w) - 1
s.dy = rnd(8)
end
end
end
function start()
w, h = theme.get 'scr.w', theme.get 'scr.h'
w = std.tonum(w)
h = std.tonum(h)
for i = 1, 100 do
table.insert(stars, { x = rnd(w) - 1, y = rnd(h) - 1, dy = rnd(8) })
end
timer:set(30)
end
\end{verbatim}
Еще один пример:
\begin{verbatim}
require "timer"
require "sprite"
require "theme"
local spr = sprite
declare {
fnt = false, ball = false, ballw = 0,
ballh = 0, bg = false, line = false,
G = false, by = false, bv = false,
bx = false, t1 = false,
}
function init()
fnt = spr.fnt(theme.get 'win.fnt.name', 32);
ball = fnt:text("INSTEAD 3.0", 'white', 1);
ballw, ballh = ball:size();
bg = spr.new 'box:640x480,black';
line = spr.new 'box:320x8,lightblue';
spr.direct(true)
end
function start()
timer:set(20)
G = 9.81
by = -ballh
bv = 0
bx = 320
t1 = instead.ticks()
end
function phys()
local t = timer:get() / 1000;
bv = bv + G * t;
by = by + bv * t;
if by > 400 then
bv = - bv
end
end
function game:timer(s)
local i
for i = 1, 10 do
phys()
end
if instead.ticks() - t1 >= 20 then
bg:copy(spr.scr(), 0, 0);
ball:draw(spr.scr(), (640 - ballw) / 2, by - ballh/2);
line:draw(spr.scr(), 320/2, 400 + ballh / 2);
t1 = instead.ticks()
end
end
\end{verbatim}
\section{Использование sprite совместно с модулем theme}
\label{Использованиеspriteсовместносмодулемtheme}
В функции start и в обработчиках вы можете менять параметры темы, в
том числе, используя в качестве графики спрайты, например:
\begin{verbatim}
require "sprite"
require "theme"
function start() -- заменим фон на спрайт
local spr = sprite.new(800, 600)
spr:fill 'blue'
spr:fill (100, 100, 32, 60, 'red')
theme.set('scr.gfx.bg', spr)
end
\end{verbatim}
Используя эту технику, вы можете наносить на фоновое изображение
статусы, элементы управления или просто менять подложку.
\section{Пиксели}
\label{Пиксели}
Модуль спрайтов поддерживает также работу с пиксельной графикой. Вы
можете создавать объекты -- наборы пикселей, модифицировать их и
рисовать в спрайты.
Создание пикселей осуществляется функцией pixels.new().
Примеры:
\begin{verbatim}
local p1 = pixels.new(320, 200) -- создали пиксели 320x200
local p2 = pixels.new 'gfx/apple.png' -- создали пиксели из
-- изображения
local p3 = pixels.new(320, 200, 2) -- создали пиксели 320x200,
-- которые при отрисовке их в спрайт -- будут смасштабированы до
-- 640x400
\end{verbatim}
Объект пиксели имеет следующие методы:
\begin{quote}
при описании использованы обозначения: r, g, b, a --
компоненты пикселя: красный, зеленый, синий, и прозрачность. Все
значения от 0 до 255). x, y - координаты левого верхнего угла, w, h
-- ширина и высота области.
\end{quote}
\begin{itemize}
\item :size() -- вернуть размер и масштаб (как 3 значения);
\item :clear(r, g, b, [a]) -- быстрая очистка пикселей;
\item :fill(r, g, b, [a]) -- заливка (с учетом прозрачности);
\item :fill(x, y, w, h, r, g, b, [a]) -- заливка области (с учетом прозрачности);
\item :val(x, y, r, g, b, a) - задать значение пикселя
\item :val(x, y) -- получить компоненты r, g, b, a
\item :pixel(x, y, r, g, b, a) -- нарисовать пиксель (с учетом
прозрачности существующего пикселя);
\item :line(x1, y1, x2, y2, r, g, b, a) -- линия;
\item :lineAA(x1, y1, x2, y2, r, g, b, a) -- линия с AA;
\item :circle(x, y, radius, r, g, b, a) -- окружность;
\item :circleAA(x, y, radius, r, g, b, a) -- окружность с AA;
\item :poly(\{x1, y1, x2, y2, {\ldots}\}, r, g, b, a) -- полигон;
\item :polyAA(\{x1, y1, x2, y2, {\ldots}\}, r, g, b, a) -- полигон с AA;
\item :blend(x1, y1, w1, h1, pixels2, x, y) -- рисовать область пикселей в
другой объект пиксели, полная форма;
\item :blend(pixels2, x, y) -- короткая форма;
\item :fill\_circle(x, y, radius, r, g, b, a) -- залитый круг;
\item :fill\_triangle(x1, y1, x2, y2, x3, y3, r, g, b, a) -- залитый
треугольник;
\item :fill\_poly(\{x1, y1, x2, y2, {\ldots}\}, r, g, b, a) -- залитый полигон;
\item :copy({\ldots}) -- как blend, но не рисовать, а копировать (быстро);
\item :scale(xscale, yscale, [smooth]) -- масштабирование в новый объект
pixels;
\item :rotate(angle, [smooth]) -- поворот в новый объект pixels;
\item :draw\_spr({\ldots}) -- как draw, но в спрайт, а не пиксели;
\item :copy\_spr({\ldots}) -- как copy, но в спрайт, а не пиксели;
\item :compose\_spr({\ldots}) -- то же самое, но в режиме compose;
\item :dup() -- создать копию пикселей;
\item :sprite() -- создать спрайт из пикселей.
\end{itemize}
Также, есть возможность работы со шрифтами:
\begin{itemize}
\item pixels.fnt(fnt(шрифт.ttf, размер) -- создать шрифт;
\end{itemize}
При этом, у созданного объекта ``шрифт'' существует метод text:
\begin{itemize}
\item :text(текст, цвет(как в спрайтах), стиль) -- создать пиксели с текстом;
\end{itemize}
Например:
\begin{verbatim}
local fnt = pixels.fnt("sans.ttf", 64)
local t = fnt:text("HELLO, INSTEAD!", 'black')
pxl:copy_spr(sprite.scr())
pxl2:draw_spr(sprite.scr(), 100, 200);
t:draw_spr(sprite.scr(), 200, 400)
\end{verbatim}
Еще один пример (автор примера, Андрей Лобанов):
\begin{verbatim}
require "sprite"
require "timer"
sprite.direct(true)
declare 'pxl' (false)
declare 't' (0)
function game:timer()
local x, y, i
t = t + 1
for x = 0, 199 do
for y = 0, 149 do
i = (x * x + y * y + t)
pxl:val(x, y, 0, i, i / 2)
end
end
pxl:copy_spr(sprite.scr())
end
function start(load)
pxl = pixels.new(200, 150, 4)
timer:set(20)
end
\end{verbatim}
При процедурной генерации с помощью pixels удобно использовать шумы
Перлина. В INSTEAD существуют функции:
\begin{itemize}
\item instead.noise1(x) - 1D шум Перлина;
\item instead.noise2(x, y) - 2D шум Перлина;
\item instead.noise3(x, y, z) - 3D шум Перлина;
\item instead.noise4(x, y, z, w) - 4D шум Перлина;
\end{itemize}
Все эти функции возвращают значение в диапазоне [--1; 1] а на вход
получают координаты с плавающей точкой.
\chapter{Модуль snd}
\label{Модульsnd}
Мы уже рассматривали базовые возможности по работе со звуком. Модуль
snd имеет еще некоторые функции по работе со звуком.
Вы можете подгрузить звук и держать его в памяти до тех пор, пока он
вам нужен.
\begin{verbatim}
require 'snd'
local wav = snd.new 'bark.ogg'
\end{verbatim}
Кроме подгрузки файлов, вы можете загрузить звук из массива lua:
\begin{verbatim}
local wav = {}
for i = 1, 10000 do
table.insert(wav, rnd() * 2 - 1) -- случайные значения от -1 до 1
end
function start()
local p = snd.new(22050, 1, wave) -- частота, число каналов и звук
p:play()
end
\end{verbatim}
Звук задается в нормированном формате: [--1 .. 1]
Звук можно проиграть методом :play([chan], [loop]), где chan -- канал
(0 - 7), loop - циклы (0 - бесконечность).
Остальные функции модуля:
\begin{itemize}
\item snd.stop([channel]) – остановить проигрывание выбранного канала или
всех каналов. Вторым параметром можно задавать время затухания звука
в мс. при его приглушении;
\item snd.playing([channel]) – узнать проигрывается ли звук на любом
канале или на выбранном канале; если выбран конкретный канал,
функция вернет хандл проигрываемого в данный момент звука или
nil. Внимание! Звук клика не учитывается и обычно занимает 0 канал;
\item snd.pan(chan, l, r) – задание паннинга. Канал, громкость
левого[0--255], громкость правого[0--255] каналов. Необходимо вызывать
перед проигрыванием звука, чтобы имело эффект;
\item snd.vol(vol) – задание громкости звука (и музыки и эффектов) от 0 до 127.
\end{itemize}
Еще одна интересная возможность -- генерирование звука на лету (пока
находится в экспериментальном статусе):
\begin{verbatim}
require "snd"
function cb(hz, len, data)
for i = 1, len do
data[i] = rnd() * 2 - 1
end
end
function start()
snd.music_callback(cb)
end
\end{verbatim}
\chapter{Модуль prefs}
\label{Модульprefs}
Этот модуль позволяет сохранять настройки игры. Другими словами,
сохраненная информация не зависит от состояния игры. Такой механизм
можно использовать, например, для реализации системы достижений или
счетчика количества прохождений игры.
По своей сути prefs это объект, все переменные которого будут
сохранены.
Cохранить настройки:
\begin{verbatim}
prefs:store()
\end{verbatim}
Настройки сохраняются автоматически при сохранении игры, но вы можете
контролировать этот процесс, вызывая prefs:store().
Уничтожить файл с настройками:
\texttt{
prefs:purge()
}
Загрузка настроек выполняется автоматически при запуске игры
(перед вызовом функции start()), но вы можете инициировать загрузку и
вручную:
\begin{verbatim}
prefs:load()
\end{verbatim}
Пример использования:
\begin{verbatim}
-- $Name: Тест модуля prefs$
-- $Version: 0.1$
-- $Author: instead$
-- подключаем модуль click
require "click"
-- подключаем модуль prefs
require "prefs"
-- устанавливаем начальное значение счетчика
prefs.counter = 0;
-- определяем функцию отслеживания количества "кликов"
game.onclick = function(s)
-- увеличиваем счетчик
prefs.counter = prefs.counter + 1;
-- сохраняем счетчик
prefs:store();
-- выводим сообщение
p("На данный момент сделано ", prefs.counter ," кликов");
end;
-- добавляем изображение, по которому можно производить клики
game.pic = 'box:320x200,black';
room {
nam = 'main',
title = "Комната кликов",
-- делаем фиксацию статичной части описания
-- добавляем описание для сцены
decor = [[ Этот тест был написан специально
для проверки работы модуля <>.
]];
};
\end{verbatim}
Обратите внимание, что после запуска игры заново, число
выполненных кликов не обнулится!
\chapter{Методы объектов}
\label{Методыобъектов}
У всех объектов STEAD3 существуют методы, которые используются при
реализации стандартной библиотеке и, обычно, не используются автором
игры напрямую. Однако, иногда полезно знать состав этих методов, хотя
бы для того, чтобы не называть свои переменные и методы именами уже
существующих методов. Ниже представлен список методов с кратким
описанием.
\section{Объект (obj)}
\label{Объектobj}
\begin{itemize}
\item :with(\{{\ldots}\}) - задание списка obj;
\item :new({\ldots}) - конструктор;
\item :actions(тип, [значение]) - задать\slash прочитать число событий объекта
заданного типа;
\item :inroom([\{\}]) - в какой комнате (комнатах) находится объект;
\item :where([\{\}]) - в каком объекте (объектах) находится объект;
\item :purge() - удалить объект из всех списков;
\item :remove() - удалить объект из всех объектов\slash комнат\slash инвентаря;
\item :close()\slash :open() - закрыть\slash открыть;
\item :disable()\slash :enable() - выключить\slash включить;
\item :closed() -- вернет true, если закрыт;
\item :disabled() -- вернет true, если выключен;
\item :empty() -- вернет true, если пуст;
\item :save(fp, n) -- функция сохранения;
\item :display() -- функция отображения в сцене;
\item :visible() -- вернет true если считается видимым;
\item :srch(w) -- поиск видимого объекта;
\item :lookup(w) -- поиск любого объекта;
\item :for\_each(fn, {\ldots}) -- итератор по объектам;
\item :lifeon()\slash :lifeoff() -- добавить\slash удалить из списка живых;
\item :live() -- вернет true, если в списке живых.
\end{itemize}
\section{Комната (room)}
\label{Комнатаroom}
Кроме методов obj, добавлены следующие методы:
\begin{itemize}
\item :from() -- откуда пришли в комнату;
\item :visited() -- была ли комната посещена ранее?;
\item :visits() -- число визитов (0 -- если не было);
\item :scene() -- отображение сцены (не объектов);
\item :display() -- отображение объектов сцены;
\end{itemize}
\section{Диалоги (dlg)}
\label{Диалогиdlg}
Кроме методов room, добавлены следующие методы:
\begin{itemize}
\item :push(фраза) - перейти к фразе с запоминанием ее в стеке;
\item :reset(фраза) -- то же самое, но со сбросом стека;
\item :pop([фраза]) -- возврат по стеку;
\item :select([фраза]) -- выбор текущей фразы;
\item :ph\_display() -- отображение выбранной фразы;
\end{itemize}
\section{Игровой мир (объект game)}
\label{Игровоймиробъектgame}
Кроме методов obj, добавлены следующие методы:
\begin{itemize}
\item :time([v]) -- установить\slash взять число игровых тактов;
\item :lifeon([v])\slash :lifeoff([v]) -- добавить\slash удалить объект из списка
живых, или включить\slash выключить живой список глобально (если не задан
аргумент);
\item :live([v]) -- проверить активность живого объекта;
\item :set\_pl(pl) -- переключить игрока;
\item :life() -- итерация живых объектов;
\item :step() -- такт игры;
\item :lastdisp([v]) -- установить\slash взять последний вывод;
\item :display(state) -- отобразить вывод;
\item :lastreact([v]) -- установить\slash взять последнюю реакцию;
\item :reaction([v]) -- установить\slash взять текущую реакцию;
\item :events(pre, bg) -- установить\slash взять события живых объектов;
\item :cmd(cmd) -- выполнение команды INSTEAD;
\end{itemize}
\section{Игрок (player)}
\label{Игрокplayer}
Кроме методов obj, добавлены следующие методы:
\begin{itemize}
\item :moved() -- игрок сделал перемещение в текущем такте игры;
\item :need\_scene([v]) -- нужна отрисовка сцены в данном такте;
\item :inspect(w) -- найти объект (видимый) в текущей сцене или себе
самом;
\item :have(w) -- поиск в инвентаре;
\item :useit(w) -- использовать предмет;
\item :useon(w, ww) -- использовать предмет на предмет;
\item :call(m, {\ldots}) -- вызов метода игрока;
\item :action(w) -- действие на предмет (act);
\item :inventory() -- вернуть инвентарь (список, по умолчанию это obj);
\item :take(w) -- взять объект;
\item :walk\slash walkin\slash walkout -- переходы;
\item :go(w) -- команда идти (проверяет доступность переходов);
\end{itemize}
\chapter{Послесловие}
\label{Послесловие}
Вот и все, здесь документация заканчивается. Но, возможно, начинается
самое интересное -- ваша история!
Я сделал первую версию INSTEAD в 2009 году. В тот момент я никогда бы
не подумал, что моя игрушка (и движок) переживут столько
изменений. Сейчас, когда я пишу это послесловие, на дворе 2017 год и
текстовые приключения все еще существуют. Правда, их влияние на
культуру по прежнему минимально. И хороших приключений -- по прежнему
очень мало.
На мой взгляд, у текстографических игр огромный потенциал. Они не
такие интерактивные, они не отбирают вашу жизнь взамен на вечно
неудовлетворенное желание, не вынуждают вас сутками просиживать за
монитором в раздражении или нездоровой нервозности{\ldots} Они могут взять
лучшее из мира литературы и компьютерных игр. И то, что жанр по
большей части некоммерческий -- даже плюс.
История INSTEAD, на мой взгляд, хорошее тому подтверждение. Было
выпущено множество игр, которые можно с уверенностью назвать
отличными! Их авторы могут отойти от дел, но созданные ими
произведения уже живут своей жизнью, отражаясь в сознании людей,
которые в них играют или помнят. Пусть ``тираж'' этих игр не так велик,
но то что я увидел, полностью ``оправдало'' все потраченные усилия на
движок. Я знаю, что это время потрачено не зря. Так что я нашел в себе
силы, и сделал движок еще лучше, выпустив STEAD3. Я надеюсь, он
понравится и вам.
Так что если вы дочитали до этого места, я могу только пожелать вам
дописать вашу первую историю. Творчество -- это и есть свобода. :)
Спасибо и удачи. Петр Косых, март 2017.
instead-3.1.2/doc/stead3.pdf 000644 001751 001751 00001106624 13146577507 016122 0 ustar 00peter peter 000000 000000 %PDF-1.5
%
6 0 obj
<>
stream
x510EwN$mJlHC~۠?^b
Dy X*V.\g \8z"Y
3]Et
T Kbj:a"ecojV/E}8A믖si/UiI0
endstream
endobj
10 0 obj
<>
stream
xS(T0T0 BCs# 2PHUp
Qw3T311WIS0337W56ԳPI1004
Rp
Q |
endstream
endobj
42 0 obj
<>
stream
x횽6<^p
le"߄-S] ֮$g?CMNtN
oӯD
MJbt\8
U 2ug)?ߗ.d{I(;PD Nb?&7YFr\;2(p ;vn[bPÅ=QB]EzJQGNKħ辁kwQv|ގ}Wgpw{aHhÂc
Y6gT
4,m 5l3&^vRK/ѽC͈g˘GKoˍe ~Z
L^ܙ]i₳"T&$xHdj1,CP%v O%*p\.9Tm/?%MwX#\K<@18kA4~r"c!MKS S^: ̷!F{?. rBjVZ{ֲ^u~? ^!,E.ίd!E21HcCckC=Z>yzuׂv hpٳ3?ԄB!8E!֩((d") ELKqfŷL.?ŔH4D $qzzRv ܦNTWx}У%(hN7; \RWՈZL9&r5,oKiVC.hYgɣfSYl"ׅ6^D ȄVsaEVOE$Zpf
h꣒QQ?*$"C pDP! X>ZهJ[}Cet 6-,.]SE
\]mS/7aG@\ZQX"͕$l[t
#=sxwPB
]WJ%顓H>DP"d)cx,/
TO|0u_ufj9 ITrs&ssCz852'ij)@$@c=ˁS=i.xwXY3z#E8Ob=[I8sEyé_j g8:˒#2@oNpzG%OHEdLsZd[V(N`6^=mX'4S?,1bluzSAzYxdQ?YhBxKuUvNJ6er(YO7)Erü5龍9o)
endstream
endobj
85 0 obj
<>
stream
x훻n6<^ p )py&C%E79
ǂdyfȀ<߆_߆'Am$AJ
/ _`Loe,^(EX%d\'3kFw|
uZsJ$sr7Θ)ְgI8p4sG?H_s4p`Z\ d4:hXBN`JY#C]i|QRdP$X
Ȯ
jv07CI U+DiQ4;Ȩ-
KGr*G|jCG ⴆNEt|C
s|:;M{)@L^,*
,I}REmQ6 YF@9e#F2fB Fj;G1).&9R-EEHf= w~t(ۡO)X9ۗc3*69ٚ2h\+:N7=jbb)JxZ{v>k?qs#PAWኰKmu!GO()͞UA4y9on8Gut9B̝*GiOREs7ãӮI.tf{?f2Ȓ3[:-Cf$EzdìJd%tc>ө5IAlߣJ5żLʐyi"]홱u(
2oc ]i%÷ B̔"s.|1]WOPE4"Jd=Po#qft H5E
mYW){|ij2aR%д?:ˬ;%.$IO?TxickiIKPk N`^G\~{눽7[VV>@Vjj`M25C%/7ENO`8w$H(xpjPƸjq]=;{I;;˿剤8jS]5e$[&[q_h|wSUy y(!#|خY}MǗZ4ڒ]yt`]Ū2YqLQ&u/+?wx?!]fo!O>''?cHz WpVS?y]V(q&-V2W::ՠcTo;)*擝]jC@>״.zAkU.2zFwp}N;åD)_svZqH2E{A_a*wu)>8E@4?ZDHsSJ\,J`r/PUB(,^W+%|VKTƯa';>
stream
x홱0>_ *II2hnnᅯe[rd+ I>O_CodR95
Dams||k_ϼ]ܿVs?:}p^JZZr
|=
9*g+EƓ5;s( lC:&>Wߣzޮ'%ZȆG~.=*WU$]axŃ`3~ R{ap9eU|_YQfmT W`hfRU[d[|gScb*|vFfx(,O+;
E%bf$f曚R-
l(=.nYʝ[[_&mQaQqUaRo&偧X1*5GAӵlSxl|pq
$iKQm*(4%./z_]nh.wwql[|ll}Y\ഠ{VKv5Ɖo)<ڴeڧ;5FN_:٧0Cf<JϢ+Pvv/tat-]6jhb]z}^y_yZ>ƍxV%nMRdNB&?͖|.a'I.EkӋg+Ly<2n"oGym_Jltl2jyT(ƱԣUj0BJW/cMZ=!dW!eOF-3IWY]W 6SqdyR9i('8qɝTnR1y?q
endstream
endobj
107 0 obj
<>
stream
xS(T0T0 BCs# 2PHUp
Qw3T311WIS0337W56ԳPI100
*3F(362ҳ0Vbff`'B)@
@l1!
\ Sg .
endstream
endobj
110 0 obj
<>
stream
xڍX6-^"4&0
p>9'&b#,fb&nʾwsOnmru%wy|}w