Skip to content

Retro style 2D game engine using crustyvm as a scripting engine.

License

Notifications You must be signed in to change notification settings

kode54/crustygame

 
 

Repository files navigation

BUILDING
    To compile crustygame, just type `make`.  You'll need SDL 2 and gcc, but
aside from that, there should be no other dependencies.  To optionally compile
the BMP converter, use `make -f Makefile.bmpconvert`.

RUNNING
`./crustygame [-D<var>=<value> ...] <scriptname>`

    Some scripts may define variables to be set on the command line for
modifying various options.

    If a script has captured the mouse, CTRL+F10 can be pressed to release it.
CTRL+F10 will have to be pressed again to allow the script to recapture the
mouse.

METADATA
    Metadata may be provided to crustygame in a comment on the absolute
beginning of the program.  the comment must read ";crustygame ", and only data
provided on this one line will be used as metadata.  There is only one
metadata item supported at the moment:

save:<size>
    Specify that the script should have persistent save storage of <size>
bytes.

THE LANGUAGE
    Before anything is done, a pass is made to find all the tokens in the
program.  Quoted strings act as a single token.  Comments are thrown out at
this stage.  Comments begin with a ; and include everything following.  Quoted
strings may contain ;s though.  At this point, only one statement is meaningful:

include <filename>
    Read in the file <filename> in the root directory of the script and start
parsing tokens in from this file.  Care is taken that files above the script
directory should be inaccessible to be included in to a script for safety
reasons, but don't rely on it for security.  Make reasonably sure like any
other program or script that you trust where it came from.

Preprocesor Statements
    The first step to a program running (after it's tokenized) is to run
through and interpret the preprocessor statements, which are responsible for
defining preprocessor-time variables, and rewriting statements based on those.
Any time a single argument is needed to have spaces, it must be enclosed in
quotes, otherwise it'll be interpreted as multiple arguments.  Quoted strings
support the following escape sequences:
\r  carriage return
\n  new line
\<carriage return>  ignore a carriage return in the string.  A following
new line is also ignored
\<new line>  ignore a new line in the string.  A following carriage return is
also ignored
\\  a literal \ (backslash)
\xNN - a two digit hex number representing a byte literal
\"  a literal " (quote)

macro <name> [arguments ...]
    Start a macro definition.  From here, lines will be passed over until
until and endmacro statement with the same name is reached, at which time
normal interpretation will resume.  The arguments are a list of symbols which
when the macro is evaluated, will be replaced by whatever values were passed
in when invoked.

endmacro <name>
    End the named macro definition.  Normal interpretation will resume.

if <number> <name> [arguments ...]
    If <number> is non-zero, start copying the named macro with the listed
arguments.  <number> may also be an expression which is to be replaced by an
expression or a macro argument, or it may also be a variable passed in on the
command line with -D, in this case, it'll always evaluate to true, even if the
variable is specified to be 0.

expr <variable> <expression>
    Evaluate an expression down to a numerical value and assign it to
<variable>.  At that point, any time <variable> appears in the program, it'll
be replaced by the value which the expression evaluated to.  An expression can
only accept integer values and all operations are done as integers and the
result is an integer.  Expressions are arithmetic statements and support the
following operators:
*  multiplication
/  division
%  modulo
+  addition
-  subtraction
<<  binary shift left
>>  binary shift right
<  logical less than
<=  logical less than or equal
>  logical greater than
>=  logical greater than or equal
==  logical equals
!=  logical not equals
&  bitwise AND
!&  bitwise NAND
|  bitwise OR
!|  bitwise NOR
^  bitwise XOR
!^ bitwise XNOR
Parentheses are also supported for grouping, otherwise it follows the
precedence followed is similar to that of C arithmetic parsing precedence.

<macroname> <arguments ...>
    Start evaluaing (copying) from macro and continue until the matched endmacro
is reached.  Replacing any argument values with the arguments passed in.  All
arguments specified must be provided.

Symbol Definition Statements
    Following the preprocessing stage, the resulting code is scanned to find
symbol definitions:  procedures, global (static) variables and procedure
(local) variables.  Before this begins though, any callback variables defined
by the VM will be added in as globals.

proc <name> [arguments ...]
    Defines the start of procedure <name> and specifies which arguments, if
any, it accepts.  Those arguments become local variables which reference the
memory of the variables passed in, that is, any modifications to them in the
procedure will persist when the procedure returns.

ret
    Return from procedure.  Marks the end of a procedure.

static <name> [N | ints <N | "N ..."> | floats <N | "N ..."> | string "..."]
    Define a global (static) variable <name>.  If a single number is provided,
it will act as a single integer initializer.  If a type is specified, a single
value may be provided to create an array of that size, otherwise, multiple
arguments may be specified in quotes, separated by spaces or tabs, to create
an array of the size of values given and initialized with those values.  The
exception is string, which can only be initialized with a single argument,
quoted or not.  Values are initialized once, on VM start.  These may be
specified anywhere, procedure or not.

local <name> [N | ints <N | "N ..."> | floats <N | "N ..."> | string "..."]
    Define a procedure (local) variable <name>.  The initializer is specified
in the same way as global variables.  These values are initialized on each
call to a procedure.  These can only be defined inside procedures.  Procedure
variables must have unique names to global variables.

stack
    Not a real instruction, just indicate to accumulate stack.

label
    Define a label within a procedure to jump to.  Label names are scoped
locally to procedures.

binclude <name> <chars | ints | floats> <filename> [start] [length]
    Read in <filename> to be the initializer for a global variable <name>.  The
type must be defined as chars (a string), ints (integer array) or floats
(double array).  A start byte and length byte may be specified to include only
a particular range of the file.  As many items of the size of type which fit
within the file or provided length will be read in and used as the array
initializer.  Like include, some care is taken to prevent arbitrary files
above the script's directory from being read in.  The same warning applies.

Program Instructions
    The final stage is parsing instructions and generating bytecode.  The
language supports a fairly limited set of instructions, but ones which vaguely
represent the selection of instructions one may have available on a more
primitive platform, just with some added feature and artistic license for the
sake of simplciity and safety, but bugs can certainly still come up.

move <destionation>[:<index>] <source>[:<index>]
    Simply move a value from source to destination.  Source may be a variable,
an integer immediate or a callback with the index passed to it, which will
return a value.  Values will be converted to the type destination is before
being stored.  If destination is a callback itself, it will be passed a
reference to the source at the index provided, assuming it's not also a
callback or an immediate, in which case only the single value ia passed in.
All indexes are range checked to avoid hidden out of bounds accesses, in which
case the program is terminated.  A read or write callback indicating an error
will also terminate execution.  An index of 0 is implied if it's excluded.

add <destination>[:<index>] <source>[:<index>]
    Add <destionation> at <index> and <source at <index> then store the value
in <destination>.  Source may be a callback in this case but destination
cannot be, even if a callback may otherwise be readable and writeable.  If
either destination or source is a float value, the operation will be done as
if they are floats, but will be converted to the type of the destination,
truncated to the range/precision of the type.  All other arithmetic operations
follow similar rules.

sub <destination>[:<index>] <source>[:<index>]
    Subtract.

mul <destination>[:<index>] <source>[:<index>]
    Multiply.

div <destination>[:<index>] <source>[:<index>]
    Divide.

mod <destination>[:<index>] <source>[:<index>]
    Modulo (remainder).

and <destination>[:<index>] <source>[:<index>]
    Bitwise AND.  Bitwise instructions follow similar access rules as
arithmetic expressions, but they are restricted to operating on integer or
string types.  A character of a string is converted to a 32 bit integer and
padded out with 0s in its most significant bits.

or <destination>[:<index>] <source>[:<index>]
    Bitwise OR.

xor <destination>[:<index>] <source>[:<index>]
    Bitwise XOR.

shr <destination>[:<index>] <source>[:<index>]
    Bitwise shift right.  <source> may be a float, but the value will of course
be converted to an integer.
 
shl <destination>[:<index>] <source>[:<index>]
    Bitwise shift left.

cmp <destination>[:<index>] <source>[:<index>]
    Compare (subtract) <destination> at <index> and <source> at <index>, but
don't store it, simply hold on to the result for use with conditional jumps.
Neither value necessarily needs to be writable, that is, either side or both
sides can be an immediate or a read-only callback.  In reality, any value
which would be written or passed on to a destination is stored as the result
from any of the above operations, for use on a following conditional jump, but
it is only 1 value, and it is always replaced on one of these operations,
regardless.

jump <label>
    Jump to a label.

jumpn <label>
    Jump to a label if result is not zero.  From a cmp operation, this
indicates that the two values were different.
(cmp a b -> a != b)

jumpz <label>
    Jump to a label if the result is zero.  From a cmp operation, this
indicates that the two values are equal.
(cmp a b -> a == b)

jumpl <label>
    jump to a label if the result is less than zero/negative.  From a cmp
operation, this indicates that the left value is less than the right value.
(cmp a b -> a < b)

jumpg <label>
    Jump to a label if the result is greater than zero/positive.  From a cmp
operation, this indicates that the left value is greater than the right value.
(cmp a b -> a > b)

call <procedure> <arguments ...>
    Call a procedure.  All arguments indicated by the procedure must be
provided and are passed in as reference to the procedure and may be changed
once the procedure returns.

PROCEDURES
    CrustyGame requires of your script that it has 3 procedures, init, event
and frame.  They can not accept any arguments.

init
    Called once before the execution loop starts.

event
    Called on each event received, at which point callbacks may be used to
inspect what the event was and its relevant values.

frame
    Called after all queued events have been processed.  Typically rendering
should be done here.  Each frame starts blank and must be fully redrawn as the
surface is double-buffered.  Vsync is on by default, but don't expect it to be
on, so this procedure may be called irregularly, or the user's display may have
a different refresh rate from yours.

CALLBACKS
    The callbacks are the interface between a script and the crustygame
engine.  They are simply used like a variable, with some caveats addressed
above in the description of the move operation.  They may have general
purposes, relate to data, video, input and hopefully (though not yet) audio.

General Callbacks
out (W)
    A single value may be written to it.  It will be output to standard
output.  A character in a string will be output as a byte and an int or float
will be output as a numerical value.

err (W)
    Like out but standard error.

string_out (W)
    Write a whole character string to standard out in one go.

string_err (W)
    Like string_out, but standard error.

get_ticks (R)
    Get roughly the number of milliseconds since SDL was initialized.  Your
only way to track the passage of time.

set_running (W)
    Set to 0 to indicate the program should stop after this frame.  Can be
reset to non-zero before a frame is finished.

get_random (R)
    Get a random number from the rand() function, seeded at program start with
the current UNIX time.  If you need something more repeatable or with other
special characteristics, you can always implement your own, but this is a
quick convenience.

set_window_title (W)
    Sets the window title.  Accepts a string.  Doesn't need to be null
terminated.

set_buffer (W)
    Pass in a buffer to be used by a following callback which needs a buffer.
The reference provided is held on to.  One can use local variables, but on
return the reference will go stale, resulting in undefined data being
referenced.  This won't result in a program crash as the whole stack is always
accessible, but you might get unexpected results if a callback which uses the
reference is then called.

get_return (R)
    Read in a value returned by a write callback, where a structure will have
been passed in and processed somehow.

Graphics Callbacks
    The way graphics works in CrustyGame is really part of the gimmick of it.
It is a fairly flexible, tile and layer based system with some differences to
work a bit better with more modern graphics systems.  Tilesets must be provided
to the engine, then tilemaps must be created and populated with tiles and
optional attributes, then layers must be created and assigned tilemaps.  Many
layers may have the same tilemap.  Then layer parameters should be specified and
finally indicated to be drawn to the screen.  Many of these have helpers in
examples/crustygame.inc.

gfx_add_tileset (W)
    Add a tileset from a buffer provided by gfx_set_buffer.  This callback
takes a buffer of 5 ints.  Width of the graphic, in pixels, height of the
graphic, in pixels, the row pitch of the graphic, in bytes, the width of each
tile, in pixels and the height of each tile in pixels.  The graphics data must
be 32 bit color in A8R8G8B8 order, with the origin pixel on the top-left, then
proceeding row by row.  A bmpconvert utility is provided.  The GIMP (and
possibly others) are able to output BMPs with alpha channel, usable with this
program.  The result is a super-simplified format that is easy to use by
scripts.  See examples/crustygame.inc for helper definitions.  Tiles are
broken out from the texture also starting from the top left corner and
proceeding row by row, starting at 0 and increasing from there.  As many tiles
as can fit the image are indexable.  The id will be returned through
gfx_get_return.

gfx_free_tileset (W)
    Free the tileset and any resouces.  Accepts an id of a tileset as an int.
This id will now be free and invalid.  The id may be returned again at a later
time.

gfx_add_tilemap (W)
    Add a tilemap.  Accepts a buffer of 2 ints.  The width in tiles of the
tilemap and the height in tiles of the tilemap.  The id will be returned
through gfx_get_return.

gfx_free_tilemap (W)
    Free a tilemap.  Accepts an id of a tilemap as an int.  Same rules as
freed tilesets.

gfx_set_tilemap_tileset (W)
    Assign a tileset to a tilemap.  A tileset may be used on many tilemaps.

gfx_set_tilemap_map (W)
    Provide tilemap data to be copied in to the tilemap's own data.  Accepts 5
ints.  X position of where to start copying to, in tiles.  Y position of where
to start copying to, in tiles.  The row pitch of the whole tilemap data, in
tiles.  The width of the tilset data to be copied, in tiles and the height of
the tileset data to be copied, in tiles.  The data is copied from the buffer
provided with gfx_set_buffer.  Each item takes an int.  A 0 may be passed in
to the width or height to indicate the full width or full height and a -1 may
be passed in for pitch to specify that the width is the same.

gfx_set_tilemap_attr_flags (W)
    Provide an array of tile attribute flags.  Arguments are the same as
gfx_set_tilemap_map.  Each item is an int, with the following flags, defined
in examples/crustygame.inc:
TILEMAP_HFLIP_MASK  Flip the tile horizontally.
TILEMAP_VFLIP_MASK  Flip the tile vertically.
TILEMAP_BFLIP_MASK  Flip the tile in both directions.
TILEMAP_ROTATE_90   Rotate the tile 90 degrees.  Square tiles only.
TILEMAP_ROTATE_180  Rotate the tile 180 degrees.
TILEMAP_ROTATE_270  Rotate the tile 270 degrees.  Square tiles only.

gfx_set_tilemap_attr_colormod (W)
    Provide an array of tile color modulations.  Arguments are the same as
gfx_set_tilemap_map.  Each item is an int, describing a 32 bit color in ARGB
format.  See the TILEMAP_COLOR macro in examples/crustygame.inc.  The tile's
colors will be proportionally scaled down by however much the color value is
lower than 255, so for example, 127 would halve the value, 63 would quarter
the value, etc.

gfx_update_tilemap (W)
    Actually create or update the tilemap based on the tileset, map and
attributes provided.  A part of the tilemap can be updated without the whole
thing being updated.  This takes four arguments: The start X position to
update in tiles, the start Y position to update in tiles, the width to update
in tiles and the height to update in tiles.

gfx_add_layer (W)
    Create a layer and return an id.

gfx_free_layer (W)
    Free a layer id.  Same rules as the other free calls.

gfx_set_layer_pos (W)
    Set the position the layer will be drawn at.  It accepts an array of 2
ints: The X position in pixels and the Y position in pixels.

gfx_set_layer_window (W)
    Set the window size within the tilemap to draw.  Accepts an array of 2
ints, X position in pixels and y position in pixels.  Window can be 1,1 to the
full size of the tilemap.  A 0 may be passed in for an axis reset to full size.

gfx_set_layer_scroll_pos (W)
    Set the scroll position within the tilemap to start drawing.  This
coincides closely with the layer window size.  The restriction is that while
it will tile out past the right edge, bottom edge and bottom-right corner, it
won't do this infinitely, only once, so the bottom-right corner must be within
1 to 1 less than twice the total length of an axis.

gfx_set_layer_scale (W)
    Set the scale of a layer to be drawn.  Accepts 2 floats, the X scale and
the Y scale.

gfx_set_layer_colormod (W)
    Sets the color modulation for drawing this layer.  May be combined with
tilemap attribute colormod, it'll just be stacked.  Accepts the same format.

gfx_set_layer_blendmode (W)
    Sets the layer blend mode.  Accepts a single integer.  Defines are in
examples/crustygame.inc, which include:
GFX_BLENDMODE_BLEND  Alpha blend the layer on top of the image.  Colors will
be blended between the background and the layer proportional to the alpha
value of the pixel, including the colormod.
GFX_BLENDMODE_ADD  Layer colors will be added to the existing colors behind
it, proportional to the layer alpha, including colormod.
GFX_BLENDMODE_MOD  Like colormod.  Color values will be proportionally
reduced.  Alpha is ignored in this case.
GFX_BLENDMODE_MUL  I'm not really sure what this is supposed to do, just one
of the ones SDL comes with and its description isn't very clear.
GFX_BLENDMODE_SUB  Like add, just subtracts the layer colors from the layer
behind it, proportional to alpha, colormod included.

gfx_set_video_mode (W)
    Set the video mode.  Accepts a string formatted as <width>x<height> to
change the size of the window.  Also accepts fullscreen, to fill the screen at
the desktop resolution.  This never actually attempts to change desktop
display modes, so you'll need to find the width and height after fullscreening
and act accordingly.

gfx_get_width (R)
    Get the width of the window in pixels, useful for getting the resolution
when fullscreened.

gfx_get_height (R)
    Get the width of the window in pixels.

event_get_type (R)
    Gets the type of an event received.  Defines for event types are in
examples/crustygame.inc.

event_get_time (R)
    Gets the timestamp of the event, in milliseconds.

event_get_button (R)
    Gets the button an event happened on.  Either what key, mouse button or
controller button.  Also tells which axis or POV hat an axis event happened
on.

event_get_x (R)
    Gets the X motion of an action.  Gets the direction of a POV hat press.
Gets mouse click positions as well as motion positions.  When the mouse is
locked to the window, it will get a relative mouse movement, if the mouse is
not locked, it'll just get the absolute position within the window the corsur
moved to.  Also gets controller axis motions indicated by the axis in
event_get_button.
 
event_get_y (R)
    Gets the Y motion of an action, like event_get_x but a bit more limited.
Doesn't respond to hats or controller axes.

event_set_mouse_capture (W)
    Write a non-zero value to capture the mouse, write a 0 to uncapture the
mouse.  The user may force uncapture the mouse with CTRL+F10, and the mouse
won't be able to be captured again until the user presses CTRL+F10 a second
time.

savedata_seek (W)
    Seek to a position in save data storage.

savedata_write (W)
    Write a value in the save data storage.  Enough space after the position
in the storage needs to be present for the write to complete.  A string
character is 1 byte, an int is 4 bytes and a float is 8 bytes.

savedata_read_char (R)
    Read a char value from save data storage.

savedata_read_int (R)
    Read an int value from save data storage.

savedata_read_float (R)
    Read a flat value from save data storage.

About

Retro style 2D game engine using crustyvm as a scripting engine.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C 99.5%
  • Other 0.5%